diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py index f49e18006f437273e91405571224dcec25ba7c8d..6a13d1a6decb11f04c6a8e65211fef9d4bb96dc0 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py @@ -289,9 +289,58 @@ class ReservationTemplateViewSet(AbstractTemplateViewSet): serializer_class = serializers.ReservationTemplateSerializer +class ReservationPropertyFilter(property_filters.PropertyFilterSet): + project = property_filters.PropertyCharFilter(field_name='project', lookup_expr='icontains') + start_time_min = property_filters.PropertyDateTimeFilter(field_name='start_time', lookup_expr='gte') + start_time_max = property_filters.PropertyDateTimeFilter(field_name='start_time', lookup_expr='lte') + stop_time_min = property_filters.PropertyDateTimeFilter(field_name='stop_time', lookup_expr='gte') + stop_time_max = property_filters.PropertyDateTimeFilter(field_name='stop_time', lookup_expr='lte') + stop_time_isnull = property_filters.PropertyBooleanFilter(field_name='stop_time', lookup_expr='isnull') + duration_min = property_filters.PropertyNumberFilter(field_name='duration', lookup_expr='gte') + duration_max = property_filters.PropertyNumberFilter(field_name='duration', lookup_expr='lte') + duration_isnull = property_filters.PropertyBooleanFilter(field_name='duration', lookup_expr='isnull') + # Specification properties + planned = filters.BooleanFilter(label='planned', field_name='specifications_doc__activity__planned') + lba_rfi = filters.BooleanFilter(label='lba_rfi', field_name='specifications_doc__effects__lba_rfi') + hba_rfi = filters.BooleanFilter(label='hba_rfi', field_name='specifications_doc__effects__lba_rfi') + expert = filters.BooleanFilter(label='expert', field_name='specifications_doc__effects__expert') + manual = filters.BooleanFilter(label='manual', field_name='specifications_doc__schedulability__manual') + dynamic = filters.BooleanFilter(label='dynamic', field_name='specifications_doc__schedulability__dynamic') + project_exclusive = filters.BooleanFilter(label='project_exclusive', field_name='specifications_doc__schedulability__project_exclusive') + # TODO: Remove hard coded values. See TMSS-984. + reservation_type = filters.ChoiceFilter(label='reservation_type', field_name='specifications_doc__activity__type', + choices=tuple((i, i) for i in ('maintenance', 'test', 'upgrade', 'outage', 'pr', 'stand-alone mode', 'test system', 'other'))) + subject = filters.ChoiceFilter(label='subject', field_name='specifications_doc__activity__subject', + choices=tuple((i, i) for i in ('environment', 'hardware', 'firmware', 'software', 'system', 'network', 'nothing'))) + stations_any = filters.MultipleChoiceFilter(label='stations [any]', field_name='specifications_doc__resources__stations', + choices=tuple((i, i) for i in ( + 'CS001', 'CS002', 'CS003', 'CS004', 'CS005', 'CS006', 'CS007', 'CS011', + 'CS013', 'CS017', 'CS021', 'CS024', 'CS026', 'CS028', 'CS030', 'CS031', + 'CS032', 'CS101', 'CS103', 'CS201', 'CS301', 'CS302', 'CS401', 'CS501', + 'RS106', 'RS205', 'RS208', 'RS210', 'RS305', 'RS306', 'RS307', 'RS310', + 'RS406', 'RS407', 'RS409', 'RS503', 'RS508', 'RS509', 'DE601', 'DE602', + 'DE603', 'DE604', 'DE605', 'DE609', 'FR606', 'SE607', 'UK608', 'PL610', + 'PL611', 'PL612', 'IE613', 'LV614')), lookup_expr='icontains') + stations_all = filters.MultipleChoiceFilter(label='stations [all]', field_name='specifications_doc__resources__stations', + choices=tuple((i, i) for i in ( + 'CS001', 'CS002', 'CS003', 'CS004', 'CS005', 'CS006', 'CS007', 'CS011', + 'CS013', 'CS017', 'CS021', 'CS024', 'CS026', 'CS028', 'CS030', 'CS031', + 'CS032', 'CS101', 'CS103', 'CS201', 'CS301', 'CS302', 'CS401', 'CS501', + 'RS106', 'RS205', 'RS208', 'RS210', 'RS305', 'RS306', 'RS307', 'RS310', + 'RS406', 'RS407', 'RS409', 'RS503', 'RS508', 'RS509', 'DE601', 'DE602', + 'DE603', 'DE604', 'DE605', 'DE609', 'FR606', 'SE607', 'UK608', 'PL610', + 'PL611', 'PL612', 'IE613', 'LV614')), lookup_expr='icontains', conjoined=True) + + class Meta: + model = models.Reservation + fields = '__all__' + filter_overrides = FILTER_OVERRIDES + + class ReservationViewSet(LOFARViewSet): queryset = models.Reservation.objects.all() serializer_class = serializers.ReservationSerializer + filter_class = ReservationPropertyFilter # note that this breaks other filter backends from LOFARViewSet class RoleViewSet(LOFARViewSet): diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index f15ad062ef1ea3a8d3cc58e943bf55e0ffda5162..1817c879c51a685360e6286afc687fc25e74a996 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -18,6 +18,8 @@ import { MultiSelect } from 'primereact/multiselect'; import { RadioButton } from 'primereact/radiobutton'; import { useExportData } from "react-table-plugins"; import { ProgressBar } from 'primereact/progressbar'; +import {Checkbox} from 'primereact/checkbox'; +import { Dropdown } from 'primereact/dropdown'; import "flatpickr/dist/flatpickr.css"; import Flatpickr from "react-flatpickr"; @@ -31,6 +33,8 @@ import "jspdf-autotable"; import TableUtil from "../utils/table.util"; import Validator from "../utils/validator" +let tblinstance; +let tableInstanceRef; let doServersideFilter = false; let tbldata = [], filteredData = []; let data = []; @@ -46,6 +50,7 @@ let parentCallbackFunction, parentCBonSelection; let showCSV = false; let multiSelectOption = {}; let filterCallback = null; +let clearAllFuncCallback = null; let tableOptionsState = null; let tableToolTipsState = {}; let setLoaderFunction = null; @@ -193,7 +198,6 @@ const setStoreData = (id, value) => { UtilService.localStore({ type: 'set', key: localstorage_key, value: storage }); } - /* Generate and download csv */ @@ -328,18 +332,8 @@ function MultiSelectColumnFilter({ column: { filterValue, setFilter, preFilteredRows, id, Header }, }) { const [value, setValue] = useState(''); + const [filtered, setFiltered] = useState(false); const [filtertype, setFiltertype] = useState('Any'); - // Set Any / All Filter type - const setSelectTypeOption = (option) => { - setFiltertype(option); - multiSelectOption[Header] = option - if (value !== '') { - if (storeFilter) { - TableUtil.saveFilter(currentTableName, `${Header}-FilterOption`, option); - } - setFilter(value); - } - }; React.useEffect(() => { if (!filterValue && value) { @@ -348,7 +342,7 @@ function MultiSelectColumnFilter({ } if (storeFilter) { const filterValue = TableUtil.getFilter(currentTableName, Header); - const filterType = TableUtil.getFilter(currentTableName, `${Header}-FilterOption`); + const filterType = TableUtil.getFilter(currentTableName, 'stationFilterType'); if(filterValue && !value){ setValue(filterValue); setFilter(filterValue); @@ -358,6 +352,41 @@ function MultiSelectColumnFilter({ } }, [filterValue, value, filtertype]); + // Set Any / All Filter type + const setSelectTypeOption = (option) => { + setFiltertype(option); + multiSelectOption[Header] = option + if (value !== '') { + if (storeFilter) { + TableUtil.saveFilter(currentTableName, 'stationFilterType', option); + } + setFilter(value); + } + }; + + /** + * Trigger server side data fetch in parent component + * @param {*} e + */ + function callSearchFunc(e) { + callServerFilter(e); + } + + // Function to call the server side filtering + const callServerFilter = (event, isCleared) => { + hasFilters = true; + tableOptionsState.filters.push({id: 'stationFilterType', value: filtertype}); + if (isCleared) { + hasFilters = false; + if (filtered) { + _.remove(tableOptionsState.filters, function(filter) { return filter.id === Header }); + filterCallback(tableOptionsState, setLoaderFunction); + } + } else { + filterCallback(tableOptionsState, setLoaderFunction); + } + }; + multiSelectOption[Header] = filtertype; const options = React.useMemo(() => { let options = new Set(); @@ -387,36 +416,50 @@ function MultiSelectColumnFilter({ return ( <div onClick={e => { e.stopPropagation() }} > <div className="p-field-radiobutton"> - <RadioButton inputId="filtertype1" name="filtertype" value="Any" onChange={(e) => setSelectTypeOption(e.value)} checked={filtertype === 'Any'} /> + <RadioButton inputId="filtertype1" name="filtertype" value="Any" + onChange={(e) => setSelectTypeOption(e.value)} checked={filtertype === '' || filtertype === 'Any'} + tooltip= "Search the row if the Station contains at least one of the selected value" /> <label htmlFor="filtertype1">Any</label> </div> <div className="p-field-radiobutton"> - <RadioButton inputId="filtertype2" name="filtertype" value="All" onChange={(e) => setSelectTypeOption(e.value)} checked={filtertype === 'All'} /> + <RadioButton inputId="filtertype2" name="filtertype" value="All" + onChange={(e) => setSelectTypeOption(e.value)} checked={filtertype === 'All'} + tooltip= "Search the row if the Station contains all of the selected value" /> <label htmlFor="filtertype2">All</label> </div> - <div style={{ position: 'relative' }} > - <MultiSelect data-testid="multi-select" id="multi-select" optionLabel="value" optionValue="value" filter={true} - value={value} - options={options} - onChange={e => { - setValue(e.target.value); - setFilter(e.target.value || undefined); - setFiltertype(filtertype); - if(storeFilter) { - if (e.target.value.length > 0) { - TableUtil.saveFilter(currentTableName, Header, e.target.value); - TableUtil.saveFilter(currentTableName, `${Header}-FilterOption`, filtertype); - } else { - TableUtil.clearColumnFilter(currentTableName, Header); - TableUtil.clearColumnFilter(currentTableName, `${Header}-FilterOption`); + <div style={{ position: 'relative', display: 'flex'}} > + <div> + <MultiSelect data-testid="multi-select" id="multi-select" optionLabel="value" optionValue="value" filter={true} + value={value} + options={options} + onChange={e => { + setValue(e.target.value); + setFilter(e.target.value || undefined); + setFiltertype(filtertype); + setFiltered(true); + if(storeFilter) { + if (e.target.value.length > 0) { + TableUtil.saveFilter(currentTableName, Header, e.target.value); + TableUtil.saveFilter(currentTableName, `${Header}-FilterOption`, filtertype); + } else { + TableUtil.clearColumnFilter(currentTableName, Header); + TableUtil.clearColumnFilter(currentTableName, `${Header}-FilterOption`); + } } - } - }} - maxSelectedLabels="1" - selectedItemsLabel="{0} Selected" - className="multi-select" - tooltip={(tableToolTipsState[Header])?tableToolTipsState[Header]:"Select one or more value from list to search"} - /> + }} + maxSelectedLabels="1" + selectedItemsLabel="{0} Selected" + className="multi-select" + tooltip={(tableToolTipsState[Header])?tableToolTipsState[Header]:"Select one or more value from list to search"} + /> + </div> + {doServersideFilter && + <div> + <button className="p-link" onClick={callSearchFunc} > + <i className="pi pi-search search-btn" /> + </button> + </div> + } </div> </div> ) @@ -477,8 +520,6 @@ function MultiSelectFilter({ // Function to call the server side filtering const callServerFilter = (event, isCleared) => { hasFilters = true; - //_.remove(tableOptionsState.filters, function(filter) { return filter.id === Header }); - //tableOptionsState.filters.push({id: Header, value: event.target.value}); if (isCleared) { hasFilters = false; if (filtered) { @@ -539,16 +580,7 @@ function SliderColumnFilter({ // Calculate the min and max // using the preFilteredRows const [value, setValue] = useState(0); - /*const [min, max] = React.useMemo(() => { - let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0 - let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0 - preFilteredRows.forEach(row => { - min = Math.min(row.values[id], min) - max = Math.max(row.values[id], max) - }) - return [min, max] - }, [id, preFilteredRows])*/ - + React.useEffect(() => { if (storeFilter) { const filterValue = TableUtil.getFilter(currentTableName, Header); @@ -649,19 +681,6 @@ function ColumnFilter({ } }, [filterValue, value]); - React.useEffect(() => { - // let localstorage_key = window.location.pathname.split('/')[1]; - // let storage = UtilService.localStore({ type: 'get', key: localstorage_key }); - // let storageStatus =_.filter(storage, function (filter) { - // if ( filter.name === Header && storeFilter) { - // setValue(filter.value); - // setFilter(filter.value); - // setFiltered(true); - // return true; - // } - // }) - }, []); - // Function to call the server side filtering const callServerFilter = (event, isCleared) => { hasFilters = true; @@ -687,29 +706,6 @@ function ColumnFilter({ setFiltered(true); callServerFilter(e); } - // if(storeFilter) { - // let localstorage_key = window.location.pathname.split('/')[1]; - // let storage = UtilService.localStore({ type: 'get', key: localstorage_key }); - // if(storage && storage.length > 0) { - // storage.forEach(function(value, index) { - // if(value.name === Header) { - // value.name = Header - // value.value = moment(e.value).format('YYYY-MM-DD') - // } - // }); - // const selected = _.filter(storage, function (filter) { - // if ( filter.name === Header) { - // return true; - // } - // }) - // if(selected.length <= 0) { - // storage.push({name: Header, value: moment(e.value).format('YYYY-MM-DD')}) - // } - // } else { - // storage = [{name: Header, value: value}] - // } - // UtilService.localStore({ type: 'set', key: localstorage_key, value: storage }); - // } }} showIcon></Calendar> {value && <i onClick={(e) => { setFilter(undefined); setValue(''); setFiltered(false); @@ -763,7 +759,7 @@ function DateRangeColumnFilter({ filterCallback(tableOptionsState, setLoaderFunction); } } -}; + }; return ( <div className="table-filter" onClick={e => { e.stopPropagation() }}> <Calendar selectionMode="range" value={filterValue} appendTo={document.body} @@ -882,25 +878,25 @@ function FlatpickrRangeColumnFilter({ } }} > - <input type="text" data-input className={`p-inputtext p-component calendar-input`} /> - <button class="p-button p-component p-button-icon-only calendar-button" data-toggle - title="Click to select the date range" > - <i class="fas fa-calendar"></i> - </button> - <button class="p-button p-component p-button-icon-only calendar-reset" - onClick={(value) => { - setFilter(undefined); setValue([]); setFiltered(false);filterValue = []; - if(storeFilter){ - TableUtil.saveFilter(currentTableName, Header, [] ); - } - if (doServersideFilter) { - setFilter(undefined); - setValue(''); - callServerFilter(value, true); - } - }} title="Clear date range" > - <i class="fa fa-times" style={{color:'white', marginTop:'-2.85px'}} ></i> - </button> + <input type="text" data-input className={`p-inputtext p-component calendar-input`} /> + <button class="p-button p-component p-button-icon-only calendar-button" data-toggle + title="Click to select the date range" > + <i class="fas fa-calendar"></i> + </button> + <button class="p-button p-component p-button-icon-only calendar-reset" + onClick={(value) => { + setFilter(undefined); setValue([]); setFiltered(false);filterValue = []; + if(storeFilter){ + TableUtil.saveFilter(currentTableName, Header, [] ); + } + if (doServersideFilter) { + setFilter(undefined); + setValue(''); + callServerFilter(value, true); + } + }} title="Clear date range" > + <i class="fa fa-times" style={{color:'white', marginTop:'-2.85px'}} ></i> + </button> </Flatpickr> </div> ) @@ -944,7 +940,6 @@ function CalendarColumnFilter({ } }; return ( - <div className="table-filter" onClick={e => { e.stopPropagation() }}> <Calendar value={filterValue} appendTo={document.body} dateFormat="yy-mm-dd" onChange={(e) => { @@ -1623,6 +1618,168 @@ function DurationRangeFilter({ ) } +// Duration Range Filter +function DurationRangeFilterWithDays({ + column: { filterValue = [], preFilteredRows, setFilter, id, Header }, +}) { + let [rangeValue, setRangeValue] = useState([0,0]); + const [value, setValue] = useState(''); + const [filtered, setFiltered] = useState(false); + const [filterType, setFilterType] = useState(); + + React.useEffect(() => { + if (!filterValue && value) { + setValue(''); + } + if (storeFilter) { + const filterValue = TableUtil.getFilter(currentTableName, Header); + const storedFilterType = TableUtil.getFilter(currentTableName, `${Header}-FilterOption`); + if (filterValue) { + setFiltered(true); + setFilterType('Range'); + } else { + setFilterType('All'); + } + + if (storedFilterType) { + //setFiltered(`${Header}-FilterOption`, true); + setFilterType(storedFilterType); + } + if(!value){ + setValue(filterValue); + } + } + }, [filterValue, value]); + + // Function to call the server side filtering + const callServerFilter = (event, isCleared) => { + hasFilters = true; + _.remove(tableOptionsState.filters, function(filter) { return filter.id === Header }); + if (isCleared) { + hasFilters = false; + if (filtered) { + filterCallback(tableOptionsState, setLoaderFunction); + } + } else { + tableOptionsState.filters.push({id: Header, value: rangeValue}); + filterCallback(tableOptionsState, setLoaderFunction); + } + }; + const filterTypeChangeEvent =(e) => { + setFilterType(e.value); + if(e.value === null || e.value === 'All') { + setFiltered(false); + _.remove(tableOptionsState.filters, function(filter) { return filter.id === 'durationNull' }); + _.remove(tableOptionsState.filters, function(filter) { return filter.id === Header }); + callServerFilter(e, true); + } else if( e.value === 'Range') { + setFiltered(true); + setRangeValue([0,0]); + setValue([0,0]); + _.remove(tableOptionsState.filters, function(filter) { return filter.id === 'durationNull' }); + } else if( e.value === 'Unknown') { + setFiltered(true); + _.remove(tableOptionsState.filters, function(filter) { return filter.id === Header }); + tableOptionsState.filters.push({id: 'durationNull', value: true}); + filterCallback(tableOptionsState, setLoaderFunction); + } + + if (storeFilter) { + if (e.value) { + TableUtil.saveFilter(currentTableName, `${Header}-FilterOption`, e.value); + tableOptionsState.filters.push({id: `${Header}-FilterOption`, value: e.value}); + setFiltered(true); + } else { + TableUtil.saveFilter(currentTableName, `${Header}-FilterOption`, null); + } + } + } + + return ( + <div + onKeyPress={(e) => { + if (e.key === "Enter" && doServersideFilter) { + TableUtil.saveFilter(currentTableName, Header, rangeValue); + setFiltered(true); + callServerFilter(e, false); + } + }} + style={{ + alignItems: 'center' + }} + > + <div onClick={e => { e.stopPropagation() }} > + <div > + <Dropdown optionLabel="name" optionValue="name" + tooltip="Select the Duration filter type to search" + value={filterType} + options={[{name:'All', value: 'All'},{name:'Range', value: 'Range'},{name:'Unknown', value: 'Unknown'}]} + onChange={(e) => {filterTypeChangeEvent(e)}} + style={{width: '10em'}} + showClear={true} + /> + { filterType === 'Range' && + <div style={{marginTop: '1em'}}> + <InputMask mask="999 99:99:99" + value={value[0]} + placeholder="DDD HH:mm:ss" + tooltip={(tableToolTipsState[Header])?tableToolTipsState[Header]:"Enter Minimum Range value in DDD HH:mm:ss format and press ‘Enter’ key to search"} + onChange={e => { + setFilter(undefined); setFiltered(false); + let val = e.target.value; + if (val.includes(":") && !Validator.isValidDDDHHmmss(val, false)) { + val = rangeValue[0]; + } + let max = rangeValue[1]; + setValue([val,max]); + setFilter([val,max] || undefined); + setRangeValue([val,max]); + filterValue[0] = val; + if(storeFilter) { + //TableUtil.saveFilter(currentTableName, Header, [val,max]); + setFilter([val,max]); + } + }} + style={{ + width: '115px', + height: '25px' + }} + /> + + <InputMask mask="999 99:99:99" + value={value[1]} + tooltip={(tableToolTipsState[Header])?tableToolTipsState[Header]:"Enter Maximum Range value in DDD HH:mm:ss format and press ‘Enter’ key to search"} + placeholder="DDD HH:mm:ss" + onChange={e => { + setFilter(undefined); setFiltered(false); + let val = e.target.value; + if (val.includes(":") && !Validator.isValidDDDHHmmss(val, false)) { + val = rangeValue[1]; + } + let min = rangeValue[0]; + setValue([min,val]); + setFilter([min,val] || undefined); + setRangeValue([min,val]); + filterValue[1] = val; + if(storeFilter) { + //TableUtil.saveFilter(currentTableName, Header, [min,val]); + setFilter([min,val]); + } + }} + style={{ + width: '115px', + height: '25px' + }} + /> + </div> + } + </div> + </div> + </div> + + ) +} + // This is a custom UI for our 'between' or number range // filter. It uses two number boxes and filters rows to // ones that have values between the two @@ -1762,8 +1919,13 @@ const filterTypes = { 'durationMinMax': { fn: DurationRangeFilter, type: durationTimeFilterFn + }, + 'durationWithDaysMinMax': { + fn: DurationRangeFilterWithDays, + type: 'between' } }; + // Let the table remove the filter if the string is empty fuzzyTextFilterFn.autoRemove = val => !val @@ -1853,7 +2015,7 @@ function Table(props) { }), [] ) - let tblinstance; + let tableParams = { columns, data, @@ -1914,10 +2076,12 @@ function Table(props) { tmpTableData = data; // while siwtch the task type or Su type, this will set the relavent default sort column if (currentTableName && currentTableName !== tablename) { - state.sortBy = defaultSortColumn + state.sortBy = defaultSortColumn; } currentTableName = props.tablename; - tableOptionsState = _.cloneDeep(state); + //if (!tableOptionsState) { + tableOptionsState = _.cloneDeep(state); + //} // Pass the table's state to the parent function if the parent function has set the callback function for it. if (props.setTableState) { props.setTableState(state); @@ -2053,6 +2217,11 @@ function Table(props) { parentCallbackFunction(filteredData); } + /** Assign current table instance to variable in parent class - just used for refresh the filter state*/ + if (tableInstanceRef) { + tableInstanceRef(tblinstance); + } + /* Select only rows than can be selected. This is required when ALL is selected */ selectedRows = _.filter(selectedFlatRows, selectedRow => { return (selectedRow.original.canSelect === undefined || selectedRow.original.canSelect) }); /* Take only the original values passed to the component */ @@ -2070,6 +2239,10 @@ function Table(props) { * Clear all filters in table and reload the data */ const clearAllFilter = () => { + // Call parent function during all clear + if (clearAllFuncCallback) { + clearAllFuncCallback(); + } hasFilters = false; setAllFilters([]); tableOptionsState.filters = []; @@ -2314,6 +2487,8 @@ function ViewTable(props) { let pageUpdated = props.pageUpdated === undefined ? true : props.pageUpdated; // Default Header to show in table and other columns header will not show until user action on UI + clearAllFuncCallback = props.clearAllFuncCallback; + tableInstanceRef = props.tableInstanceRef; let defaultheader = props.defaultcolumns; let optionalheader = props.optionalcolumns; let defaultSortColumn = props.defaultSortColumn; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js index 351f79852bb544acdd3f6ed3eb4de310fecf4e58..f6a519d8fdf5bb66f48b7cd050fd9331700a31f1 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js @@ -3,8 +3,11 @@ import _ from 'lodash'; import moment from 'moment'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; -import { MultiSelect } from 'primereact/multiselect'; -import { Calendar } from 'primereact/calendar'; +import { Dropdown } from 'primereact/dropdown'; + +import "flatpickr/dist/flatpickr.css"; +import Flatpickr from "react-flatpickr"; +import confirmDatePlugin from "flatpickr/dist/plugins/confirmDate/confirmDate"; import { CustomDialog } from '../../layout/components/CustomDialog'; import { appGrowl } from '../../layout/components/AppGrowl'; @@ -14,6 +17,8 @@ import PageHeader from '../../layout/components/PageHeader'; import UnitService from '../../utils/unit.converter'; import UIConstants from '../../utils/ui.constants'; +import Validator from '../../utils/validator'; + import ReservationService from '../../services/reservation.service'; import CycleService from '../../services/cycle.service'; import UtilService from '../../services/util.service'; @@ -27,32 +32,32 @@ export class ReservationList extends Component{ super(props); this.state = { validFields: {}, - fStartTime: null, // Filter Start time - fEndTime: null, // Filter End Time + fStartTime: '', // Filter Start time + fEndTime: '', // Filter End Time reservationsList: [], - filteredRowsList: [], - cycle: [], + cycle: '', errors: {}, dialog: {}, defaultcolumns: [{ - name:"System Id", - description:"Description", + name: {name:"System Id"}, + description: {name:"Description"}, start_time: { name: "Start Time", - filter: "fromdatetime", + filter: "flatpickrDateRange", format:UIConstants.CALENDAR_DATETIME_FORMAT }, stop_time: { name: "End Time", - filter: "todatetime", + filter: "flatpickrDateRange", format:UIConstants.CALENDAR_DATETIME_FORMAT }, duration:{ name:"Duration (Days HH:mm:ss)", - format:UIConstants.CALENDAR_TIME_FORMAT + format:UIConstants.CALENDAR_TIME_FORMAT, + filter: "durationWithDaysMinMax", }, - type: { - name:"Reservation type", + reservation_type: { + name:"Reservation Type", filter:"select" }, subject: { @@ -100,7 +105,7 @@ export class ReservationList extends Component{ optionalcolumns: [{ }], columnclassname: [{ - "Duration (Days HH:mm:ss)":"filter-input-75", + "Duration (Days HH:mm:ss)":"filter-input-150", "Reservation type":"filter-input-100", "Subject":"filter-input-75", "Planned":"filter-input-50", @@ -111,78 +116,92 @@ export class ReservationList extends Component{ "Expert":"filter-input-50", "HBA-RFI":"filter-input-50", "LBA-RFI":"filter-input-50", - + "Start Time":"filter-input-150", + "End Time":"filter-input-150", }], defaultSortColumn: [{id: "System Id", desc: false}], isLoading: true, cycleList: [], userrole: AuthStore.getState() } - - this.formRules = { - // fStartTime: {required: true, message: "Start Date can not be empty"}, - // fEndTime: {required: true, message: "Stop Date can not be empty"} - }; + this.reservationTypeOptionList = []; + this.subjectsOptionList = []; + this.stationList = []; + // let optionsList = new Set(); + this.filterQry = ''; + this.orderBy = ''; + this.limit = 10; + this.offset = 0; + this.currentPageSize = 10; + this.tableInstance = null; this.reservations= []; this.cycleList= []; this.selectedRows = []; - this.pageUpdated = false; + this.totalPage = 0; + this.pageUpdated = true; this.onRowSelection = this.onRowSelection.bind(this); this.confirmDeleteReservations = this.confirmDeleteReservations.bind(this); this.deleteReservations = this.deleteReservations.bind(this); this.closeDialog = this.closeDialog.bind(this); this.getReservationDialogContent = this.getReservationDialogContent.bind(this); this.closeList = this.closeList.bind(this); + this.fetchTableData = this.fetchTableData.bind(this); + this.getFilterOptions = this.getFilterOptions.bind(this); + this.resetStartDateTime = this.resetStartDateTime.bind(this); + this.resetEndDateTime = this.resetEndDateTime.bind(this); + this.clearAllFilter = this.clearAllFilter.bind(this); + this.seTableInstanceRef = this.seTableInstanceRef.bind(this); + this.setDateRange = this.setDateRange.bind(this); } async componentDidMount() { this. setToggleBySorting(); - const promises = [ ReservationService.getReservations(), - CycleService.getAllCycles(), - ]; - const permission = await AuthUtil.getUserRolePermission(); - this.setState({userrole: permission}); - - this.reservations = []; - await Promise.all(promises).then(responses => { - let reservation = {}; - this.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'] = ''; - } - if(reservation.duration === null || reservation.duration === ''){ - reservation.duration = 'Unknown'; - reservation['stop_time']= 'Unknown'; - } else { - let duration = reservation.duration; - reservation.duration = UnitService.getSecsToDDHHmmss(reservation.duration); - reservation['stop_time']= moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); - } - reservation['start_time']= moment(reservation['start_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); - reservation['actionpath'] = `/reservation/view/${reservation.id}`; - reservation['canSelect'] = true; - this.reservations.push(reservation); - }; - this.cycleList.map(cycle => { - cycle['url'] = cycle.name; - }); - this.pageUpdated = true; - this.setState({ - isLoading: false, - reservationsList: this.reservations, - filteredRowsList: this.reservations, - cycleList: this.cycleList - }); + await this.getFilterColumns(); + this.cycleList = await CycleService.getAllCycles(); + this.setLocalFilters(); + await this.setState({ + isLoading: false, + cycleList: this.cycleList, + reservationsList: this.reservations, }); } + /** + * Set Top filters when the state changed or while init the page + */ + setLocalFilters() { + let filters = UtilService.localStore({ type: 'get', key: "reservation_list"}); + if (filters) { + let filter = _.find(filters, {'id': 'CycleId'}); + if (filter) { + this.setState({cycle: filter.value}); + } + filter = _.find(filters, {'id': 'Start Time'}); + if (filter) { + const values = filter.value; + this.setState({fStartTime: values[0]}); + } else { + this.setState({fStartTime: '', cycle: ''}); + } + filter = _.find(filters, {'id': 'End Time'}); + if (filter) { + const values = filter.value; + this.setState({fEndTime: values.length === 2 ? values[1] : values[0]}); + } else { + this.setState({fEndTime: '', cycle: ''}); + } + } + let filterStartTime = _.find(filters, function(filter){ + return (filter.id === 'Start Time' && filter.value.length>0); + }); + let filterEndTime = _.find(filters, function(filter){ + return (filter.id === 'End Time' && filter.value.length>0) + }); + if (!filterStartTime && !filterEndTime) { + this.setState({cycle: ''}); + } + } + toggleBySorting=(sortData) => { UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); } @@ -212,154 +231,116 @@ export class ReservationList extends Component{ /** * Filter reservation based on cycle filter selected - * table data = Cycle.start_time < Reservation Start time and End time/Unknown > Cycle.end time + * Here to search use Cycle.start time as Reservation Start time and Cycle.stop time as Reservation End time */ async filterTableData(cycleValues) { - let reservationList= []; - if (cycleValues.length === 0) { - await this.setState({ - cycle: cycleValues, - filteredRowsList: this.state.reservationsList, - }) + let filters = UtilService.localStore({ type: 'get', key: "reservation_list"}); + _.remove(filters, function(filter) { + return filter.id === 'Cycle' || filter.id === 'CycleId' || filter.id === 'Start Time' || filter.id === 'End Time'; + }); + if (!cycleValues) { + UtilService.localStore({ type: 'set', key: 'reservation_list', value: filters}); + await this.setState({cycle: '',fStartTime:'', fEndTime: ''}); + this.setTableProperty(filters, 'Filter'); + await this.fetchTableData(null); } else { - cycleValues.forEach( cycleValue => { - const filterCycleList = _.filter(this.cycleList, function(o) { return o.name === cycleValue }); - if (filterCycleList) { - let cycle = filterCycleList[0]; - let cycle_Start_time = moment.utc(moment(cycle['start']).format("YYYY-MM-DD")); - let cycle_End_time = moment.utc(moment(cycle['stop']).format("YYYY-MM-DD")); - this.state.reservationsList.forEach( reservation => { - let res_Start_time = moment.utc(moment(reservation['start_time']).format("YYYY-MM-DD")); - let res_End_time = moment.utc(moment(reservation['stop_time']).format("YYYY-MM-DD")); - if (cycle_Start_time.isSameOrBefore(res_Start_time) && cycle_End_time.isSameOrAfter(res_Start_time)) { - if ( reservation['stop_time'] === 'Unknown'|| cycle_End_time.isSameOrAfter(res_End_time)) { - const tmpList = _.filter(reservationList, function(o) { return o.id === reservation.id }); - if( tmpList.length === 0) { - reservationList.push(reservation); - } - } - } - }); - } - }); - this.pageUpdated = true; - await this.setState({ - cycle: cycleValues, - filteredRowsList: reservationList - }); - + const filterCycleList = _.filter(this.cycleList, function(o) { return o.name === cycleValues }); + if (filterCycleList) { + let cycle = filterCycleList[0]; + this.setState({fStartTime: cycle['start'], fEndTime: cycle['stop']}); + let cycleFilter = [{'id': 'Cycle', value:[cycle['start'], cycle['stop']]}]; + cycleFilter.push({'id': 'CycleId', value:cycle['name']}); + cycleFilter.push({'id': 'Start Time', value:[cycle['start'], '']}); + cycleFilter.push({'id': 'End Time', value:['', cycle['stop']]}); + filters = [...cycleFilter, ...filters]; + UtilService.localStore({ type: 'set', key: 'reservation_list', value: filters}); + this.setTableProperty(filters, 'Filter'); + await this.fetchTableData(null); + } + await this.setState({cycle: cycleValues,}); } + this.pageUpdated = true; } /** - * Set Filter: Start/End date and time. It will display the reservation which is active during the time frame - * @param {*} type - Date Filter Name - * @param {*} value - Date Value + Reset the Reserved Start Datetime filter */ - async setDateRange(type, value) { - let fStartTime, fEndTime = 0; - let reservationList= []; - if(value !== 'undefine' && type === 'fStartTime'){ - await this.setState({'fStartTime': value, validForm: this.validateForm(type)}); - } - else if(value !== 'undefine' && type === 'fEndTime'){ - await this.setState({'fEndTime': value, validForm: this.validateForm(type)}); - } - if(this.state.fStartTime !== null && this.state.fEndTime !== null) { - fStartTime = moment.utc(moment(this.state.fStartTime)).valueOf(); - fEndTime = moment.utc(moment(this.state.fEndTime)).valueOf(); - await this.state.reservationsList.forEach( reservation => { - let res_Start_time = moment.utc(moment(reservation['start_time'])).valueOf(); - let res_End_time = 'Unknown'; - if(reservation['stop_time'] === 'Unknown') { - if(res_Start_time <= fEndTime){ - const tmpList = _.filter(reservationList, function(o) { return o.id === reservation.id }); - if( tmpList.length === 0) { - reservationList.push(reservation); - } - } - } - else { - res_End_time = moment.utc(moment(reservation['stop_time'])).valueOf(); - if(res_Start_time <= fStartTime && res_End_time >= fStartTime) { - const tmpList = _.filter(reservationList, function(o) { return o.id === reservation.id }); - if( tmpList.length === 0) { - reservationList.push(reservation); - } - } - else if(res_Start_time >= fStartTime && res_Start_time <=fEndTime) { - const tmpList = _.filter(reservationList, function(o) { return o.id === reservation.id }); - if( tmpList.length === 0) { - reservationList.push(reservation); - } - } - } - }); - this.pageUpdated = true; - await this.setState({filteredRowsList: reservationList}); - } - else { - this.pageUpdated = false; - await this.setState({filteredRowsList: this.state.reservationsList}); - } - + async resetStartDateTime() { + await this.setState({'fStartTime': ''}); + let filters = UtilService.localStore({ type: 'get', key: "reservation_list"}); + _.remove(filters, function(filter) { + let result = filter.id === 'Start Time'; + return result; + }); + UtilService.localStore({ type: 'set', key: 'reservation_list', value: filters}); + this.setTableProperty(filters, 'Filter'); + await this.fetchTableData(null); } /** - * Validate Filter : start/End time - * @param {*} fieldName + Reset the Reserved End Datetime filter */ - async validateForm(fieldName) { - let validForm = false; - let errors = this.state.errors; - let validFields = this.state.validFields; - if (fieldName) { - delete errors[fieldName]; - delete validFields[fieldName]; - if (this.formRules[fieldName]) { - const rule = this.formRules[fieldName]; - const fieldValue = this.state[fieldName]; - if (rule.required) { - if (!fieldValue) { - errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; - } else { - validFields[fieldName] = true; - } - } - } - } else { - errors = {}; - validFields = {}; - for (const fieldName in this.formRules) { - const rule = this.formRules[fieldName]; - const fieldValue = this.state[fieldName]; - if (rule.required) { - if (!fieldValue) { - errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; - } else { - validFields[fieldName] = true; - } - } - } - } - - await this.setState({errors: errors, validFields: validFields}); - if (Object.keys(validFields).length === Object.keys(this.formRules).length) { - validForm = true; - } + async resetEndDateTime() { + await this.setState({'fEndTime': ''}); + let filters = UtilService.localStore({ type: 'get', key: "reservation_list"}); + _.remove(filters, function(filter) { + let result = filter.id === 'End Time'; + return result; + }); + UtilService.localStore({ type: 'set', key: 'reservation_list', value: filters}); + this.setTableProperty(filters, 'Filter'); + await this.fetchTableData(null); + } + + /** + - To clear all filters above the table, it will be called from ViewTable-ClearAllFilter + */ + clearAllFilter() { + this.setState({cycle: '', fStartTime: '', fEndTime: ''}); + } - if(this.state['fStartTime'] && this.state['fEndTime']){ - var isSameOrAfter = moment(this.state['fEndTime']).isSameOrAfter(this.state['fStartTime']); - if(!isSameOrAfter){ - errors['fEndTime'] = `Reserved Between-To can not be before Reserved Between - From`; - validForm = false; - }else{ - validForm = true; + /** + * Get View Table instance + * @param {*} instanceRef + */ + seTableInstanceRef(instanceRef) { + this.tableInstance = instanceRef; + } + + /** + * Set View table Properties from parent component + * @param {*} value - Value to set + * @param {*} propsType - Property type like to set the Filter, Columns Header + */ + setTableProperty(value, propsType) { + if (this.tableInstance) { + if (propsType === 'Filter') { + this.tableInstance.state.filters = value; } } - return validForm; } - + + /** + * Set Filter: Start/End date and time. It will display the reservation which is active during the time frame + * @param {*} type - Date Filter Name + * @param {*} value - Date Value + */ + async setDateRange() { + let filters = UtilService.localStore({ type: 'get', key: "reservation_list"}); + _.remove(filters, function(filter) { + let result = filter.id === 'Start Time' || filter.id === 'End Time'; + return result; + }); + await this.setState({cycle: ''}); + let reservedTimeFilter = [{'id': 'Start Time', value:[Validator.isEmpty(this.state.fStartTime)?'':moment(new Date(this.state.fStartTime)).format("YYYY-MM-DDTHH:mm:SS"), '']}]; + reservedTimeFilter.push({'id': 'End Time', value:['', Validator.isEmpty(this.state.fEndTime)?'':moment(new Date(this.state.fEndTime)).format("YYYY-MM-DDTHH:mm:SS")]}); + filters = [...reservedTimeFilter, ...filters]; + this.setTableProperty(filters, 'Filter'); + UtilService.localStore({ type: 'set', key: 'reservation_list', value: filters}); + await this.fetchTableData(null); + this.pageUpdated = true; + } + /** * Set selected rows form view table * @param {Row} selectedRows - rows selected in view table @@ -436,6 +417,270 @@ export class ReservationList extends Component{ } } + /** + * Prepare API FIlter column to view table component + * @param {*} apiFilters + * @param {*} columnDef + * @returns + */ + getAPIFilter(apiFilters, columnDef) { + const defaultColKeys = Object.keys(columnDef); + defaultColKeys.forEach(key => { + let tmpColMap = {}; + let tempKey = key; + tmpColMap['orgField'] = tempKey; + tmpColMap['tmpField'] = tempKey; + if(columnDef[key]) { + tmpColMap['displayName'] = columnDef[key]['name']; + } + this.columnMap.push(tmpColMap); + //Set Enable/Disable the Filter & SortBy in each column + if(apiFilters.data.filters[tempKey]) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, tempKey); + columnDef[key]['disableFilters'] = false; + if( (tempKey !== 'start_time' && tempKey !== 'stop_time') && UIConstants.FILTER_MAP[apiFilters.data.filters[tempKey].type]) { + columnDef[key]['filter'] = UIConstants.FILTER_MAP[apiFilters.data.filters[tempKey].type]; + } + } else if (key === 'project_id' && apiFilters.data.filters['project']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'project'); + columnDef[key]['disableFilters'] = false; + columnDef[key]['filter'] = ''; + } else if (key === 'stations' && apiFilters.data.filters['stations_any']) { + columnDef[key]['disableSortBy'] = false; + columnDef[key]['disableFilters'] = false; + } else if (key === 'duration' && apiFilters.data.filters['duration_min']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'duration_min'); + columnDef[key]['disableFilters'] = false; + } else if (columnDef[key]['name']) { + columnDef[key]['disableSortBy'] = true; + columnDef[key]['disableFilters'] = true; + } + }); + return columnDef; + } + + /** + * Remove column in custom column order + * @param {array} arrayValue + * @param {array} keys + */ + removeArrayIndex(arrayValue, keys) { + keys.forEach(key => { + let index = arrayValue.indexOf(key) ; + if (index !== -1) { + arrayValue.splice(index, 1); + } + }); + } + + /** + * Remove column definition + * @param {column definition array} arrayValue + * @param {array} keys + */ + removeColumns(arrayValue, keys) { + keys.forEach(key => { + delete arrayValue[key]; + }); + } + /** + * Get server side filter column details form API + */ + async getFilterColumns() { + const apiFilters = await ReservationService.getReservationFilterDefinition(); + this.columnMap = []; + let tmpDefaulColumns = _.cloneDeep(this.state.defaultcolumns[0]); + let tmpOptionalColumns = _.cloneDeep(this.state.optionalcolumns[0]); + let tmpColumnOrders = _.cloneDeep(this.state.columnOrders); + + if(apiFilters) { + this.getDropDownOptionList(apiFilters); + tmpDefaulColumns = this.getAPIFilter(apiFilters, tmpDefaulColumns); + tmpOptionalColumns = this.getAPIFilter(apiFilters, tmpOptionalColumns); + await this.setState({tmpDefaulcolumns: [tmpDefaulColumns], tmpOptionalcolumns:[tmpOptionalColumns], tmpColumnOrders: tmpColumnOrders, columnMap: this.columnMap}) + } + } + + /** + * Get Status list frol filter + * @param {Array} suFilters + */ + getDropDownOptionList(apiFilters) { + this.getOptions('reservation_type', apiFilters); + this.getOptions('subject', apiFilters); + this.stationList = []; + this.getOptions('stations_any', apiFilters); + } + + getOptions(key, apiFilter) { + if (apiFilter.data.filters[key]) { + apiFilter.data.filters[key].choices.forEach(choice => { + if (key === 'subject') { + this.subjectsOptionList.push(choice.value); + } else if(key === 'reservation_type') { + this.reservationTypeOptionList.push(choice.value); + } else if (key === 'stations_any') { + this.stationList.push({name: choice.display_name, value:choice.value}); + } + }) + } + } + + /** + * Fetch data from server side - while doing pagination, filtering, sorting + * @param {Table State} Table props state + * @returns + */ + async fetchTableData(state) { + // await this.getFilterColumns(); + await this.setLocalFilters(); + this.filterQry = ''; + this.orderBy = ''; + this.pageUpdated = true; + this.setState({loadingStatus:true}); + let filters = UtilService.localStore({ type: 'get', key: "reservation_list"}); + const sortByValue = UtilService.localStore({ type: 'get', key: "ReservationListSortData"}); + if(filters.length > 0 ) { + for( const filter of filters) { + if (filter.id === 'Start Time') { + const values = filter.value; + if (values[0] && values[0] !== '') { + this.filterQry += 'start_time_min='+ moment(new Date(values[0])).format("YYYY-MM-DD HH:mm:SS&"); + } + if (values[1] && values[1] !== '') { + this.filterQry += 'start_time_max='+moment(new Date(values[1])).format("YYYY-MM-DD HH:mm:ss&"); + } + } else if (filter.id === 'End Time') { + const values = filter.value; + if (values[0] && values[0] !== '') { + this.filterQry += 'stop_time_min='+ moment(new Date(values[0])).format("YYYY-MM-DD 00:00:00&"); + } + if (values[1] && values[1] !== '') { + this.filterQry += 'stop_time_max='+moment(new Date(values[1])).format("YYYY-MM-DD 23:59:59&"); + } + } else if (filter.id === 'Project') { + this.filterQry += 'project='+ filter.value+'&' ; + } else if (filter.id === 'Stations') { + const stationFilterType = _.find(filters, {id:'stationFilterType'}); + if(filter.value.length>0) { + const values = _.split(filter.value, ","); + for ( const value of values) { + if(stationFilterType && stationFilterType.value === 'All') { + this.filterQry += 'stations_all='+value+"&"; + } else { + this.filterQry += 'stations_any='+value+"&"; + } + } + } + } else if (filter.id === 'Duration (Days HH:mm:ss)' && filter.value != '') { + let columnDetails = _.find(this.state.columnMap, {displayName:filter.id}); + const values = _.split(filter.value, ","); + if ((values.length === 0) || (values[0] === '' || values[0] === '0') && (values[1] === '' || values[1] === '0')) { + this.filterQry += "duration_isnull=false&"; + } else { + if (values[0].includes(":")) { + this.filterQry += columnDetails.orgField+"_min" +'='+UnitService.getDDDHHmmssToSecs(values[0])+'&'; + } + if (values[1].includes(":")) { + this.filterQry += columnDetails.orgField+"_max" +'='+UnitService.getDDDHHmmssToSecs(values[1])+'&'; + } + } + } else if (filter.id === 'durationNull') { + this.filterQry += "duration_isnull="+filter.value+'&'; + } else { + let columnDetails = _.find(this.state.columnMap, {displayName:filter.id}); + if(columnDetails) { + this.filterQry += columnDetails.orgField +'='+filter.value+'&' + } + } + } + } + + let sortBy = state && state.sortBy?state.sortBy[0]:(sortByValue)?sortByValue:null; + if (sortBy) { + this.defaultSortColumn = sortBy; + //this.setState({defaultSortColumn: [sortBy]}); + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn ,value:sortBy}); + let columnDetails = _.find(this.state.columnMap, {displayName:sortBy.id}); + if(columnDetails) { + this.orderBy = 'ordering='+((sortBy.desc)?'-':'')+columnDetails.orgField; + } + } + this.filterQry = this.filterQry.substring(0,this.filterQry.length-1); + + this.currentPageSize = (state && state.pageSize) ? state.pageSize : this.currentPageSize; + let offset = (state && state.pageIndex) ? state.pageIndex*this.currentPageSize : 0; + await this.getReservationList(this.filterQry, this.orderBy, this.currentPageSize, offset); + return [this.state.reservationsList, this.totalPage]; + } + + /** + * Get reservation list + */ + async getReservationList(filterQry, orderBy, limit, offset) { + const promises = [ + ReservationService.getReservationsWithFilter(filterQry, orderBy, limit, offset), + ]; + const permission = await AuthUtil.getUserRolePermission(); + this.setState({userrole: permission}); + + this.reservations = []; + await Promise.all(promises).then(responses => { + let reservation = {}; + this.totalPage = responses[0].data?responses[0].data.count:0; + for( const response of responses[0].data.results){ + 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 ); + reservation['reservation_type'] = reservation.type; + if (response.specifications_doc.resources.stations ) { + reservation['stations'] = response.specifications_doc.resources.stations.join(', '); + } else { + reservation['stations'] = ''; + } + if(reservation.duration === null || reservation.duration === ''){ + reservation.duration = 'Unknown'; + reservation['stop_time']= 'Unknown'; + } else { + reservation.duration = UnitService.getSecsToDDHHmmss(reservation.duration); + reservation['stop_time']= moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); + } + reservation['start_time']= moment(reservation['start_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); + reservation['actionpath'] = `/reservation/view/${reservation.id}`; + reservation['canSelect'] = true; + this.reservations.push(reservation); + }; + this.cycleList.map(cycle => { + cycle['url'] = cycle.name; + }); + this.pageUpdated = true; + this.setState({ + isLoading: false, + reservationsList: this.reservations, + cycleList: this.cycleList + }); + }); + } + + /** + * Get Option-list values for Select Dropdown filter in 'Viewtable' + * @param {String} id : Column id + * @returns + */ + getFilterOptions(id) { + let options = null; + if(id && id === 'Reservation Type') { + options = this.reservationTypeOptionList; + } else if (id && id === 'Subject') { + options = this.subjectsOptionList; + } else if (id === 'Stations') { + this.stationList = _.orderBy(this.stationList, ['name'], ['asc']); + return this.stationList; + } + return options; + } + render() { const permissions = this.state.userrole.userRolePermission.reservation; return ( @@ -446,65 +691,92 @@ export class ReservationList extends Component{ disabled: permissions.create? !permissions.create: true, props : { pathname: `/reservation/create`}}, {icon: 'fa-window-close', title:'Click to close Reservation list', type: 'button', actOn: 'click', props:{ callback: this.closeList }}]}/> - {this.state.isLoading? <AppLoader /> : (this.state.reservationsList && this.state.reservationsList.length>0) ? + {this.state.isLoading? <AppLoader /> : (this.state.reservationsList) ? <> {permissions.list? <> - <div className="p-select " style={{position: 'relative'}}> + <div className="p-select " style={{position: 'relative', marginTop: '-2em'}}> <div className="p-field p-grid"> <div className="col-lg-3 col-md-3 col-sm-12 ms-height"> <span className="p-float-label"> - <MultiSelect data-testid="cycle" id="cycle" optionLabel="name" optionValue="url" filter={true} - tooltip="Select Cycle" tooltipOptions={this.tooltipOptions} - value={this.state.cycle} - options={this.state.cycleList} - onChange={(e) => {this.filterTableData(e.value)}} - className="ms-width" - // placeholder= 'Select Cycle' + <Dropdown data-testid="cycle" id="cycle" optionLabel="name" optionValue="url" + value={this.state.cycle} + options={this.state.cycleList} + onChange={(e) => {this.filterTableData(e.value)}} + className="ms-width" + filter={true} + showClear={true} + showFilterClear={true} + tooltip="Select cycle to view reservations that start and end in the cycle (will not include those are reserved for unknown duration)" /> <label htmlFor="cycle" >Filter by Cycle</label> </span> </div> <div className="col-lg-3 col-md-3 col-sm-6 ms-height" style={{ marginLeft: '1em'}}> <span className="p-float-label"> - <Calendar + <Flatpickr data-enable-time data-input id="fstartdate" - d dateFormat={UIConstants.CALENDAR_DATE_FORMAT} - value= {this.state.fStartTime} - // placeholder="Select Start Date Time" - onChange= {e => this.setDateRange('fStartTime', e.value)} - tooltip="Select Reserved Between - From" tooltipOptions={this.tooltipOptions} - showIcon={true} - showTime={true} - showSeconds={true} - /> - <label htmlFor="fstartdate" style={{width: '13em'}}>Reserved Between - From</label> + style={{width: '12em'}} + options={{ "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "defaultHour": 0, + "plugins": [new confirmDatePlugin()] + }} + title="Select Reserved Between - From" + value={this.state.fStartTime} + onClose={this.setDateRange} + onChange={date => {this.setState({fStartTime :date})}} + > + <input type="text" data-input className={`p-inputtext p-component calendar-input`} placeholder="Reserved - From" /> + <button class="p-button p-component p-button-icon-only calendar-button" data-toggle + title="Click to select the date range" > + <i class="fas fa-calendar"></i> + </button> + <button class="p-button p-component p-button-icon-only calendar-reset" + onClick={this.resetStartDateTime} + title="Clear date range" > + <i class="fa fa-times" style={{color:'white', marginTop:'-2.85px'}} ></i> + </button> + </Flatpickr> </span> - {this.state.fStartTime && <i className="pi pi-times pi-primary" style={{position: 'relative', left:'7.5em', bottom:'25px', cursor:'pointer'}} - onClick={() => {this.setDateRange('fStartTime', null)}}></i> - } <label className={this.state.errors.fStartTime?"error":"info"} style={{position: 'relative', bottom: '27px'}}> {this.state.errors.fStartTime ? this.state.errors.fStartTime : ""} </label> </div> - <div className="col-lg-3 col-md-3 col-sm-6 ms-height" style={{ marginLeft: '4em'}}> + <div className="col-lg-3 col-md-3 col-sm-6 ms-height" style={{ marginLeft: '2em'}}> <span className="p-float-label"> - <Calendar - id="fenddate" - d dateFormat={UIConstants.CALENDAR_DATE_FORMAT} - value= {this.state.fEndTime} - // placeholder="Select End Date Time" - onChange= {e => this.setDateRange('fEndTime', e.value)} - tooltip="Select Reserved Between-To" tooltipOptions={this.tooltipOptions} - showIcon={true} - showTime={true} - showSeconds={true} - /> - <label htmlFor="fenddate" style={{width: '13em'}}>Reserved Between-To</label> + <Flatpickr data-enable-time data-input + id="fenddate" + style={{width: '12em'}} + options={{ "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "defaultHour": 0, + "plugins": [new confirmDatePlugin()] + }} + title="Select Reserved Between-To" + value={this.state.fEndTime} + onClose={this.setDateRange} + onChange={date => {this.setState({fEndTime :date})}} + > + <input type="text" data-input className={`p-inputtext p-component calendar-input`} placeholder="Reserved - To" /> + <button class="p-button p-component p-button-icon-only calendar-button" data-toggle + title="Click to select the date range" > + <i class="fas fa-calendar"></i> + </button> + <button class="p-button p-component p-button-icon-only calendar-reset" + onClick={this.resetEndDateTime} title="Clear date range" > + <i class="fa fa-times" style={{color:'white', marginTop:'-2.85px'}} ></i> + </button> + </Flatpickr> </span> - {this.state.fEndTime && <i className="pi pi-times pi-primary" style={{position: 'relative', left:'7.5em', bottom:'25px', cursor:'pointer'}} - onClick={() => {this.setDateRange('fEndTime', null)}}></i> - } <label className={this.state.errors.fEndTime?"error":"info"} style={{position: 'relative', bottom: '27px'}} > {this.state.errors.fEndTime ? this.state.errors.fEndTime : ""} </label> @@ -523,9 +795,9 @@ export class ReservationList extends Component{ </div> </div> <ViewTable - data={this.state.filteredRowsList} - defaultcolumns={this.state.defaultcolumns} - optionalcolumns={this.state.optionalcolumns} + data={this.state.reservationsList} + defaultcolumns={this.state.tmpDefaulcolumns ? this.state.tmpDefaulcolumns : this.state.defaultcolumns} + optionalcolumns={this.state.tmpOptionalcolumns ? this.state.tmpOptionalcolumns : this.state.optionalcolumns} columnclassname={this.state.columnclassname} defaultSortColumn={this.defaultSortColumn} showaction="true" @@ -538,6 +810,11 @@ export class ReservationList extends Component{ lsKeySortColumn={this.lsKeySortColumn} pageUpdated={this.pageUpdated} storeFilter={true} + callBackFunction={this.fetchTableData} + showFilterOption={this.getFilterOptions} + totalPage={this.totalPage} + clearAllFuncCallback={this.clearAllFilter} // Callback function will call when the clearAllFilter called in ViewTable + tableInstanceRef={this.seTableInstanceRef} /> </>: <AccessDenied/>} </> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js index b150f448db2b7d3b771f96bc911a6251914bac94..a0115217fc6d24771ded184046df2e087f4606b1 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js @@ -109,6 +109,29 @@ const ReservationService = { console.error(error); } }, + getReservationFilterDefinition: async function (){ + let res = []; + try { + res = await axios.options(`/api/reservation/`); + } catch(error) { + console.error('[reservation.services.getReservationFilterDefinition]',error); + } + return res; + }, + getReservationsWithFilter: async function (filters, orderBy, limit, offset){ + let response = null; + try { + let api = `/api/reservation/?`; + api += (filters === '')? '' : filters+'&'; + api += (orderBy === '')? '' : orderBy+'&'; + api += 'limit='+limit+'&offset=' + api += (offset === '') ? 0 : offset; + response = await axios.get(api); + } catch(error) { + console.error('[reservation.services.getReservationsWithFilter]',error); + } + return response; + }, } export default ReservationService; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js index a5f59b0b9d6fec5aaeb89b36e1addaccb6b27205..373a25edbd514f8e00125cfc47ee57119d342b06 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js @@ -72,6 +72,15 @@ const UnitConverter = { } return 0; }, + getDDDHHmmssToSecs: function (duration) { + if (duration) { + duration = duration.replaceAll("_", "0"); + var values = duration.split(' '); + var days = values[0]; + return days*86400+UnitConverter.getHHmmssToSecs(values[1]); + } + return 0; + }, radiansToDegree: function (object) { for (let type in object) { if (type === 'transit_offset') { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js index 4198a7ea71537b9719213bb0e75a5111c1043ea5..4453a7ceca38bf1dfa7d24a9ef25df4d499f1e1d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js @@ -60,7 +60,7 @@ const Validator = { var timeFormat = /^([0-9]|[0-9][0-9]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$/; isValid = timeFormat.test(time); } else { - var timeFormat = /^([0-9]|[0-2][0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$/; + var timeFormat = /^([0-9]|[0-1][0-9]|[0-2][0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$/; isValid = timeFormat.test(time); } @@ -68,6 +68,34 @@ const Validator = { isValid = false; } return isValid; + }, + /** + * Validate Day time + * @param {string value with DDD HH:mm:ss format} Days time + * @returns + */ + isValidDDDHHmmss(duration, excludeHour) { + let isValid = true; + var values = duration.split(' '); + isValid = Validator.isValidHHmmss(values[1], excludeHour) + return isValid; + }, + + /** + * Check object is empty or not + * @param {String object} + * @returns True/False + */ + isEmpty(value) { + if (value === undefined) { + return true; + } else if (!value) { + return true; + } else if (value.length === 0) { + return true; + } else { + return false; + } } };