diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index 8ea8c891bfb85fc1baa0ce409be0069be339257c..c48b3841ad9652b641607fc7da5a21f6090ed984 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -5,7 +5,7 @@ import _, { filter } from 'lodash'; import moment from 'moment'; import { useHistory } from "react-router-dom"; import { OverlayPanel } from 'primereact/overlaypanel'; -//import {InputSwitch} from 'primereact/inputswitch'; +import { InputMask } from 'primereact/inputmask'; import { InputText } from 'primereact/inputtext'; import { Calendar } from 'primereact/calendar'; import { Paginator } from 'primereact/paginator'; @@ -18,7 +18,12 @@ import { MultiSelect } from 'primereact/multiselect'; import { RadioButton } from 'primereact/radiobutton'; import { useExportData } from "react-table-plugins"; import { ProgressBar } from 'primereact/progressbar'; -//import UIConstants from '../utils/ui.constants'; + +import "flatpickr/dist/flatpickr.css"; +import Flatpickr from "react-flatpickr"; +import confirmDatePlugin from "flatpickr/dist/plugins/confirmDate/confirmDate"; +import shortcutButtonsPlugin from "shortcut-buttons-flatpickr"; + import UtilService from '../../src/services/util.service' import Papa from "papaparse"; import JsPDF from "jspdf"; @@ -49,7 +54,7 @@ let tmpTableData = null; let currentTableName = null; let storeFilter = false; let storage_array = []; - +//let confirmDatePlugin = new confirmDatePlugin(); // Define a default UI for filtering function GlobalFilter({ preGlobalFilteredRows, @@ -250,9 +255,9 @@ function SelectColumnFilter({ _.remove(tableOptionsState.filters, function(filter) { return filter.id === Header }); if (isCleared) { hasFilters = false; - if (filtered) { + //if (filtered) { filterCallback(tableOptionsState, setLoaderFunction); - } + //} } else { tableOptionsState.filters.push({id: Header, value: event.target.value}); filterCallback(tableOptionsState, setLoaderFunction); @@ -731,6 +736,99 @@ function DateRangeColumnFilter({ </div> ) } + +// This is a custom filter UI that uses a +// flatpickr range calendar to set the value +function FlatpickrRangeColumnFilter({ + column: { setFilter, filterValue, Header }, +}) { + const [value, setValue] = useState(''); + const [filtered, setFiltered] = useState(false); + React.useEffect(() => { + if (!filterValue && value) { + setValue(null); + } + if (storeFilter) { + const filter = TableUtil.getFilter(currentTableName, Header); + const filterValue = _.map(filter, date => {return new Date(date)} ) + if (filterValue[1] && !value ){ + setValue(filterValue); + setFilter(filterValue); + } + } + }, [filterValue, value]); + // Function to call the server side filtering + const callServerFilter = (value, isCleared) => { + hasFilters = true; + if (isCleared) { + hasFilters = false; + if (filtered) { + _.remove(tableOptionsState.filters, function(filter) { return filter.id === Header }); + filterCallback(tableOptionsState, setLoaderFunction); + } + } else { + let filterColumn = _.find(tableOptionsState.filters, {id: Header }); + if(filterColumn) { + filterColumn.value = value; + filterCallback(tableOptionsState, setLoaderFunction); + } else { + filterCallback(tableOptionsState, setLoaderFunction); + } + } + }; + return ( + <div className="table-filter" onClick={e => { e.stopPropagation() }}> + <Flatpickr data-enable-time data-input + options={{ "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "mode": "range", + "defaultHour": 0, + "plugins": [new confirmDatePlugin()] + }} + title="" + value={filterValue} + onClose={value => { + if(value.length === 2) { + setValue(value); + setFilter(value); + if(storeFilter) { + TableUtil.saveFilter(currentTableName, Header, value); + } + if ((value !== '' && value !== 'Invalid date' ) && doServersideFilter) { + setFiltered(true); + callServerFilter(value); + } + } + }} + > + <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); + 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:'-1.85px'}} ></i> + </button> + </Flatpickr> + </div> + ) +} + // This is a custom filter UI that uses a // calendar to set the value function CalendarColumnFilter({ @@ -1167,33 +1265,154 @@ function NumberRangeFilter({ ) } -// 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 -// This is for server side filtering - -/*function NumberRangeFilter({ +// Priority Rank Range Filter +function RankRangeFilter({ column: { filterValue = [], preFilteredRows, setFilter, id, Header }, }) { let [rangeValue, setRangeValue] = useState([0,0]); - let [value, setValue] = useState(''); + const [value, setValue] = useState(''); const [filtered, setFiltered] = useState(false); - const [errorProps, setErrorProps] = useState({}); - const [maxErr, setMaxErr] = useState(false); - const [min, max] = React.useMemo(() => { - if(!preFilteredRows) { - preFilteredRows = []; + React.useEffect(() => { + if (!filterValue && value) { + setValue(''); } - 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) - }) - //setRangeValue([min, max]); - return [min, max] - }, [id, preFilteredRows]) + if (storeFilter) { + const filterValue = TableUtil.getFilter(currentTableName, Header); + if (filterValue) { + setFiltered(true); + } + if(!value){ + setValue(filterValue); + //setFilter(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); + } + }; + return ( + <div + style={{ + alignItems: 'center' + }} + > + <input type="number" + max="1" + min="0" + className="p-inputtext p-component" + value={value[0]} + step="0.0001" + onKeyUp={(e) => { + if (e.key === "Enter" && doServersideFilter) { + TableUtil.saveFilter(currentTableName, Header, rangeValue); + setFiltered(true); + callServerFilter(e, false); + } + }} + onChange={e => { + setFilter(undefined); setFiltered(false); + let val = e.target.value; + val = val.replace(/([^0-9.]+)/, ""); + const match = /(\d{0,1})[^.]*((?:\.\d{0,4})?)/g.exec(val); + val = match[1] + match[2]; + if (val == '' || (val >= 0 && val <= 1)) { + 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]); + } + } else { + e.target.value = rangeValue[0]; + } + }} + style={{ + width: '75px', + height: '25px' + }} + /> + + <input type="number" + max="1" + min="0" + className="p-inputtext p-component" + value={value[1]} + step="0.0001" + onKeyUp={(e) => { + if (e.key === "Enter" && doServersideFilter) { + TableUtil.saveFilter(currentTableName, Header, rangeValue); + setFiltered(true); + callServerFilter(e, false); + } + }} + onChange={e => { + setFilter(undefined); setFiltered(false); + let val = e.target.value; + val = val.replace(/([^0-9.]+)/, ""); + const match = /(\d{0,1})[^.]*((?:\.\d{0,4})?)/g.exec(val); + val = match[1] + match[2]; + if (val == '' || (val >= 0 && val <= 1)) { + let min = rangeValue[0]; + setRangeValue([min,val]); + filterValue[1] = val; + setValue([min,val]); + setFilter([min,val] || undefined); + if(storeFilter) { + TableUtil.saveFilter(currentTableName, Header, [min,val]); + setFilter([min,val]); + } + } else { + e.target.value = rangeValue[0]; + } + }} + style={{ + width: '75px', + height: '25px' + }} + /> + </div> + ) +} + +// Duration Range Filter +function DurationRangeFilter({ + column: { filterValue = [], preFilteredRows, setFilter, id, Header }, +}) { + let [rangeValue, setRangeValue] = useState([0,0]); + const [value, setValue] = useState(''); + const [filtered, setFiltered] = useState(false); + React.useEffect(() => { + if (!filterValue && value) { + setValue(''); + } + if (storeFilter) { + const filterValue = TableUtil.getFilter(currentTableName, Header); + if (filterValue) { + setFiltered(true); + } + if(!value){ + setValue(filterValue); + //setFilter(filterValue); + } + } + }, [filterValue, value]); + // Function to call the server side filtering const callServerFilter = (event, isCleared) => { hasFilters = true; @@ -1211,75 +1430,65 @@ function NumberRangeFilter({ return ( <div + onKeyPress={(e) => { + if (e.key === "Enter" && doServersideFilter) { + TableUtil.saveFilter(currentTableName, Header, rangeValue); + setFiltered(true); + callServerFilter(e, false); + } + }} style={{ - // display: 'flex', - // flexdirection:'column', alignItems: 'center' }} > - <InputText - value={filterValue[0]} - type="number" - onKeyUp={(e) => { - if (e.key === "Enter" && doServersideFilter) { - TableUtil.saveFilter(currentTableName, Header, rangeValue); - setFiltered(true); - callServerFilter(e, false); - } - }} - onChange={e => { - setFilter(undefined); setValue(''); setFiltered(false); - const val = e.target.value; - let max = rangeValue[1]; - setRangeValue([val,max]); - filterValue[0] = val; - //setFilter((old = []) => [val ? parseFloat(val, 10) : undefined, old[1]]); - if(storeFilter) { - TableUtil.saveFilter(currentTableName, Header, [val,max]); + <InputMask mask="99:99:99" + value={value[0]} + placeholder="HH:MM:SS" + onChange={e => { + setFilter(undefined); setFiltered(false); + let val = e.target.value; + let max = rangeValue[1]; setValue([val,max]); - setFilter([val,max]); - } - }} - placeholder={min} - style={{ - width: '55px', - height: '25px' - // marginRight: '0.5rem', - }} + setFilter([val,max] || undefined); + setRangeValue([val,max]); + filterValue[0] = val; + if(storeFilter) { + TableUtil.saveFilter(currentTableName, Header, [val,max]); + setFilter([val,max]); + } + }} + style={{ + width: '85px', + height: '25px' + }} /> - <InputText - value={filterValue[1]} - type="number" - {...errorProps} - className={maxErr && 'field-error'} - onKeyUp={(e) => { - if (e.key === "Enter" && doServersideFilter) { - setFiltered(true); - callServerFilter(e, false); - } - }} - onChange={e => { - const val = e.target.value; - let min = rangeValue[0]; - setRangeValue([min,val]); - filterValue[1] = val; - if(storeFilter) { - TableUtil.saveFilter(currentTableName, Header, [min,val]); + + <InputMask mask="99:99:99" + value={value[1]} + placeholder="HH:MM:SS" + onChange={e => { + setFilter(undefined); setFiltered(false); + let val = e.target.value; + let min = rangeValue[0]; setValue([min,val]); - setFilter([min,val]); - } + setFilter([min,val] || undefined); + setRangeValue([min,val]); + filterValue[1] = val; + if(storeFilter) { + TableUtil.saveFilter(currentTableName, Header, [min,val]); + setFilter([min,val]); + } }} - placeholder={max} style={{ - width: '55px', + width: '85px', height: '25px' - // marginLeft: '0.5rem', }} - /> + /> + </div> + ) } -*/ // This is a custom UI for our 'between' or number range // filter. It uses two number boxes and filters rows to @@ -1386,6 +1595,11 @@ const filterTypes = { fn: DateRangeColumnFilter, type: dateRangeFilterFn }, + 'flatpickrDateRange': { + fn: FlatpickrRangeColumnFilter, + type: dateRangeFilterFn + }, + 'fromdatetime': { fn: DateTimeColumnFilter, type: fromDatetimeFilterFn @@ -1405,6 +1619,14 @@ const filterTypes = { 'numberRangeMinMax': { fn: NumberRangeFilter, type: 'between' + }, + 'rankMinMax': { + fn: RankRangeFilter, + type: 'between' + }, + 'durationMinMax': { + fn: DurationRangeFilter, + type: 'between' } }; // Let the table remove the filter if the string is empty diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js index b0ba60927dea7a0b630aad34f83220cdd3041a31..500be0ca36a6ffdfde526bbe70a5eb4b54ec78cc 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -34,28 +34,28 @@ class SchedulingUnitList extends Component{ this. setToggleBySorting(); this.defaultcolumns = { status: {name: "Status",filter: "select"}, - workflowStatus: {name: "Workflow Status"}, + workflowStatus: {name: "Workflow Status",filter: "select"}, suid: {name: "Scheduling Unit ID"}, project:{name:"Project",}, name:{name:"Name",}, description: {name: "Description"}, - priority: {name: "Priority"}, + priority: {name: "Priority", filter: "numberRangeMinMax"}, scheduling_set: {name: "Scheduling Set",}, observation_strategy_template_name: {name: "Template Name"}, observation_strategy_template_description: {name: "Template Description"}, start_time:{ name:"Start Time", - filter:"dateRange", + filter:"flatpickrDateRange", format:UIConstants.CALENDAR_DATETIME_FORMAT }, stop_time:{ name:"End Time", - filter:"dateRange", + filter:"flatpickrDateRange", format:UIConstants.CALENDAR_DATETIME_FORMAT }, duration:{ name:"Duration (HH:mm:ss)", - filter: "numberRangeMinMax", + filter: "durationMinMax", format:UIConstants.CALENDAR_TIME_FORMAT }, station_group: {name: "Stations (CS/RS/IS)"}, @@ -74,6 +74,7 @@ class SchedulingUnitList extends Component{ this.mainStationGroups = {}; this.suTypeList = [{name: 'Blueprint'}, {name: 'Draft'}] this.totalPage = 0; + this.statusList = []; this.state = { showSpinner: false, suType: this.suUIAttr['listType'] || "Draft", @@ -119,22 +120,19 @@ class SchedulingUnitList extends Component{ actionpath:"actionpath", draft:{name: "Linked Blueprint Id"}, priority_queue: {name: "Priority Queue", filter: "select"}, - priority_rank: {name: "Priority Rank",filter: "numberRangeMinMax"}, - observation_strategy_template_id:{ - name: "Template ID", - filter: "select" - }, + priority_rank: {name: "Priority Rank",filter: "rankMinMax"}, + observation_strategy_template_id:{name: "Template ID"}, created_at:{ name:"Created_At", - filter: "dateRange", + filter: "flatpickrDateRange", format:UIConstants.CALENDAR_DATETIME_FORMAT }, updated_at:{ name:"Updated_At", - filter: "dateRange", + filter: "flatpickrDateRange", format:UIConstants.CALENDAR_DATETIME_FORMAT }, - output_pinned:{name: "Prevent Autodeletion"}, + output_pinned:{name: "Prevent Autodeletion", filter: "switch"}, }], columnclassname: [{ "Scheduling Unit ID":"filter-input-100", @@ -142,8 +140,8 @@ class SchedulingUnitList extends Component{ "Template Description": "filter-input-200", "Scheduling Set": "filter-input-150", "Project":"filter-input-100", - "Priority Rank":"filter-input-50", - "Duration (HH:mm:ss)":"filter-input-75", + "Priority Rank":"filter-input-75", + "Duration (HH:mm:ss)":"filter-input-100", "Linked Draft ID":"filter-input-100", "Linked Blueprint ID":"filter-input-100", "Type": "filter-input-75", @@ -154,6 +152,9 @@ class SchedulingUnitList extends Component{ "Number of SAPs in the target observation":"filter-input-50", "Start Time":"filter-input-150", "End Time":"filter-input-150", + "Created_At":"filter-input-150", + "Updated_At":"filter-input-150", + "Template ID":"filter-input-75", }], //defaultSortColumn: [{id: "Name", desc: false}], dialog: {header: 'Confirm', detail: 'Do you want to create a Scheduling Unit Blueprint?'} @@ -319,7 +320,7 @@ class SchedulingUnitList extends Component{ columnDef[key]['disableSortBy'] = !_.includes(suFilters.data.ordering, tempKey); columnDef[key]['disableFilters'] = false; if(UIConstants.FILTER_MAP[suFilters.data.filters[tempKey].type]) { - if (tempKey === 'draft') { //this condition because to avoid dropdown filter for 'Linked Draft ID' + if (tempKey === 'draft' || tempKey === 'observation_strategy_template') { //this condition because to avoid dropdown filter for 'Linked Draft ID' suFilters.data.filters[tempKey].type = 'CharFilter'; } columnDef[key]['filter'] = UIConstants.FILTER_MAP[suFilters.data.filters[tempKey].type]; @@ -391,12 +392,25 @@ class SchedulingUnitList extends Component{ this.removeColumns(tmpOptionalColumns, columnDefinitionToBeRemove); } if(suFilters) { + this.getStatusList(suFilters); tmpDefaulColumns = this.getAPIFilter(suFilters, tmpDefaulColumns); tmpOptionalColumns = this.getAPIFilter(suFilters, tmpOptionalColumns); await this.setState({tmpDefaulcolumns: [tmpDefaulColumns], tmpOptionalcolumns:[tmpOptionalColumns], tmpColumnOrders: tmpColumnOrders, columnMap: this.columnMap}) } } + /** + * Get Status list frol filter + * @param {Array} suFilters + */ + getStatusList(suFilters) { + if (suFilters.data.filters['status']) { + suFilters.data.filters['status'].choices.forEach(choice => { + this.statusList.push(choice.value); + }) + } + } + /** * * @param {Boolean} isInitial - true => While loading page, false => while doing server side access @@ -1146,8 +1160,12 @@ class SchedulingUnitList extends Component{ this.filterQry += 'stop_time_after='+values[0]+'&'; this.filterQry += 'stop_time_before='+_.replace(values[1], '00:00:00', '23:59:59')+'&'; } + } else if ((filter.id === 'Scheduling Unit ID' || filter.id === 'Linked Draft ID') && filter.value != '') { + let columnDetails = _.find(this.state.columnMap, {displayName:filter.id}); + const values = UnitConverter.getSubbandOutput(filter.value); + this.filterQry += columnDetails.orgField+'='+values.toString()+"&"; } else if (filter.id === 'Linked Blueprint ID' && filter.value != '') { - const values = _.split(filter.value, ","); + const values = UnitConverter.getSubbandOutput(filter.value);//_.split(filter.value, ","); for ( const value of values) { this.filterQry += 'scheduling_unit_blueprints='+value+"&"; } @@ -1173,13 +1191,17 @@ class SchedulingUnitList extends Component{ } else if (filter.id === 'Duration (HH:mm:ss)' && filter.value != '') { let columnDetails = _.find(this.state.columnMap, {displayName:filter.id}); const values = _.split(filter.value, ","); - if (values[0] && !isNaN(values[0]) && values[0]>0) { - this.filterQry += columnDetails.orgField+"_min" +'=PT'+parseFloat(values[0])+'S&'; + if (values[0]) { + this.filterQry += columnDetails.orgField+"_min" +'=PT'+UnitConverter.getHHmmssToSecs(values[0])+'S&'; } - if (values[1] && !isNaN(values[1]) && values[1]>0) { - this.filterQry += columnDetails.orgField+"_max" +'=PT'+parseFloat(values[1])+'S&'; + if (values[1]) { + this.filterQry += columnDetails.orgField+"_max" +'=PT'+UnitConverter.getHHmmssToSecs(values[1])+'S&'; } + } else if (filter.id === 'Template ID' && filter.value != '') { + // Since it is ModelChoiceFilter, it will allow single parameter, so use last parameter if contains multi parameter + const values = _.split(filter.value, ","); + this.filterQry += 'observation_strategy_template='+values[values.length-1]+"&"; } else { let columnDetails = _.find(this.state.columnMap, {displayName:filter.id}); if(columnDetails) { @@ -1232,9 +1254,11 @@ class SchedulingUnitList extends Component{ getFilterOptions(id) { let options = null; if(id && id === 'Status') { - options = UIConstants.SU_STATUS; + options = this.statusList;//UIConstants.SU_STATUS; } else if (id === 'Linked Draft ID') { options = _.sortBy(this.draftIds); + } else if (id === 'Priority Queue') { + options = UIConstants.SU_PRIORITY_QUEUE; } return options; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js index 649d3d069c973b2d3fd8edc3a172ca57e653fa7c..d89aadf99a448db85546f15d0ffec2b6ec8272b0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/ui.constants.js @@ -29,6 +29,7 @@ const UIConstants = { 'PropertyIsoDateTimeFromToRangeFilter':'', }, SU_STATUS:['cancelled', 'error', 'defining', 'defined', 'schedulable', 'scheduled','started', 'observing', 'observed', 'processing', 'processed', 'ingesting','finished', 'unschedulable'], - CURRENT_WORKFLOW_STAGE:['Waiting To Be Scheduled','Scheduled','QA Reporting (TO)', 'QA Reporting (SDCO)', 'PI Verification', 'Decide Acceptance','Ingesting','Unpin Data','Done'] + CURRENT_WORKFLOW_STAGE:['Waiting To Be Scheduled','Scheduled','QA Reporting (TO)', 'QA Reporting (SDCO)', 'PI Verification', 'Decide Acceptance','Ingesting','Unpin Data','Done'], + SU_PRIORITY_QUEUE:['A','B'], } export default UIConstants; \ No newline at end of file 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 1f135a9fd6fd152fab1765bb0909f1886c72a260..bebc80963156fe80fdbb6efe4f13c6d305934efe 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js @@ -236,6 +236,24 @@ const UnitConverter = { radian = this.convertToRadians((degreeHour * 15 + minute / 4 + second / 240)); } return radian; + }, + /** + * Convert the string input for subband list to Array + * @param {String} prpOutput + */ + getSubbandOutput(prpOutput) { + const subbandArray = prpOutput ? prpOutput.split(",") : []; + let subbandList = []; + for (const subband of subbandArray) { + const subbandRange = subband.split('..'); + if (subbandRange.length > 1) { + subbandList = subbandList.concat(_.range(subbandRange[0], (parseInt(subbandRange[1]) + 1))); + } else if (subbandRange[0] !== ''){ + subbandList = subbandList.concat(parseInt(subbandRange[0])); + } + } + prpOutput = subbandList; + return prpOutput; } };