From 14475882151b53d19bc97ada0158f6bdce76c85c Mon Sep 17 00:00:00 2001
From: Muthukrishnan <m.krishnan@redkarma.eu>
Date: Fri, 18 Dec 2020 07:12:04 +0530
Subject: [PATCH] TMSS-407 - Reservation table implemented

Reservation table implemented
---
 .../tmss_webapp/src/components/ViewTable.js   | 201 +++++++++++++++++-
 .../src/layout/sass/reservation.scss          |   3 +
 .../tmss_webapp/src/routes/Timeline/index.js  |   3 +-
 .../tmss_webapp/src/routes/Timeline/list.js   | 167 +++++++++++++++
 .../tmss_webapp/src/routes/Timeline/view.js   |   4 +-
 .../frontend/tmss_webapp/src/routes/index.js  |   9 +-
 .../src/services/reservation.service.js       |  35 +++
 7 files changed, 417 insertions(+), 5 deletions(-)
 create mode 100644 SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss
 create mode 100644 SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.js
 create mode 100644 SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js

diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js
index 3428a67afc8..8206f04304b 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js
@@ -13,6 +13,9 @@ import {TriStateCheckbox} from 'primereact/tristatecheckbox';
 import { Slider } from 'primereact/slider';
 import { Button } from "react-bootstrap";
 import { InputNumber } from "primereact/inputnumber";
+import {MultiSelect} from 'primereact/multiselect';
+import { RadioButton } from 'primereact/radiobutton';
+import {Dropdown} from 'primereact/dropdown';
 
 let tbldata =[], filteredData = [] ;
 let selectedRows = [];
@@ -24,7 +27,8 @@ let allowColumnSelection = true;
 let allowRowSelection = false;
 let columnclassname =[];
 let parentCallbackFunction, parentCBonSelection;
