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 b3b6e9a072b8cd157549746d8fbfbc59ebd1b0b1..ab3c4cd054133df1ac284d67734cfa5278e36b7f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -26,6 +26,7 @@ import Flatpickr from "react-flatpickr"; import confirmDatePlugin from "flatpickr/dist/plugins/confirmDate/confirmDate"; import shortcutButtonsPlugin from "shortcut-buttons-flatpickr"; import UIConstants from '../../utils/ui.constants'; +import DateTimeNavigator from "../../routes/Timeline/components/DateTimeNavigator"; // Label formats for day headers based on the interval label width const DAY_HEADER_FORMATS = [{ name: "longer", minWidth: 300, maxWidth: 50000, format: "DD dddd, MMMM YYYY"}, @@ -165,6 +166,8 @@ export class CalendarTimeline extends Component { //>>>>>> Public functions of the component this.updateTimeline = this.updateTimeline.bind(this); + this.changeZoomLevel = this.changeZoomLevel.bind(this); + this.changeWeek = this.changeWeek.bind(this); //<<<<<< Public functions of the component } @@ -1543,129 +1546,41 @@ export class CalendarTimeline extends Component { } render() { - let isWeekView = this.state.viewType===UIConstants.timeline.types.WEEKVIEW; + let isWeekView = this.state.viewType === UIConstants.timeline.types.WEEKVIEW; + const isRange = this.state.viewType !== UIConstants.timeline.types.WEEKVIEW; + let onCloseDateFunction = isRange ? + (value) => { + this.setZoomRange(value, true); + this.validateRange(value) + } + : (value) => { + // Set the selected date as the current week day and calculate the number of weeks between and change to the week + this.setState({weekDay: value, showCustomDate: true, reset: false}); + this.changeWeek(null, true); + } return ( <React.Fragment> - {/* <CustomPageSpinner visible={this.state.showSpinner} /> */} + <DateTimeNavigator + currentUTC={this.state.currentUTC} + allowLive={isRange} + timelineUIAttributes={this.timelineUIAttributes} + timelineCommonUtils={this.timelineCommonUtils} + isRange={isRange} + currentDateValue={isRange ? this.state.zoomRange : this.state.weekDay} + onChangeDateCallback={isRange ? (value) => this.setZoomRange(value, false) : undefined} + onCloseDateCallback={onCloseDateFunction} + onClickDateResetButtonCallback={isRange ? () => this.setZoomRange(null, true) : this.resetToCurrentTime} + onWeekChangeCallback={isRange ? undefined : this.changeWeek} + onClickTimeZoomResetButtonCallback={this.resetToCurrentTime} + moveLeftCallback={this.moveLeft} + moveRightCallback={this.moveRight} + zoomOutCallback={this.zoomIn} + zoomInCallback={this.zoomOut} + zoomLevel={this.state.zoomLevel} + allZoomLevels={this.ZOOM_LEVELS} + onChangeZoomLevelCallback={this.changeZoomLevel} + /> {/* Toolbar for the timeline */} - <div className={`p-fluid p-grid timeline-toolbar ${this.props.className}`}> - {/* Clock Display */} - <div className="p-col-2" style={{padding: '0px 0px 0px 10px'}}> - <div style={{marginTop: "0px"}}> - <label style={{marginBottom: "0px"}}>Date:</label><span>{this.state.currentUTC.format(UTC_DATE_FORMAT)}</span> - </div> - <div style={{marginTop: "0px"}}> - <label style={{marginBottom: "0px"}}>UTC:</label><span>{this.state.currentUTC.format(UTC_TIME_FORMAT)}</span> - </div> - {this.state.currentLST && - <div style={{marginTop: "0px"}}> - <label style={{marginBottom: "0px"}}>LST:</label><span>{this.state.currentLST.format(UIConstants.CALENDAR_TIME_FORMAT)}</span> - </div> - } - </div> - {this.state.allowLive && - <div className="p-col-1 timeline-filters" style={{paddingTop: "5px"}}> - <> - <div><label style={{paddingRight: "3px", marginBottom: "0px"}}>Live </label></div> - <Checkbox checked={this.state.isLive} label="Live" onChange={(e) => { this.setLive(e.checked)}} ></Checkbox> - </> - </div>} - {/* Date Range Selection */} - <div className={`${this.state.allowLive?'p-col-4':'p-col-5'} timeline-filters`} style={{paddingTop: "5px"}}> - {this.state.allowDateSelection && - <> - <span>Showing Date Range</span> - <Flatpickr data-enable-time data-input - options={{ "inlineHideInput": true, - "wrap": true, - "enableTime": true, - "enableSeconds": true, - "time_24hr": true, - "minuteIncrement": 1, - "allowInput": true, - "mode": "range", - "defaultHour": 0, - "plugins": [this.state.confirmDatePlugin, this.renderNowButton()] - }} - title="" - value={this.state.zoomRange} - onChange={value => {this.setZoomRange(value, false)}} - onClose={value => {this.setZoomRange(value, true);this.validateRange(value)}}> - <input type="text" data-input className={`p-inputtext p-component calendar-input`} title={this.getZoomRangeTitle()} /> - <button className="p-button p-component p-button-icon-only calendar-button" data-toggle - title="Click to change the date range" > - <i className="fas fa-calendar"></i> - </button> - <button className="p-button p-component p-button-icon-only calendar-reset" onClick={() => {this.setZoomRange(null)}} - title="Reset to the default date range" > - <i className="fas fa-sync-alt"></i> - </button> - </Flatpickr> - </>} - {isWeekView && - <div className="p-grid" style={{marginTop: "10px"}}> - <div className="col-lg-7"> - <span>Showing week of </span> - <Flatpickr data-input - options={{ "inlineHideInput": true, - "wrap": true, - "enableTime": false, - "allowInput": true, - "mode": "single", - "defaultHour": 0, - // "plugins": [this.state.confirmDatePlugin, this.renderNowButton()] - }} - title="" - value={this.state.weekDay} - // onChange={value => {this.setState({weekDay: value})}} - onClose={value => { - // Set the selected date as the current week day and calculate the number of weeks between and change to the week - this.setState({weekDay: value, showCustomDate: true, reset: false}); - this.changeWeek(null, true); - }}> - <input type="text" data-input className={`p-inputtext p-component calendar-input`} title={"Week Day"} /> - <button className="p-button p-component p-button-icon-only calendar-button" data-toggle - title="Click to select the date of the week" > - <i className="fas fa-calendar"></i> - </button> - <button className="p-button p-component p-button-icon-only calendar-reset" - onClick={this.resetToCurrentTime} - title="Reset to the current week" > - <i className="fas fa-sync-alt"></i> - </button> - </Flatpickr> - </div> - <div className="col-lg-5" style={{marginTop: "12px"}}> - <Button label="" icon="pi pi-angle-double-left" title="Previous Week" onClick={(e) =>{this.changeWeek(-1)}}/> - <label className="timeline-week-span">Week {this.state.isWeekLoading}</label> - <Button label="" icon="pi pi-angle-double-right" title="Next Week" onClick={(e) =>{this.changeWeek(1)}}/> - {this.state.isWeekLoading && <ProgressSpinner style={{width: '30px', height: '30px', marginLeft:'5px', paddingTop: '5px'}} strokeWidth="4" />} - </div> - </div> - } - </div> - {/* Reset to default zoom and current timeline */} - <div className="p-col-1 timeline-button" > - <Button label="" icon="pi pi-arrow-down" className="p-button-rounded p-button-success" id="now-btn" onClick={this.resetToCurrentTime} title="Reset Zoom & Move to Current Time"/> - </div> - {/* Zoom Select */} - <div className="p-col-2 timeline-filters" style={{paddingRight: '0px'}}> - <Dropdown optionLabel="name" optionValue="name" - style={{fontSize: '10px'}} - value={this.state.zoomLevel} - options={this.ZOOM_LEVELS} - filter showClear={false} filterBy="name" - onChange={(e) => {this.changeZoomLevel(e.value, false)}} - placeholder="Zoom"/> - </div> - {/* Zoom and Move Action */} - <div className="p-col-2 timeline-actionbar timeline-filters"> - <button className="p-link" title="Move Left" onClick={e=> { this.moveLeft() }}><i className="pi pi-angle-left"></i></button> - <button className="p-link" title="Zoom Out" onClick={e=> { this.zoomOut() }} disabled={this.state.zoomLevel.startsWith('Custom')}><i className="pi pi-minus-circle"></i></button> - <button className="p-link" title="Zoom In" onClick={e=> { this.zoomIn() }} disabled={this.state.zoomLevel.startsWith('Custom')}><i className="pi pi-plus-circle"></i></button> - <button className="p-link" title="Move Right" onClick={e=> { this.moveRight() }}><i className="pi pi-angle-right"></i></button> - </div> - </div> <div className="p-grid legendbar"> <div className="col-9"> <div style={{fontWeight:'500', height: '25px'}}>Scheduling Unit / Task Status</div> 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 new file mode 100644 index 0000000000000000000000000000000000000000..e504dfc79bbe55070f1e8f51522ab18609209e72 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/DateTimeNavigator.js @@ -0,0 +1,208 @@ +import UIConstants from "../../../utils/ui.constants"; +import {Checkbox} from "primereact/checkbox"; +import Flatpickr from "react-flatpickr"; +import {Button} from "primereact/button"; +import {Dropdown} from "primereact/dropdown"; +import React, {useEffect, useState} from "react"; +import UtilService from "../../../services/util.service"; +import moment from "moment/moment"; +import confirmDatePlugin from "flatpickr/dist/plugins/confirmDate/confirmDate"; + +const UTC_DATE_FORMAT = "YYYY-MM-DD"; +const UTC_TIME_FORMAT = "HH:mm:ss"; + +function getDateTimeElement(title, time, formatter) { + if (!time) { + return null + } + return <div style={{marginTop: "0px"}}> + <label + style={{marginBottom: "0px"}}>{title}:</label><span>{time.format(formatter)}</span> + </div> +} + +function getDateTimeInfo(currentUTC, currentLST) { + return <div className="p-col-2" style={{padding: '0px 0px 0px 10px'}}> + {getDateTimeElement("Date", currentUTC, UTC_DATE_FORMAT)} + {getDateTimeElement("UTC", currentUTC, UTC_TIME_FORMAT)} + {getDateTimeElement("LST", currentLST, UIConstants.CALENDAR_TIME_FORMAT)} + </div> +} + +function getMoveWithTimeCheckbox(allowLive, isLive, setIsLive) { //TODO: make sure it updates the timeline + if (!allowLive || !setIsLive) { + return null + } + return <div className="p-col-1 timeline-filters" style={{paddingTop: "5px"}}> + <label style={{paddingRight: "3px", marginBottom: "0px"}}>Move with time</label> + <Checkbox checked={isLive} onChange={(e) => setIsLive(e.checked)} + title="Move the timeline every 5 seconds a little to the right"/> + </div> +} + + +function getDateSelector(isRange, currentDateValue, onChangeDateCallback, onCloseDateCallback, onClickDateResetButtonCallback) { + if (!currentDateValue || !onCloseDateCallback || !onClickDateResetButtonCallback) { + return null; + } + const title = isRange ? "Showing Date Range" : "Showing week of"; + const mode = isRange ? "range" : "single" + const options = { + "allowInput": true, + "enableSeconds": isRange, + "time_24hr": isRange, + "mode": mode, + "minuteIncrement": 1, + "defaultHour": 0, + "wrap": true, + "inlineHideInput": true, + } + const calenderButtonTitle = isRange ? "Click to change the date range" : "Click to select the date of the week" + const resetButtonTitle = isRange ? "Reset to the default date range" : "Reset to the current week" + + return <div> + <span>{title}</span> + <Flatpickr data-enable-time={isRange} + data-input + value={currentDateValue} + options={options} + onChange={value => onChangeDateCallback(value)} + onClose={value => onCloseDateCallback(value)}> + <input type="text" data-input className="p-inputtext p-component calendar-input"/> + <button className="p-button p-component p-button-icon-only calendar-button" + title={calenderButtonTitle} data-toggle> + <i className="fas fa-calendar"></i> + </button> + <button className="p-button p-component p-button-icon-only calendar-reset" + onClick={() => onClickDateResetButtonCallback()} + title={resetButtonTitle}> + <i className="fas fa-sync-alt"></i> + </button> + </Flatpickr> + </div> +} + +function getWeekChanger(isRange, onWeekChangeCallback) { + if (isRange || !onWeekChangeCallback) { + return null + } + return <div className="col-lg-5" style={{marginTop: "12px"}}> + <Button icon="pi pi-angle-double-left" title="Previous Week" onClick={() => onWeekChangeCallback(-1)}/> + <label className="timeline-week-span">Week</label> + <Button icon="pi pi-angle-double-right" title="Next Week" onClick={() => onWeekChangeCallback(1)}/> + </div> +} + +function getResetButton(onClickTimeZoomResetButtonCallback) { + return <div className="p-col-1 timeline-button"> + <Button icon="pi pi-arrow-down" + className="p-button-rounded p-button-success" + id="now-btn" + onClick={onClickTimeZoomResetButtonCallback} + title="Reset Zoom & Move to Current Time"/> + </div> +} + +function getIconButton(title, onClickCallback, iconClassName, disabled = false) { + return <button className="p-link" + title={title} + onClick={() => onClickCallback()} + disabled={disabled}> + <i className={`pi ${iconClassName}`}/> + </button> +} + +function getZoomAndMoveActions(moveLeftCallback, moveRightCallback, zoomOutCallback, zoomInCallback, zoomLevel = "") { + return <div className="p-col-2 timeline-actionbar timeline-filters"> + {getIconButton("Move Left", moveLeftCallback, "pi-angle-left")} + {getIconButton("Zoom out", zoomOutCallback, "pi-minus-circle", zoomLevel.startsWith("Custom"))} + {getIconButton("Zoom in", zoomInCallback, "pi-plus-circle", zoomLevel.startsWith("Custom"))} + {getIconButton("Move Right", moveRightCallback, "pi-angle-right")} + </div> +} + +function getZoomSelect(zoomLevel, allZoomLevels, onChangeZoomLevelCallback) { + return <div className="p-col-2 timeline-filters" style={{paddingRight: '0px'}}> + <Dropdown optionLabel="name" optionValue="name" + style={{fontSize: '10px'}} + value={zoomLevel} + options={allZoomLevels} + filter + showClear={false} + filterBy="name" + onChange={(e) => onChangeZoomLevelCallback(e.value, false)} + placeholder="Zoom"/> + </div> +} + +export default function DateTimeNavigator(props) { + const { + currentUTC, + allowLive, + timelineUIAttributes, + timelineCommonUtils, + isRange, + currentDateValue, + onChangeDateCallback, + onCloseDateCallback, + onClickDateResetButtonCallback, + onWeekChangeCallback, + onClickTimeZoomResetButtonCallback, + moveLeftCallback, + moveRightCallback, + zoomOutCallback, + zoomInCallback, + zoomLevel, + allZoomLevels, + onChangeZoomLevelCallback + } = props + + const [currentLST, setCurrentLST] = useState(undefined) + const [isLive, setIsLive] = useState(false) + + useEffect(() => { //retrieve the initial LST from the server + if (!currentLST) { + UtilService.getUTC() + .then((utcString) => { + UtilService.getLST(utcString) + .then(lstString => { + const utcDay = moment.utc(utcString).format('DD-MMM-YYYY '); + const lstTime = lstString.split('.')[0]; + setCurrentLST(moment(utcDay + lstTime, 'DD-MMM-YYYY HH:mm:ss')) + }).catch(error => { + console.error("Cannot retrieve the LST and thus cannot render the date time navigator: ", error) + }) + }).catch(error => { + console.log("Cannot retrieve the UTC and thus cannot render the date time navigator: ", error) + }); + } + }, [currentUTC]) + + useEffect(() => { //update the LST date's seconds after initialization + if (currentLST) { + setCurrentLST(currentLST.add(1, 'second')) + } + }, [moment.utc().seconds()]) + + + useEffect(() => { + if (timelineUIAttributes) { + isLive ? timelineUIAttributes.isLive = isLive : delete timelineUIAttributes["isLive"]; + timelineCommonUtils.storeUIAttributes(timelineUIAttributes); + } + }, [isLive]) + + if (!currentUTC || !currentLST) { + return null; + } + + return <div className="p-fluid p-grid timeline-toolbar" style={{backgroundColor: "cyan"}}> + {getDateTimeInfo(currentUTC, currentLST)} + {getMoveWithTimeCheckbox(allowLive, isLive, setIsLive)} + {getDateSelector(isRange, currentDateValue, onChangeDateCallback, onCloseDateCallback, onClickDateResetButtonCallback)} + {getWeekChanger(isRange, onWeekChangeCallback)} + {getResetButton(onClickTimeZoomResetButtonCallback)} + {getZoomSelect(zoomLevel, allZoomLevels, onChangeZoomLevelCallback)} + {getZoomAndMoveActions(moveLeftCallback, moveRightCallback, zoomOutCallback, zoomInCallback, zoomLevel)} + </div> +} \ 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 e1a4a50c3ae3de635df114e59918f4a57786947d..9af0c667d6f17f59c8bc4eb584aae414d47246a6 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -23,7 +23,6 @@ import Validator from '../../utils/validator'; import SchedulingUnitSummary from '../Scheduling/summary'; import ReservationSummary from '../Reservation/reservation.summary'; import { TieredMenu } from 'primereact/tieredmenu'; -import { MultiSelect } from 'primereact/multiselect'; import TimelineListTabs from './list.tabs'; import TimelineCommonUtils from './common.utils'; import AuthStore from '../../authenticate/auth.store'; @@ -34,6 +33,7 @@ import UnitConversion from '../../utils/unit.converter'; import { appGrowl } from '../../layout/components/AppGrowl'; import TopProgressBar from '../../layout/components/TopProgressBar'; import TimelineItemPopover from "./components/TimelineItemPopover"; +import Toolbar from "./components/Toolbar" //import { TRUE } from 'node-sass'; // Color constant for SU status @@ -1793,7 +1793,7 @@ export class TimelineView extends Component { reservation = _.find(this.reservations, { id: parseInt(this.state.selectedItem.id.split('-')[1]) }); reservation.project = this.state.selectedItem.project; } - let mouseOverItem = this.state.mouseOverItem; + let mouseOverItem = this.state.mouseOverItem; return ( <React.Fragment> <TieredMenu className="app-header-menu" model={this.state.menuOptions} popup ref={el => this.optionsMenu = el} />