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 19ee68919a9d2397e8e4691152bc713246427d73..2a46eb3640812b65abfe632a9fd2100907b5d50f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -170,6 +170,10 @@ export class CalendarTimeline extends Component { return true; } + componentWillUnmount() { + this.componentUnmounting = true; // Variable to check and terminate any API calls in loop + } + /** * Sets current UTC and LST time either from the server or locally. * @param {boolean} systemClock - to differetiate whether tosync with server or local update @@ -719,7 +723,7 @@ export class CalendarTimeline extends Component { zIndex: item.type==="SUNTIME"?79:80 }, onMouseDown: () => { - if (item.type !== "SUNTIME" && item.type !== "RESERVATION") { + if (item.type !== "SUNTIME") { this.onItemClick(item); } else { @@ -814,7 +818,9 @@ export class CalendarTimeline extends Component { * @param {Object} item */ onItemMouseOver(evt, item) { - if ((item.type==="SCHEDULE" || item.type==="TASK") && this.props.itemMouseOverCallback) { + if ((item.type==="SCHEDULE" || item.type==="TASK" || item.type==="RESERVATION") + && this.props.itemMouseOverCallback) { + this.setState({mouseEvent: true}); this.props.itemMouseOverCallback(evt, item); } } @@ -824,7 +830,9 @@ export class CalendarTimeline extends Component { * @param {Object} item */ onItemMouseOut(evt, item) { - if ((item.type==="SCHEDULE" || item.type==="TASK") && this.props.itemMouseOutCallback) { + if ((item.type==="SCHEDULE" || item.type==="TASK"|| item.type==="RESERVATION") + && this.props.itemMouseOutCallback) { + this.setState({mouseEvent: true}); this.props.itemMouseOutCallback(evt); } } @@ -835,13 +843,14 @@ export class CalendarTimeline extends Component { * @param {moment} endTime */ async changeDateRange(startTime, endTime, refreshData) { - if (this.props.showSunTimings && this.state.viewType===UIConstants.timeline.types.NORMAL) { + if (this.props.showSunTimings && this.state.viewType===UIConstants.timeline.types.NORMAL && !this.loadingNormalSuntimes) { this.setNormalSuntimings(startTime, endTime); } const result = await this.props.dateRangeCallback(startTime, endTime, refreshData); - if (!this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL) { + if (!this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL && !this.loadingStationSunTimes) { result.items = await this.addStationSunTimes(startTime, endTime, result.group, result.items); - } else if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) { + result.items = _.orderBy(result.items, ['type'], ['desc']); + } else if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW && !this.loadingWeekSunTimes) { let group = DEFAULT_GROUP.concat(result.group); result.items = await this.addWeekSunTimes(startTime, endTime, group, result.items); } @@ -853,14 +862,16 @@ export class CalendarTimeline extends Component { * @param {moment} startTime * @param {moment} endTime */ - setNormalSuntimings(startTime, endTime) { + async setNormalSuntimings(startTime, endTime) { let sunRiseTimings = [], sunSetTimings = [], sunTimeMap={}; const noOfDays = endTime.diff(startTime, 'days'); - for (const number of _.range(noOfDays+3)) { // Added 3 to have suntimes of day before start time and day after end time so that for small time duration also, suntimes will be available. - let prevStartTime = startTime.clone().add(-1, 'days'); - const date = prevStartTime.clone().add(number, 'days').hours(12).minutes(0).seconds(0); - const formattedDate = date.format("YYYY-MM-DD"); - UtilService.getSunTimings(formattedDate).then(timings => { + if (!this.loadingNormalSuntimes) { + this.loadingNormalSuntimes = true; + for (const number of _.range(noOfDays+3)) { // Added 3 to have suntimes of day before start time and day after end time so that for small time duration also, suntimes will be available. + let prevStartTime = startTime.clone().add(-1, 'days'); + const date = prevStartTime.clone().add(number, 'days').hours(12).minutes(0).seconds(0); + const formattedDate = date.format("YYYY-MM-DD"); + let timings = await UtilService.getSunTimings(formattedDate); if (timings) { const sunriseStartTime = moment.utc(timings.sun_rise.start.split('.')[0]); const sunriseEndTime = moment.utc(timings.sun_rise.end.split('.')[0]); @@ -877,7 +888,10 @@ export class CalendarTimeline extends Component { sunTimeMap[formattedDate] = {sunrise: sunriseTime, sunset: sunsetTime}; this.setState({sunRiseTimings: sunRiseTimings, sunSetTimings: sunSetTimings, sunTimeMap: sunTimeMap}); } - }); + if (number === (noOfDays+2)) { + this.loadingNormalSuntimes = false; + } + } } } @@ -891,78 +905,84 @@ export class CalendarTimeline extends Component { async addStationSunTimes(startTime, endTime, stationGroup, items) { const noOfDays = endTime.diff(startTime, 'days'); let sunItems = _.cloneDeep(items); + this.loadingStationSunTimes = true; for (const number of _.range(noOfDays+1)) { for (const station of stationGroup) { - const date = startTime.clone().add(number, 'days').hours(12).minutes(0).seconds(0); - const timings = await UtilService.getSunTimings(date.format("YYYY-MM-DD"), station.id); - if (timings) { - let sunriseItem = { id: `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(timings.sun_rise.start), - end_time: moment.utc(timings.sun_rise.end), - bgColor: "yellow", - selectedBgColor: "yellow", - type: "SUNTIME"}; - sunItems.push(sunriseItem); - let sunsetItem = _.cloneDeep(sunriseItem); - sunsetItem.id = `sunset-${number}-${station.id}`; - // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; - sunsetItem.title = ""; - sunsetItem.start_time = moment.utc(timings.sun_set.start); - sunsetItem.end_time = moment.utc(timings.sun_set.end); - sunsetItem.bgColor = "orange"; - sunsetItem.selectedBgColor = "orange"; - sunItems.push(sunsetItem); - let befSunriseItem = _.cloneDeep(sunriseItem); - befSunriseItem.id = `bef-sunrise-${number}-${station.id}`; - // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; - befSunriseItem.title = ""; - befSunriseItem.start_time = moment.utc(timings.sun_rise.start).hours(0).minutes(0).seconds(0); - befSunriseItem.end_time = moment.utc(timings.sun_rise.start); - befSunriseItem.bgColor = "grey"; - befSunriseItem.selectedBgColor = "grey"; - sunItems.push(befSunriseItem); - let afterSunsetItem = _.cloneDeep(sunriseItem); - afterSunsetItem.id = `aft-sunset-${number}-${station.id}`; - // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; - afterSunsetItem.title = ""; - afterSunsetItem.start_time = moment.utc(timings.sun_set.end); - afterSunsetItem.end_time = moment.utc(timings.sun_set.end).hours(23).minutes(59).seconds(59); - afterSunsetItem.bgColor = "grey"; - afterSunsetItem.selectedBgColor = "grey"; - sunItems.push(afterSunsetItem); - let dayItem = _.cloneDeep(sunriseItem); - dayItem.id = `day-${number}-${station.id}`; - // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; - dayItem.title = ""; - dayItem.start_time = moment.utc(timings.sun_rise.end); - dayItem.end_time = moment.utc(timings.sun_set.start); - dayItem.bgColor = "white"; - dayItem.selectedBgColor = "white"; - sunItems.push(dayItem); + if (!this.componentUnmounting) { + const date = startTime.clone().add(number, 'days').hours(12).minutes(0).seconds(0); + const timings = await UtilService.getSunTimings(date.format("YYYY-MM-DD"), station.id); + if (timings) { + let sunriseItem = { id: `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(timings.sun_rise.start), + end_time: moment.utc(timings.sun_rise.end), + bgColor: "yellow", + selectedBgColor: "yellow", + type: "SUNTIME"}; + sunItems.push(sunriseItem); + let sunsetItem = _.cloneDeep(sunriseItem); + sunsetItem.id = `sunset-${number}-${station.id}`; + // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; + sunsetItem.title = ""; + sunsetItem.start_time = moment.utc(timings.sun_set.start); + sunsetItem.end_time = moment.utc(timings.sun_set.end); + sunsetItem.bgColor = "orange"; + sunsetItem.selectedBgColor = "orange"; + sunItems.push(sunsetItem); + let befSunriseItem = _.cloneDeep(sunriseItem); + befSunriseItem.id = `bef-sunrise-${number}-${station.id}`; + // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; + befSunriseItem.title = ""; + befSunriseItem.start_time = moment.utc(timings.sun_rise.start).hours(0).minutes(0).seconds(0); + befSunriseItem.end_time = moment.utc(timings.sun_rise.start); + befSunriseItem.bgColor = "grey"; + befSunriseItem.selectedBgColor = "grey"; + sunItems.push(befSunriseItem); + let afterSunsetItem = _.cloneDeep(sunriseItem); + afterSunsetItem.id = `aft-sunset-${number}-${station.id}`; + // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; + afterSunsetItem.title = ""; + afterSunsetItem.start_time = moment.utc(timings.sun_set.end); + afterSunsetItem.end_time = moment.utc(timings.sun_set.end).hours(23).minutes(59).seconds(59); + afterSunsetItem.bgColor = "grey"; + afterSunsetItem.selectedBgColor = "grey"; + sunItems.push(afterSunsetItem); + let dayItem = _.cloneDeep(sunriseItem); + dayItem.id = `day-${number}-${station.id}`; + // sunsetItem.title = `${timings.sun_set.start} to ${timings.sun_set.end}`; + dayItem.title = ""; + dayItem.start_time = moment.utc(timings.sun_rise.end); + dayItem.end_time = moment.utc(timings.sun_set.start); + 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); + } } 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); + break; } } } + this.loadingStationSunTimes = false; if (!this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL) { items = sunItems; } @@ -978,6 +998,7 @@ export class CalendarTimeline extends Component { */ async addWeekSunTimes(startTime, endTime, weekGroup, items) { let sunItems = _.cloneDeep(items); + this.loadingWeekSunTimes = true; for (const weekDay of weekGroup) { if (weekDay.value) { const timings = await UtilService.getSunTimings(weekDay.value.format("YYYY-MM-DD"), 'CS001'); @@ -1028,6 +1049,7 @@ export class CalendarTimeline extends Component { sunItems.push(afterSunsetItem); } } + this.loadingWeekSunTimes = false; } if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) { items = _.orderBy(sunItems, ['type'], ['desc']); @@ -1245,22 +1267,26 @@ export class CalendarTimeline extends Component { * @param {Object} props */ async updateTimeline(props) { - this.setState({ showSpinner: true }); - let group = DEFAULT_GROUP.concat(props.group); - if (!this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL) { - props.items = await this.addStationSunTimes(this.state.defaultStartTime, this.state.defaultEndTime, props.group, props.items); - } else if(this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL) { - this.setNormalSuntimings(this.state.defaultStartTime, this.state.defaultEndTime); - } else if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW) { - props.items = await this.addWeekSunTimes(this.state.defaultStartTime, this.state.defaultEndTime, group, props.items); + if (!this.state.mouseEvent) { // No need to update timeline items for mouseover and mouseout events + // this.setState({ showSpinner: true }); + let group = DEFAULT_GROUP.concat(props.group); + if (!this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL && !this.loadingStationSunTimes) { + props.items = await this.addStationSunTimes(this.state.defaultStartTime, this.state.defaultEndTime, props.group, props.items); + } else if(this.props.showSunTimings && this.state.viewType === UIConstants.timeline.types.NORMAL && !this.loadingNormalSuntimes) { + this.setNormalSuntimings(this.state.defaultStartTime, this.state.defaultEndTime); + } else if (this.state.viewType === UIConstants.timeline.types.WEEKVIEW && !this.loadingWeekSunTimes) { + props.items = await this.addWeekSunTimes(this.state.defaultStartTime, this.state.defaultEndTime, group, props.items); + } + this.setState({group: group, showSpinner: false, items: _.orderBy(props.items, ['type'], ['desc'])}); + } else { + this.setState({mouseEvent: false}); } - this.setState({group: group, showSpinner: false, items: _.orderBy(props.items, ['type'], ['desc'])}); } render() { return ( <React.Fragment> - <CustomPageSpinner visible={this.state.showSpinner} /> + {/* <CustomPageSpinner visible={this.state.showSpinner} /> */} {/* Toolbar for the timeline */} <div className={`p-fluid p-grid timeline-toolbar ${this.props.className}`}> {/* Clock Display */} 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 7cf493ad504c52e7f507c474d121e00df44f57e1..e9e71c99a8042f651bc6227ef639e90b6f6841b5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss @@ -242,7 +242,7 @@ color: white; } -.reserve.dynamic { +.reserve-dynamic { background-color: #9b9999; color: white; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/index.js index 2076201f6695b183946734cc9e1aa6a2b26b5ea0..658c2a00acf6f252714630e1c88ad3eec7f8b3d7 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/index.js @@ -2,4 +2,5 @@ import {TimelineView} from './view'; import {WeekTimelineView} from './week.view'; import { ReservationList} from './reservation.list'; import { ReservationCreate } from './reservation.create'; -export {TimelineView, WeekTimelineView, ReservationCreate, ReservationList} ; +import { ReservationSummary } from './reservation.summary'; +export {TimelineView, WeekTimelineView, ReservationCreate, ReservationList, ReservationSummary} ; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/reservation.summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/reservation.summary.js new file mode 100644 index 0000000000000000000000000000000000000000..50a80c71b0af29f2090e8f00ab6cac1c057b54f3 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/reservation.summary.js @@ -0,0 +1,139 @@ +import React, {Component} from 'react'; +import { Link } from 'react-router-dom/cjs/react-router-dom.min'; +import moment from 'moment'; +import _ from 'lodash'; +import { JsonToTable } from "react-json-to-table"; +import UIConstants from '../../utils/ui.constants'; +import UnitConverter from '../../utils/unit.converter'; + +/** + * Component to view summary of the Reservation + */ +export class ReservationSummary extends Component { + + constructor(props) { + super(props); + this.closeSUDets = this.closeSUDets.bind(this); + } + + componentDidMount() {} + + /** + * Function to close the summary panel and call parent callback function to close. + */ + closeSUDets() { + if(this.props.closeCallback) { + this.props.closeCallback(); + } + } + + /** + * Function to order or format all specifications to readable values + * @param {Object} specifications + */ + getOrderedSpecifications(specifications) { + for (const specKey of _.keys(specifications)) { + let specification = this.getFormattedSpecification(specifications[specKey]); + specifications[specKey] = specification; + } + return specifications; + } + + /** + * Function to format date, boolean, array object to readable values + * @param {Object} specification + */ + getFormattedSpecification(specification) { + if (specification !== null) { + const objectType = typeof specification; + switch(objectType) { + case "string": { + try { + const dateValue = moment.utc(specification); + if (dateValue.isValid()) { + specification = dateValue.format(UIConstants.CALENDAR_DATETIME_FORMAT); + } + } catch (error) {} + break; + } + case "boolean": { + specification = specification?'True':'False'; + break; + } + case "object": { + if (Array.isArray(specification)) { + let newArray = [], isStringArray = false; + for (let arrayObj of specification) { + arrayObj = this.getFormattedSpecification(arrayObj); + if (arrayObj) { + if ((typeof arrayObj) === "string") { + isStringArray = true; + } + newArray.push(arrayObj); + } + } + specification = newArray.length > 0?(isStringArray?newArray.join(", "):newArray):null; + } else { + let newObject = {}; + let keys = _.keys(specification); + for (const objectKey of _.keys(specification)) { + let object = this.getFormattedSpecification(specification[objectKey]); + if (object) { + newObject[objectKey.replace(/_/g, ' ')] = object; + } + } + specification = (!_.isEmpty(newObject))? newObject:null; + } + break; + } + default: {} + } + } + return specification; + } + + render() { + const reservation = this.props.reservation; + let specifications = reservation?_.cloneDeep(reservation.specifications_doc):null; + if (specifications) { + // Remove $schema variable + delete specifications['$schema']; + } + return ( + <React.Fragment> + { reservation && + <div className="p-grid timeline-details-pane" style={{marginTop: '10px'}}> + <h6 className="col-lg-10 col-sm-10">Reservation Details</h6> + {/* TODO: Enable the link once Reservation view page is created */} + {/* <Link to={`/su/timeline/reservation/view/${reservation.id}`} title="View Full Details" ><i className="fa fa-eye"></i></Link> */} + <i className="fa fa-eye" style={{color: 'grey'}}></i> + <Link to={this.props.location?this.props.location.pathname:"/su/timelineview"} onClick={this.closeSUDets} title="Close Details"><i className="fa fa-times"></i></Link> + <div className="col-4"><label>Name:</label></div> + <div className="col-8">{reservation.name}</div> + <div className="col-4"><label>Description:</label></div> + <div className="col-8">{reservation.description}</div> + <div className="col-4"><label>Project:</label></div> + <div className="col-8">{reservation.project}</div> + <div className="col-4"><label>Start Time:</label></div> + <div className="col-8">{moment.utc(reservation.start_time).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <div className="col-4"><label>Stop Time:</label></div> + <div className="col-8">{moment.utc(reservation.stop_time).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <div className="col-4"><label>Duration (HH:mm:ss):</label></div> + <div className="col-8">{UnitConverter.getSecsToHHmmss(reservation.duration)}</div> + {/* Reservation parameters Display in table format */} + {reservation.specifications_doc && + <> + <div className="col-12 constraints-summary"> + <label>Parameters:</label> + <JsonToTable json={this.getOrderedSpecifications(specifications)} /> + </div> + </> + } + </div> + } + </React.Fragment> + ); + } +} + +export default ReservationSummary; \ No newline at end of file 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 fd0d2b161d76feb6acd61c2ac80abf1d17de8ffe..7c8436aeacf4e17fa5b73fef1a7c73b81b7eb308 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -22,6 +22,7 @@ import TaskService from '../../services/task.service'; import UnitConverter from '../../utils/unit.converter'; import Validator from '../../utils/validator'; import SchedulingUnitSummary from '../Scheduling/summary'; +import ReservationSummary from './reservation.summary'; import { Dropdown } from 'primereact/dropdown'; import { OverlayPanel } from 'primereact/overlaypanel'; import { RadioButton } from 'primereact/radiobutton'; @@ -88,6 +89,7 @@ export class TimelineView extends Component { this.onItemMouseOver = this.onItemMouseOver.bind(this); this.onItemMouseOut = this.onItemMouseOut.bind(this); this.showSUSummary = this.showSUSummary.bind(this); + this.showReservationSummary = this.showReservationSummary.bind(this); this.showTaskSummary = this.showTaskSummary.bind(this); this.closeSUDets = this.closeSUDets.bind(this); this.dateRangeCallback = this.dateRangeCallback.bind(this); @@ -295,6 +297,8 @@ export class TimelineView extends Component { onItemClick(item) { if (item.type === "SCHEDULE") { this.showSUSummary(item); + } else if (item.type === "RESERVATION") { + this.showReservationSummary(item); } else { this.showTaskSummary(item); } @@ -349,6 +353,14 @@ export class TimelineView extends Component { } } + /** + * To load and show Reservation summary + * @param {Object} item + */ + showReservationSummary(item) { + this.setState({selectedItem: item, isReservDetsVisible: true, isSUDetsVisible: false}); + } + /** * To load task summary and show * @param {Object} item - Timeline task item object @@ -361,7 +373,7 @@ export class TimelineView extends Component { * Closes the SU details section */ closeSUDets() { - this.setState({isSUDetsVisible: false, isTaskDetsVisible: false, canExtendSUList: true, canShrinkSUList: false}); + this.setState({isSUDetsVisible: false, isReservDetsVisible: false, isTaskDetsVisible: false, canExtendSUList: true, canShrinkSUList: false}); } /** @@ -378,23 +390,34 @@ export class TimelineView extends Component { * @param {Object} item */ onItemMouseOver(evt, item) { - const itemSU = _.find(this.state.suBlueprints, {id: (item.suId?item.suId:item.id)}); - const itemStations = this.getSUStations(itemSU); - const itemStationGroups = this.groupSUStations(itemStations); - item.stations = {groups: "", counts: ""}; - item.suName = itemSU.name; - for (const stationgroup of _.keys(itemStationGroups)) { - let groups = item.stations.groups; - let counts = item.stations.counts; - if (groups) { - groups = groups.concat("/"); - counts = counts.concat("/"); + if (item.type === "SCHEDULE" || item.type === "TASK") { + const itemSU = _.find(this.state.suBlueprints, {id: (item.suId?item.suId:item.id)}); + const itemStations = this.getSUStations(itemSU); + const itemStationGroups = this.groupSUStations(itemStations); + item.stations = {groups: "", counts: ""}; + item.suName = itemSU.name; + 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; } - // 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; + } else { + const reservation = _.find(this.reservations, {'id': parseInt(item.id.split("-")[1])}); + const reservStations = reservation.specifications_doc.resources.stations; + const reservStationGroups = this.groupSUStations(reservStations); + item.name = reservation.name; + item.contact = reservation.specifications_doc.activity.contact + item.activity_type = reservation.specifications_doc.activity.type; + item.stations = reservStations; + item.planned = reservation.specifications_doc.activity.planned; } this.popOver.toggle(evt); this.setState({mouseOverItem: item}); @@ -463,7 +486,8 @@ export class TimelineView extends Component { // On range change close the Details pane // this.closeSUDets(); // console.log(_.orderBy(group, ["parent", "id"], ['asc', 'desc'])); - return {group: this.stationView? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'),["parent", "start"], ['asc', 'asc']), items: items}; + group = this.state.stationView ? this.getStationsByGroupName() : _.orderBy(_.uniqBy(group, 'id'),["parent", "start"], ['asc', 'asc']); + return {group: group, items: items}; } /** @@ -849,13 +873,18 @@ export class TimelineView extends Component { // return <AppLoader /> // } const isSUDetsVisible = this.state.isSUDetsVisible; + const isReservDetsVisible = this.state.isReservDetsVisible; const isTaskDetsVisible = this.state.isTaskDetsVisible; const canExtendSUList = this.state.canExtendSUList; const canShrinkSUList = this.state.canShrinkSUList; - let suBlueprint = null; + let suBlueprint = null, reservation = null; if (isSUDetsVisible) { suBlueprint = _.find(this.state.suBlueprints, {id: this.state.stationView?parseInt(this.state.selectedItem.id.split('-')[0]):this.state.selectedItem.id}); } + if (isReservDetsVisible) { + reservation = _.find(this.reservations, {id: parseInt(this.state.selectedItem.id.split('-')[1])}); + reservation.project = this.state.selectedItem.project; + } let mouseOverItem = this.state.mouseOverItem; return ( <React.Fragment> @@ -869,7 +898,7 @@ export class TimelineView extends Component { { this.state.isLoading ? <AppLoader /> : <div className="p-grid"> {/* SU List Panel */} - <div className={isSUDetsVisible || isTaskDetsVisible || (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")} + <div className={isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible || (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")} style={{position: "inherit", borderRight: "5px solid #efefef", paddingTop: "10px"}}> <ViewTable viewInNewWindow @@ -895,7 +924,7 @@ export class TimelineView extends Component { /> </div> {/* Timeline Panel */} - <div className={isSUDetsVisible || isTaskDetsVisible || (!canExtendSUList && canShrinkSUList)?"col-lg-5 col-md-5 col-sm-12":((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")}> + <div className={isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible || (!canExtendSUList && canShrinkSUList)?"col-lg-5 col-md-5 col-sm-12":((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")}> {/* Panel Resize buttons */} <div className="resize-div"> <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} @@ -990,12 +1019,20 @@ export class TimelineView extends Component { } </div> } + {this.state.isReservDetsVisible && + <div className="col-lg-3 col-md-3 col-sm-12" + style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> + {this.state.isSummaryLoading?<AppLoader /> : + <ReservationSummary reservation={reservation} closeCallback={this.closeSUDets}></ReservationSummary> + } + </div> + } </div> } {/* SU Item Tooltip popover with SU status color */} <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> - {mouseOverItem && + {(mouseOverItem && (["SCHEDULE", "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> <hr></hr> @@ -1030,6 +1067,37 @@ export class TimelineView extends Component { <div className="col-7">{mouseOverItem.duration}</div> </div> } + {(mouseOverItem && mouseOverItem.type == "RESERVATION") && + <div className={`p-grid`} style={{width: '350px', backgroundColor: mouseOverItem.bgColor, color: mouseOverItem.color}}> + <h3 className={`col-12`}>Reservation Overview</h3> + <hr></hr> + <label className={`col-5`} style={{color: mouseOverItem.color}}>Name:</label> + <div className="col-7">{mouseOverItem.name}</div> + <label className={`col-5`} style={{color: mouseOverItem.color}}>Description:</label> + <div className="col-7">{mouseOverItem.desc}</div> + <label className={`col-5`} style={{color: mouseOverItem.color}}>Type:</label> + <div className="col-7">{mouseOverItem.activity_type}</div> + <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> + {/* <div className="col-7"><ListBox options={mouseOverItem.stations} /></div> */} + <div className="col-7 station-list"> + {mouseOverItem.stations.map((station, index) => ( + <div key={`stn-${index}`}>{station}</div> + ))} + </div> + <label className={`col-5`} style={{color: mouseOverItem.color}}>Project:</label> + <div className="col-7">{mouseOverItem.project?mouseOverItem.project:"-"}</div> + <label className={`col-5`} style={{color: mouseOverItem.color}}>Start Time:</label> + <div className="col-7">{mouseOverItem.start_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + <label className={`col-5`} style={{color: mouseOverItem.color}}>End Time:</label> + <div className="col-7">{mouseOverItem.end_time.format(UIConstants.CALENDAR_DATETIME_FORMAT)}</div> + {/* <label className={`col-5`} style={{color: mouseOverItem.color}}>Stations:</label> + <div className="col-7">{mouseOverItem.stations.groups}:{mouseOverItem.stations.counts}</div> */} + <label className={`col-5`} style={{color: mouseOverItem.color}}>Duration:</label> + <div className="col-7">{mouseOverItem.duration}</div> + <label className={`col-5`} style={{color: mouseOverItem.color}}>Planned:</label> + <div className="col-7">{mouseOverItem.planned?'Yes':'No'}</div> + </div> + } </OverlayPanel> {!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/util.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js index d46b2fba3e90b785a60bf6f1f95e9ac0edc0dc74..036165fc7138afbbed3f02de55ff5a1564b4f438 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/util.service.js @@ -64,6 +64,19 @@ const UtilService = { return null; } }, + /** + * + * @param {String} timestamps - Date in 'YYYY-MM-DD' format. Multiples dates are separated by commas (2020-08-15, 2021-01-26). + */ + getAllStationSunTimings: async(timestamps) => { + try { + let allStations = (await axios.get("/api/station_groups/stations/1/All")).data.stations; + let allStationSuntimes = (await axios.get(`/api/util/sun_rise_and_set?stations=${allStations.join(",")}×tamps=${timestamps}`)).data; + return allStationSuntimes; + } catch(error) { + console.error(error); + } + }, /** Gets all reservations in the system */ getReservations: async() => { try {