diff --git a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js index 709589985c0be13878f5a21a91adcc19be862904..14a8bdddfd9a3526662a003c79ae4817b2577c75 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js @@ -142,7 +142,8 @@ const PermissionStackUtil = { permissionStack[module][id] ={ view : allowedPermission?(_.includes(allowedPermission, 'GET')):false, edit : allowedPermission?(_.includes(allowedPermission, 'PUT')):false, - delete : allowedPermission?(_.includes(allowedPermission, 'DELETE')):false + delete : allowedPermission?(_.includes(allowedPermission, 'DELETE')):false, + cancel : allowedPermission?(_.includes(allowedPermission, 'POST')):false } } AuthStore.dispatch({ type: 'loadpermissionbyid', payload: permissionStack, id: id, module: module}); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js index bed0a742a19ac942a70fa7491b5e09451bcf628c..a35d8400d3fc09694b821ebfc8bf0de5faf98949 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -794,7 +794,7 @@ export class CalendarTimeline extends Component { } { (item.type === "SUNTIME" || item.type === "RESERVATION") && - <div style={itemContentStyle}><span>{item.title}</span> + <div style={itemContentStyle}><span>{item.actType}</span> {item.type === "RESERVATION" && <div style={itemContentStyle}><span>{item.desc}</span></div> } </div> } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index f1f76550b4ce980e796cfd1e2189b86e3b7d1cf4..91d39dff9a68a7d7a9b307dc7950de088d9c1016 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -802,21 +802,21 @@ function FlatpickrRangeColumnFilter({ const [value, setValue] = useState(''); const [filtered, setFiltered] = useState(false); React.useEffect(() => { - if (!filterValue && value) { + if (!filterValue && value) { setValue(null); - } - if (storeFilter) { + } + if (storeFilter) { const filter = TableUtil.getFilter(currentTableName, Header); const filterValue = _.map(filter, date => {return new Date(date)} ) if (filter === '') { TableUtil.saveFilter(currentTableName, Header, [] ); setFilter(undefined); } - if (filterValue[1] && !value ){ - setValue(filterValue); - setFilter(filterValue); + if (filterValue && !value ){ + setValue(filterValue); + setFilter(filterValue); } - } + } }, [filterValue, value]); // Function to call the server side filtering const callServerFilter = (value, isCleared) => { @@ -855,17 +855,30 @@ function FlatpickrRangeColumnFilter({ }} title={(tableToolTipsState[Header])?tableToolTipsState[Header]:"Enter the date & time range to search and press ‘Ok’ button"} value={filterValue} - onClose={value => { - if(value.length === 2) { - setValue(value); - setFilter(value); - if(storeFilter) { - TableUtil.saveFilter(currentTableName, Header, value); - } - if ((value !== '' && value !== 'Invalid date' ) && doServersideFilter) { - setFiltered(true); - callServerFilter(value); + onClose={newValue => { + if(newValue) { + // To apply serverside filter only when the value is changed + let isValueChanged = false; + if (value.length !== newValue.length) { + isValueChanged = true; + } else if (value.length === newValue.length) { + if (value.length === 1 && !(moment(value[0]).isSame(moment(newValue[0])))) { + isValueChanged = true + } else if (value.length === 2 && + (!(moment(value[0]).isSame(moment(newValue[0]))) || + !(moment(value[1]).isSame(moment(newValue[1]))))) { + isValueChanged = true; } + } + setValue(newValue); + setFilter(newValue); + if(storeFilter) { + TableUtil.saveFilter(currentTableName, Header, newValue); + } + if ((newValue !== '' && newValue !== 'Invalid date' ) && isValueChanged && doServersideFilter) { + setFiltered(true); + callServerFilter(newValue); + } } }} > @@ -1160,11 +1173,49 @@ function dateFilterFn(rows, id, filterValue) { let rowValue = moment.utc(row.values[id].split('.')[0]); if (!rowValue.isValid()) { // For cell data in format 'YYYY-MMM-DD' - rowValue = moment.utc(moment(row.values[id], 'YYYY-MM-DD').format("YYYY-MM-DDT00:00:00")); + rowValue = moment.utc(moment(row.values[id], 'YYYY-MM-DDTHH:mm:SS').format("YYYY-MM-DDTHH:mm:SS")); + } + const start = moment.utc(moment(filterValue[0], 'YYYY-MM-DDTHH:mm:SS').format("YYYY-MM-DDTHH:mm:SS")); + const end = moment.utc(moment(filterValue[1], 'YYYY-MM-DDTHH:mm:SS').format("YYYY-MM-DDTHH:mm:SS")); + if(moment(end,'YYYY-MM-DDTHH:mm:SS', true).isValid() && moment(start,'YYYY-MM-DDTHH:mm:SS', true).isValid()) { + return (start.isSameOrBefore(rowValue) && end.isSameOrAfter(rowValue)); + } + else if(moment(start,'YYYY-MM-DDTHH:mm:SS', true).isValid()) { + return (start.isSameOrBefore(rowValue)); + } + else { + return true; + } + }); + return filteredRows; +} + +function durationTimeFilterFn(rows, id, filterValue) { + const filteredRows = _.filter(rows, function (row) { + // If cell value is null or empty + if (!row.values[id]) { + return false; + } + //Remove microsecond if value passed is UTC string in format "HH:mm:ss.sssss" + let rowValue = moment(row.values[id], 'HH:mm:SS'); + if (!rowValue.isValid()) { + // For cell data in format 'HH:mm:SS' + rowValue = moment(row.values[id], 'HH:mm:SS'); + } + const start = moment(filterValue[0], 'HH:mm:SS'); + const end = moment(filterValue[1], 'HH:mm:SS'); + if(moment(end,'HH:mm:SS', true).isValid() && moment(start,'HH:mm:SS', true).isValid()) { + return (start.isSameOrBefore(rowValue) && end.isSameOrAfter(rowValue)); + } + else if (!(moment(end,'HH:mm:SS', true).isValid()) && !(moment(start,'HH:mm:SS', true).isValid()) ){ + return true + } + else if(!(moment(start,'HH:mm:SS', true).isValid())) { + return end.isSameOrAfter(rowValue); + } + else if(!(moment(end,'HH:mm:SS', true).isValid())) { + return start.isSameOrBefore(rowValue); } - const start = moment.utc(moment(filterValue[0], 'YYYY-MM-DD').format("YYYY-MM-DDT00:00:00")); - const end = moment.utc(moment(filterValue[1], 'YYYY-MM-DD').format("YYYY-MM-DDT23:59:59")); - return (start.isSameOrBefore(rowValue) && end.isSameOrAfter(rowValue)); }); return filteredRows; } @@ -1463,7 +1514,7 @@ function RankRangeFilter({ function DurationRangeFilter({ column: { filterValue = [], preFilteredRows, setFilter, id, Header }, }) { - let [rangeValue, setRangeValue] = useState([0,0]); + let [rangeValue, setRangeValue] = useState(['','']); const [value, setValue] = useState(''); const [filtered, setFiltered] = useState(false); React.useEffect(() => { @@ -1475,9 +1526,12 @@ function DurationRangeFilter({ if (filterValue) { setFiltered(true); } - if(!value){ + if(filterValue && !value){ setValue(filterValue); - //setFilter(filterValue); + setFilter(filterValue); + } + if(!filterValue && value.length>0){ + setValue([]); } } }, [filterValue, value]); @@ -1505,6 +1559,9 @@ function DurationRangeFilter({ setFiltered(true); callServerFilter(e, false); } + else if(e.key === "Enter" ) { + setFilter(rangeValue); + } }} style={{ alignItems: 'center' @@ -1515,19 +1572,19 @@ function DurationRangeFilter({ placeholder="HH:mm:ss" tooltip={(tableToolTipsState[Header])?tableToolTipsState[Header]:"Enter Minimum Range value in HH:mm:ss format and press ‘Enter’ key to search"} onChange={e => { - setFilter(undefined); setFiltered(false); let val = e.target.value; if (val.includes(":") && !Validator.isValidHHmmss(val, true)) { val = rangeValue[0]; } let max = rangeValue[1]; setValue([val,max]); - setFilter([val,max] || undefined); + if(doServersideFilter) { + setFilter([val,max] || undefined); + } setRangeValue([val,max]); filterValue[0] = val; if(storeFilter) { - //TableUtil.saveFilter(currentTableName, Header, [val,max]); - setFilter([val,max]); + TableUtil.saveFilter(currentTableName, Header, [val,max]); } }} style={{ @@ -1541,19 +1598,19 @@ function DurationRangeFilter({ tooltip={(tableToolTipsState[Header])?tableToolTipsState[Header]:"Enter Maximum Range value in HH:mm:ss format and press ‘Enter’ key to search"} placeholder="HH:mm:ss" onChange={e => { - setFilter(undefined); setFiltered(false); let val = e.target.value; if (val.includes(":") && !Validator.isValidHHmmss(val, true)) { val = rangeValue[1]; } let min = rangeValue[0]; setValue([min,val]); - setFilter([min,val] || undefined); + if(doServersideFilter) { + setFilter([min,val] || undefined); + } setRangeValue([min,val]); filterValue[1] = val; if(storeFilter) { - //TableUtil.saveFilter(currentTableName, Header, [min,val]); - setFilter([min,val]); + TableUtil.saveFilter(currentTableName, Header, [min,val]); } }} style={{ @@ -1704,7 +1761,7 @@ const filterTypes = { }, 'durationMinMax': { fn: DurationRangeFilter, - type: 'between' + type: durationTimeFilterFn } }; // Let the table remove the filter if the string is empty @@ -2183,7 +2240,7 @@ function Table(props) { {row.cells.map(cell => { if (cell.column.id !== 'actionpath') { return <td {...cell.getCellProps()}> - {(cell.row.original.links || []).includes(cell.column.id) ? <Link to={cell.row.original.linksURL[cell.column.id]}>{cell.render('Cell')}</Link> : cell.render('Cell')} + {(cell.row.original.links || []).includes(cell.column.id) ? <a href={cell.row.original.linksURL[cell.column.id]}>{cell.render('Cell')}</a> : cell.render('Cell')} </td> } else { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js index 804838a703147d84cd988e75e94a45efdfc53815..52b56fb2f62ce687458c1784e9ee4c2e4df5e9a0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -1144,22 +1144,20 @@ class SchedulingUnitList extends Component{ if(filters.length > 0 ) { for( const filter of filters) { if (filter.id === 'Start Time') { - const values = _.split(filter.value, ","); - if (values.length>2){ - continue; + const values = filter.value; + if (values[0]) { + this.filterQry += 'start_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } - if((values[0] && values[0] != '' && values[0] != 'null') && (values[1] && values[1] != '' && values[1] != 'null')) { - this.filterQry += 'start_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDT00:00:00")+".000Z&"; - this.filterQry += 'start_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDT23:59:59")+".000Z&"; + if (values[1]) { + this.filterQry += 'start_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } } else if (filter.id === 'End Time') { - const values = _.split(filter.value, ","); - if (values.length>2){ - continue; + const values = filter.value; + if (values[0]) { + this.filterQry += 'stop_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } - if((values[0] && values[0] != '' && values[0] != 'null') && (values[1] && values[1] != '' && values[1] != 'null')) { - this.filterQry += 'stop_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDT00:00:00")+".000Z&"; - this.filterQry += 'stop_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDT23:59:59")+".000Z&"; + if (values[1]) { + this.filterQry += 'stop_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } } else if ((filter.id === 'Scheduling Unit ID' || filter.id === 'Linked Draft ID') && filter.value != '') { let columnDetails = _.find(this.state.columnMap, {displayName:filter.id}); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js index db8455cd7a83b34cf74cc8b5464558a0926a0ffb..092c08eee35c1f03eb78fdffbd0ce6a8439f0521 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js @@ -245,7 +245,7 @@ export class TaskList extends Component { taskBlueprint['blueprint_draft'] = this.getLinksList([taskBlueprint['draft_id']],'draft'); taskBlueprint['relative_start_time'] = 0; taskBlueprint['relative_stop_time'] = 0; - taskBlueprint.duration = moment.utc((taskBlueprint.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + taskBlueprint.duration = moment.utc((taskBlueprint.duration && taskBlueprint.duration>0?taskBlueprint.duration:0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); taskBlueprint.template = taskBlueprint.specifications_template; taskBlueprint.schedulingUnitName = taskBlueprint.scheduling_unit_blueprint.name; for (const subtask of taskBlueprint.subtasks) { @@ -278,7 +278,7 @@ export class TaskList extends Component { scheduletask[key] = task[key]; } scheduletask['specifications_doc'] = task['specifications_doc']; - scheduletask.duration = moment.utc((scheduletask.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + scheduletask.duration = moment.utc((scheduletask.duration&&scheduletask.duration>0?scheduletask.duration:0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); scheduletask.relative_start_time = moment.utc(scheduletask.relative_start_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); scheduletask.relative_stop_time = moment.utc(scheduletask.relative_stop_time * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); scheduletask.template = task.specifications_template; @@ -785,22 +785,20 @@ export class TaskList extends Component { if(filters.length > 0 ) { for( const filter of filters) { if (filter.id === 'Start Time') { - const values = _.split(filter.value, ","); - if (values.length>2){ - continue; + const values = filter.value; + if (values[0]) { + this.filterQry += 'start_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } - if((values[0] && values[0] != '' && values[0] != 'null') && (values[1] && values[1] != '' && values[1] != 'null')) { - this.filterQry += 'start_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDT00:00:00")+".000Z&"; - this.filterQry += 'start_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDT23:59:59")+".000Z&"; + if (values[1]) { + this.filterQry += 'start_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } } else if (filter.id === 'End Time') { - const values = _.split(filter.value, ","); - if (values.length>2){ - continue; + const values = filter.value; + if (values[0]) { + this.filterQry += 'stop_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } - if((values[0] && values[0] != '' && values[0] != 'null') && (values[1] && values[1] != '' && values[1] != 'null')) { - this.filterQry += 'stop_time_after='+ moment(new Date(values[0])).format("YYYY-MM-DDT00:00:00")+".000Z&"; - this.filterQry += 'stop_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDT23:59:59")+".000Z&"; + if (values[1]) { + this.filterQry += 'stop_time_before='+moment(new Date(values[1])).format("YYYY-MM-DDTHH:mm:ss")+".000Z&"; } } else if (filter.id === 'Scheduling Unit Name') { if (this.state.taskType === 'Draft') { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js index 5ad3ca755b7d9b8a2ae7f2839069143ac30f3bc0..f0d20d3815f9cce131470c75ebdc253a4fd154ae 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js @@ -17,6 +17,7 @@ import { Column } from 'primereact/column'; import UtilService from '../../services/util.service'; import AuthUtil from '../../utils/auth.util'; import AccessDenied from '../../layout/components/AccessDenied'; +import ViewTable from '../../components/ViewTable'; export class TaskView extends Component { // DATE_FORMAT = 'YYYY-MMM-DD HH:mm:ss'; @@ -34,8 +35,58 @@ export class TaskView extends Component { userrole: { userRolePermission: {} }, - permissionById: {} + permissionById: {}, + subtaskRowList: [], + defaultcolumns: [{ + type: { + name:"Subtask Type", + filter:"select", + tooltip: 'Select Subtask type' + }, + id: { + name: "Subtask ID", + tooltip: 'Enter few characters' + }, + status: { + name: "Subtask Status", + filter:"select", + tooltip: 'Select Subtask status' + }, + start_time: { + name: "Start Time", + filter: "flatpickrDateRange", + format:UIConstants.CALENDAR_DATETIME_FORMAT, + }, + stop_time: { + name: "End Time", + filter: "flatpickrDateRange", + format:UIConstants.CALENDAR_DATETIME_FORMAT + }, + duration:{ + name:"Duration (HH:mm:ss)", + filter: "durationMinMax", + format:UIConstants.CALENDAR_TIME_FORMAT + }, + parset: { + name: "Link to Parset", + tooltip: 'Enter few characters' + } + }], + optionalcolumns: [{ + }], + columnclassname: [{ + "Duration (Days HH:mm:ss)":"filter-input-75", + "Subtask Type":"filter-input-125", + "Subtask ID":"filter-input-100", + "Link to Parset":"filter-input-150", + "Subtask Status":"filter-input-125" + }], + defaultSortColumn: [{id: "Start Time", desc: false}], }; + this.lsKeySortColumn = ""; + this.statusList = []; + this.subtaskTypeList = []; + this.fetchSubtask = true; this.access_denied_message = "Don't have permission"; this.setEditorFunction = this.setEditorFunction.bind(this); this.deleteTask = this.deleteTask.bind(this); @@ -44,6 +95,7 @@ export class TaskView extends Component { this.getTaskDeleteDialogContent = this.getTaskDeleteDialogContent.bind(this); this.showCancelConfirmation = this.showCancelConfirmation.bind(this); this.cancelTask = this.cancelTask.bind(this); + this.getFilterOptions = this.getFilterOptions.bind(this); this.cancelView = this.cancelView.bind(this); @@ -53,7 +105,6 @@ export class TaskView extends Component { if (this.props.match.params.type) { this.state.taskType = this.props.match.params.type; } - } // static getDerivedStateFromProps(nextProps, prevstate){ @@ -88,6 +139,9 @@ export class TaskView extends Component { const taskId = this.props.location.state?this.props.location.state.id:this.state.taskId; let taskType = this.props.location.state && this.props.location.state.type?this.props.location.state.type:this.state.taskType; taskType = taskType?taskType:'draft'; + this.lsKeySortColumn = 'subtaskSort'; + this.setToggleBySorting(); + await this.getStatusList(taskType.toLowerCase()); const moduleName = taskType === 'draft' ? 'task_draft': 'task_blueprint' const permission = await AuthUtil.getUserRolePermission(); const permissionById = await AuthUtil.getUserPermissionByModuleId(moduleName, taskId) @@ -114,9 +168,10 @@ export class TaskView extends Component { getTaskDetails(taskId, taskType) { if (taskId) { taskType = taskType?taskType:'draft'; - TaskService.getTaskDetails(taskType, taskId) + TaskService.getTaskDetails(taskType, taskId, this.fetchSubtask) .then((task) => { if (task) { + taskType === 'blueprint' && this.getSubtaskDetails(task.subtasks); TaskService.getSchedulingUnit(taskType, (taskType==='draft'?task.scheduling_unit_draft_id:task.scheduling_unit_blueprint_id)) .then((schedulingUnit) => { let path = _.join(['/schedulingunit','view',((this.state.taskType === "draft")?'draft':'blueprint'),schedulingUnit.id], '/'); @@ -143,6 +198,44 @@ export class TaskView extends Component { } } + async getSubtaskDetails(subtasks) { + let subtaskList = []; + for(const subtask of subtasks) { + let subtaskRow = {}; + subtaskRow['type'] = subtask.subtask_type; + subtaskRow['id'] = subtask.id; + subtaskRow['status'] = subtask.state_value; + subtaskRow['start_time']= subtask.start_time; + subtaskRow['stop_time']= subtask.stop_time; + subtaskRow['duration']= moment.utc((subtask.duration || 0) * 1000).format(UIConstants.CALENDAR_TIME_FORMAT); + subtaskRow['parset']= `https://proxy.lofar.eu/inspect/${subtask.id}/rtcp-${subtask.id}.parset`; + subtaskRow['links'] = ['Link to Parset']; + subtaskRow['linksURL'] = { + 'Link to Parset': `https://proxy.lofar.eu/inspect/${subtask.id}/rtcp-${subtask.id}.parset` + }; + subtaskList.push(subtaskRow); + } + await this.setState({ + subtaskRowList: subtaskList + }); + } + + toggleBySorting = (sortData) => { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); + } + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if (sortData) { + if (Object.prototype.toString.call(sortData) === '[object Array]') { + this.defaultSortColumn = sortData; + } + else { + this.defaultSortColumn = [{ ...sortData }]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } /** * Show confirmation dialog */ @@ -182,7 +275,7 @@ export class TaskView extends Component { } cancelView(){ - this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/project`); + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/task`); } /** @@ -245,6 +338,30 @@ export class TaskView extends Component { } } + async getStatusList(type) { + const taskFilters = await TaskService.getTaskFilterDefinition(type); + if (taskFilters.data.filters['status']) { + taskFilters.data.filters['status'].choices.forEach(choice => { + this.statusList.push(choice.value); + }) + } + const subtaskTypes = await TaskService.getSubtaskType(); + subtaskTypes.results.forEach(subtaskType => { + this.subtaskTypeList.push(subtaskType.value); + }) + } + + getFilterOptions(id) { + let options = null; + if(id && id === 'Subtask Status') { + options = this.statusList; + } + if(id && id === 'Subtask Type') { + options = this.subtaskTypeList; + } + return options; + } + render() { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> @@ -252,7 +369,7 @@ export class TaskView extends Component { const {task} = this.state.userrole.userRolePermission; let jeditor = null if (this.state.taskTemplate) { - jeditor = React.createElement(Jeditor, {title: "Specification", + jeditor = React.createElement(Jeditor, {title: " Task Specification", schema: this.state.taskTemplate.schema, initValue: this.state.task.specifications_doc, disabled: true, @@ -278,15 +395,18 @@ export class TaskView extends Component { title: 'Cannot edit blueprint'}]; if (this.state.task) { actions.push({icon: 'fa-ban', type: 'button', actOn: 'click', - title: this.TASK_END_STATUSES.indexOf(this.state.task.status.toLowerCase())>=0?'Cannot Cancel Task':'Cancel Task', - disabled:this.TASK_END_STATUSES.indexOf(this.state.task.status.toLowerCase())>=0, + title: this.TASK_END_STATUSES.indexOf(this.state.task.status.toLowerCase())>=0?'Cannot Cancel Task': + this.state.permissionById && this.state.permissionById[this.state.taskId].cancel? 'Cancel Task': `${this.access_denied_message} to cancel`, + disabled:this.TASK_END_STATUSES.indexOf(this.state.task.status.toLowerCase())>=0? true: + this.state.permissionById && this.state.permissionById[this.state.taskId].cancel? false: true, props: { callback: this.showCancelConfirmation } }); } } actions.push({icon: 'fa fa-trash',title:this.state.hasBlueprint ? 'Cannot delete Draft when Blueprint exists': this.state.permissionById && this.state.permissionById[this.state.taskId].delete? 'Delete Task': `${this.access_denied_message} to delete`, - type: 'button', disabled: this.state.hasBlueprint, actOn: 'click', props:{ callback: this.showDeleteConfirmation}}); + type: 'button', disabled: this.state.hasBlueprint ? true: + this.state.permissionById && this.state.permissionById[this.state.taskId].delete? false: true, actOn: 'click', props:{ callback: this.showDeleteConfirmation}}); actions.push({ icon: 'fa-window-close', link: this.props.history.goBack, title:'Click to Close Task', type: 'button', actOn: 'click', props:{ callback: this.cancelView }}); @@ -411,12 +531,33 @@ export class TaskView extends Component { </div> </div> } + {this.state.taskType === 'blueprint' && + <div style={{marginBottom: "10px"}}> + <div style={{marginTop: "10px"}}> + <h3>Subtasks</h3> + </div> + <ViewTable + data={this.state.subtaskRowList} + defaultcolumns={this.state.defaultcolumns} + optionalcolumns={this.state.optionalcolumns} + columnclassname={this.state.columnclassname} + defaultSortColumn={this.defaultSortColumn} + showFilterOption={this.getFilterOptions} + paths={this.state.paths} + lsKeySortColumn={this.lsKeySortColumn} + storeFilter={true} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + tablename="subtask_list" + /> + </div> + } <div className="p-fluid"> <div className="p-grid"><div className="p-col-12"> {this.state.taskTemplate?jeditor:""} </div></div> </div> </div> + </React.Fragment> } <CustomDialog type="confirmation" visible={this.state.confirmDialogVisible} width={this.state.dialog.width} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.tabs.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.tabs.js index 1d6b270679205a88a36a258e980942d7e49b2fbd..d912e1ed14be8889456b2dd5fc74c7df68f68678 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.tabs.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.tabs.js @@ -27,6 +27,7 @@ class TimelineListTabs extends Component { this.timelineUIAttributes = UtilService.localStore({ type: 'get', key: "TIMELINE_UI_ATTR" }) || {}; this.suListFilterCallback = this.suListFilterCallback.bind(this); this.taskListFilterCallback = this.taskListFilterCallback.bind(this); + this.reservListFilterCallback = this.reservListFilterCallback.bind(this); this.getTaskList = this.getTaskList.bind(this); this.getSUFilterOptions = this.getSUFilterOptions.bind(this); this.getTaskFilterOptions = this.getTaskFilterOptions.bind(this); @@ -70,7 +71,8 @@ class TimelineListTabs extends Component { */ suListFilterCallback(filteredData) { this.filteredSUB = filteredData; - this.props.suListFilterCallback(filteredData); + this.filteredTasks = null; + this.props.suListFilterCallback(filteredData, this.filteredTasks, this.filteredReservs); } /** @@ -79,7 +81,18 @@ class TimelineListTabs extends Component { * @param {Array} filteredData - Array of task table rows */ taskListFilterCallback(filteredData) { - this.props.suListFilterCallback(this.filteredSUB, filteredData); + this.filteredTasks = filteredData; + this.props.suListFilterCallback(this.filteredSUB, filteredData, this.filteredReservs); + } + + /** + * Callback function passed to Reservation list table which in turn call back the parenst (View or Weekview) callback function + * to show or hide the reservations shown in the timeline. + * @param {Array} filteredData - Array of reservation table rows + */ + reservListFilterCallback(filteredData) { + this.filteredReservs = filteredData; + this.props.suListFilterCallback(this.filteredSUB, this.filteredTasks, filteredData); } /** @@ -210,7 +223,7 @@ class TimelineListTabs extends Component { showColumnFilter={true} tablename={`timeline_reservation_list`} showTopTotal={false} - // filterCallback={this.taskListFilterCallback} // TODO: Implementing filter callback to timeline + filterCallback={this.reservListFilterCallback} lsKeySortColumn={"ResListSortColumn"} toggleBySorting={(sortData) => this.storeSortingColumn("ResListSortColumn", sortData)} pageUpdated={this.pageUpdated} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js index 4be3566be8f1d9b73f068c97e27c9668bb6066e5..7dbaeb520b44657ba6f61fdbc5d4082ea241cbd3 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -22,8 +22,6 @@ import UnitConverter from '../../utils/unit.converter'; import Validator from '../../utils/validator'; import SchedulingUnitSummary from '../Scheduling/summary'; import ReservationSummary from '../Reservation/reservation.summary'; -import { Dropdown } from 'primereact/dropdown'; -import { OverlayPanel } from 'primereact/overlaypanel'; import { TieredMenu } from 'primereact/tieredmenu'; import { MultiSelect } from 'primereact/multiselect'; import { Button } from 'primereact/button'; @@ -97,6 +95,7 @@ export class TimelineView extends Component { taskStatusList: [], datasetStartTime: null, datasetEndTime:null, showDialog: false, + popPosition: {display: 'none'} } this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable']; // Statuses before scheduled to get station_group this.allStationsGroup = []; @@ -146,10 +145,6 @@ export class TimelineView extends Component { ] this.setState({menuOptions: menuOptions, loader: true }); TaskService.getTaskTypes().then(results => {taskTypes = results}); - // Fetch Reservations and keep ready to use in station view - ReservationService.getReservations().then(reservations => { - this.reservations = reservations; - }); UtilService.getReservationTemplates().then(templates => { this.reservationTemplate = templates.length > 0 ? templates[0] : null; if (this.reservationTemplate) { @@ -301,6 +296,11 @@ export class TimelineView extends Component { * @returns - Object with both original SUBs from backend and formatted SUBs for table list. */ async loadSchedulingUnits(startTime, endTime) { + ReservationService.getTimelineReservations(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), + endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)) + .then(reservations => { + this.reservations = _.uniqBy(this.reservations.concat(reservations), 'id'); + }); let suBlueprints = await ScheduleService.getExpandedSUList(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)); suBlueprints = _.filter(suBlueprints, suBlueprint => suBlueprint.status.toLowerCase !== 'obsolete'); @@ -385,7 +385,6 @@ export class TimelineView extends Component { let items = [], itemGroup = []; const subtaskTemplates = this.subtaskTemplates; for (let task of suBlueprint.tasks) { - console.log(task); if (((!this.state.stationView && this.state.selectedTaskTypes.indexOf(task.task_type)>=0) || (this.state.stationView && task.task_type === 'observation')) && ( filteredTasks.length===0 || filteredTasks.indexOf(task.id)>=0 ) @@ -545,7 +544,7 @@ export class TimelineView extends Component { * @param {Event} evt */ onItemMouseOut(evt) { - this.popOver.toggle(evt); + this.setState({popPosition: {display: 'none'}}); } /** @@ -554,6 +553,13 @@ export class TimelineView extends Component { * @param {Object} item */ onItemMouseOver(evt, item) { + let popPosition = {display:"block", + left:`${evt.pageX+400>window.innerWidth?evt.pageX-400:evt.pageX+20}px`}; + if (evt.clientY > window.screen.height/2) { + popPosition.bottom = `${evt.clientY - evt.pageY+30}px`; + } else { + popPosition.top = `${evt.pageY}px`; + } if (item.type === "SCHEDULE" || item.type === "TASK" || item.type==="STATION_TASK" ) { const itemSU = _.find(this.state.suBlueprints, { id: (item.suId ? item.suId : item.id) }); item.suName = itemSU.name; @@ -568,8 +574,7 @@ export class TimelineView extends Component { item.stations = reservStations; item.planned = reservation.specifications_doc.activity.planned; } - this.popOver.toggle(evt); - this.setState({ mouseOverItem: item }); + this.setState({ mouseOverItem: item, popPosition: popPosition }); } /** @@ -682,9 +687,10 @@ export class TimelineView extends Component { * @param {moment} startTime * @param {moment} endTime */ - addStationReservations(items, startTime, endTime) { + addStationReservations(items, startTime, endTime, reservFilterData) { let reservations = this.reservations; let reservationItems = []; + let filteredReservations = reservFilterData?_.map(reservFilterData, data => {data.id = data.actionpath.split("view/")[1]; return parseInt(data.id);}):[]; for (const reservation of reservations) { const reservationStartTime = moment.utc(reservation.start_time); const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : endTime; @@ -697,6 +703,7 @@ export class TimelineView extends Component { || reservationEndTime.isBetween(startTime, endTime) || (reservationStartTime.isSameOrBefore(startTime) && reservationEndTime.isSameOrAfter(endTime))) + && (filteredReservations.length === 0 || filteredReservations.indexOf(reservation.id)>=0) && (this.state.reservationFilter.length === 0 || // No reservation filter added this.state.reservationFilter.indexOf(reservationSpec.activity.type) >= 0 )) { // Reservation reason == Filtered reaseon if (this.state.stationView) { @@ -730,7 +737,8 @@ export class TimelineView extends Component { start_time: start_time, end_time: end_time, name: reservationSpec.activity.type, project: reservation.project_id, group: station, type: 'RESERVATION', - title: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + actType: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + title: "", desc: reservation.description, duration: reservation.duration ? UnitConverter.getSecsToHHmmss(reservation.duration) : "Unknown", bgColor: blockColor.bgColor, selectedBgColor: blockColor.bgColor, color: blockColor.color @@ -744,7 +752,8 @@ export class TimelineView extends Component { start_time: start_time, end_time: end_time, name: reservationSpec.activity.type, project: reservation.project_id, group: "RESERVATION", type: 'RESERVATION', - title: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + actType: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + title: "", desc: reservation.description, duration: reservation.duration ? UnitConverter.getSecsToHHmmss(reservation.duration) : "Unknown", bgColor: blockColor.bgColor, selectedBgColor: blockColor.bgColor, color: blockColor.color @@ -790,16 +799,18 @@ export class TimelineView extends Component { let reservations = this.reservations; for (const reservation of reservations) { const reservationStartTime = moment.utc(reservation.start_time); - const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : endTime; + const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : null; const reservationSpec = reservation.specifications_doc; - if ((reservationStartTime.isSame(startTime) - || reservationStartTime.isSame(endTime) - || reservationStartTime.isBetween(startTime, endTime) - || reservationEndTime.isSame(startTime) - || reservationEndTime.isSame(endTime) - || reservationEndTime.isBetween(startTime, endTime) - || (reservationStartTime.isSameOrBefore(startTime) - && reservationEndTime.isSameOrAfter(endTime))) + // if ((reservationStartTime.isSame(startTime) + // || reservationStartTime.isSame(endTime) + // || reservationStartTime.isBetween(startTime, endTime) + // || reservationEndTime.isSame(startTime) + // || reservationEndTime.isSame(endTime) + // || reservationEndTime.isBetween(startTime, endTime) + // || (reservationStartTime.isSameOrBefore(startTime) + // && reservationEndTime.isSameOrAfter(endTime))) + if ((reservationStartTime.isSameOrBefore(endTime) + && (reservationEndTime === null || reservationEndTime.isSameOrAfter(startTime))) && (this.state.reservationFilter.length === 0 || // No reservation filter added this.state.reservationFilter.indexOf(reservationSpec.activity.type) >= 0 )) { // Reservation reason == Filtered reaseon if (!this.state.stationView || @@ -886,12 +897,13 @@ export class TimelineView extends Component { /** * Callback function to pass to the Scheduling Unit Table component to pass back filtered data. - * The same function is called when the filter applied in the task table so that the tasks shown in the timeline - * are updated accordingly. + * The same function is called when the filter applied in the task and reservation tables so that + * the tasks and reservations shown in the timeline are updated accordingly. * @param {Array} filteredSUData - filtered data from Scheduling Unit list table * @param {Array} filteredTaskData - filtered data from Task list table. This will be null when the function is called by SU List table. + * @param {Array} filteredReservData - filtered data from Reservation list table. */ - suListFilterCallback(filteredSUData, filteredTaskData) { + suListFilterCallback(filteredSUData, filteredTaskData, filteredReservData) { let group = [], items = []; const suBlueprints = this.state.suBlueprints; for (const data of filteredSUData) { @@ -920,7 +932,7 @@ export class TimelineView extends Component { } } if (this.state.stationView || this.state.showReservation) { - items = this.addStationReservations(items, this.state.currentStartTime, this.state.currentEndTime); + items = this.addStationReservations(items, this.state.currentStartTime, this.state.currentEndTime, filteredReservData); } if (this.state.showReservation) { let reservationGroup = [{id: "RESERVATION", parent: "RESERVATION", @@ -1475,7 +1487,8 @@ export class TimelineView extends Component { } {/* SU Item Tooltip popover with SU status color */} - <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> + <div class="p-overlaypanel p-component timeline-popover" style={{...this.state.popPosition, zIndex: 1324, opacity: 1.944}}> + <div class="p-overlaypanel-content"> {(mouseOverItem && (["SCHEDULE", "TASK", "STATION_TASK"].indexOf(mouseOverItem.type) >= 0)) && <div className={`p-grid su-${mouseOverItem.status}`} style={{ width: '350px' }}> <h3 className={`col-12 su-${mouseOverItem.status}-icon`}>{mouseOverItem.type === 'SCHEDULE' ? 'Scheduling Unit ' : 'Task '}Overview</h3> @@ -1545,7 +1558,8 @@ export class TimelineView extends Component { <div className="col-7">{mouseOverItem.planned ? 'Yes' : 'No'}</div> </div> } - </OverlayPanel> + </div> + </div> {!this.state.isLoading && <Websocket url={process.env.REACT_APP_WEBSOCKET_URL} onOpen={this.onConnect} onMessage={this.handleData} onClose={this.onDisconnect} />} {this.state.showDialog && diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js index c33cd2a91c08c3af6c976a193570990288a63707..5c97d3c7250de69c2b59bbc4280cf328aa871eac 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js @@ -21,7 +21,6 @@ import UnitConverter from '../../utils/unit.converter'; import Validator from '../../utils/validator'; import SchedulingUnitSummary from '../Scheduling/summary'; import UIConstants from '../../utils/ui.constants'; -import { OverlayPanel } from 'primereact/overlaypanel'; import { TieredMenu } from 'primereact/tieredmenu'; import { InputSwitch } from 'primereact/inputswitch'; import { Dropdown } from 'primereact/dropdown'; @@ -76,6 +75,7 @@ export class WeekTimelineView extends Component { datasetStartTime: null, datasetEndTime:null, showDialog: false, userrole: AuthStore.getState(), + popPosition: {display: 'none'} } this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable']; // Statuses before scheduled to get station_group this.reservations = []; @@ -115,9 +115,9 @@ export class WeekTimelineView extends Component { ] this.setState({menuOptions: menuOptions, userPermission: weekviewPermission}); - ReservationService.getReservations().then(reservations => { - this.reservations = reservations; - }); + // ReservationService.getReservations().then(reservations => { + // this.reservations = reservations; + // }); UtilService.getReservationTemplates().then(templates => { this.reservationTemplate = templates.length > 0 ? templates[0] : null; if (this.reservationTemplate) { @@ -244,6 +244,11 @@ export class WeekTimelineView extends Component { */ async loadSchedulingUnits(startTime, endTime) { let suList = []; + ReservationService.getTimelineReservations(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), + endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)) + .then(reservations => { + this.reservations = _.uniqBy(this.reservations.concat(reservations), 'id'); + }); let suBlueprints = await ScheduleService.getExpandedSUList(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)); suBlueprints = _.filter(suBlueprints, suBlueprint => suBlueprint.status.toLowerCase !== 'obsolete'); @@ -396,7 +401,7 @@ export class WeekTimelineView extends Component { * @param {Event} evt */ onItemMouseOut(evt) { - this.popOver.toggle(evt); + this.setState({popPosition: {display: 'none'}}); } /** @@ -405,6 +410,13 @@ export class WeekTimelineView extends Component { * @param {Object} item */ onItemMouseOver(evt, item) { + let popPosition = {display:"block", + left:`${evt.pageX+400>window.innerWidth?evt.pageX-400:evt.pageX+20}px`}; + if (evt.clientY > window.screen.height/2) { + popPosition.bottom = `${evt.clientY - evt.pageY+30}px`; + } else { + popPosition.top = `${evt.pageY}px`; + } if (item.type === "SCHEDULE") { const itemSU = _.find(this.state.suBlueprints, { id: parseInt(item.id.split("-")[0]) }); const itemStations = itemSU.stations; @@ -436,8 +448,7 @@ export class WeekTimelineView extends Component { item.displayStartTime = moment.utc(reservation.start_time); item.displayEndTime = reservation.duration ? moment.utc(reservation.stop_time) : null; } - this.popOver.toggle(evt); - this.setState({ mouseOverItem: item }); + this.setState({ mouseOverItem: item, popPosition: popPosition }); } /** @@ -460,10 +471,11 @@ export class WeekTimelineView extends Component { currentUTC = this.state.currentUTC.clone().add(direction * 7, 'days'); if (startTime && endTime) { for (const suBlueprint of suBlueprints) { - if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) + if ((moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime, endTime) && - moment.utc(suBlueprint.stop_time).isSameOrAfter(startTime, endTime))) { + moment.utc(suBlueprint.stop_time).isSameOrAfter(startTime, endTime))) + ) { suBlueprintList.push(suBlueprint); const suStartTime = moment.utc(suBlueprint.start_time); const suEndTime = moment.utc(suBlueprint.stop_time); @@ -548,22 +560,47 @@ export class WeekTimelineView extends Component { } /** - * Callback function to pass to the ViewTable component to pass back filtered data - * @param {Array} filteredData + * Callback function to pass to the list tab component to pass back filtered data from the tables. + * Updates the timeline with filtered SU and reservation blocks. + * @param {Array} filteredSUData + * @param {Array} filteredTaskData + * @param {Array} filteredReservData */ - suListFilterCallback(filteredData) { - /*let group=[], items = []; + async suListFilterCallback(filteredSUData, filteredTaskData, filteredReservData) { + let currentUTC = this.state.currentUTC; + const suFilters = _.map(filteredSUData, data => parseInt(data.Id)); + let group=[], items = []; + const startTime = this.state.startTime; + const endTime = this.state.endTime; const suBlueprints = this.state.suBlueprints; - for (const data of filteredData) { - const suBlueprint = _.find(suBlueprints, {actionpath: data.actionpath}); - items.push(this.getTimelineItem(suBlueprint)); - if (!_.find(group, {'id': suBlueprint.suDraft.id})) { - group.push({'id': suBlueprint.suDraft.id, title: suBlueprint.suDraft.name}); + for (const suBlueprint of suBlueprints) { + if ((moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) + || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) + || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime, endTime) && + moment.utc(suBlueprint.stop_time).isSameOrAfter(startTime, endTime))) + && (suFilters.indexOf(suBlueprint.id) >= 0) + ) { + const suStartTime = moment.utc(suBlueprint.start_time); + const suEndTime = moment.utc(suBlueprint.stop_time); + if (suStartTime.format("MM-DD-YYYY") !== suEndTime.format("MM-DD-YYYY")) { + let suBlueprintStart = _.cloneDeep(suBlueprint); + let suBlueprintEnd = _.cloneDeep(suBlueprint); + suBlueprintStart.stop_time = suStartTime.hour(23).minutes(59).seconds(59).format('YYYY-MM-DDTHH:mm:ss.00000'); + suBlueprintEnd.start_time = suEndTime.hour(0).minutes(0).seconds(0).format('YYYY-MM-DDTHH:mm:ss.00000'); + items.push(await this.getTimelineItem(suBlueprintStart, currentUTC)); + items.push(await this.getTimelineItem(suBlueprintEnd, currentUTC)); + + } else { + items.push(await this.getTimelineItem(suBlueprint, currentUTC)); + } } } + if (this.state.reservationEnabled) { + items = this.addWeekReservations(items, startTime, endTime, currentUTC, filteredReservData); + } if (this.timeline) { - this.timeline.updateTimeline({group: group, items: items}); - }*/ + this.timeline.updateTimeline({group: this.state.group, items: items}); + } } filterByProject(project) { @@ -755,8 +792,9 @@ export class WeekTimelineView extends Component { * @param {moment} startTime * @param {moment} endTime */ - addWeekReservations(items, startTime, endTime, currentUTC) { + addWeekReservations(items, startTime, endTime, currentUTC, reservFilterData) { let reservations = _.cloneDeep(this.reservations); + let filteredReservations = reservFilterData?_.map(reservFilterData, data => {data.id = data.actionpath.split("view/")[1]; return parseInt(data.id);}):[]; for (const reservation of reservations) { const reservationStartTime = moment.utc(reservation.start_time); const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : endTime; @@ -769,6 +807,7 @@ export class WeekTimelineView extends Component { || reservationEndTime.isBetween(startTime, endTime) || (reservationStartTime.isSameOrBefore(startTime) && reservationEndTime.isSameOrAfter(endTime))) + && (filteredReservations.length === 0 || filteredReservations.indexOf(reservation.id)>=0) && (!this.state.reservationFilter || // No reservation filter added reservationSpec.activity.type === this.state.reservationFilter)) { // Reservation reason == Filtered reaseon reservation.stop_time = reservationEndTime; @@ -835,7 +874,8 @@ export class WeekTimelineView extends Component { name: reservationSpec.activity.type, project: reservation.project_id, group: group, type: 'RESERVATION', - title: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + actType: `${reservationSpec.activity.type}${reservation.project_id ? ("-" + reservation.project_id) : ""}`, + title: '', desc: reservation.description, duration: reservation.duration ? UnitConverter.getSecsToHHmmss(reservation.duration) : "Unknown", bgColor: blockColor.bgColor, selectedBgColor: blockColor.bgColor, color: blockColor.color @@ -871,18 +911,12 @@ export class WeekTimelineView extends Component { let reservations = this.reservations; for (const reservation of reservations) { const reservationStartTime = moment.utc(reservation.start_time); - const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : endTime; + const reservationEndTime = reservation.duration ? reservationStartTime.clone().add(reservation.duration, 'seconds') : null; const reservationSpec = reservation.specifications_doc; - if ((reservationStartTime.isSame(startTime) - || reservationStartTime.isSame(endTime) - || reservationStartTime.isBetween(startTime, endTime) - || reservationEndTime.isSame(startTime) - || reservationEndTime.isSame(endTime) - || reservationEndTime.isBetween(startTime, endTime) - || (reservationStartTime.isSameOrBefore(startTime) - && reservationEndTime.isSameOrAfter(endTime))) + if ((reservationStartTime.isSameOrBefore(endTime) + && (reservationEndTime === null || reservationEndTime.isSameOrAfter(startTime))) && (!this.state.reservationFilter || // No reservation filter added - reservationSpec.activity.type === this.state.reservationFilter)) { // Reservation reason == Filtered reaseon + reservationSpec.activity.type === this.state.reservationFilter)) { // Reservation reason == Filtered reaseon let item = _.cloneDeep(reservation); item.stop_time = item.stop_time || "Unknown"; item.duration = UnitConverter.getSecsToDDHHmmss(item.duration) || "Unknown"; @@ -1103,7 +1137,8 @@ export class WeekTimelineView extends Component { </> } {/* SU Item Tooltip popover with SU status color */} - <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> + <div class="p-overlaypanel p-component timeline-popover" style={{...this.state.popPosition, zIndex: 1324, opacity: 1.944}}> + <div class="p-overlaypanel-content"> {mouseOverItem && mouseOverItem.type === "SCHEDULE" && <div className={`p-grid su-${mouseOverItem.status}`} style={{ width: '350px' }}> <label className={`col-5 su-${mouseOverItem.status}-icon`}>Project:</label> @@ -1159,7 +1194,8 @@ export class WeekTimelineView extends Component { <div className="col-7">{mouseOverItem.planned ? 'Yes' : 'No'}</div> </div> } - </OverlayPanel> + </div> + </div> {/* Open Websocket after loading all initial data */} {!this.state.isLoading && <Websocket url={process.env.REACT_APP_WEBSOCKET_URL} onOpen={this.onConnect} onMessage={this.handleData} onClose={this.onDisconnect} />} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js index c0a348feefdcc9f90b0a8ab4f158b59f484c6af5..3ca64af657c4f18c6e2fd8893bc6d5e1ae8b2578 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js @@ -45,6 +45,33 @@ const ReservationService = { } return reservations; }, + /** Get the reservations that are present during the period specified by startTime and stopTime */ + getTimelineReservations: async(startTime, stopTime) => { + let reservations = []; + try { + let url = `/api/reservation/?ordering=id`; + if (stopTime) { // Gets the reservations started and stopped between the period + url = `${url}&start_time_before=${stopTime || ''}&stop_time_after=${startTime || ''}`; + } else if (startTime) { // Gets the reservations started before the startTime and still exists + url = `${url}&start_time_before=${startTime || ''}&stop_time_isnull=true`; + } + let initialResponse = await axios.get(url); + const totalCount = initialResponse.data.count; + const initialCount = initialResponse.data.results.length + reservations = reservations.concat(initialResponse.data.results); + if (totalCount > initialCount) { + let secondResponse = await axios.get(`${url}&limit=${totalCount-initialCount}&offset=${initialCount}`); + reservations = reservations.concat(secondResponse.data.results); + } + if (stopTime) { // Get the reservations started before the stopTime and exists. + let indefiniteReservations = await ReservationService.getTimelineReservations(stopTime); + reservations = reservations.concat(indefiniteReservations); + } + } catch(error) { + console.error('[schedule.services.getTimelineReservations]',error); + } + return reservations; + }, getReservation: async function (id) { try { const response = await axios.get(`/api/reservation/${id}`); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js index 67a80523c6c95435e55440140bf06c1daed5689b..975a98d6cad27318c10ca731d4da3c08646ae747 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js @@ -1,9 +1,9 @@ const axios = require('axios'); const TaskService = { - getTaskDetails: async function (taskType, taskId) { + getTaskDetails: async function (taskType, taskId, fetchSubtask) { try { - const responseData = await this.getTask(taskType, taskId); + const responseData = await this.getTask(taskType, taskId, fetchSubtask); responseData.predecessors = []; responseData.successors = []; if (taskType === 'blueprint') { @@ -27,9 +27,15 @@ const TaskService = { console.error(error); } }, - getTask : async function (taskType, taskId) { + getTask : async function (taskType, taskId, fetchSubtask) { try { - const url = taskType === 'blueprint'? '/api/task_blueprint/'+taskId+'?expand=subtasks': '/api/task_draft/'+taskId; + let url = '' + if(fetchSubtask){ + url = taskType === 'blueprint'? '/api/task_blueprint/'+taskId+'?expand=subtasks': '/api/task_draft/'+taskId; + } + else { + url = taskType === 'blueprint'? '/api/task_blueprint/'+taskId: '/api/task_draft/'+taskId; + } const response = await axios.get(url);// + taskId); return response.data; } catch (error) { @@ -260,6 +266,14 @@ const TaskService = { } return taskRelations; }, + getSubtaskType: async function() { + try { + return (await axios.get(`/api/subtask_type`)).data; + + } catch(error) { + + } + }, /** * Delete task based on task type * @param {*} type