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 71489a909d5d729c8c3ca06751d5d63bd39e073f..a045bc5f47a2b7559902a5ba1f0b9f996bafff9a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -572,7 +572,7 @@ export class CalendarTimeline extends Component { if (this.state.viewType===UIConstants.timeline.types.NORMAL) { const startTime = moment().utc().add(-24, 'hours'); const endTime = moment().utc().add(24, 'hours'); - let result = this.props.dateRangeCallback(startTime, endTime); + let result = await this.props.dateRangeCallback(startTime, endTime); let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: startTime, defaultEndTime: endTime, zoomLevel: DEFAULT_ZOOM_LEVEL, dayHeaderVisible: true, @@ -626,12 +626,7 @@ export class CalendarTimeline extends Component { } } this.loadLSTDateHeaderMap(startTime, endTime, 'hour'); - let result = {}; - if (this.state.viewType===UIConstants.timeline.types.WEEKVIEW) { - result = await this.props.dateRangeCallback(startTime, endTime); - } else { - result = this.props.dateRangeCallback(startTime, endTime); - } + let result = await this.props.dateRangeCallback(startTime, endTime); let group = DEFAULT_GROUP.concat(result.group); this.setState({zoomLevel: zoomLevel, defaultStartTime: startTime, defaultEndTime: endTime, isTimelineZoom: isTimelineZoom, zoomRange: null, @@ -650,17 +645,12 @@ export class CalendarTimeline extends Component { let secondsToMove = visibleTimeDiff / 1000 / 10 ; let newVisibleTimeStart = visibleTimeStart.clone().add(-1 * secondsToMove, 'seconds'); let newVisibleTimeEnd = visibleTimeEnd.clone().add(-1 * secondsToMove, 'seconds'); - let result = {}; if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW && newVisibleTimeStart.isBefore(this.state.timelineStartDate)) { newVisibleTimeStart = this.state.timelineStartDate.clone().hours(0).minutes(0).seconds(0); newVisibleTimeEnd = newVisibleTimeStart.clone().add(visibleTimeDiff/1000, 'seconds'); } - if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) { - result = await this.props.dateRangeCallback(newVisibleTimeStart, newVisibleTimeEnd); - } else { - result = this.props.dateRangeCallback(newVisibleTimeStart, newVisibleTimeEnd); - } + let result = await this.props.dateRangeCallback(newVisibleTimeStart, newVisibleTimeEnd); this.loadLSTDateHeaderMap(newVisibleTimeStart, newVisibleTimeEnd, 'hour'); let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: newVisibleTimeStart, @@ -678,17 +668,12 @@ export class CalendarTimeline extends Component { const secondsToMove = visibleTimeDiff / 1000 / 10 ; let newVisibleTimeStart = visibleTimeStart.clone().add(1 * secondsToMove, 'seconds'); let newVisibleTimeEnd = visibleTimeEnd.clone().add(1 * secondsToMove, 'seconds'); - let result = {}; if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW && newVisibleTimeEnd.isAfter(this.state.timelineEndDate)) { newVisibleTimeEnd = this.state.timelineEndDate.clone().hours(23).minutes(59).minutes(59); newVisibleTimeStart = newVisibleTimeEnd.clone().add((-1 * visibleTimeDiff/1000), 'seconds'); } - if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) { - result = await this.props.dateRangeCallback(visibleTimeStart, visibleTimeEnd); - } else { - result = this.props.dateRangeCallback(visibleTimeStart, visibleTimeEnd); - } + let result = await this.props.dateRangeCallback(visibleTimeStart, visibleTimeEnd); this.loadLSTDateHeaderMap(newVisibleTimeStart, newVisibleTimeEnd, 'hour'); let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: newVisibleTimeStart, @@ -726,7 +711,7 @@ export class CalendarTimeline extends Component { * calls back parent to get updated group and item records, LST date header values * @param {array} value - array of moment object */ - setZoomRange(value){ + async setZoomRange(value){ let startDate, endDate = null; if (value) { // Set all values only when both range values available in the array else just set the value to reflect in the date selection component @@ -746,7 +731,7 @@ export class CalendarTimeline extends Component { dayHeaderVisible: dayHeaderVisible, weekHeaderVisible: weekHeaderVisible, lstDateHeaderUnit: lstDateHeaderUnit }); - const result = this.props.dateRangeCallback(startDate, endDate); + const result = await this.props.dateRangeCallback(startDate, endDate); let group = DEFAULT_GROUP.concat(result.group); this.setState({group: group, items: result.items}); this.loadLSTDateHeaderMap(startDate, endDate, lstDateHeaderUnit); @@ -795,7 +780,7 @@ export class CalendarTimeline extends Component { return ( <React.Fragment> {/* Toolbar for the timeline */} - <div className="p-fluid p-grid timeline-toolbar"> + <div className={`p-fluid p-grid timeline-toolbar ${this.props.className}`}> {/* Clock Display */} <div className="p-col-2" style={{padding: '0px 0px 0px 10px'}}> <div style={{marginTop: "0px"}}> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss index 7161a159b4277ff85a1b7b7e1b107b0d97dca3e1..1c5e635be11f8ff590511f5b9407cbc7242a250a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss @@ -6,6 +6,20 @@ background-color: #f0f0f0; } +.timeline-view-toolbar { + margin-left: 10px; +} + +.timeline-view-toolbar label { + margin-bottom: 0px; + vertical-align: top; + margin-right: 10px; +} + +.timeline-toolbar-margin-top-0 { + margin-top: 0px !important; +} + .timeline-toolbar { margin-top: 25px; margin-bottom: 2px; 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 4f4277be8391a5b0f342c1382493301ad863965f..16524edc0f57b879a3302545d2ba588c2fcc087e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -4,6 +4,7 @@ import moment from 'moment'; import _ from 'lodash'; // import SplitPane, { Pane } from 'react-split-pane'; +import {InputSwitch} from 'primereact/inputswitch'; import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; @@ -44,6 +45,7 @@ export class TimelineView extends Component { suTaskList:[], isSummaryLoading: false } + this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable']; // Statuses before scheduled to get station_group this.onItemClick = this.onItemClick.bind(this); this.closeSUDets = this.closeSUDets.bind(this); @@ -59,7 +61,7 @@ export class TimelineView extends Component { ScheduleService.getSchedulingUnitDraft(), ScheduleService.getSchedulingSets(), UtilService.getUTC()] ; - Promise.all(promises).then(responses => { + Promise.all(promises).then(async(responses) => { const projects = responses[0]; const suBlueprints = _.sortBy(responses[1].data.results, 'name'); const suDrafts = responses[2].data.results; @@ -81,15 +83,24 @@ export class TimelineView extends Component { suBlueprint.suSet = suSet; suBlueprint.durationInSec = suBlueprint.duration; suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); + // Load subtasks also to get stations from subtask if status is before scheduled + const loadSubtasks = this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 ; // Select only blueprints with start_time and stop_time in the default time limit if (suBlueprint.start_time && (moment.utc(suBlueprint.start_time).isBetween(defaultStartTime, defaultEndTime) || moment.utc(suBlueprint.stop_time).isBetween(defaultStartTime, defaultEndTime))) { + // suBlueprint.tasks = await ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true); + suBlueprint.tasks = await ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true, loadSubtasks); items.push(this.getTimelineItem(suBlueprint)); if (!_.find(group, {'id': suDraft.id})) { group.push({'id': suDraft.id, title: suDraft.name}); } suList.push(suBlueprint); + } else if (suBlueprint.start_time) { // For other SUs with start_time load details asynchronously + ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true, loadSubtasks) + .then(tasks => { + suBlueprint.tasks = tasks; + }) } } } @@ -108,7 +119,7 @@ export class TimelineView extends Component { getTimelineItem(suBlueprint) { // Temporary for testing const diffOfCurrAndStart = moment().diff(moment(suBlueprint.stop_time), 'seconds'); - suBlueprint.status = diffOfCurrAndStart>=0?"FINISHED":"DEFINED"; + // suBlueprint.status = diffOfCurrAndStart>=0?"FINISHED":"DEFINED"; let item = { id: suBlueprint.id, group: suBlueprint.suDraft.id, title: `${suBlueprint.project.name} - ${suBlueprint.suDraft.name} - ${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`, @@ -136,7 +147,7 @@ export class TimelineView extends Component { suTaskList: !fetchDetails?this.state.suTaskList:[], canExtendSUList: false, canShrinkSUList:false}); if (fetchDetails) { - const suBlueprint = _.find(this.state.suBlueprints, {id: item.id}); + const suBlueprint = _.find(this.state.suBlueprints, {id: (this.state.stationView?parseInt(item.id.split('-')[0]):item.id)}); ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true) .then(taskList => { for (let task of taskList) { @@ -163,17 +174,24 @@ export class TimelineView extends Component { * @param {moment} startTime * @param {moment} endTime */ - dateRangeCallback(startTime, endTime) { + async dateRangeCallback(startTime, endTime) { let suBlueprintList = [], group=[], items = []; if (startTime && endTime) { for (const suBlueprint of this.state.suBlueprints) { if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime)) { - suBlueprintList.push(suBlueprint); - items.push(this.getTimelineItem(suBlueprint)); - if (!_.find(group, {'id': suBlueprint.suDraft.id})) { - group.push({'id': suBlueprint.suDraft.id, title: suBlueprint.suDraft.name}); + let timelineItem = this.getTimelineItem(suBlueprint); + if (this.state.stationView) { + const loadSubtasks = this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 ; + suBlueprint.tasks = await ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true, loadSubtasks); + this.getStationItemGroups(suBlueprint, timelineItem, group, items); + } else { + items.push(timelineItem); + if (!_.find(group, {'id': suBlueprint.suDraft.id})) { + group.push({'id': suBlueprint.suDraft.id, title: suBlueprint.suDraft.name}); + } } + suBlueprintList.push(suBlueprint); } } } else { @@ -184,7 +202,48 @@ export class TimelineView extends Component { this.setState({suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => {return suBlueprint.start_time!=null})}); // On range change close the Details pane // this.closeSUDets(); - return {group: group, items: items}; + return {group: _.sortBy(group,'id'), items: items}; + } + + /** + * To get items and groups for station view + * @param {Object} suBlueprint + * @param {Object} timelineItem + * @param {Array} group + * @param {Array} items + */ + getStationItemGroups(suBlueprint, timelineItem, group, items) { + /** Get all observation tasks */ + const observtionTasks = _.filter(suBlueprint.tasks, (task) => { return task.template.type_value.toLowerCase() === "observation"}); + let stations = []; + for (const observtionTask of observtionTasks) { + /** If the status of SU is before scheduled, get all stations from the station_groups from the task specification_docs */ + if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) >= 0 + && observtionTask.specifications_doc.station_groups) { + for (const grpStations of _.map(observtionTask.specifications_doc.station_groups, "stations")) { + stations = _.concat(stations, grpStations); + } + } else if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 + && observtionTask.subTasks) { + /** If the status of SU is scheduled or after get the stations from the subtask specification tasks */ + for (const subtask of observtionTask.subTasks) { + if (subtask.specifications_doc.stations) { + stations = _.concat(stations, subtask.specifications_doc.stations.station_list); + } + } + } + } + stations = _.uniq(stations); + /** Group the items by station */ + for (const station of stations) { + let stationItem = _.cloneDeep(timelineItem); + stationItem.id = `${stationItem.id}-${station}`; + stationItem.group = station; + items.push(stationItem); + if (!_.find(group, {'id': station})) { + group.push({'id': station, title: station}); + } + } } /** @@ -215,13 +274,18 @@ export class TimelineView extends Component { 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}); + let timelineItem = this.getTimelineItem(suBlueprint); + if (this.state.stationView) { + this.getStationItemGroups(suBlueprint, timelineItem, group, items); + } else { + items.push(timelineItem); + if (!_.find(group, {'id': suBlueprint.suDraft.id})) { + group.push({'id': suBlueprint.suDraft.id, title: suBlueprint.suDraft.name}); + } } } if (this.timeline) { - this.timeline.updateTimeline({group: group, items: items}); + this.timeline.updateTimeline({group: _.sortBy(group,"id"), items: items}); } } @@ -234,7 +298,7 @@ export class TimelineView extends Component { const canShrinkSUList = this.state.canShrinkSUList; let suBlueprint = null; if (isSUDetsVisible) { - suBlueprint = _.find(this.state.suBlueprints, {id: this.state.selectedItem.id}); + suBlueprint = _.find(this.state.suBlueprints, {id: this.state.stationView?parseInt(this.state.selectedItem.id.split('-')[0]):this.state.selectedItem.id}); } return ( <React.Fragment> @@ -274,12 +338,17 @@ export class TimelineView extends Component { <i className="pi pi-step-forward"></i> </button> </div> + <div className="timeline-view-toolbar"> + <label>Station View</label> + <InputSwitch checked={this.state.stationView} onChange={(e) => {this.closeSUDets();this.setState({stationView: e.value})}} /> + </div> <Timeline ref={(tl)=>{this.timeline=tl}} group={this.state.group} items={this.state.items} currentUTC={this.state.currentUTC} rowHeight={30} itemClickCallback={this.onItemClick} - dateRangeCallback={this.dateRangeCallback}></Timeline> + dateRangeCallback={this.dateRangeCallback} + className="timeline-toolbar-margin-top-0"></Timeline> </div> {/* Details Panel */} {this.state.isSUDetsVisible && diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index 9b0126fa1d0f054f860f3b8c08994e15a2602675..0b77e10fc80469dba49272d93eec6e6bc720459e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -53,24 +53,31 @@ const ScheduleService = { return null; } }, - getTaskBlueprintById: async function(id, loadTemplate){ + getTaskBlueprintById: async function(id, loadTemplate, loadSubtasks){ let result; try { result = await axios.get('/api/task_blueprint/'+id); if (result.data && loadTemplate) { result.data.template = await TaskService.getTaskTemplate(result.data.specifications_template_id); } + if (result.data && loadSubtasks) { + let subTasks = []; + for (const subtaskId of result.data.subtasks_ids) { + subTasks.push((await TaskService.getSubtaskDetails(subtaskId))); + } + result.data.subTasks = subTasks; + } } catch(error) { console.error('[schedule.services.getTaskBlueprintById]',error); } return result; }, - getTaskBlueprintsBySchedulingUnit: async function(scheduleunit, loadTemplate){ + getTaskBlueprintsBySchedulingUnit: async function(scheduleunit, loadTemplate, loadSubtasks){ // there no single api to fetch associated task_blueprint, so iteare the task_blueprint id to fetch associated task_blueprint let taskblueprintsList = []; if(scheduleunit.task_blueprints_ids){ for(const id of scheduleunit.task_blueprints_ids){ - await this.getTaskBlueprintById(id, loadTemplate).then(response =>{ + await this.getTaskBlueprintById(id, loadTemplate, loadSubtasks).then(response =>{ let taskblueprint = response.data; taskblueprint['tasktype'] = 'Blueprint'; taskblueprint['actionpath'] = '/task/view/blueprint/'+taskblueprint['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 a6044b01419f50c99b67a577aaf486edf7abaf67..34a1e75b3d052010bf1deb74540d3c7761835ae4 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js @@ -169,7 +169,7 @@ const TaskService = { let subtaskTemplates = {}; const taskDetails = (await axios.get(`/api/task_blueprint/${taskId}`)).data; for (const subtaskId of taskDetails.subtasks_ids) { - const subtaskDetails = (await axios.get(`/api/subtask/${subtaskId}`)).data; + const subtaskDetails = await this.getSubtaskDetails(subtaskId); const subtaskLogs = await this.getSubtaskStatusLogs(subtaskId); let template = subtaskTemplates[subtaskDetails.specifications_template_id]; if (!template) { @@ -194,7 +194,13 @@ const TaskService = { console.error(error); } }, - + getSubtaskDetails: async function(subtaskId) { + try { + return (await axios.get(`/api/subtask/${subtaskId}`)).data; + } catch(error) { + console.error(error); + } + } } export default TaskService;