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 1825ef55a1a8191016e852cba1b9206e0b884c2b..60d4a0d5e84268c4ec80546baed0fa36e7fdac1b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -18,6 +18,7 @@ import UtilService from '../../services/util.service'; import 'react-calendar-timeline/lib/Timeline.css'; import { Calendar } from 'primereact/calendar'; +import { Checkbox } from 'primereact/checkbox'; // 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"}, @@ -30,7 +31,8 @@ const DAY_HEADER_FORMATS = [{ name: "longer", minWidth: 300, maxWidth: 50000, fo {name: "nano", minWidth: 0, maxWidth: 0, format: ""}]; //>>>>>> Constants for date/time formats, zoom level definition & defaults -const UTC_DISPLAY_FORMAT = "YYYY-MM-DDTHH:mm:ss"; +const UTC_DATE_FORMAT = "YYYY-MM-DD"; +const UTC_TIME_FORMAT = "HH:mm:ss"; const UTC_LST_KEY_FORMAT = "YYYY-MM-DDTHH:mm:00"; const UTC_LST_HOUR_FORMAT = "YYYY-MM-DDTHH:00:00"; const UTC_LST_DAY_FORMAT = "YYYY-MM-DDT00:00:00"; @@ -95,7 +97,8 @@ export class CalendarTimeline extends Component { lstDateHeaderUnit: 'hour', // Unit to be considered for the LST axis header based on the visible duration isLSTDateHeaderLoading: true, dayHeaderVisible: true, // To control the Day header visibility based on the zoom level - weekHeaderVisible: false // To control the Week header visibility based on the zoom level + weekHeaderVisible: false, // To control the Week header visibility based on the zoom level + isLive: false } this.itemClickCallback = props.itemClickCallback; // Pass timeline item click event back to parent @@ -125,6 +128,10 @@ export class CalendarTimeline extends Component { this.zoomOut = this.zoomOut.bind(this); this.setZoomRange = this.setZoomRange.bind(this); //<<<<<< Functions of this component + + //>>>>>> Public functions of the component + this.updateTimeline = this.updateTimeline.bind(this); + //<<<<<< Public functions of the component } componentDidMount() { @@ -158,12 +165,16 @@ export class CalendarTimeline extends Component { const currentUTC = moment.utc(utcString); this.setState({currentUTC: currentUTC}); let currentLST = await UtilService.getLST(utcString); - this.setState({currentLST: moment(currentUTC.format('DD-MMM-YYYY ') + currentLST)}) + this.setState({currentLST: moment(currentUTC.format('DD-MMM-YYYY ') + currentLST.split('.')[0])}) } ); } else { this.setState({currentUTC: this.state.currentUTC.add(1, 'second'), currentLST: this.state.currentLST?this.state.currentLST.add(1, 'second'):null}); } + if (this.state.isLive) { + const result = this.props.dateRangeCallback(this.state.defaultStartTime.add(1, 'second'), this.state.defaultEndTime.add(1, 'second')); + let group = DEFAULT_GROUP.concat(result.group); + } } /** @@ -183,7 +194,7 @@ export class CalendarTimeline extends Component { const formattedColUTC = colUTC.format(lstDateHeaderUnit==="hour"?UTC_LST_HOUR_FORMAT:UTC_LST_DAY_FORMAT); // if (!lstDateHeaderMap[formattedColUTC]) { const lst = await UtilService.getLST(formattedColUTC); - const lstDate = moment(colUTC.format(`DD-MMM-YYYY ${lst}`)).add(30, 'minutes'); + const lstDate = moment(colUTC.format(`DD-MMM-YYYY ${lst.split('.')[0]}`)).add(30, 'minutes'); lstDateHeaderMap[formattedColUTC] = lstDateHeaderUnit==="hour"?lstDate.format('HH'):lstDate.format('DD'); // } } @@ -506,6 +517,8 @@ export class CalendarTimeline extends Component { onTimeChange(visibleTimeStart, visibleTimeEnd, updateScrollCanvas) { this.loadLSTDateHeaderMap(moment(visibleTimeStart).utc(), moment(visibleTimeEnd).utc(), this.state.lstDateHeaderUnit); updateScrollCanvas(visibleTimeStart, visibleTimeEnd); + const result = this.props.dateRangeCallback(moment(visibleTimeStart).utc(), moment(visibleTimeEnd).utc()); + let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: moment(visibleTimeStart), defaultEndTime: moment(visibleTimeEnd)}) } @@ -570,8 +583,11 @@ export class CalendarTimeline extends Component { let visibleTimeEnd = this.state.defaultEndTime; const visibleTimeDiff = visibleTimeEnd.valueOf()-visibleTimeStart.valueOf(); const secondsToMove = visibleTimeDiff / 1000 / 10 ; + const result = this.props.dateRangeCallback(visibleTimeStart, visibleTimeEnd); + let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: visibleTimeStart.add(-1 * secondsToMove, 'seconds'), - defaultEndTime: visibleTimeEnd.add(-1 * secondsToMove, 'seconds')}); + defaultEndTime: visibleTimeEnd.add(-1 * secondsToMove, 'seconds'), + group: group, items: result.items}); } /** @@ -582,8 +598,11 @@ export class CalendarTimeline extends Component { let visibleTimeEnd = this.state.defaultEndTime; const visibleTimeDiff = visibleTimeEnd.valueOf()-visibleTimeStart.valueOf(); const secondsToMove = visibleTimeDiff / 1000 / 10 ; + const result = this.props.dateRangeCallback(visibleTimeStart, visibleTimeEnd); + let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: visibleTimeStart.add(1 * secondsToMove, 'seconds'), - defaultEndTime: visibleTimeEnd.add(1 * secondsToMove, 'seconds')}); + defaultEndTime: visibleTimeEnd.add(1 * secondsToMove, 'seconds'), + group: group, items: result.items}); } /** @@ -663,15 +682,27 @@ export class CalendarTimeline extends Component { } } + /** + * Public function that can be called by its implementation class or function to pass required data and parameters + * as objects + * @param {Object} props + */ + updateTimeline(props) { + this.setState({group: DEFAULT_GROUP.concat(props.group), items: props.items}); + } + render() { return ( <React.Fragment> {/* Toolbar for the timeline */} <div className="p-fluid p-grid timeline-toolbar"> {/* Clock Display */} - <div className="p-col-3" style={{padding: '0px 0px 0px 10px'}}> + <div className="p-col-2" style={{padding: '0px 0px 0px 10px'}}> <div style={{marginTop: "0px"}}> - <label style={{marginBottom: "0px"}}>UTC:</label><span>{this.state.currentUTC.format(UTC_DISPLAY_FORMAT)}</span> + <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"}}> @@ -679,8 +710,12 @@ export class CalendarTimeline extends Component { </div> } </div> + <div className="p-col-1 timeline-filters"> + <label style={{paddingRight: "3px"}}>Live </label> + <Checkbox checked={this.state.isLive} label="Live" onChange={(e) => { this.setState({'isLive': e.checked})}} ></Checkbox> + </div> {/* Date Range Selection */} - <div className="p-col-4"> + <div className="p-col-4 timeline-filters"> {/* <span className="p-float-label"> */} <Calendar id="range" placeholder="Select Date Range" selectionMode="range" showIcon={!this.state.zoomRange} value={this.state.zoomRange} onChange={(e) => this.setZoomRange( e.value )} readOnlyInput /> @@ -690,11 +725,11 @@ export class CalendarTimeline extends Component { onClick={() => {this.setZoomRange( null)}}></i>} </div> {/* Reset to default zoom and current timeline */} - <div className="p-col-1" style={{padding: '5px 0px'}}> + <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="Rest Zoom & Move to Current Time"/> </div> {/* Zoom Select */} - <div className="p-col-2" style={{paddingRight: '0px'}}> + <div className="p-col-2 timeline-filters" style={{paddingRight: '0px'}}> <Dropdown optionLabel="name" optionValue="name" style={{fontSize: '10px'}} value={this.state.zoomLevel} @@ -704,7 +739,7 @@ export class CalendarTimeline extends Component { placeholder="Zoom"/> </div> {/* Zoom and Move Action */} - <div className="p-col-2 timeline-actionbar"> + <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> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index 21f9326233262c6e69a655eb877a7868d4ca4d9b..904166532404bbea9b00a73c540a276b9e8e5783 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -11,10 +11,14 @@ import {Paginator} from 'primereact/paginator'; import { Button } from "react-bootstrap"; import { InputNumber } from "primereact/inputnumber"; -let tbldata =[]; +let tbldata =[], filteredData = [] ; let isunittest = false; let showTopTotal = true; +let showGlobalFilter = true; +let showColumnFilter = true; +let allowColumnSelection = true; let columnclassname =[]; +let parentCallbackFunction; // Define a default UI for filtering function GlobalFilter({ @@ -39,13 +43,13 @@ function GlobalFilter({ // Define a default UI for filtering function DefaultColumnFilter({ - column: { filterValue, preFilteredRows, setFilter }, + column: { filterValue, preFilteredRows, setFilter, filteredRows }, }) { return ( <input value={filterValue || ''} onChange={e => { - setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely + setFilter(e.target.value || undefined); // Set undefined to remove the filter entirely }} /> ) @@ -291,7 +295,7 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn useGlobalFilter, useSortBy, usePagination - ) + ); React.useEffect(() => { setHiddenColumns( columns.filter(column => !column.isVisible).map(column => column.accessor) @@ -345,11 +349,18 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn }) localStorage.setItem(tablename,JSON.stringify(lsToggleColumns)) } + + filteredData = _.map(rows, 'values'); + if (parentCallbackFunction) { + parentCallbackFunction(filteredData); + } + return ( <> <div id="block_container"> + { allowColumnSelection && <div style={{textAlign:'left', marginRight:'30px'}}> - <i className="fa fa-columns col-filter-btn" label="Toggle Columns" onClick={(e) => op.current.toggle(e)} /> + <i className="fa fa-columns col-filter-btn" label="Toggle Columns" onClick={(e) => op.current.toggle(e)} /> <OverlayPanel ref={op} id="overlay_panel" showCloseIcon={false} > <div> <div style={{textAlign: 'center'}}> @@ -377,9 +388,9 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn </div> </OverlayPanel> </div> - + } <div style={{textAlign:'right'}}> - {tbldata.length>0 && !isunittest && + {tbldata.length>0 && !isunittest && showGlobalFilter && <GlobalFilter preGlobalFilteredRows={preGlobalFilteredRows} globalFilter={state.globalFilter} @@ -470,10 +481,14 @@ filterGreaterThan.autoRemove = val => typeof val !== 'number' function ViewTable(props) { const history = useHistory(); // Data to show in table - tbldata = props.data; + tbldata = props.data; + parentCallbackFunction = props.filterCallback; isunittest = props.unittest; columnclassname = props.columnclassname; - showTopTotal = props.showTopTotal==='false'? false:true; + showTopTotal = props.showTopTotal===undefined?true:props.showTopTotal; + showGlobalFilter = props.showGlobalFilter===undefined?true:props.showGlobalFilter; + showColumnFilter = props.showColumnFilter===undefined?true:props.showColumnFilter; + allowColumnSelection = props.allowColumnSelection===undefined?true:props.allowColumnSelection; // Default Header to show in table and other columns header will not show until user action on UI let defaultheader = props.defaultcolumns; let optionalheader = props.optionalcolumns; @@ -519,8 +534,8 @@ function ViewTable(props) { Header: isString ? defaultheader[0][header] : defaultheader[0][header].name, id: header, accessor: header, - filter: (!isString && defaultheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText', - Filter: isString ? DefaultColumnFilter : (filterTypes[defaultheader[0][header].filter] ? filterTypes[defaultheader[0][header].filter] : DefaultColumnFilter), + filter: (showColumnFilter?((!isString && defaultheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText'):""), + Filter: (showColumnFilter?(isString ? DefaultColumnFilter : (filterTypes[defaultheader[0][header].filter] ? filterTypes[defaultheader[0][header].filter] : DefaultColumnFilter)):""), isVisible: true, Cell: props => <div> {updatedCellvalue(header, props.value)} </div>, }) @@ -534,8 +549,8 @@ optionaldataheader.forEach(header => { Header: isString ? optionalheader[0][header] : optionalheader[0][header].name, id: isString ? header : optionalheader[0][header].name, accessor: header, - filter: (!isString && optionalheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText', - Filter: isString ? DefaultColumnFilter : (filterTypes[optionalheader[0][header].filter] ? filterTypes[optionalheader[0][header].filter] : DefaultColumnFilter), + filter: (showColumnFilter?((!isString && optionalheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText'):""), + Filter: (showColumnFilter?(isString ? DefaultColumnFilter : (filterTypes[optionalheader[0][header].filter] ? filterTypes[optionalheader[0][header].filter] : DefaultColumnFilter)):""), isVisible: false, Cell: props => <div> {updatedCellvalue(header, props.value)} </div>, }) 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 c2b32f1d6f45d477613e4d68d7257c3fedaff4ab..cb6fae3a347c4a13ed26a31b0b9e5b9ce597860a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss @@ -19,6 +19,7 @@ .timeline-actionbar button { padding-top: 3px; font-size: 1.0rem; + padding-left: 3px; // float: right; } @@ -31,6 +32,21 @@ white-space: nowrap; } +.timeline-filters,.timeline-bottom { + padding-top: 25px; +} + +.timeline-button { + padding-bottom: 5px; + padding-left: 0px; + padding-right: 0px; + padding-top: 25px; +} + +.timeline-details-pane { + font-size: 14px; +} + #now-btn { margin-left: 20px; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss index 47803d3ad81264ab7634da3e4b9194d89af2b6f8..45b581f6c23bd3bb129886a494ba5305c6ffcc76 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss @@ -102,27 +102,27 @@ body .p-paginator { max-width: 175px; } -.filter-input-50 input{ +.filter-input-50,.filter-input-50 input{ width: 50px; } -.filter-input-75 input{ +.filter-input-75,.filter-input-75 input{ width: 75px; } -.filter-input-100 input{ +.filter-input-100,.filter-input-100 input{ width: 100px; } -.filter-input-125 input{ +.filter-input-125,.filter-input-125 input{ width: 125px; } -.filter-input-150 input{ +.filter-input-150,.filter-input-150 input{ width: 150px; } -.filter-input-175 input{ +.filter-input-175,.filter-input-175 input{ width: 175px; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js new file mode 100644 index 0000000000000000000000000000000000000000..356ea4d05cb33821aede487bf3b1ceffb1fffc6b --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js @@ -0,0 +1,71 @@ +import React, {Component} from 'react'; +import { Link, Redirect } from 'react-router-dom/cjs/react-router-dom.min'; +import moment from 'moment'; +import ViewTable from '../../components/ViewTable'; + +/** + * Component to view summary of the scheduling unit with limited task details + */ +export class SchedulingUnitSummary extends Component { + + constructor(props) { + super(props); + this.state = { + schedulingUnit: props.schedulingUnit || null + } + this.closeSUDets = this.closeSUDets.bind(this); + } + + componentDidMount() {} + + closeSUDets() { + if(this.props.closeCallback) { + this.props.closeCallback(); + } + } + + render() { + const schedulingUnit = this.props.schedulingUnit; + const suTaskList = this.props.suTaskList; + return ( + <React.Fragment> + { schedulingUnit && + <div className="p-grid timeline-details-pane" style={{marginTop: '10px'}}> + <h6 className="col-lg-10 col-sm-10">Details</h6> + <Link to={`/schedulingunit/view/blueprint/${schedulingUnit.id}`} title="View Full Details"><i className="fa fa-eye"></i></Link> + <Link to={`/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">{schedulingUnit.name}</div> + <div className="col-4"><label>Project:</label></div> + <div className="col-8">{schedulingUnit.project.name}</div> + <div className="col-4"><label>Start Time:</label></div> + <div className="col-8">{moment.utc(schedulingUnit.start_time).format("DD-MMM-YYYY HH:mm:ss")}</div> + <div className="col-4"><label>Stop Time:</label></div> + <div className="col-8">{moment.utc(schedulingUnit.stop_time).format("DD-MMM-YYYY HH:mm:ss")}</div> + <div className="col-4"><label>Status:</label></div> + <div className="col-8">{schedulingUnit.status}</div> + <div className="col-12"> + <ViewTable + data={suTaskList} + defaultcolumns={[{id: "ID", start_time:"Start Time", stop_time:"End Time", status: "Status", + antenna_set: "Antenna Set", band: 'Band'}]} + optionalcolumns={[{actionpath: "actionpath"}]} + columnclassname={[{"ID": "filter-input-50", "Start Time": "filter-input-75", "End Time": "filter-input-75", + "Status": "filter-input-75", "Antenna Set": "filter-input-75", "Band": "filter-input-75"}]} + defaultSortColumn= {[{id: "ID", desc: false}]} + showaction="false" + tablename="timeline_su_taskslist" + showTopTotal={false} + showGlobalFilter={false} + showColumnFilter={false} + allowColumnSelection={false} + /> + </div> + </div> + } + </React.Fragment> + ); + } +} + +export default SchedulingUnitSummary; \ 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 7b1577973c9b081ff5534cb129c013846e890b5b..73128c9336aa85ffc9deed25efb87fe9d9708cb6 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -1,5 +1,5 @@ import React, {Component} from 'react'; -import { Redirect } from 'react-router-dom/cjs/react-router-dom.min'; +import { Link, Redirect } from 'react-router-dom/cjs/react-router-dom.min'; import moment from 'moment'; import _ from 'lodash'; @@ -15,6 +15,7 @@ import ScheduleService from '../../services/schedule.service'; import UtilService from '../../services/util.service'; import UnitConverter from '../../utils/unit.converter'; +import SchedulingUnitSummary from '../Scheduling/summary'; // Color constant for status const STATUS_COLORS = { "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", @@ -39,13 +40,16 @@ export class TimelineView extends Component { isSUDetsVisible: false, canExtendSUList: true, canShrinkSUList: false, - selectedItem: null + selectedItem: null, + suTaskList:[], + isSummaryLoading: false } this.onItemClick = this.onItemClick.bind(this); this.closeSUDets = this.closeSUDets.bind(this); this.dateRangeCallback = this.dateRangeCallback.bind(this); this.resizeSUList = this.resizeSUList.bind(this); + this.suListFilterCallback = this.suListFilterCallback.bind(this); } async componentDidMount() { @@ -126,7 +130,24 @@ export class TimelineView extends Component { if (this.state.isSUDetsVisible && item.id===this.state.selectedItem.id) { this.closeSUDets(); } else { - this.setState({selectedItem: item, isSUDetsVisible: true, canExtendSUList: false, canShrinkSUList:false}); + const fetchDetails = !this.state.selectedItem || item.id!==this.state.selectedItem.id + this.setState({selectedItem: item, isSUDetsVisible: true, + isSummaryLoading: fetchDetails, + suTaskList: !fetchDetails?this.state.suTaskList:[], + canExtendSUList: false, canShrinkSUList:false}); + if (fetchDetails) { + const suBlueprint = _.find(this.state.suBlueprints, {id: item.id}); + ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true) + .then(taskList => { + for (let task of taskList) { + if (task.template.type_value.toLowerCase() === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.band; + } + } + this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false}) + }); + } } } @@ -162,7 +183,7 @@ export class TimelineView extends Component { } this.setState({suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => {return suBlueprint.start_time!=null})}); // On range change close the Details pane - this.closeSUDets(); + // this.closeSUDets(); return {group: group, items: items}; } @@ -185,6 +206,25 @@ export class TimelineView extends Component { this.setState({canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList}); } + /** + * Callback function to pass to the ViewTable component to pass back filtered data + * @param {Array} filteredData + */ + suListFilterCallback(filteredData) { + let group=[], items = []; + const suBlueprints = this.state.suBlueprints; + for (const data of filteredData) { + const suBlueprint = _.find(suBlueprints, {actionpath: data.actionpath}); + items.push(this.getTimelineItem(suBlueprint)); + if (!_.find(group, {'id': suBlueprint.suDraft.id})) { + group.push({'id': suBlueprint.suDraft.id, title: suBlueprint.suDraft.name}); + } + } + if (this.timeline) { + this.timeline.updateTimeline({group: group, items: items}); + } + } + render() { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> @@ -192,6 +232,10 @@ export class TimelineView extends Component { const isSUDetsVisible = this.state.isSUDetsVisible; const canExtendSUList = this.state.canExtendSUList; const canShrinkSUList = this.state.canShrinkSUList; + let suBlueprint = null; + if (isSUDetsVisible) { + suBlueprint = _.find(this.state.suBlueprints, {id: this.state.selectedItem.id}); + } return ( <React.Fragment> <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} /> @@ -221,10 +265,11 @@ export class TimelineView extends Component { showaction="true" tablename="timeline_scheduleunit_list" showTopTotal="false" + filterCallback={this.suListFilterCallback} /> </div> {/* Timeline Panel */} - <div className={isSUDetsVisible || (!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")}> + <div className={isSUDetsVisible || (!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} @@ -248,18 +293,43 @@ export class TimelineView extends Component { {/* </SplitPane> */} {/* Details Panel */} {this.state.isSUDetsVisible && - <div className="col-lg-2 col-md-2 col-sm-12" + <div className="col-lg-3 col-md-3 col-sm-12" style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> - <div className="p-grid" style={{marginTop: '10px'}}> + {this.state.isSummaryLoading?<AppLoader /> : + <SchedulingUnitSummary schedulingUnit={suBlueprint} suTaskList={this.state.suTaskList} + closeCallback={this.closeSUDets}></SchedulingUnitSummary> + } + {/* <div className="p-grid timeline-details-pane" style={{marginTop: '10px'}}> <h6 className="col-lg-10 col-sm-10">Details</h6> - <button className="p-link" onClick={this.closeSUDets}><i className="fa fa-times"></i></button> - + <Link to={`/schedulingunit/view/blueprint/${suBlueprint.id}`}><i className="fa fa-eye"></i></Link> + <Link to={`/su/timelineview`} onClick={this.closeSUDets}><i className="fa fa-times"></i></Link> + <div className="col-4"><label>Name:</label></div> + <div className="col-8">{suBlueprint.name}</div> + <div className="col-4"><label>Project:</label></div> + <div className="col-8">{suBlueprint.project.name}</div> + <div className="col-4"><label>Start Time:</label></div> + <div className="col-8">{moment.utc(suBlueprint.start_time).format("DD-MMM-YYYY HH:mm:ss")}</div> + <div className="col-4"><label>Stop Time:</label></div> + <div className="col-8">{moment.utc(suBlueprint.stop_time).format("DD-MMM-YYYY HH:mm:ss")}</div> + <div className="col-4"><label>Status:</label></div> + <div className="col-8">{suBlueprint.status}</div> <div className="col-12"> - {this.state.selectedItem.title} + <ViewTable + data={this.state.suTaskList} + defaultcolumns={[{id: "ID", start_time:"Start Time", stop_time:"End Time", status: "Status", + antenna_set: "Antenna Set", band: 'Band'}]} + optionalcolumns={[{actionpath: "actionpath"}]} + columnclassname={[{"ID": "filter-input-50", "Start Time": "filter-input-75", "End Time": "filter-input-75", + "Status": "filter-input-75", "Antenna Set": "filter-input-75", "Band": "filter-input-75"}]} + defaultSortColumn= {[{id: "ID", desc: false}]} + showaction="false" + tablename="timeline_su_taskslist" + showTopTotal={false} + showGlobalFilter={false} + showColumnFilter={false} + /> </div> - - <div className="col-12">Still In Development</div> - </div> + </div> */} </div> } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index 30a2c0db09bf506a44b47f156ba4f8b26ec2c3f6..88e0ee4eb195ad8ad8c5a642f13ea1b8c21f6f6d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -52,22 +52,25 @@ const ScheduleService = { return null; } }, - getTaskBlueprintById: async function(id){ - let res = []; - await axios.get('/api/task_blueprint/'+id) - .then(response => { - res= response; - }).catch(function(error) { + getTaskBlueprintById: async function(id, loadTemplate){ + let result; + console.log(loadTemplate); + try { + result = await axios.get('/api/task_blueprint/'+id); + if (result.data && loadTemplate) { + result.data.template = await TaskService.getTaskTemplate(result.data.specifications_template_id); + } + } catch(error) { console.error('[schedule.services.getTaskBlueprintById]',error); - }); - return res; + } + return result; }, - getTaskBlueprintsBySchedulingUnit: async function(scheduleunit){ + getTaskBlueprintsBySchedulingUnit: async function(scheduleunit, loadTemplate){ // there no single api to fetch associated task_blueprint, so iteare the task_blueprint id to fetch associated task_blueprint let taskblueprintsList = []; if(scheduleunit.task_blueprints_ids){ for(const id of scheduleunit.task_blueprints_ids){ - await this.getTaskBlueprintById(id).then(response =>{ + await this.getTaskBlueprintById(id, loadTemplate).then(response =>{ let taskblueprint = response.data; taskblueprint['tasktype'] = 'Blueprint'; taskblueprint['actionpath'] = '/task/view/blueprint/'+taskblueprint['id'];