-
+let cyclelist =[];
+let cycle= [];
 // Define a default UI for filtering
 function GlobalFilter({
     preGlobalFilteredRows,
@@ -114,6 +118,69 @@ function SelectColumnFilter({
   )
 }
 
+// This is a custom filter UI for selecting
+// a unique option from a list
+function MultiSelectColumnFilter({
+  column: { filterValue, setFilter, preFilteredRows, id },
+}) {
+  const [value, setValue] = useState('');
+  //const [filtertype, setFiltertype] = useState('');
+  React.useEffect(() => {
+    if (!filterValue && value) {
+      setValue('');
+    }
+  }, [filterValue, value]);
+    const options = React.useMemo(() => {
+    let options = new Set();
+    preFilteredRows.forEach(row => {
+      row.values[id].split(',').forEach( value => {
+        if ( value !== '') {
+          let hasValue = false;
+          options.forEach( option => {
+              if(option.name === value){
+                hasValue = true;
+              }
+          });
+          if(!hasValue) {
+            let option = { 'name': value, 'value':value};
+            options.add(option);
+          }
+        }
+      });
+    });
+    return [...options.values()]
+  }, [id, preFilteredRows])
+   // Render a multi-select box
+  return (
+    <div onClick={e => { e.stopPropagation() }} >
+       {/*} <div className="p-field-radiobutton">
+            <RadioButton inputId="filtertype1" name="filtertype" value="Any Of" onChange={(e) => this.setState({filtertype: e.value})} checked={this.state.filtertype === 'Any Of'} />
+            <label htmlFor="filtertype1">Any Of</label>
+        </div>
+        <div className="p-field-radiobutton">
+            <RadioButton inputId="filtertype2" name="filtertype" value="All" onChange={(e) => this.setState({filtertype: e.value})} checked={this.state.filtertype === 'All'} />
+            <label htmlFor="filtertype2">All</label>
+  </div> */}
+
+        <MultiSelect data-testid="stations" id="stations" optionLabel="value" optionValue="value" filter={true}
+            value={value}
+            options={options} 
+            onChange={e => { setValue(e.target.value);
+              setFilter(e.target.value|| undefined)
+            }}
+
+            style={{
+              height: '24.2014px',
+              width: '60px',
+              border:'1px solid lightgrey',
+             }}
+        />
+         
+       
+   </div>
+  )
+}
+
 // This is a custom filter UI that uses a
 // slider to set the filter value between a column's
 // min and max values
@@ -185,6 +252,115 @@ function CalendarColumnFilter({
   )
 }
 
+// This is a custom filter UI that uses a
+// calendar to set the value
+function DateTimeColumnFilter({
+  column: { setFilter, filterValue},
+}) {
+  // Calculate the min and max
+  // using the preFilteredRows
+  const [value, setValue] = useState('');
+  React.useEffect(() => {
+    if (!filterValue && value) {
+      setValue(null);
+    }
+  }, [filterValue, value]);
+  return (
+    
+    <div className="table-filter" onClick={e => { e.stopPropagation() }}>
+       <Calendar value={value} appendTo={document.body} onChange={(e) => {
+        const value = moment(e.value, moment.ISO_8601).format("YYYY-MMM-DD HH:mm:SS")
+          setValue(value); setFilter(value); 
+        }} showIcon
+       // showTime= {true}
+        //showSeconds= {true}
+       // hourFormat= "24"
+        ></Calendar>
+       {value && <i onClick={() => {setFilter(undefined); setValue('') }} className="tb-cal-reset fa fa-times" />}
+        </div>
+  )
+}
+
+/**
+ * Custom function to filter data from date field.
+ * @param {Array} rows 
+ * @param {String} id 
+ * @param {String} filterValue 
+ */
+function fromDatetimeFilterFn(rows, id, filterValue) {
+  const filteredRows = _.filter(rows, function(row) {
+                        // If cell value is null or empty
+                        if (!row.values[id]) {
+                          return false;
+                        }
+                        //Remove microsecond if value passed is UTC string in format "YYYY-MM-DDTHH:mm:ss.sssss"
+                        let rowValue = moment.utc(row.values[id].split('.')[0]);
+                        if (!rowValue.isValid()) {
+                            // For cell data in format 'YYYY-MMM-DD'
+                            rowValue = moment.utc(moment(row.values[id], 'YYYY-MMM-DDTHH:mm:SS').format("YYYY-MM-DDTHH:mm:SS"));
+                        }
+                        const start = moment.utc(moment(filterValue, 'YYYY-MMM-DDTHH:mm:SS').format("YYYY-MM-DDTHH:mm:SS"));
+                        
+                        return (start.isSameOrBefore(rowValue));
+                      } );
+  return filteredRows;
+}
+
+/**
+ * Custom function to filter data from date field.
+ * @param {Array} rows 
+ * @param {String} id 
+ * @param {String} filterValue 
+ */
+function multiSelectFilterFn(rows, id, filterValue) {
+  const filteredRows = _.filter(rows, function(row) {
+    alert('tbldata',tbldata.length)
+                        if ( filterValue.length === 0){
+                          return true;
+                        }
+                        // If cell value is null or empty
+                        if (!row.values[id]) {
+                          return false;
+                        }
+                       
+                        let rowValue = row.values[id];
+                        
+                        let hasData = false;
+                        filterValue.forEach(filter => {
+                          if( rowValue.includes( filter )) {
+                              hasData = true;
+                          }
+                        });                        
+                        return hasData;
+                      } );
+  return filteredRows;
+}
+
+/**
+ * Custom function to filter data from date field.
+ * @param {Array} rows 
+ * @param {String} id 
+ * @param {String} filterValue 
+ */
+function toDatetimeFilterFn(rows, id, filterValue) {
+  const filteredRows = _.filter(rows, function(row) {
+                        // If cell value is null or empty
+                        if (!row.values[id]) {
+                          return false;
+                        }
+                        //Remove microsecond if value passed is UTC string in format "YYYY-MM-DDTHH:mm:ss.sssss"
+                        let rowValue = moment.utc(row.values[id].split('.')[0]);
+                        if (!rowValue.isValid()) {
+                            // For cell data in format 'YYYY-MMM-DD'
+                            rowValue = moment.utc(moment(row.values[id], 'YYYY-MMM-DDTHH:mm:SS').format("YYYY-MM-DDTHH:mm:SS"));
+                        }
+                        const end = moment.utc(moment(filterValue, 'YYYY-MMM-DDTHH:mm:SS').format("YYYY-MM-DDTHH:mm:SS"));
+                        
+                        return (end.isSameOrAfter(rowValue));
+                      } );
+  return filteredRows;
+}
+
 /**
  * Custom function to filter data from date field.
  * @param {Array} rows 
@@ -328,6 +504,10 @@ const filterTypes = {
   'select': { 
     fn: SelectColumnFilter,
   },
+  'multiselect': { 
+    fn: MultiSelectColumnFilter,
+    type: multiSelectFilterFn 
+  },
   'switch': {
     fn: BooleanColumnFilter
   },
@@ -338,6 +518,14 @@ const filterTypes = {
     fn: CalendarColumnFilter,
     type: dateFilterFn
   },
+  'fromdatetime': {
+    fn: DateTimeColumnFilter,
+    type: fromDatetimeFilterFn
+  },
+  'todatetime': {
+    fn: DateTimeColumnFilter,
+    type: toDatetimeFilterFn
+  },
   'range': {
     fn: RangeColumnFilter,
     type: 'between'
@@ -540,6 +728,15 @@ const defaultColumn = React.useMemo(
         </div>
         { showTopTotal && filteredData.length === data.length &&
           <div className="total_records_top_label"> <label >Total records ({data.length})</label></div>
+        } 
+         { cyclelist && cyclelist.length>0 &&
+          <div className="total_records_top_label">
+             <label >Cycles </label>  
+              <MultiSelect data-testid="cycle" id="cycle" optionLabel="name" optionValue="url" filter={true}
+                      value={cycle} 
+                      options={cyclelist} 
+              />
+          </div>
         }
         { showTopTotal && filteredData.length < data.length &&
             <div className="total_records_top_label" ><label >Filtered {filteredData.length} from {data.length}</label></div>}
@@ -629,6 +826,8 @@ function ViewTable(props) {
     const history = useHistory();
     // Data to show in table
     tbldata = props.data;
+    cyclelist= props.cycleList;
+
     parentCallbackFunction = props.filterCallback; 
     parentCBonSelection = props.onRowSelection;
     isunittest = props.unittest;
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss
new file mode 100644
index 00000000000..19a02279d53
--- /dev/null
+++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss
@@ -0,0 +1,3 @@
+.p-multiselect{
+    height: 10px;
+}
\ No newline at end of file
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 b48cd64f554..67d78a0e432 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/index.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/index.js
@@ -1,4 +1,5 @@
 import {TimelineView} from './view';
 import {WeekTimelineView} from './week.view';
+import { ReservationList} from './list';
 
-export {TimelineView, WeekTimelineView} ;
+export {TimelineView, WeekTimelineView, ReservationList} ;
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.js
new file mode 100644
index 00000000000..0aeaf573046
--- /dev/null
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/list.js
@@ -0,0 +1,167 @@
+import React, {Component} from 'react';
+import ReservationService from './../../services/reservation.service'; 
+import AppLoader from "./../../layout/components/AppLoader";
+import ViewTable from './../../components/ViewTable';
+import PageHeader from '../../layout/components/PageHeader';
+import CycleService from '../../services/cycle.service';
+import _ from 'lodash';
+import moment from 'moment';
+
+export class ReservationList extends Component{
+    constructor(props){
+        super(props);
+        this.state = {
+            reservationsList: [],
+            defaultcolumns: [{
+                name:"System Id",
+                description:"Description",
+                start_time: {
+                    name: "Start Time",
+                    filter: "fromdatetime"
+                },
+                end_time: {
+                    name: "End Time",
+                    filter: "todatetime"
+                },
+                duration:"Duration (HH:mm:ss)",
+                type: "Reservation type",
+                subject: "Subject",
+                planned:"Planned",
+                stations:{
+                    name:"Stations",
+                    filter:"multiselect"
+                },
+               /* cycles:{
+                    name:"Cycle Id",
+                    filter:"select"
+                },*/
+                manual:  "Manual",
+                dynamic: "Dynamic",
+                project_id: "Project",
+                expert: "Expert",
+                hba_rfi: "HBA-RFI",
+                lba_rfi: "LBA-RFI"
+            }],
+            optionalcolumns:  [{ 
+                actionpath:"actionpath"
+            }],
+            columnclassname: [{
+                "Duration (HH:mm:ss)":"filter-input-75",
+                "Reservation type":"filter-input-100",
+                "Subject":"filter-input-50",
+                "Planned":"filter-input-50",
+                "Stations":"filter-input-150",
+                "Manual":"filter-input-50",
+                "Dynamic":"filter-input-50",
+                "Expert":"filter-input-50",
+                "HBA-RFI":"filter-input-50",
+                "LBA-RFI":"filter-input-50",
+                
+            }],
+            defaultSortColumn: [{id: "System Id", desc: false}],
+            isLoading: true,
+            cycleList: [],
+        }
+        this.reservations= [];
+        this.cycleList= [];
+        this.sortListCycle= [];
+    }
+    
+    async componentDidMount() {
+        const promises = [  ReservationService.getAllReservation(),
+            CycleService.getAllCycles(),
+        ];
+             
+        this.reservations = [];
+        await Promise.all(promises).then(responses => {
+            let reservation = {};
+            const cycleList = responses[1];
+            for( const response  of responses[0]){
+                reservation = response;
+                reservation = this.mergeResourceWithReservation( reservation, response.specifications_doc.activity) ;
+                reservation = this.mergeResourceWithReservation( reservation, response.specifications_doc.effects );
+                reservation = this.mergeResourceWithReservation( reservation, response.specifications_doc.schedulability );
+                if (response.specifications_doc.resources.stations ) {
+                    reservation['stations'] = response.specifications_doc.resources.stations.join();
+                } else {
+                    reservation['stations'] = '';
+                }
+                //Get associated cycle list
+                reservation['cycles'] = this.getCycleByProject(reservation.project_id, cycleList).join();
+                if(reservation.duration === null || reservation.duration === ''){
+                    reservation.duration = 'Unknown';
+                    reservation['end_time']= 'Unknown';
+                } else {
+                    reservation.duration = moment.utc((reservation.duration || 0)*1000).format('HH:mm:ss'); 
+                    let endDate = moment(reservation.start_time);
+                    endDate.add(Number(reservation.duration), 'seconds');
+                    reservation['end_time']= moment(endDate).format('YYYY-MM-DD HH:mm:ss');
+                }
+                
+                
+                reservation['start_time']= moment(reservation.start_time).format('YYYY-MM-DD HH:mm:ss');
+                this.reservations.push(reservation);
+            };
+ 
+            //specifications_doc.resources
+            this.setState({
+                isLoading: false,
+                reservationsList: this.reservations,
+                cycleList: this.sortListCycle,
+            });
+        });
+    }
+
+    getCycleByProject (name, cycleList) {
+        let cycleName= [];
+        cycleList.map(cycle => {
+            if (_.includes( cycle.projects_ids, name)) {
+                cycleName.push(cycle.name);
+                const tmpCycleList = _.filter(this.sortListCycle, function(o) { return o.name === cycle.name });
+                if (tmpCycleList.length === 0) {
+                    this.sortListCycle.push(cycle);
+                }
+            }
+             
+        });
+        return cycleName;
+    }
+
+    mergeResourceWithReservation ( reservation, params) {
+        if( params ){
+            Object.keys(params).map((key, i) => (
+                reservation[key]= params[key]
+              ));
+        }
+        return reservation;
+    }
+
+    render() {
+         
+        return ( 
+            <React.Fragment>
+                <PageHeader location={this.props.location} title={'Reservation - List'} 
+                actions={[]}
+                />
+               {this.state.isLoading? <AppLoader /> : (this.state.reservationsList && this.state.reservationsList.length>0) ?
+                    <ViewTable 
+                        data={this.state.reservationsList} 
+                        defaultcolumns={this.state.defaultcolumns} 
+                        optionalcolumns={this.state.optionalcolumns}
+                        columnclassname={this.state.columnclassname}
+                        defaultSortColumn={this.state.defaultSortColumn}
+                        showaction="true"
+                        paths={this.state.paths}
+                        keyaccessor="name"
+                        unittest={this.state.unittest}
+                        tablename="reservation_list"
+                        cycleList={this.state.cycleList}
+                    />
+                : <div>No Reservation found </div>
+                
+                }
+            </React.Fragment>
+        );
+    }
+ 
+}
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 9a0b6cc381d..9dec851bd66 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js
@@ -326,7 +326,9 @@ export class TimelineView extends Component {
         return (
             <React.Fragment>
                 <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} 
-                    actions={[{icon: 'fa-calendar-alt',title:'Week View', props : { pathname: `/su/timelineview/week`}}]}/>
+                    actions={[
+                        {icon: 'fa-plus-square',title:'Reservation List', props : { pathname: `/su/timelineview/reservation/list`}},
+                        {icon: 'fa-calendar-alt',title:'Week View', props : { pathname: `/su/timelineview/week`}}]}/>
                 { this.state.isLoading ? <AppLoader /> :
                         <div className="p-grid">
                             {/* SU List Panel */}
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js
index 156267acb0f..c5125cf8d2b 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js
@@ -14,7 +14,7 @@ import ViewSchedulingUnit from './Scheduling/ViewSchedulingUnit'
 import SchedulingUnitCreate from './Scheduling/create';
 import EditSchedulingUnit from './Scheduling/edit';
 import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle';
-import {TimelineView, WeekTimelineView} from './Timeline';
+import {ReservationList, TimelineView, WeekTimelineView} from './Timeline';
 import SchedulingSetCreate from './Scheduling/create.scheduleset';
 import Workflow from './Workflow';
 
@@ -159,7 +159,12 @@ export const routes = [
        name: 'Workflow',
        title: 'QA Reporting (TO)'
     },
-    
+    {
+        path: "/su/timelineview/reservation/list",
+        component: ReservationList,
+        name: 'Reservation List',
+        title:'Reservation List'
+    },
 ];
 
 export const RoutedContent = () => {
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js
new file mode 100644
index 00000000000..78dc3f49759
--- /dev/null
+++ b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js
@@ -0,0 +1,35 @@
+const axios = require('axios');
+
+axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0';
+
+const ReservationService = {
+    getReservation: async function () {
+        try {
+            const url = `/api/reservation_template`;
+            const response = await axios.get(url);
+            return response.data.results;
+        } catch (error) {
+            console.error(error);
+        }
+    },
+    saveReservation: async function (reservation) {
+        try {
+            const response = await axios.post(('/api/reservation/'), reservation);
+            return response.data;
+          } catch (error) {
+            console.error(error);
+            return null;
+          }
+    },
+    getAllReservation: async function () {
+        try {
+            const url = `/api/reservation`;
+            const response = await axios.get(url);
+            return response.data.results;
+        } catch (error) {
+            console.error(error);
+        }
+    },
+}
+
+export default ReservationService;
-- 
GitLab