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 237eefd86136b5d7ee9fcd14b08528f5413962bf..ba59f506ee48ddee32e91bc4d2b3cc6d58dc7800 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -18,10 +18,11 @@ import { Dropdown } from 'primereact/dropdown'; import UtilService from '../../services/util.service'; import 'react-calendar-timeline/lib/Timeline.css'; -import { Calendar } from 'primereact/calendar'; +import "flatpickr/dist/flatpickr.css"; import { Checkbox } from 'primereact/checkbox'; import { ProgressSpinner } from 'primereact/progressspinner'; -import { CustomPageSpinner } from '../CustomPageSpinner'; +// import { CustomPageSpinner } from '../CustomPageSpinner'; +import Flatpickr from "react-flatpickr"; import UIConstants from '../../utils/ui.constants'; // Label formats for day headers based on the interval label width @@ -69,9 +70,11 @@ export class CalendarTimeline extends Component { group = group.concat(props.group); } const defaultZoomLevel = _.find(ZOOM_LEVELS, {name: DEFAULT_ZOOM_LEVEL}); + const defaultStartTime = props.startTime?props.startTime.clone():null || moment().utc().add(-1 * defaultZoomLevel.value/2, 'seconds'); + const defaultEndTime = props.endTime?props.endTime.clone():null || moment().utc().add(1 * defaultZoomLevel.value/2, 'seconds'); this.state = { - defaultStartTime: props.startTime?props.startTime.clone():null || moment().utc().add(-1 * defaultZoomLevel.value/2, 'seconds'), - defaultEndTime: props.endTime?props.endTime.clone():null || moment().utc().add(1 * defaultZoomLevel.value/2, 'seconds'), + defaultStartTime: defaultStartTime, + defaultEndTime: defaultEndTime, group: group, items: props.items || [], //>>>>>> Properties to pass to react-calendar-timeline component @@ -81,7 +84,7 @@ export class CalendarTimeline extends Component { maxZoom: props.maxZoom || (32 * 24 * 60 * 60 * 1000), // 32 hours zoomLevel: props.zoomLevel || DEFAULT_ZOOM_LEVEL, isTimelineZoom: true, - zoomRange: null, + zoomRange: this.getZoomRange(defaultStartTime, defaultEndTime), prevZoomRange: null, lineHeight: props.rowHeight || 50, // Row line height sidebarWidth: props.sidebarWidth || 200, @@ -141,6 +144,7 @@ export class CalendarTimeline extends Component { this.zoomIn = this.zoomIn.bind(this); this.zoomOut = this.zoomOut.bind(this); this.setZoomRange = this.setZoomRange.bind(this); + this.getZoomRangeTitle = this.getZoomRangeTitle.bind(this); //<<<<<< Functions of this component //>>>>>> Public functions of the component @@ -193,6 +197,9 @@ export class CalendarTimeline extends Component { } if (this.state.isLive) { this.changeDateRange(this.state.defaultStartTime.add(1, 'second'), this.state.defaultEndTime.add(1, 'second')); + if (systemClock) { + this.setState({zoomRange: this.getZoomRange(this.state.defaultStartTime, this.state.defaultEndTime)}); + } // const result = this.props.dateRangeCallback(this.state.defaultStartTime.add(1, 'second'), this.state.defaultEndTime.add(1, 'second')); // let group = DEFAULT_GROUP.concat(result.group); } @@ -800,7 +807,8 @@ export class CalendarTimeline extends Component { updateScrollCanvas(newVisibleTimeStart.valueOf(), newVisibleTimeEnd.valueOf()); this.changeDateRange(newVisibleTimeStart, newVisibleTimeEnd); // this.setState({defaultStartTime: moment(visibleTimeStart), defaultEndTime: moment(visibleTimeEnd)}) - this.setState({defaultStartTime: newVisibleTimeStart, defaultEndTime: newVisibleTimeEnd}); + this.setState({defaultStartTime: newVisibleTimeStart, defaultEndTime: newVisibleTimeEnd, + zoomRange: this.getZoomRange(newVisibleTimeStart, newVisibleTimeEnd)}); } /** @@ -1066,7 +1074,8 @@ export class CalendarTimeline extends Component { const endTime = moment().utc().add(24, 'hours'); let result = await this.changeDateRange(startTime, endTime); let group = DEFAULT_GROUP.concat(result.group); - this.setState({defaultStartTime: startTime, defaultEndTime: endTime, + this.setState({defaultStartTime: startTime, defaultEndTime: endTime, + zoomRange: this.getZoomRange(startTime, endTime), zoomLevel: DEFAULT_ZOOM_LEVEL, dayHeaderVisible: true, weekHeaderVisible: false, lstDateHeaderUnit: "hour", group: group, items: result.items}); @@ -1122,7 +1131,8 @@ export class CalendarTimeline extends Component { let result = await this.changeDateRange(startTime, endTime); let group = DEFAULT_GROUP.concat(result.group); this.setState({zoomLevel: zoomLevel, defaultStartTime: startTime, defaultEndTime: endTime, - isTimelineZoom: true, zoomRange: null, + isTimelineZoom: true, + zoomRange: this.getZoomRange(startTime, endTime), dayHeaderVisible: true, weekHeaderVisible: false, lstDateHeaderUnit: 'hour', group: group, items: result.items}); } @@ -1148,6 +1158,7 @@ export class CalendarTimeline extends Component { let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: newVisibleTimeStart, defaultEndTime: newVisibleTimeEnd, + zoomRange: this.getZoomRange(newVisibleTimeStart, newVisibleTimeEnd), group: group, items: result.items}); } @@ -1171,6 +1182,7 @@ export class CalendarTimeline extends Component { let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: newVisibleTimeStart, defaultEndTime: newVisibleTimeEnd, + zoomRange: this.getZoomRange(newVisibleTimeStart, newVisibleTimeEnd), group: group, items: result.items}); } @@ -1206,11 +1218,11 @@ export class CalendarTimeline extends Component { */ async setZoomRange(value){ let startDate, endDate = null; - if (value) { + if (value && value.length>0) { // Set all values only when both range values available in the array else just set the value to reflect in the date selection component - if (value[1]!==null) { - startDate = moment.utc(moment(value[0]).format("YYYY-MM-DD")); - endDate = moment.utc(moment(value[1]).format("YYYY-MM-DD 23:59:59")); + if (value[1]) { + startDate = moment.utc(moment(value[0]).format("YYYY-MM-DD HH:mm:ss")); + endDate = moment.utc(moment(value[1]).format("YYYY-MM-DD HH:mm:ss")); let dayHeaderVisible = this.state.dayHeaderVisible; let weekHeaderVisible = this.state.weekHeaderVisible; let lstDateHeaderUnit = this.state.lstDateHeaderUnit; @@ -1231,12 +1243,49 @@ export class CalendarTimeline extends Component { } else { this.setState({zoomRange: value}); } + } else if (value && value.length===0) { + this.setState({zoomRange: this.getZoomRange(this.state.defaultStartTime, this.state.defaultEndTime)}); } else { this.resetToCurrentTime(); } } - async changeWeek(direction) { + /** + * Function to set previous selected or zoomed range if only one date is selected and + * closed the caldendar without selecting second date. + * @param {Array} value - array of Date object. + */ + validateRange(value) { + if (value && value.length===1) { + this.setState({zoomRange: this.getZoomRange(this.state.defaultStartTime, this.state.defaultEndTime)}); + } + } + + /** + * Function to convert moment objects of the zoom range start and end time to Date object array. + * @param {moment} startTime + * @param {moment} endTime + * @returns Array of Date object + */ + getZoomRange(startTime, endTime) { + return [moment(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)).toDate(), + moment(endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)).toDate()]; + } + + /** + * Function to get the formatted string of zoom range times. + * @returns String - formatted string with start time and end time in the zoom range + */ + getZoomRangeTitle() { + const zoomRange = this.state.zoomRange; + if (zoomRange && zoomRange.length === 2) { + return `${moment(zoomRange[0]).format(UIConstants.CALENDAR_DATETIME_FORMAT)} to ${moment(zoomRange[1]).format(UIConstants.CALENDAR_DATETIME_FORMAT)}`; + } else { + return 'Select Date Range' + } + } + + async changeWeek(direction) { this.setState({isWeekLoading: true}); let startDate = this.state.group[1].value.clone().add(direction * 7, 'days'); let endDate = this.state.group[this.state.group.length-1].value.clone().add(direction * 7, 'days').hours(23).minutes(59).seconds(59); @@ -1314,13 +1363,32 @@ export class CalendarTimeline extends Component { <div className="p-col-4 timeline-filters"> {this.state.allowDateSelection && <> - {/* <span className="p-float-label"> */} - <Calendar id="range" placeholder="Select Date Range" selectionMode="range" dateFormat="yy-mm-dd" showIcon={!this.state.zoomRange} - value={this.state.zoomRange} onChange={(e) => this.setZoomRange( e.value )} readOnlyInput /> - {/* <label htmlFor="range">Select Date Range</label> - </span> */} - {this.state.zoomRange && <i className="pi pi-times pi-primary" style={{position: 'relative', left:'90%', bottom:'20px', cursor:'pointer'}} - onClick={() => {this.setZoomRange( null)}}></i>} + <Flatpickr data-enable-time + data-input options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "mode": "range", + "defaultHour": 0 + }} + title="" + value={this.state.zoomRange} + onChange={value => {this.setZoomRange(value)}} + onClose={value => {this.validateRange(value)}}> + <input type="text" data-input className={`p-inputtext p-component calendar-input`} title={this.getZoomRangeTitle()} /> + <button class="p-button p-component p-button-icon-only calendar-button" data-toggle + title="Reset to the default date range" > + <i class="fas fa-calendar"></i> + </button> + <button class="p-button p-component p-button-icon-only calendar-reset" onClick={() => {this.setZoomRange( null)}} + title="Reset to the default date range" > + <i class="fas fa-sync-alt"></i> + </button> + </Flatpickr> + <span>Showing Date Range</span> </>} {this.state.viewType===UIConstants.timeline.types.WEEKVIEW && <> 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 e9e71c99a8042f651bc6227ef639e90b6f6841b5..573ce55702ebf05f50fd2d7fe384da36dc6b03b3 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss @@ -69,6 +69,35 @@ // width: auto !important; } +.timeline-filters .p-calendar .p-inputtext { + font-size: 12px; +} + +.calendar-input { + width: 75% !important; + border-top-right-radius: 0px !important; + border-bottom-right-radius: 0px !important; + font-size:12px !important; + height: 29px; +} + +.calendar-button { + position: relative; + width: 20px !important; + height: 29px; + margin-left: -2px !important; + border-radius: 0px !important; +} + +.calendar-reset { + position: relative; + width: 20px !important; + height: 29px; + margin-left: 0px !important; + border-top-left-radius: 0px !important; + border-bottom-left-radius: 0px !important; +} + .timeline-week-span { margin-left: 5px; margin-right: 5px; @@ -149,6 +178,22 @@ color: orange; } +.su-visible { + margin-top: 30px; + // margin-left: -59px !important; +} + +.su-hidden { + margin-left: -20px !important; + z-index: 0 !important; + margin-top:40px; +} + +.su-hidden>button { + width: 80px; + transform: translateX(-50%) translateY(-50%) rotate(-90deg); + height: 20px; +} .resize-div, .resize-div-min, .resize-div-avg, 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 7c8436aeacf4e17fa5b73fef1a7c73b81b7eb308..ce65086715c0cff157723a58b548aac3df7dfe49 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -6,7 +6,6 @@ import Websocket from 'react-websocket'; // import SplitPane, { Pane } from 'react-split-pane'; import { InputSwitch } from 'primereact/inputswitch'; -import { CustomPageSpinner } from '../../components/CustomPageSpinner'; import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; @@ -63,6 +62,7 @@ export class TimelineView extends Component { isTaskDetsVisible: false, canExtendSUList: true, canShrinkSUList: false, + isSUListVisible: true, selectedItem: null, mouseOverItem: null, suTaskList:[], @@ -412,7 +412,7 @@ export class TimelineView extends Component { } else { const reservation = _.find(this.reservations, {'id': parseInt(item.id.split("-")[1])}); const reservStations = reservation.specifications_doc.resources.stations; - const reservStationGroups = this.groupSUStations(reservStations); + // const reservStationGroups = this.groupSUStations(reservStations); item.name = reservation.name; item.contact = reservation.specifications_doc.activity.contact item.activity_type = reservation.specifications_doc.activity.type; @@ -872,6 +872,7 @@ export class TimelineView extends Component { // if (this.state.loader) { // return <AppLoader /> // } + const isSUListVisible = this.state.isSUListVisible; const isSUDetsVisible = this.state.isSUDetsVisible; const isReservDetsVisible = this.state.isReservDetsVisible; const isTaskDetsVisible = this.state.isTaskDetsVisible; @@ -898,8 +899,10 @@ export class TimelineView extends Component { { this.state.isLoading ? <AppLoader /> : <div className="p-grid"> {/* SU List Panel */} - <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"}}> + <div className={isSUListVisible && (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={isSUListVisible?{position: "inherit", borderRight: "3px solid #efefef", paddingTop: "10px"}:{display: 'none'}}> <ViewTable viewInNewWindow data={this.state.suBlueprintList} @@ -924,8 +927,14 @@ export class TimelineView extends Component { /> </div> {/* Timeline Panel */} - <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")}> + <div className={isSUListVisible?((isSUDetsVisible || isReservDetsVisible)?"col-lg-5 col-md-5 col-sm-12": + (!canExtendSUList && canShrinkSUList)?"col-lg-6 col-md-6 col-sm-12": + ((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")): + ((isSUDetsVisible || isReservDetsVisible || isTaskDetsVisible)?"col-lg-9 col-md-9 col-sm-12":"col-lg-12 col-md-12 col-sm-12")} + // style={{borderLeft: "3px solid #efefef"}} + > {/* Panel Resize buttons */} + {isSUListVisible && <div className="resize-div"> <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} title="Shrink List/Expand Timeline" @@ -933,12 +942,28 @@ export class TimelineView extends Component { <i className="pi pi-step-backward"></i> </button> <button className="p-link resize-btn" disabled={!this.state.canExtendSUList} - title="Expandd List/Shrink Timeline" + title="Expand List/Shrink Timeline" onClick={(e)=> { this.resizeSUList(1)}}> <i className="pi pi-step-forward"></i> </button> </div> - + } + <div className={isSUListVisible?"resize-div su-visible":"resize-div su-hidden"}> + {isSUListVisible && + <button className="p-link resize-btn" + title="Hide List" + onClick={(e)=> { this.setState({isSUListVisible: false})}}> + <i className="pi pi-eye-slash"></i> + </button> + } + {!isSUListVisible && + <button className="p-link resize-btn" + title="Show List" + onClick={(e)=> { this.setState({isSUListVisible: true})}}> + <i className="pi pi-eye"> Show List</i> + </button> + } + </div> <div className={`timeline-view-toolbar ${this.state.stationView && 'alignTimeLineHeader'}`}> <div className="sub-header"> <label >Station View</label> @@ -1067,7 +1092,7 @@ export class TimelineView extends Component { <div className="col-7">{mouseOverItem.duration}</div> </div> } - {(mouseOverItem && mouseOverItem.type == "RESERVATION") && + {(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> 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 08322699a4c79cdcfe50a79e093874b69509c2ad..806991f765090c3dabfbd0c3d744cd7acd618f50 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 @@ -50,6 +50,7 @@ export class WeekTimelineView extends Component { suBlueprintList: [], // SU Blueprints filtered to view group:[], // Timeline group from scheduling unit draft name items:[], // Timeline items from scheduling unit blueprints grouped by scheduling unit draft + isSUListVisible: true, isSUDetsVisible: false, canExtendSUList: true, canShrinkSUList: false, @@ -332,7 +333,7 @@ export class WeekTimelineView extends Component { } else { const reservation = _.find(this.reservations, {'id': parseInt(item.id.split("-")[1])}); const reservStations = reservation.specifications_doc.resources.stations; - const reservStationGroups = this.groupSUStations(reservStations); + // const reservStationGroups = this.groupSUStations(reservStations); item.name = reservation.name; item.contact = reservation.specifications_doc.activity.contact item.activity_type = reservation.specifications_doc.activity.type; @@ -771,6 +772,7 @@ export class WeekTimelineView extends Component { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> } + const isSUListVisible = this.state.isSUListVisible; const isSUDetsVisible = this.state.isSUDetsVisible; const isReservDetsVisible = this.state.isReservDetsVisible; const canExtendSUList = this.state.canExtendSUList; @@ -803,8 +805,10 @@ export class WeekTimelineView extends Component { </div> */} <div className="p-grid"> {/* SU List Panel */} - <div className={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")} - style={{position: "inherit", borderRight: "5px solid #efefef", paddingTop: "10px"}}> + <div className={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"))} + style={isSUListVisible?{position: "inherit", borderRight: "5px solid #efefef", paddingTop: "10px"}:{display: "none"}}> <ViewTable viewInNewWindow data={this.state.suBlueprintList} defaultcolumns={[{name: "Name", @@ -822,8 +826,14 @@ export class WeekTimelineView extends Component { /> </div> {/* Timeline Panel */} - <div className={isSUDetsVisible || isReservDetsVisible || (!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={isSUListVisible?((isSUDetsVisible || isReservDetsVisible)?"col-lg-5 col-md-5 col-sm-12": + (!canExtendSUList && canShrinkSUList)?"col-lg-6 col-md-6 col-sm-12": + ((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")): + ((isSUDetsVisible || isReservDetsVisible)?"col-lg-9 col-md-9 col-sm-12":"col-lg-12 col-md-12 col-sm-12")} + // style={{borderLeft: "3px solid #efefef"}} + > {/* Panel Resize buttons */} + {isSUListVisible && <div className="resize-div"> <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} title="Shrink List/Expand Timeline" @@ -835,7 +845,24 @@ export class WeekTimelineView extends Component { onClick={(e)=> { this.resizeSUList(1)}}> <i className="pi pi-step-forward"></i> </button> - </div> + </div> + } + <div className={isSUListVisible?"resize-div su-visible":"resize-div su-hidden"}> + {isSUListVisible && + <button className="p-link resize-btn" + title="Hide List" + onClick={(e)=> { this.setState({isSUListVisible: false})}}> + <i className="pi pi-eye-slash"></i> + </button> + } + {!isSUListVisible && + <button className="p-link resize-btn" + title="Show List" + onClick={(e)=> { this.setState({isSUListVisible: true})}}> + <i className="pi pi-eye"> Show List</i> + </button> + } + </div> <div className={`timeline-view-toolbar ${this.state.reservationEnabled && 'alignTimeLineHeader'}`}> <div className="sub-header"> <label >Show Reservations</label> @@ -902,7 +929,7 @@ export class WeekTimelineView extends Component { } {/* SU Item Tooltip popover with SU status color */} <OverlayPanel className="timeline-popover" ref={(el) => this.popOver = el} dismissable> - {mouseOverItem && mouseOverItem.type == "SCHEDULE" && + {mouseOverItem && mouseOverItem.type === "SCHEDULE" && <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> @@ -924,7 +951,7 @@ export class WeekTimelineView extends Component { <div className="col-7">{mouseOverItem.duration}</div> </div> } - {(mouseOverItem && mouseOverItem.type == "RESERVATION") && + {(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>