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 5dc26ae7a829f64e0013f55bb206c8db6370038b..0bb3fdc0837baca05a84e561b3c4f265b0f6e150 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -1617,7 +1617,6 @@ export class CalendarTimeline extends Component { zoomLevel={this.state.zoomLevel} allZoomLevels={this.ZOOM_LEVELS} onChangeZoomLevelCallback={this.changeZoomLevel}/> - <Legendbar/> </div> <div onMouseOut={e => this.cursorTime = null }> <Timeline 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 16f3dd1e9ec607d936bb28c09b0e85ed3377c5ae..1fb7cb213da429472bed175e16a46a5deef2335b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss @@ -17,11 +17,6 @@ } .timeline-tools { - //The timeline tools consists of 3 elements: - // - Toolbar (.timeline-view-toolbar), - // - DateTimeNavigator (.timeline-datetime-navigator), - // - Legendbar (.legendbar) - //adapt the width accordingly display: flex; .header { @@ -34,6 +29,12 @@ padding-bottom: 1rem; } + .section { + border-right: 2px solid var(--gray-100); + padding: 0 1rem; + height: calc(100% - 0.5rem); + } + .group { display: flex; width: 100%; @@ -51,7 +52,6 @@ } .timeline-view-toolbar { - margin-right: 2.5rem; flex-direction: row; overflow: scroll; @@ -169,22 +169,40 @@ } +.legend-header { + display: flex; + justify-content: center; + text-decoration: underline; + font-size: 14px; + font-weight: 600; + color: #004B93; + padding-bottom: 1rem; +} + .legendbar { - flex-direction: row; font-size: 10px !important; + //flex-direction: column; + flex-wrap: wrap; + + .left, + .right { + flex: 1 0 50%; + white-space: nowrap; + text-align: center; + box-sizing: border-box; + + .row { + display: flex; + flex-direction: row; - .section { - display: flex; - flex-direction: row; - line-height: 1; - - label { - font-size: 0.75rem; - text-overflow: ellipsis; - overflow: hidden; + label { + font-size: 0.75rem; + text-overflow: ellipsis; + overflow: hidden; + text-align: end; + } } } - } .p-dropdown { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/DateTimeNavigator.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/DateTimeNavigator.js index 89ace643567415923011117370dface86562601e..26cfd5d8d8f5bca8b907c3965fe34c19387fa959 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/DateTimeNavigator.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/DateTimeNavigator.js @@ -200,7 +200,7 @@ export default function DateTimeNavigator(props) { } return <div className="p-grid timeline-datetime-navigator"> - <div> + <div className="section"> <div className="header">Navigation</div> <div className="group"> {getDateTimeInfo(currentUTC, currentLST)} @@ -211,7 +211,7 @@ export default function DateTimeNavigator(props) { </div> </div> </div> - <div> + <div className="section"> <div className="header">Zoom</div> <div className="group group--row"> {getZoomSelect(zoomLevel, allZoomLevels, onChangeZoomLevelCallback)} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Legendbar.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Legendbar.js index 802439392323986a2c7ef89af56869aa0a5b0e2f..3d1fdbf94d61c85163730ea62dbc1495b8b2222c 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Legendbar.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Legendbar.js @@ -39,8 +39,9 @@ const LEGEND_SECTIONS_LEFT = const LEGEND_SECTIONS_RIGHT = [ { - title: "Station Reservation", - elements: [{classKey: "reserve-not-available", value: "Not Available"}, + title: "Station", + subtitle: "Station Reservation", + elements: [{classKey: "reserve-not-available", value: "N/A", title: "Not Available"}, {classKey: "reserve-available", value: "Available"}, {classKey: "reserve-fixed_time", value: "Fixed Time"}, {classKey: "reserve-dynamic", value: "Dynamic"} @@ -48,6 +49,7 @@ const LEGEND_SECTIONS_RIGHT = }, { title: "Indicators", + subtitle: "Indication about the settings", elements: [{classKey: "su-fixed_time", value: "Fixed Time"}, {classKey: "su-dynamic", value: "Dynamic"}, {classKey: "su-placed", value: "Placed"}, @@ -64,9 +66,10 @@ const LEGEND_SECTIONS_RIGHT = ] function getLegendItem(section) { - return <div className="section"> - <label className="col-3" title={section.title}>{section.title}:</label> - {section.elements.map(element => <div className={`su-legend ${element.classKey}`} title={element.value}> + return <div className="row"> + <label className="col-3" title={section.subtitle ? section.subtitle : section.title}>{section.title}:</label> + {section.elements.map(element => <div className={`su-legend ${element.classKey}`} + title={element.title ? element.title : element.value}> {element.value} </div>)} </div> @@ -74,11 +77,10 @@ function getLegendItem(section) { export default function Legendbar() { return <div className="p-grid legendbar"> - <div className="header">Legend</div> - <div> + <div className="left"> {LEGEND_SECTIONS_LEFT.map(section => getLegendItem(section))} </div> - <div> + <div className="right"> {LEGEND_SECTIONS_RIGHT.map(section => getLegendItem(section))} </div> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Toolbar.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Toolbar.js index 74ebe075b13c1e2d91da8607071460b7f4c2daa8..c12238387d566eb5808525c75c66a7c3291ada88 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Toolbar.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/Toolbar.js @@ -116,7 +116,7 @@ export default function Toolbar(props) { const shouldDefineOnSkyViewToggle = stationsViewToggle ? false : onSkyViewToggle return <div className="p-grid timeline-view-toolbar"> - <div> + <div className="section"> <Tooltip target=".header" mouseTrack mouseTrackLeft={10}/> <div className="header" data-pr-tooltip="Some options might not be available for your view so are disabled">Filters @@ -152,7 +152,7 @@ export default function Toolbar(props) { shouldDefineOnSkyViewToggle ? showTimeLineItems : changeViewBlocks)} </div> </div> - <div> + <div className="section"> <div className="header">Grouping</div> <div className="group group--disabled"> {getRadioButtonsForToggle(onSkyViewToggle, setGroupByProject, [ 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 9b8878843ee36d2296e5297acfde0ef512d6d4cc..35cc26d72ff779ddac7a2fa82ac746568d2c9ae8 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -34,6 +34,7 @@ import { appGrowl } from '../../layout/components/AppGrowl'; import TopProgressBar from '../../layout/components/TopProgressBar'; import TimelineItemPopover from "./components/TimelineItemPopover"; import Toolbar from "./components/Toolbar" +import Legendbar from "./components/Legendbar"; //import { TRUE } from 'node-sass'; // Color constant for SU status @@ -62,7 +63,7 @@ const OFFSET_DATA_DAYS = process.env.REACT_APP_TIMELINE_DATA_PRELOAD_PERIOD_DAYS */ export class TimelineView extends Component { timelineCommonUtils = new TimelineCommonUtils(); - + constructor(props) { super(props); this.timelineUIAttributes = UtilService.localStore({ type: 'get', key: "TIMELINE_UI_ATTR" }) || {}; @@ -164,7 +165,7 @@ export class TimelineView extends Component { } /* Cancel on Scheduling Constraint Popup onEdit */ - cancelCheckstatus(status){ + cancelCheckstatus(status){ this.setState({ cancelStatus:status }); @@ -173,17 +174,17 @@ export class TimelineView extends Component { /* Getting updated value from the ScheduleConstraints Popup onEdit*/ newPopupvalue(jsonOutput){ this.setState({ - suBlueprint: { - scheduling_constraints_doc: jsonOutput + suBlueprint: { + scheduling_constraints_doc: jsonOutput } }); } - + /* * Onsavechanges for scheduling Constraints * @id : Schedule Id, @val : update state from child components */ - async onSaveChanges(id,val){ + async onSaveChanges(id,val){ const constStrategy = _.cloneDeep(val); if (constStrategy.hasOwnProperty('time')) { if (constStrategy.time.hasOwnProperty('at') && !constStrategy.time.at) { @@ -218,15 +219,15 @@ export class TimelineView extends Component { } // getConstraintsEditorOutputService - service call is done for getting updated blueprint id - const bluePrintValue = await ScheduleService.getConstraintsEditorOutputService(id); - // setConstraintsEditorOutputService - service call is done for upddating the blueprint object + const bluePrintValue = await ScheduleService.getConstraintsEditorOutputService(id); + // setConstraintsEditorOutputService - service call is done for upddating the blueprint object bluePrintValue.scheduling_constraints_doc = constStrategy; const updatedResponse = await ScheduleService.setConstraintsEditorOutputService(bluePrintValue); /* * updating suBlueprints value with current object * Currently setConstraintsEditorOutputService with not updating current values - */ + */ if(updatedResponse.hasOwnProperty('data')){ const updateStateVal = await Object.assign([],this.state.suBlueprints); updateStateVal.map((updateConstraints) => { @@ -234,7 +235,7 @@ export class TimelineView extends Component { updateConstraints.scheduling_constraints_doc = updatedResponse.data.scheduling_constraints_doc; } return updateConstraints; - }); + }); this.setState({ suBlueprints: updateStateVal, openEditConstraintDialog: false, @@ -244,7 +245,7 @@ export class TimelineView extends Component { this.updateSchedulingUnit(this.state.stationView?this.state.selectedItem.suId:this.state.selectedItem.id) } else{ appGrowl.show({ severity: 'error', summary: 'Error', detail: updatedResponse.message }); - } + } } /* @@ -261,9 +262,9 @@ export class TimelineView extends Component { this.setState({ openEditConstraintDialog: val, cancelStatus:false, - onSavedisable:false + onSavedisable:false }); - } + } } /* To close Pop-up */ @@ -301,13 +302,13 @@ export class TimelineView extends Component { } this.setState({subSystemStatus: status}); }) - + } /** * Set current status when the Dynamic Scheduling On/Off - * - * @param {boolean} status + * + * @param {boolean} status */ async updateDSStatus(status, scheduleType) { if(scheduleType === 'dynamic') { @@ -322,7 +323,7 @@ export class TimelineView extends Component { this.setState({dsStatus: status}); } if(scheduleType === 'fixed') { - + this.setState({fsStatus: status}); } } @@ -333,15 +334,15 @@ export class TimelineView extends Component { this.getTaskTypeList(); const permission = await AuthUtil.getUserRolePermission(); const timelinePermission = permission.userRolePermission.timeline; - + let menuOptions = [ - { label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, + { label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, command: (e) => { this.selectOptionMenu(e, 'Add Reservation') } }, - { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, + { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, command: (e) => { this.selectOptionMenu(e, 'Reservation List') } }, - { label: 'Add System Event', icon: "fa fa-", disabled: !timelinePermission.addsystemevent, + { label: 'Add System Event', icon: "fa fa-", disabled: !timelinePermission.addsystemevent, command: (e) => { this.selectOptionMenu(e, 'Add System Event') } }, - { label: 'System Issues', icon: "fa fa-", disabled: !timelinePermission.listsystemevent, + { label: 'System Issues', icon: "fa fa-", disabled: !timelinePermission.listsystemevent, command: (e) => { this.selectOptionMenu(e, 'System Issues') } }, ] this.setState({menuOptions: menuOptions, loader: true }); @@ -365,7 +366,7 @@ export class TimelineView extends Component { let defaultStartTime = this.timelineUIAttributes.dateRange?moment.utc(this.timelineUIAttributes.dateRange.startTime):null; // Default end time, this should be updated if default view is changed. Take from localstorage if available let defaultEndTime = this.timelineUIAttributes.dateRange?moment.utc(this.timelineUIAttributes.dateRange.endTime):null; - + // Set default time if previous selected date range and zoom level available. if (this.timelineUIAttributes.zoomLevel) { const defaultZoomLevel = _.find(this.timelineCommonUtils.ZOOM_LEVELS, {name: this.timelineUIAttributes.zoomLevel}); @@ -378,7 +379,7 @@ export class TimelineView extends Component { defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-24, 'hours'); // Default start time, this should be updated if default view is changed. defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(24, 'hours'); // Default end time, this should be updated if default view is changed. } - + // Fetch all details from server and prepare data to pass to timeline and table components const promises = [ScheduleService.getStations('All')]; Promise.all(promises).then(async (responses) => { @@ -426,7 +427,7 @@ export class TimelineView extends Component { schedulingUnits.datasetEndTime = datasetEndTime; // Loads the offset data in the background this.loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime); - } else if (startTime.isSameOrBefore(datasetStartTime) && endTime.isSameOrAfter(datasetEndTime)) { + } else if (startTime.isSameOrBefore(datasetStartTime) && endTime.isSameOrAfter(datasetEndTime)) { /* Enters this loop when timeline range or zoom level changes and the new start time is before datasetStartTime and new end time is after dataset end time DST---OST--------------OET---DET NST-----------------------------------NET @@ -586,7 +587,7 @@ export class TimelineView extends Component { suBlueprints = suBlueprints.concat(susTrail.original); suBlueprintList = suBlueprintList.concat(susTrail.formatted); } - this.setState({suBlueprints: _.uniqBy(suBlueprints, 'id'), + this.setState({suBlueprints: _.uniqBy(suBlueprints, 'id'), suBlueprintList: _.uniqBy(suBlueprintList, 'id'), datasetStartTime: datasetStartTime, datasetEndTime: datasetEndTime @@ -608,7 +609,7 @@ export class TimelineView extends Component { suBlueprints = _.filter(suBlueprints, suBlueprint => suBlueprint.status.toLowerCase !== 'obsolete', suBlueprint => suBlueprint.scheduling_constraints_doc); let suList = []; const suIds = _.map(suBlueprints, 'id'); - let workflows = await this.timelineCommonUtils.getWorkflowsAndTasks(suIds); + let workflows = await this.timelineCommonUtils.getWorkflowsAndTasks(suIds); for (let suBlueprint of suBlueprints){ suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; suBlueprint.suDraft = suBlueprint.draft; @@ -623,10 +624,10 @@ export class TimelineView extends Component { suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template?suBlueprint.draft.observation_strategy_template.name:null; suBlueprint.tasks = suBlueprint.task_blueprints; const itemStations = this.timelineCommonUtils.getSUStations(suBlueprint); - suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(itemStations).counts; + suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(itemStations).counts; for (let task of suBlueprint.tasks) { const controlTask = _.find(task.subtasks, subtask => { return subtask.primary}); - task.controlId = controlTask?controlTask.id:""; + task.controlId = controlTask?controlTask.id:""; if (task.task_type === "observation") { task.antenna_set = task.specifications_doc.station_configuration?.antenna_set; task.band = task.specifications_doc.station_configuration?.filter; @@ -664,7 +665,7 @@ export class TimelineView extends Component { /** * Function to get/prepare Item object to be passed to Timeline component - * @param {Object} suBlueprint + * @param {Object} suBlueprint */ getTimelineItem(suBlueprint) { let antennaSet = ""; @@ -673,7 +674,7 @@ export class TimelineView extends Component { && task.specifications_doc.station_configuration?.antenna_set) { antennaSet = task.specifications_doc.station_configuration.antenna_set; } - } + } let item = { id: suBlueprint.id, group: this.state.groupByProject ? suBlueprint.project : suBlueprint.id, @@ -700,7 +701,7 @@ export class TimelineView extends Component { /** * Get Timeline items for obsercation tasks of the SU Bluprint. Task Items are grouped to the SU draft and Task draft IDs - * @param {Object} suBlueprint + * @param {Object} suBlueprint */ getTaskItems(suBlueprint, startTime, endTime, taskFilterData) { let taskItems = {}; @@ -709,7 +710,7 @@ export class TimelineView extends Component { if (suBlueprint.tasks) { let items = [], itemGroup = []; for (let task of suBlueprint.tasks) { - if (((!this.state.stationView && this.state.selectedTaskTypes.indexOf(task.task_type)>=0) + 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 ) && task.start_time && task.stop_time && task.obsolete_since === null) { @@ -769,14 +770,14 @@ export class TimelineView extends Component { /** * Callback function to pass to Timeline component for item click. - * @param {Object} item + * @param {Object} item */ onItemClick(item) { if (item.type === "UNSCHEDULABLE") { this.showSUSummary(item, this.state.unschedulableList, false); } else if (item.type === "RESERVATION") { this.showReservationSummary(item); - } else if (item.type === "SCHEDULE") { + } else if (item.type === "SCHEDULE") { this.showSUSummary(item, this.state.suBlueprintList, true); } else { this.showTaskSummary(item); @@ -785,11 +786,11 @@ export class TimelineView extends Component { /** * To load SU summary and show - * @param {Object} item - SU item object. + * @param {Object} item - SU item object. * @param {Array} blueprintList - List of blueprints based on the item type clicked - * @param {Boolean} isSchedulable - To check if selected item is unschedulable or not + * @param {Boolean} isSchedulable - To check if selected item is unschedulable or not */ - async showSUSummary(item, blueprintList, isSchedulable) { + async showSUSummary(item, blueprintList, isSchedulable) { if (this.state.isSUDetsVisible && item.id === this.state.selectedItem.id) { this.closeSummaryPanel(); } else { @@ -800,14 +801,14 @@ export class TimelineView extends Component { isSummaryLoading: fetchDetails, suTaskList: !fetchDetails ? this.state.suTaskList : [], canExtendSUList: false, canShrinkSUList: false - }); - if (fetchDetails) { + }); + if (fetchDetails) { const suBlueprint = _.find(blueprintList, { id: (this.state.stationView ? parseInt(item.id.split('-')[0]) : item.id) }); const suConstraintTemplate = _.find(this.suConstraintTemplates, { id: suBlueprint.scheduling_constraints_template_id }); let primarySubtasks = []; for(const task of suBlueprint.task_blueprints){ const subTaskList = (task.subtasks || []).filter(sTask => (sTask.primary && sTask.subtask_type === 'observation')); - primarySubtasks = [...primarySubtasks, ...subTaskList]; + primarySubtasks = [...primarySubtasks, ...subTaskList]; } let missingStations = []; for(const sTask of primarySubtasks) { @@ -822,7 +823,7 @@ export class TimelineView extends Component { this.setState({ suTaskList: _.sortBy(suBlueprint.tasks, "id"), suConstraintTemplate: suConstraintTemplate, stationGroup: this.timelineCommonUtils.getSUStations(suBlueprint), isSummaryLoading: false, missingStations: missingStations - }); + }); } else { ScheduleService.getTaskBPWithSubtaskTemplateOfSU(suBlueprint) .then(taskList => { @@ -857,7 +858,7 @@ export class TimelineView extends Component { /** * To load and show Reservation summary - * @param {Object} item + * @param {Object} item */ showReservationSummary(item) { this.setState({ selectedItem: item, isReservDetsVisible: true, isSUDetsVisible: false, isTaskDetsVisible: false }); @@ -865,7 +866,7 @@ export class TimelineView extends Component { /** * To load task summary and show - * @param {Object} item - Timeline task item object + * @param {Object} item - Timeline task item object */ async showTaskSummary(item) { if (this.state.isTaskDetsVisible && item.id === this.state.selectedItem.id) { @@ -876,7 +877,7 @@ export class TimelineView extends Component { this.setState({ selectedItem: item, isTaskDetsVisible: true, isSUDetsVisible: false, isReservDetsVisible: false, taskDetails: taskDetails }); } - + } /** @@ -887,13 +888,13 @@ export class TimelineView extends Component { const canExtendSUList = this.timelineUIAttributes.canExtendSUList!==undefined?this.timelineUIAttributes.canExtendSUList:true; const canShrinkSUList = this.timelineUIAttributes.canShrinkSUList || false; this.setState({ isSUDetsVisible: false, isReservDetsVisible: false, isTaskDetsVisible: false, isUnscheduledDetVisible: false, - canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList + canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList }); } /** * Hide Tooltip popover on item mouseout event. - * @param {Event} evt + * @param {Event} evt */ onItemMouseOut(evt) { this.setState({popPosition: {display: 'none'}}); @@ -901,11 +902,11 @@ export class TimelineView extends Component { /** * Show Tooltip popover on item mouseover event. Item & SU content formatted to show in Popover. - * @param {Event} evt + * @param {Event} evt * @param {Object} item */ onItemMouseOver(evt, item) { - let popPosition = {display:"block", + 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`; @@ -932,8 +933,8 @@ export class TimelineView extends Component { /** * Callback function to pass to timeline component which is called on date range change to fetch new item and group records - * @param {moment} startTime - * @param {moment} endTime + * @param {moment} startTime + * @param {moment} endTime */ async dateRangeCallback(startTime, endTime, loadOldData) { let suBlueprints = this.state.suBlueprints; @@ -994,7 +995,7 @@ export class TimelineView extends Component { items = this.addStationReservations(items, startTime, endTime); } if (this.state.showReservation) { - let reservationGroup = [{id: "RESERVATION", parent: "RESERVATION", + let reservationGroup = [{id: "RESERVATION", parent: "RESERVATION", start: moment.utc("1900-01-01", "YYYY-MM-DD"), title: "RESERVATIONS"} ]; @@ -1019,10 +1020,10 @@ export class TimelineView extends Component { /** * To get items and groups for station view - * @param {Object} suBlueprint - * @param {Object} timelineItem - * @param {Array} group - * @param {Array} items + * @param {Object} suBlueprint + * @param {Object} timelineItem + * @param {Array} group + * @param {Array} items */ getStationItemGroups(suBlueprint, timelineItem, group, items, filteredTaskData) { /* Get stations based on SU status */ @@ -1050,7 +1051,7 @@ export class TimelineView extends Component { /** * Add Station Reservations during the visible timeline period - * @param {Array} items + * @param {Array} items * @param {moment} startTime * @param {moment} endTime */ @@ -1089,8 +1090,8 @@ export class TimelineView extends Component { /** * Get reservation timeline items. If the reservation doesn't have duration, item endtime should be timeline endtime. - * @param {Object} reservation - * @param {moment} endTime + * @param {Object} reservation + * @param {moment} endTime */ getReservationItems(reservation, endTime, stationView) { const reservationSpec = reservation.specifications_doc; @@ -1134,7 +1135,7 @@ export class TimelineView extends Component { /** * Get the schedule type from the schedulability object. It helps to get colors of the reservation blocks * according to the type. - * @param {Object} schedulability + * @param {Object} schedulability */ getReservationType(schedulability) { if (schedulability.fixed_time && schedulability.dynamic) { @@ -1150,7 +1151,7 @@ export class TimelineView extends Component { /** * Set reservation filter - * @param {String} filter + * @param {String} filter */ async setReservationFilter(filter) { await this.setState({ reservationFilter: filter }); @@ -1178,11 +1179,11 @@ export class TimelineView extends Component { // || reservationEndTime.isBetween(startTime, endTime) // || (reservationStartTime.isSameOrBefore(startTime) // && reservationEndTime.isSameOrAfter(endTime))) - if ((reservationStartTime.isSameOrBefore(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 || + if (!this.state.stationView || (this.state.stationView && reservationSpec.resources.stations)) { let item = _.cloneDeep(reservation); item.stop_time = item.stop_time || "Unknown"; @@ -1207,7 +1208,7 @@ export class TimelineView extends Component { /** * To enable displaying SU or Task or Both items in timeline. - * @param {String} value + * @param {String} value */ async showTimelineItems(value) { // If previous selected option is stored, show the selected option else show the default. @@ -1234,7 +1235,7 @@ export class TimelineView extends Component { /** * Function to show or hide the List panel. - * @param {boolean} value + * @param {boolean} value */ showListPanel(value) { this.timelineUIAttributes.isSUListVisible = value; @@ -1258,16 +1259,16 @@ export class TimelineView extends Component { canShrinkSUList = (canShrinkSUList && !canExtendSUList) ? true : false; canExtendSUList = true; } - this.timelineUIAttributes = {...this.timelineUIAttributes, - ...{canExtendSUList: canExtendSUList, + this.timelineUIAttributes = {...this.timelineUIAttributes, + ...{canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList }}; this.timelineCommonUtils.storeUIAttributes(this.timelineUIAttributes); this.setState({canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList }); } /** - * 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 and reservation tables so that + * 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 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. @@ -1282,7 +1283,7 @@ export class TimelineView extends Component { _.map(filteredSUData, sub => sub.Id = sub.id); } if (this.state.datasetStartTime) { - this.filteredSUBs = _.map(filteredSUData, "Id"); + this.filteredSUBs = _.map(filteredSUData, "Id"); } // If no tasks are filtered in table, filter tasks of the filtered SUBs if (!filteredTaskData) { @@ -1331,14 +1332,14 @@ export class TimelineView extends Component { items = this.addStationReservations(items, this.state.currentStartTime, this.state.currentEndTime, filteredReservData); } if (this.state.showReservation) { - let reservationGroup = [{id: "RESERVATION", parent: "RESERVATION", + let reservationGroup = [{id: "RESERVATION", parent: "RESERVATION", start: moment.utc("1900-01-01", "YYYY-MM-DD"), title: "RESERVATIONS"} ]; group = reservationGroup.concat(group); } if (this.timeline) { - this.timeline.updateTimeline({ group: this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'), ["parent", "start"], ['asc', 'asc']), + this.timeline.updateTimeline({ group: this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'), ["parent", "start"], ['asc', 'asc']), items: items, stationView: this.state.stationView}); } @@ -1404,7 +1405,7 @@ export class TimelineView extends Component { else { this.props.history.push('/reservation/create') this.setState({ redirect: `/reservation/create`}); - } + } break; } case 'System Issues': { @@ -1424,7 +1425,7 @@ export class TimelineView extends Component { else { this.props.history.push('/systemevent/create') this.setState({ redirect: `/systemevent/create`}); - } + } break; } default: { @@ -1510,7 +1511,7 @@ export class TimelineView extends Component { default: { break; } } } - + /** * Delete existing data from the list */ @@ -1528,11 +1529,11 @@ export class TimelineView extends Component { /** * If any if the given properties of the object is modified, update the schedulingUnit object in the list of the state. * It is validated for both scheduling_unit_blueprint and task_blueprint objects - * @param {Number} id - * @param {String} type - * @param {Object} object + * @param {Number} id + * @param {String} type + * @param {Object} object */ - async updateExistingData(id, type, object) { + async updateExistingData(id, type, object) { const objectProps = ['status', 'start_time', 'stop_time', 'duration']; switch (type) { // case 'scheduling_unit_draft': { @@ -1573,16 +1574,16 @@ export class TimelineView extends Component { this.updateTimeline(); }); break; - } + } default: { break; } } } /** * Add or update the SUDraft object in the state suDraft list after fetching through API call - * @param {Number} id + * @param {Number} id */ - updateSUDraft(id) { + updateSUDraft(id) { let suDrafts = this.state.suDrafts; let suSets = this.state.suSets; ScheduleService.getSchedulingUnitDraftById(id) @@ -1598,7 +1599,7 @@ export class TimelineView extends Component { /** * Fetch the latest SUB object from the backend and format as required for the timeline and pass them to the timeline component * to update the timeline view with latest data. - * @param {Number} id + * @param {Number} id */ async updateSchedulingUnit(id) { if(this.state.result===true) { @@ -1606,7 +1607,7 @@ export class TimelineView extends Component { } ScheduleService.getExpandedSUB(id) .then(async(suBlueprint) => { - let workflows = await this.timelineCommonUtils.getWorkflowsAndTasks([id]); + let workflows = await this.timelineCommonUtils.getWorkflowsAndTasks([id]); let unschedulableList = this.state.unschedulableList; // If the SUB status is changed to Unschedulable, add/update it to the unschedulable list if (suBlueprint.status === "unschedulable") { @@ -1656,7 +1657,7 @@ export class TimelineView extends Component { } /** - * Function set selected task types. Selected types are stored in local storage. + * Function set selected task types. Selected types are stored in local storage. * If default('observation') is selected, removed from local storage. * @param {Array} types - Array of task types */ @@ -1674,7 +1675,7 @@ export class TimelineView extends Component { /** * Set the grouping type project/SU. Default by SU. Store the selected grouping type in local storage. * Remove the local storage if default is selected. - * @param {boolean} groupByProject + * @param {boolean} groupByProject */ async setGroupByProject(groupByProject) { if (groupByProject) { @@ -1701,7 +1702,7 @@ export class TimelineView extends Component { await this.setState({isStationTasksVisible: !this.state.isStationTasksVisible}); this.updateTimeline(); } - + /** * Function sets the flag to show or hide reservation blocks in normal timeline view and the options is remembered. */ @@ -1755,7 +1756,7 @@ export class TimelineView extends Component { */ showDymanicSchedulerPopup(){ this.setState({showDialog: true}); - } + } /** * Close Dynamic Scheduling popup @@ -1763,15 +1764,15 @@ export class TimelineView extends Component { close() { this.setState({showDialog: false}); } - + async updateTimeline() { if (this.timeline) { const rangeData = await this.dateRangeCallback(this.state.currentStartTime, this.state.currentEndTime, true); - this.timeline.updateTimeline({ group: this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(rangeData.group, 'id'), ["parent", "start"], ['asc', 'asc']), + this.timeline.updateTimeline({ group: this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(rangeData.group, 'id'), ["parent", "start"], ['asc', 'asc']), items: rangeData.items, stationView: this.state.stationView}); } } - + render() { if (this.state.redirect) { return <Redirect to={{ pathname: this.state.redirect }}></Redirect> @@ -1796,23 +1797,27 @@ export class TimelineView extends Component { reservation.project = this.state.selectedItem.project; } let mouseOverItem = this.state.mouseOverItem; + const leftPanelClassName = isSUListVisible && (isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible || + (canExtendSUList && !canShrinkSUList) ? "col-lg-3 col-md-3 col-sm-12" : + ((canExtendSUList && canShrinkSUList) ? "col-lg-5 col-md-5 col-sm-12" : "col-lg-6 col-md-6 col-sm-12")) + return ( <React.Fragment> <TieredMenu className="app-header-menu" model={this.state.menuOptions} popup ref={el => this.optionsMenu = el} /> <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} actions={[ this.state.dsStatus && { - title: this.state.subSystemStatus?'Dynamic Scheduler is Active':'Dynamic Scheduler is Idle', + title: this.state.subSystemStatus?'Dynamic Scheduler is Active':'Dynamic Scheduler is Idle', className:` fa fa-${this.state.subSystemStatus? 'play' : 'pause'} subsystem p-chips-token-label su-${this.state.subSystemStatus? 'finished': 'warning'}`, - type: 'tag' }, - { content: 'D', + type: 'tag' }, + { content: 'D', title: this.state.dsStatus?'Dynamic Scheduling is On':'Dynamic Scheduling is Off', className:`tag p-chips-token-label su-${this.state.dsStatus? 'finished': 'error'}`, - type: 'tag' }, - { content: 'F', - title: this.state.fsStatus?'Fixed time Scheduling is On':'Fixed time Scheduling is Off', + type: 'tag' }, + { content: 'F', + title: this.state.fsStatus?'Fixed time Scheduling is On':'Fixed time Scheduling is Off', className:`tag p-chips-token-label su-${this.state.fsStatus? 'finished': 'error'}`, - type: 'tag' }, + type: 'tag' }, { icon: 'fa-cog', title: 'Scheduling Settings', type: 'button', actOn: 'click', props: { callback: this.showDymanicSchedulerPopup }, }, { icon: 'fa-bars', title: '', @@ -1825,21 +1830,23 @@ export class TimelineView extends Component { } { this.state.isLoading ? <AppLoader /> : <div className="p-grid"> - {/* SU List Panel */} - <div className={isSUListVisible && (isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible || - (canExtendSUList && !canShrinkSUList) ? "timeline-panel col-lg-3 col-md-3 col-sm-12" : - ((canExtendSUList && canShrinkSUList) ? "timeline-panel col-lg-5 col-md-5 col-sm-12" : "timeline-panel col-lg-6 col-md-6 col-sm-12"))} - style={isSUListVisible ? { position: "inherit", borderRight: "3px solid #efefef", paddingTop: "10px" } : { display: 'none' }}> - <TimelineListTabs suBlueprintList={this.state.suBlueprintList} - suListFilterCallback={this.suListFilterCallback} - reservationList={this.getReservationList()} - showSummary={this.onItemClick} - suStatusList={this.state.suStatusList} - taskStatusList={this.state.taskStatusList} - taskTypeList={this.state.taskTypeList} - unschedulableList={this.state.unschedulableList} - viewName="Timeline" - ></TimelineListTabs> + <div className={leftPanelClassName}> + <div className="legend-header">Legend</div> + <Legendbar/> + {/* SU List Panel */} + <div className="timeline-panel" + style={isSUListVisible ? { position: "inherit", borderRight: "3px solid #efefef", paddingTop: "10px" } : { display: 'none' }}> + <TimelineListTabs suBlueprintList={this.state.suBlueprintList} + suListFilterCallback={this.suListFilterCallback} + reservationList={this.getReservationList()} + showSummary={this.onItemClick} + suStatusList={this.state.suStatusList} + taskStatusList={this.state.taskStatusList} + taskTypeList={this.state.taskTypeList} + unschedulableList={this.state.unschedulableList} + viewName="Timeline" + ></TimelineListTabs> + </div> </div> {/* Timeline Panel */} <div className={isSUListVisible ? ((isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible || this.state.isUnscheduledDetVisible) ? "timeline-panel col-lg-6 col-md-6 col-sm-12" : @@ -1928,9 +1935,9 @@ export class TimelineView extends Component { constraintsTemplate={this.state.suConstraintTemplate} onUpdate = {this.onSaveChanges} newPopupvalue={this.newPopupvalue} - onClose= {this.state.openEditConstraintDialog} - onSavedisable={this.state.onSavedisable} - onCloseFn={this.editConstraint} + onClose= {this.state.openEditConstraintDialog} + onSavedisable={this.state.onSavedisable} + onCloseFn={this.editConstraint} stationGroup={this.state.stationGroup} missingStations={this.state.missingStations} cancelCheckstatus={this.cancelCheckstatus} @@ -1968,14 +1975,14 @@ export class TimelineView extends Component { <TimelineItemPopover mouseOverItem={mouseOverItem}/> </div> {!this.state.isLoading && - <Websocket ref={websocket => this.websocket = websocket} url={process.env.REACT_APP_WEBSOCKET_URL} + <Websocket ref={websocket => this.websocket = websocket} url={process.env.REACT_APP_WEBSOCKET_URL} onOpen={this.onConnect} onMessage={this.handleData} onClose={this.onDisconnect} />} {this.state.showDialog && <div className="p-grid" data-testid="confirm_dialog"> <CustomDialog type="success" visible={this.state.showDialog} width="25vw" showIcon={false} - header="Scheduling Settings" message={<DynamicScheduler callBack={this.updateDSStatus} />} - content={''} - actions={ [ + header="Scheduling Settings" message={<DynamicScheduler callBack={this.updateDSStatus} />} + content={''} + actions={ [ {id:"no", title: 'Close', callback: this.close} ]} onClose={this.close} /> 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 cac0e7c9b4a919ee2cda13eddcd6a3ff06c304bd..c966ced15bdb31ed55052415b5314ea3c089714a 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 @@ -32,6 +32,7 @@ import TopProgressBar from '../../layout/components/TopProgressBar'; import {getTimelineItem} from "./week.view.helper"; import TimelineItemPopover from "./components/TimelineItemPopover"; import Toolbar from "./components/Toolbar"; +import Legendbar from "./components/Legendbar"; // Color constant for status export const STATUS_COLORS = { @@ -1416,6 +1417,9 @@ export class WeekTimelineView extends Component { reservation.project = this.state.selectedItem.project; } const mouseOverItem = this.state.mouseOverItem; + const leftPanelClassName = isSUListVisible && (isSUDetsVisible || isReservDetsVisible || + (canExtendSUList && !canShrinkSUList) ? "col-lg-4 col-md-4 col-sm-12" : + ((canExtendSUList && canShrinkSUList) ? "col-lg-5 col-md-5 col-sm-12" : "col-lg-6 col-md-6 col-sm-12")) return ( <React.Fragment> <TieredMenu className="app-header-menu" model={this.state.menuOptions} popup @@ -1456,34 +1460,28 @@ export class WeekTimelineView extends Component { } {this.state.isLoading ? <AppLoader/> : <> - {/* <div className="p-field p-grid"> - <div className="col-lg-6 col-md-6 col-sm-12" data-testid="project" > - <Dropdown inputId="project" optionLabel="name" optionValue="name" - value={this.state.selectedProject} options={this.state.projects} - onChange={(e) => {this.filterByProject(e.value)}} - placeholder="Filter by Project" /> - </div> - </div> */} <div className="p-grid"> + <div className={leftPanelClassName}> + <div className="legend-header">Legend</div> + <Legendbar/> {/* SU List Panel */} - <div className={isSUListVisible && (isSUDetsVisible || isReservDetsVisible || - (canExtendSUList && !canShrinkSUList) ? "timeline-panel col-lg-4 col-md-4 col-sm-12" : - ((canExtendSUList && canShrinkSUList) ? "timeline-panel col-lg-5 col-md-5 col-sm-12" : "timeline-panel col-lg-6 col-md-6 col-sm-12"))} - style={isSUListVisible ? { - position: "inherit", - borderRight: "5px solid #efefef", - paddingTop: "10px" - } : {display: "none"}}> - <TimelineListTabs suBlueprintList={this.state.suBlueprintList} - suListFilterCallback={this.suListFilterCallback} - reservationList={this.getReservationList()} - suStatusList={this.state.suStatusList} - showSummary={this.onItemClick} - unschedulableList={this.state.unschedulableList} - taskStatusList={this.state.taskStatusList} - taskTypeList={this.state.taskTypeList} - viewName="Weekview" - ></TimelineListTabs> + <div className="timeline-panel" + style={isSUListVisible ? { + position: "inherit", + borderRight: "5px solid #efefef", + paddingTop: "10px" + } : {display: "none"}}> + <TimelineListTabs suBlueprintList={this.state.suBlueprintList} + suListFilterCallback={this.suListFilterCallback} + reservationList={this.getReservationList()} + suStatusList={this.state.suStatusList} + showSummary={this.onItemClick} + unschedulableList={this.state.unschedulableList} + taskStatusList={this.state.taskStatusList} + taskTypeList={this.state.taskTypeList} + viewName="Weekview" + ></TimelineListTabs> + </div> </div> {/* Timeline Panel */} <div @@ -1551,7 +1549,7 @@ export class WeekTimelineView extends Component { showLive={false} showDateRange={false} viewType={UIConstants.timeline.types.WEEKVIEW} dateRangeCallback={this.dateRangeCallback} - //Toolbar properties + //Toolbar properties onSkyViewToggle={this.state.isOnSkyView} setOnSkyViewToggle={this.setOnSkyView} reservationsViewToggle={this.state.reservationEnabled}