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 6c6b371c6a6ad5452316735763a5d7c1c3b673dd..b89a8f9b65878a3bf4b8943eafa06ffc8ee32bb2 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -486,7 +486,7 @@ export class CalendarTimeline extends Component { intervalStyle = sunsetStyle; } return ( - <div clasName={`suntime-header, ${intervalStyle}`} + <div className={`suntime-header, ${intervalStyle}`} {...getIntervalProps({ interval, style: intervalStyle @@ -644,7 +644,7 @@ export class CalendarTimeline extends Component { cursorTextStyles.backgroundColor = '#c40719' cursorTextStyles.width = `${this.state.lineHeight*4}px`; cursorTextStyles.color = '#ffffff'; - cursorTextStyles.zIndex = '9999'; + cursorTextStyles.zIndex = '999'; cursorTextStyles.fontSize = `${this.state.lineHeight/30*8}px`; cursorTextStyles.height = `${this.state.lineHeight - 2}px`; cursorTextStyles.position = styles.position; @@ -681,7 +681,7 @@ export class CalendarTimeline extends Component { itemContext.dimensions.top -= 20; } } else if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) { - itemContext.dimensions.top -= (this.props.rowHeight-10); + itemContext.dimensions.top -= (this.props.rowHeight-5); } else { itemContext.dimensions.top += 3; } @@ -693,10 +693,11 @@ export class CalendarTimeline extends Component { fontSize: "14px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", textAlign: "center"}; - if (this.state.viewType===UIConstants.timeline.types.WEEKVIEW) { + if (item.type === "SCHEDULE") { itemContentStyle = {lineHeight: `${Math.floor(itemContext.dimensions.height/3)}px`, + maxHeight: itemContext.dimensions.height, fontSize: "12px", fontWeight: "600", - overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", + overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "inherit", textAlign: "center"}; } return ( @@ -722,31 +723,23 @@ export class CalendarTimeline extends Component { } } - })} + })} onMouseOver={(evt) => { this.onItemMouseOver(evt, item)}} + onMouseOut={(evt) => { this.onItemMouseOut(evt, item)}} > {itemContext.useResizeHandle ? <div {...leftResizeProps} /> : null} - <div - style={{ - height: itemContext.dimensions.height, - //overflow: "hidden", - paddingLeft: 3, - //textOverflow: "ellipsis", - //whiteSpace: "nowrap" - }} - > - { this.state.viewType===UIConstants.timeline.types.WEEKVIEW && item.type !== "SUNTIME" && - <><div style={itemContentStyle}> - <i style={{fontSize:"12px"}} className={`fa fa-user su-${item.status}-icon`} title="Friend"></i><span>{item.project}</span></div> - <div style={itemContentStyle}><span>{item.duration}</span></div> - <div style={itemContentStyle}><span>{item.band}</span></div> </>} - {this.state.viewType===UIConstants.timeline.types.NORMAL && + { item.type === "SCHEDULE" && + <div style={itemContentStyle}> + <i style={{fontSize:"12px"}} className={`fa fa-user su-${item.status}-icon`} ></i> + <span>{`${item.project} - ${item.suId?item.suId:item.id} - ${item.name} - ${item.band} - ${item.duration}`}</span></div> + } + + { (item.type === "SUNTIME" || item.type === "RESERVATION") && <div style={itemContentStyle}><span>{item.title}</span> {item.type === "RESERVATION" && <div style={itemContentStyle}><span>{item.desc}</span></div> } </div> } - </div> {itemContext.useResizeHandle ? <div {...rightResizeProps} /> : null} </div> ); @@ -810,6 +803,26 @@ export class CalendarTimeline extends Component { } } + /** + * Mouse Over event passed back to the parent. + * @param {Object} item + */ + onItemMouseOver(evt, item) { + if (item.type==="SCHEDULE" && this.props.itemMouseOverCallback) { + this.props.itemMouseOverCallback(evt, item); + } + } + + /** + * Mouse out event passed back to the parent. + * @param {Object} item + */ + onItemMouseOut(evt, item) { + if (item.type==="SCHEDULE" && this.props.itemMouseOutCallback) { + this.props.itemMouseOutCallback(evt); + } + } + /** * Function to call the parent function callback and fetch new data. It also retrieves sunrise and sunset time. * @param {moment} startTime @@ -925,6 +938,21 @@ export class CalendarTimeline extends Component { dayItem.bgColor = "white"; dayItem.selectedBgColor = "white"; sunItems.push(dayItem); + } else { + /* If no sunrise and sunset, show it as night time. Later it should be done as either day or night. */ + let befSunriseItem = { id: `bef-sunrise-${number}-${station.id}`, + group: station.id, + // title: `${timings.sun_rise.start} to ${timings.sun_rise.end}`, + title: "", + project: "", + name: "", + duration: "", + start_time: moment.utc(date.format("YYYY-MM-DD 00:00:00")), + end_time: moment.utc(date.format("YYYY-MM-DD 23:59:59")), + bgColor: "grey", + selectedBgColor: "grey", + type: "SUNTIME"}; + sunItems.push(befSunriseItem); } } } 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 1c97ef47ffd88642225dbd63a8ab7ed8784d0be5..4ab3f51872d4cbbb82bfd6b9a423f9924381f7c5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss @@ -335,4 +335,16 @@ z-index: 999; margin-top: 5px; // margin-left: 8px; +} + +.timeline-popover { + z-index: 1000; +} + +.timeline-popover:before { + display: none !important; +} + +.timeline-popover:after { + display: none !important; } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js index fa2465bdbb0ec42928a66dfeeecc5402a691eef4..13f3b76d4d1c23a3f797f6cd51853037eac53e16 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js @@ -127,7 +127,7 @@ export class SchedulingUnitSummary extends Component { <div className="col-4"><label>Name:</label></div> <div className="col-8">{schedulingUnit.name}</div> <div className="col-4"><label>Project:</label></div> - <div className="col-8">{schedulingUnit.project.name}</div> + <div className="col-8">{schedulingUnit.project}</div> <div className="col-4"><label>Start Time:</label></div> <div className="col-8">{moment.utc(schedulingUnit.start_time).format("DD-MMM-YYYY HH:mm:ss")}</div> <div className="col-4"><label>Stop Time:</label></div> 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 491117c12e330aba951de5669e74b6c25642bcbe..4da1e5ee8d9c53b9fa6925b51162e4d5f1bc11fe 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -18,6 +18,7 @@ import UtilService from '../../services/util.service'; import UnitConverter from '../../utils/unit.converter'; import SchedulingUnitSummary from '../Scheduling/summary'; import { Dropdown } from 'primereact/dropdown'; +import { OverlayPanel } from 'primereact/overlaypanel'; // Color constant for status const STATUS_COLORS = { "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", @@ -46,6 +47,7 @@ export class TimelineView extends Component { canExtendSUList: true, canShrinkSUList: false, selectedItem: null, + mouseOverItem: null, suTaskList:[], isSummaryLoading: false, stationGroup: [], @@ -53,10 +55,13 @@ export class TimelineView extends Component { } this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable']; // Statuses before scheduled to get station_group this.allStationsGroup = []; + this.mainStationGroups = {}; // To group the stations under CS,RS,IS to show the count in Popover this.reservations = []; this.reservationReasons = []; this.onItemClick = this.onItemClick.bind(this); + this.onItemMouseOver = this.onItemMouseOver.bind(this); + this.onItemMouseOut = this.onItemMouseOut.bind(this); this.closeSUDets = this.closeSUDets.bind(this); this.dateRangeCallback = this.dateRangeCallback.bind(this); this.resizeSUList = this.resizeSUList.bind(this); @@ -137,6 +142,9 @@ export class TimelineView extends Component { items: items, currentUTC: currentUTC, isLoading: false, currentStartTime: defaultStartTime, currentEndTime: defaultEndTime}); }); + // Get maingroup and its stations + ScheduleService.getMainGroupStations() + .then(stationGroups => {this.mainStationGroups = stationGroups}); } /** @@ -144,11 +152,20 @@ export class TimelineView extends Component { * @param {Object} suBlueprint */ getTimelineItem(suBlueprint) { + let antennaSet = ""; + for (let task of suBlueprint.tasks) { + if (task.template.type_value.toLowerCase() === "observation") { + antennaSet = task.specifications_doc.antenna_set; + } + } let item = { id: suBlueprint.id, group: suBlueprint.suDraft.id, - title: `${suBlueprint.project} - ${suBlueprint.suDraft.name} - ${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`, + //title: `${suBlueprint.project} - ${suBlueprint.suDraft.name} - ${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`, + title: "", project: suBlueprint.project, type: 'SCHEDULE', name: suBlueprint.suDraft.name, + band: antennaSet.split("_")[0], + antennaSet: antennaSet, duration: suBlueprint.durationInSec?`${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`:"", start_time: moment.utc(suBlueprint.start_time), end_time: moment.utc(suBlueprint.stop_time), @@ -174,24 +191,30 @@ export class TimelineView extends Component { canExtendSUList: false, canShrinkSUList:false}); if (fetchDetails) { const suBlueprint = _.find(this.state.suBlueprints, {id: (this.state.stationView?parseInt(item.id.split('-')[0]):item.id)}); - ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) - .then(taskList => { - for (let task of taskList) { - //Control Task Id - const subTaskIds = (task.subTasks || []).filter(sTask => sTask.subTaskTemplate.name.indexOf('control') > 1); - task. subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; - if (task.template.type_value.toLowerCase() === "observation") { - task.antenna_set = task.specifications_doc.antenna_set; - task.band = task.specifications_doc.filter; + /* If tasks are not loaded on component mounting fetch from API */ + if (suBlueprint.tasks) { + this.setState({suTaskList: _.sortBy(suBlueprint.tasks, "id"), + stationGroup: this.getSUStations(suBlueprint)}); + } else { + ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) + .then(taskList => { + for (let task of taskList) { + //Control Task Id + const subTaskIds = (task.subTasks || []).filter(sTask => sTask.subTaskTemplate.name.indexOf('control') > 1); + task. subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + if (task.template.type_value.toLowerCase() === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; + } } - } - this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, - stationGroup: this.getSUStations(suBlueprint)}); - }); + this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, + stationGroup: this.getSUStations(suBlueprint)}); + }); + } // Get the scheduling constraint template of the selected SU block ScheduleService.getSchedulingConstraintTemplate(suBlueprint.suDraft.scheduling_constraints_template_id) .then(suConstraintTemplate => { - this.setState({suConstraintTemplate: suConstraintTemplate}); + this.setState({suConstraintTemplate: suConstraintTemplate, isSummaryLoading: false}); }); } } @@ -204,6 +227,53 @@ export class TimelineView extends Component { this.setState({isSUDetsVisible: false, canExtendSUList: true, canShrinkSUList: false}); } + /** + * Hide Tooltip popover on item mouseout event. + * @param {Event} evt + */ + onItemMouseOut(evt) { + this.popOver.toggle(evt); + } + + /** + * Show Tooltip popover on item mouseover event. Item & SU content formatted to show in Popover. + * @param {Event} evt + * @param {Object} item + */ + onItemMouseOver(evt, item) { + const itemSU = _.find(this.state.suBlueprints, {id: (this.state.stationView?item.suId:item.id)}); + const itemStations = this.getSUStations(itemSU); + const itemStationGroups = this.groupSUStations(itemStations); + item.stations = {groups: "", counts: ""}; + for (const stationgroup of _.keys(itemStationGroups)) { + let groups = item.stations.groups; + let counts = item.stations.counts; + if (groups) { + groups = groups.concat("/"); + counts = counts.concat("/"); + } + // Get station group 1st character and append 'S' to get CS,RS,IS + groups = groups.concat(stationgroup.substring(0,1).concat('S')); + counts = counts.concat(itemStationGroups[stationgroup].length); + item.stations.groups = groups; + item.stations.counts = counts; + } + this.popOver.toggle(evt); + this.setState({mouseOverItem: item}); + } + + /** + * Group the SU stations to main groups Core, Remote, International + * @param {Object} stationList + */ + groupSUStations(stationList) { + let suStationGroups = {}; + for (const group in this.mainStationGroups) { + suStationGroups[group] = _.intersection(this.mainStationGroups[group], stationList); + } + return suStationGroups; + } + /** * Callback function to pass to timeline component which is called on date range change to fetch new item and group records * @param {moment} startTime @@ -261,12 +331,13 @@ export class TimelineView extends Component { let stationItem = _.cloneDeep(timelineItem); stationItem.id = `${stationItem.id}-${station}`; stationItem.group = station; + stationItem.suId = timelineItem.id; items.push(stationItem); } } /** - * Get all stations of the SU bleprint from the observation task or subtask bases on the SU status. + * Get all stations of the SU bleprint from the observation task or subtask based on the SU status. * @param {Object} suBlueprint */ getSUStations(suBlueprint) { @@ -429,6 +500,7 @@ export class TimelineView extends Component { if (isSUDetsVisible) { suBlueprint = _.find(this.state.suBlueprints, {id: this.state.stationView?parseInt(this.state.selectedItem.id.split('-')[0]):this.state.selectedItem.id}); } + let mouseOverItem = this.state.mouseOverItem; return ( <React.Fragment> <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} @@ -487,7 +559,10 @@ export class TimelineView extends Component { group={this.state.group} items={this.state.items} currentUTC={this.state.currentUTC} - rowHeight={this.state.stationView?30:30} itemClickCallback={this.onItemClick} + rowHeight={this.state.stationView?50:50} + itemClickCallback={this.onItemClick} + itemMouseOverCallback={this.onItemMouseOver} + itemMouseOutCallback={this.onItemMouseOut} dateRangeCallback={this.dateRangeCallback} showSunTimings={!this.state.stationView} stackItems ={this.state.stationView} @@ -509,6 +584,31 @@ export class TimelineView extends Component { </div> } + {/* SU Item Tooltip popover with SU status color */} + <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> + {mouseOverItem && + <div className={`p-grid su-${mouseOverItem.status}`} style={{width: '350px'}}> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Project:</label> + <div className="col-7">{mouseOverItem.project}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduling Unit:</label> + <div className="col-7">{mouseOverItem.name}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Friends:</label> + <div className="col-7">{mouseOverItem.friends?mouseOverItem.friends:"-"}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Start Time:</label> + <div className="col-7">{mouseOverItem.start_time.format("YYYY-MM-DD HH:mm:ss")}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>End Time:</label> + <div className="col-7">{mouseOverItem.end_time.format("YYYY-MM-DD HH:mm:ss")}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Antenna Set:</label> + <div className="col-7">{mouseOverItem.antennaSet}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Stations:</label> + <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Status:</label> + <div className="col-7">{mouseOverItem.status}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Duration:</label> + <div className="col-7">{mouseOverItem.duration}</div> + </div> + } + </OverlayPanel> </React.Fragment> ); } 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 dfa2a14c18ee06159a120032d499eeca39dc5206..37b3646b53726775347e4f3e9f9afd4fec354649 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 @@ -18,6 +18,7 @@ import UtilService from '../../services/util.service'; import UnitConverter from '../../utils/unit.converter'; import SchedulingUnitSummary from '../Scheduling/summary'; import UIConstants from '../../utils/ui.constants'; +import { OverlayPanel } from 'primereact/overlaypanel'; // Color constant for status const STATUS_COLORS = { "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", @@ -47,9 +48,13 @@ export class WeekTimelineView extends Component { isSummaryLoading: false, stationGroup: [] } + this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable']; // Statuses before scheduled to get station_group + this.mainStationGroups = {}; this.onItemClick = this.onItemClick.bind(this); this.closeSUDets = this.closeSUDets.bind(this); + this.onItemMouseOver = this.onItemMouseOver.bind(this); + this.onItemMouseOut = this.onItemMouseOut.bind(this); this.dateRangeCallback = this.dateRangeCallback.bind(this); this.resizeSUList = this.resizeSUList.bind(this); this.suListFilterCallback = this.suListFilterCallback.bind(this); @@ -119,7 +124,37 @@ export class WeekTimelineView extends Component { items: items, currentUTC: currentUTC, isLoading: false, startTime: defaultStartTime, endTime: defaultEndTime }); + // Load SU tasks asynchronously for SUs + this.loadSUTasks(); }); + // Get maingroup and its stations + ScheduleService.getMainGroupStations() + .then(stationGroups => {this.mainStationGroups = stationGroups}); + } + + /** + * Loads tasks, subtasks, templates and stations of the Scheduling Units + */ + loadSUTasks() { + let suBlueprints = this.state.suBlueprints; + for (let suBlueprint of suBlueprints) { + ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) + .then(taskList => { + for (let task of taskList) { + //Control Task ID + const subTaskIds = (task.subTasks || []).filter(sTask => sTask.subTaskTemplate.name.indexOf('control') > 1); + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + if (task.template.type_value.toLowerCase() === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; + } + } + suBlueprint.tasks = taskList; + let stations = this.getSUStations(suBlueprint); + suBlueprint.stations = _.uniq(stations); + }); + } + this.setState({suBlueprints: suBlueprints}); } /** @@ -135,12 +170,15 @@ export class WeekTimelineView extends Component { } } let item = { id: `${suBlueprint.id}-${suBlueprint.start_time}`, + suId: suBlueprint.id, // group: suBlueprint.suDraft.id, group: moment.utc(suBlueprint.start_time).format("MMM DD ddd"), - title: `${suBlueprint.project} - ${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs - ${antennaSet}`, + // title: `${suBlueprint.project} - ${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs - ${antennaSet}`, + title: "", project: suBlueprint.project, name: suBlueprint.suDraft.name, - band: antennaSet, + band: antennaSet.split("_")[0], + antennaSet: antennaSet, duration: suBlueprint.durationInSec?`${(suBlueprint.durationInSec/3600).toFixed(2)}Hrs`:"", start_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${suBlueprint.start_time.split('T')[1]}`), end_time: moment.utc(`${displayDate.format('YYYY-MM-DD')} ${suBlueprint.stop_time.split('T')[1]}`), @@ -167,32 +205,31 @@ export class WeekTimelineView extends Component { canExtendSUList: false, canShrinkSUList:false}); if (fetchDetails) { const suBlueprint = _.find(this.state.suBlueprints, {id: parseInt(item.id.split('-')[0])}); - ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) - .then(taskList => { - const observationTask = _.find(taskList, (task)=> {return task.template.type_value==='observation' && task.specifications_doc.station_groups}); - for (let task of taskList) { - //Control Task ID - const subTaskIds = (task.subTasks || []).filter(sTask => sTask.subTaskTemplate.name.indexOf('control') > 1); - task. subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; - if (task.template.type_value.toLowerCase() === "observation") { - task.antenna_set = task.specifications_doc.antenna_set; - task.band = task.specifications_doc.filter; + /* If tasks are not loaded on component mounting fetch from API */ + if (suBlueprint.tasks) { + this.setState({suTaskList: _.sortBy(suBlueprint.tasks, "id"), + stationGroup: suBlueprint.stations}) + } else { + ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) + .then(taskList => { + const observationTask = _.find(taskList, (task)=> {return task.template.type_value==='observation' && task.specifications_doc.station_groups}); + for (let task of taskList) { + //Control Task ID + const subTaskIds = (task.subTasks || []).filter(sTask => sTask.subTaskTemplate.name.indexOf('control') > 1); + task.subTaskID = subTaskIds.length ? subTaskIds[0].id : ''; + if (task.template.type_value.toLowerCase() === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; + } } - } - let stations = []; - //>>>>>> TODO: Station groups from subtasks based on the status of SU - if (observationTask) { - for (const grpStations of _.map(observationTask.specifications_doc.station_groups, "stations")) { - stations = _.concat(stations, grpStations); - } - } - this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, - stationGroup: _.uniq(stations)}) - }); + this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false, + stationGroup: this.getSUStations(suBlueprint)}) + }); + } // Get the scheduling constraint template of the selected SU block ScheduleService.getSchedulingConstraintTemplate(suBlueprint.suDraft.scheduling_constraints_template_id) .then(suConstraintTemplate => { - this.setState({suConstraintTemplate: suConstraintTemplate}); + this.setState({suConstraintTemplate: suConstraintTemplate, isSummaryLoading: false}); }); } } @@ -205,6 +242,80 @@ export class WeekTimelineView extends Component { this.setState({isSUDetsVisible: false, canExtendSUList: true, canShrinkSUList: false}); } + /** + * Hide Tooltip popover on item mouseout event. + * @param {Event} evt + */ + onItemMouseOut(evt) { + this.popOver.toggle(evt); + } + + /** + * Show Tooltip popover on item mouseover event. Item & SU content formatted to show in Popover. + * @param {Event} evt + * @param {Object} item + */ + onItemMouseOver(evt, item) { + const itemSU = _.find(this.state.suBlueprints, {id: parseInt(item.id.split("-")[0])}); + const itemStations = itemSU.stations; + const itemStationGroups = this.groupSUStations(itemStations); + item.stations = {groups: "", counts: ""}; + for (const stationgroup of _.keys(itemStationGroups)) { + let groups = item.stations.groups; + let counts = item.stations.counts; + if (groups) { + groups = groups.concat("/"); + counts = counts.concat("/"); + } + groups = groups.concat(stationgroup.substring(0,1).concat('S')); + counts = counts.concat(itemStationGroups[stationgroup].length); + item.stations.groups = groups; + item.stations.counts = counts; + } + this.popOver.toggle(evt); + this.setState({mouseOverItem: item}); + } + + /** + * Group the SU stations to main groups Core, Remote, International + * @param {Object} stationList + */ + groupSUStations(stationList) { + let suStationGroups = {}; + for (const group in this.mainStationGroups) { + suStationGroups[group] = _.intersection(this.mainStationGroups[group], stationList); + } + return suStationGroups; + } + + /** + * Get all stations of the SU bleprint from the observation task or subtask based on the SU status. + * @param {Object} suBlueprint + */ + getSUStations(suBlueprint) { + let stations = []; + /* Get all observation tasks */ + const observationTasks = _.filter(suBlueprint.tasks, (task) => { return task.template.type_value.toLowerCase() === "observation"}); + for (const observationTask of observationTasks) { + /** 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 + && observationTask.specifications_doc.station_groups) { + for (const grpStations of _.map(observationTask.specifications_doc.station_groups, "stations")) { + stations = _.concat(stations, grpStations); + } + } else if (this.STATUS_BEFORE_SCHEDULED.indexOf(suBlueprint.status.toLowerCase()) < 0 + && observationTask.subTasks) { + /** If the status of SU is scheduled or after get the stations from the subtask specification tasks */ + for (const subtask of observationTask.subTasks) { + if (subtask.specifications_doc.stations) { + stations = _.concat(stations, subtask.specifications_doc.stations.station_list); + } + } + } + } + return _.uniq(stations); + } + /** * Callback function to pass to timeline component which is called on week change to fetch new item and group records * @param {moment} startTime @@ -309,6 +420,7 @@ export class WeekTimelineView extends Component { if (isSUDetsVisible) { suBlueprint = _.find(this.state.suBlueprints, {id: parseInt(this.state.selectedItem.id.split('-')[0])}); } + const mouseOverItem = this.state.mouseOverItem; return ( <React.Fragment> <PageHeader location={this.props.location} title={'Scheduling Units - Week View'} @@ -362,7 +474,10 @@ export class WeekTimelineView extends Component { group={this.state.group} items={this.state.items} currentUTC={this.state.currentUTC} - rowHeight={50} itemClickCallback={this.onItemClick} + rowHeight={50} + itemClickCallback={this.onItemClick} + itemMouseOverCallback={this.onItemMouseOver} + itemMouseOutCallback={this.onItemMouseOut} sidebarWidth={150} stackItems={true} startTime={moment.utc(this.state.currentUTC).hour(0).minutes(0).seconds(0)} @@ -389,6 +504,31 @@ export class WeekTimelineView extends Component { </div> </> } + {/* SU Item Tooltip popover with SU status color */} + <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> + {mouseOverItem && + <div className={`p-grid su-${mouseOverItem.status}`} style={{width: '350px'}}> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Project:</label> + <div className="col-7">{mouseOverItem.project}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Scheduling Unit:</label> + <div className="col-7">{mouseOverItem.name}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Friends:</label> + <div className="col-7">{mouseOverItem.friends?mouseOverItem.friends:"-"}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Start Time:</label> + <div className="col-7">{mouseOverItem.start_time.format("YYYY-MM-DD HH:mm:ss")}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>End Time:</label> + <div className="col-7">{mouseOverItem.end_time.format("YYYY-MM-DD HH:mm:ss")}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Antenna Set:</label> + <div className="col-7">{mouseOverItem.antennaSet}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Stations:</label> + <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Status:</label> + <div className="col-7">{mouseOverItem.status}</div> + <label className={`col-5 su-${mouseOverItem.status}-icon`}>Duration:</label> + <div className="col-7">{mouseOverItem.duration}</div> + </div> + } + </OverlayPanel> </React.Fragment> ); } 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 3e7646d162cbd04337a6b55d24e4677976f1b408..c4361162f4cf7b3b0b874925f0c8972792716827 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -460,15 +460,32 @@ const ScheduleService = { return []; }; }, - getStations: async function(e) { + getStations: async function(group) { try { // const response = await axios.get('/api/station_groups/stations/1/dutch'); - const response = await axios.get(`/api/station_groups/stations/1/${e}`); + const response = await axios.get(`/api/station_groups/stations/1/${group}`); return response.data; } catch(error) { console.error(error); return []; } + }, + // To get stations of main groups + getMainGroupStations: async function() { + let stationGroups = {}; + try { + const stationPromises = [this.getStations('Core'), + this.getStations('Remote'), + this.getStations('International')] + await Promise.all(stationPromises).then(async(results) => { + for (const result of results) { + stationGroups[result.group] = result.stations; + } + }); + } catch(error) { + console.error(error); + } + return stationGroups; }, getProjectList: async function() { try {