From 7343b82eba128df9010a7d8ba8c1fa2be085ee47 Mon Sep 17 00:00:00 2001 From: Kevin Jackson <30411845+KevinJJackson@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:07:26 -0500 Subject: [PATCH] feature/reorder-collection-groups (#242) --- .gitignore | 1 + package.json | 2 +- src/app-bundles/collection-group-bundle.js | 27 +++- .../collection-group/collection-group.jsx | 1 + .../collectiongroup-timeseries-list.jsx | 144 +++++++++++++++--- .../collectiongroup-timeseries-picker.jsx | 62 ++++---- .../project/batch-plotting/batch-plotting.jsx | 12 +- .../project/batch-plotting/helper.js | 4 +- 8 files changed, 191 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index 8228b3e7..fb83c3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ rollup-analyze.* .env.development.local .env.test.local .env.production.local +notes.txt npm-debug.log* yarn-debug.log* diff --git a/package.json b/package.json index cb89f92d..83fce011 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hhd-ui", - "version": "0.18.0", + "version": "0.18.1", "private": true, "dependencies": { "@ag-grid-community/client-side-row-model": "^30.0.3", diff --git a/src/app-bundles/collection-group-bundle.js b/src/app-bundles/collection-group-bundle.js index 88e2d2d0..2b473ef8 100644 --- a/src/app-bundles/collection-group-bundle.js +++ b/src/app-bundles/collection-group-bundle.js @@ -41,9 +41,9 @@ export default createRestBundle({ payload: {}, }); - const url = `/projects/${projectId}/collection_groups/${collectionGroupId}/timeseries/${timeseriesId}`; + const uri = `/projects/${projectId}/collection_groups/${collectionGroupId}/timeseries/${timeseriesId}`; - apiDelete(url, (_err, _body) => { + apiDelete(uri, (_err, _body) => { dispatch({ type: 'COLLECTIONGROUP_REMOVE_TIMESERIES_FINISH', payload: {}, @@ -57,15 +57,34 @@ export default createRestBundle({ payload: {}, }); - const url = `/projects/${projectId}/collection_groups/${collectionGroupId}/timeseries/${timeseriesId}`; + const uri = `/projects/${projectId}/collection_groups/${collectionGroupId}/timeseries/${timeseriesId}`; - apiPost(url, {}, (_err, _body) => { + apiPost(uri, {}, (_err, _body) => { dispatch({ type: 'COLLECTIONGROUP_ADD_TIMESERIES_FINISH', payload: {}, }); }); }, + + doCollectionGroupReorderTimeseries: ({ projectId, collectionGroupId, timeseries = [] }) => async ({ dispatch, apiPut }) => { + dispatch({ + type: 'COLLECTIONGROUP_REORDER_TIMESERIES_START', + payload: {}, + }); + + await timeseries.forEach((timeseries, index) => { + const { id } = timeseries; + const uri = `/projects/${projectId}/collection_groups/${collectionGroupId}/timeseries/${id}?sort_order=${index}`; + + apiPut(uri, {}); + }); + + dispatch({ + type: 'COLLECTIONGROUP_REORDER_TIMESERIES_FINISH', + payload: {}, + }); + }, }, reduceFurther: (state, { type, payload }) => { diff --git a/src/app-pages/collection-group/collection-group.jsx b/src/app-pages/collection-group/collection-group.jsx index cc7cef5c..2e7f9ed0 100644 --- a/src/app-pages/collection-group/collection-group.jsx +++ b/src/app-pages/collection-group/collection-group.jsx @@ -144,6 +144,7 @@ export default connect( { diff --git a/src/app-pages/collection-group/collectiongroup-timeseries-list.jsx b/src/app-pages/collection-group/collectiongroup-timeseries-list.jsx index 3d79a926..221021ad 100644 --- a/src/app-pages/collection-group/collectiongroup-timeseries-list.jsx +++ b/src/app-pages/collection-group/collectiongroup-timeseries-list.jsx @@ -1,9 +1,12 @@ import React, { useState } from 'react'; +import { Add, DeleteOutline, Lock, LockClock, LockOpen } from '@mui/icons-material'; +import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; +import { IconButton, List, ListItem } from '@mui/material'; import { formatDistanceToNow, parseISO } from 'date-fns'; -import { Add, DeleteOutline } from '@mui/icons-material'; import Button from '../../app-components/button'; import RoleFilter from '../../app-components/role-filter'; +import { connect } from 'redux-bundler-react'; const TimeseriesListEntry = ({ handleItemSaveValue, @@ -15,7 +18,7 @@ const TimeseriesListEntry = ({ return ( // Flex Wrapper -
+
{/* Column 1 */}
@@ -89,23 +92,126 @@ const TimeseriesListEntry = ({ ); }; -const CollectionGroupTimeseriesList = ({ - items, - project, - handleItemDelete, - handleItemSaveValue, -}) => ( -
- {items.map((item, idx) => ( - - ))} -
+const CollectionGroupTimeseriesList = connect( + 'doCollectionGroupReorderTimeseries', + ({ + doCollectionGroupReorderTimeseries, + items, + project, + collectionGroup, + handleItemDelete, + handleItemSaveValue, + }) => { + const [isDraggingDisabled, setIsDraggingDisabled] = useState(true); + const [sortedItems, setSortedItems] = useState(items); + const [isDirty, setIsDirty] = useState(false); + + const handleDragEnd = (params) => { + const { source, destination } = params; + const { index: sourceIndex } = source; + const { index: destIndex } = destination; + + if (sourceIndex !== destIndex && !isDirty) { + setIsDirty(true); + } + + const clone = [...sortedItems]; + const movedItem = clone.splice(sourceIndex, 1); + const newItems = []; + + clone.forEach((el, index) => { + if (index === destIndex) { + newItems.push(movedItem[0]); + } + newItems.push(el); + }); + + if (items.length !== newItems.length) { + newItems.push(movedItem[0]); + } + + setSortedItems(newItems); + }; + + const handleSaveNewOrder = () => { + setIsDirty(false); + + doCollectionGroupReorderTimeseries({ + projectId: project.id, + collectionGroupId: collectionGroup.id, + timeseries: sortedItems, + }); + }; + + return ( + <> + { + if (isDirty) { + handleSaveNewOrder(); + } + setIsDraggingDisabled(prev => !prev); + }}> + {isDraggingDisabled ? + : + isDirty ? + : + + } + + + {isDraggingDisabled ? 'Dragging Disabled' : 'Dragging Enabled'} + {isDirty ? ' | Click lock to save changes.' :''} + +
+ + + {(provided) => ( + + {sortedItems.map((item, index) => ( + + {(provided) => ( + + + + )} + + ))} + {provided.placeholder} + + )} + + +
+ + ); + }, ); export default CollectionGroupTimeseriesList; diff --git a/src/app-pages/collection-group/collectiongroup-timeseries-picker.jsx b/src/app-pages/collection-group/collectiongroup-timeseries-picker.jsx index 4940204a..fad73d33 100644 --- a/src/app-pages/collection-group/collectiongroup-timeseries-picker.jsx +++ b/src/app-pages/collection-group/collectiongroup-timeseries-picker.jsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; +import { Autocomplete, TextField } from '@mui/material'; import { connect } from 'redux-bundler-react'; -import Dropdown from '../../app-components/dropdown'; import { ModalFooter, ModalHeader } from '../../app-components/modal'; export default connect( @@ -15,48 +15,46 @@ export default connect( collectionGroupByRoute: collectionGroup, nonComputedTimeseriesItemsByRoute: timeseries, }) => { - const [timeseriesSelected, setTimeseriesSelected] = useState(null); + const [selectedTimeseries, setSelectedTimeseries] = useState(null); - const handleClickAdd = (e) => { - e.preventDefault(); + const timeseriesOptions = useMemo(() => { + return timeseries?.map(ts => { + const { id, name, instrument } = ts; - if (timeseriesSelected) { - doCollectionGroupAddTimeseries({ - projectId: collectionGroup.project_id, - collectionGroupId: collectionGroup.id, - timeseriesId: timeseriesSelected.id, - }); - } else { - // eslint-disable-next-line no-console - console.log('No Timeseries Selected; Skipping POST'); - } + return { + value: id, + label: `${instrument} - ${name}`, + instrument, + } + }) + }, [timeseries]); + + const handleClickAdd = () => { + doCollectionGroupAddTimeseries({ + projectId: collectionGroup.project_id, + collectionGroupId: collectionGroup.id, + timeseriesId: selectedTimeseries.value, + }); }; return (
- - {timeseries.map((t, idx) => ( - setTimeseriesSelected(t)}> - {`${t.instrument} | ${t.name}`} - - - ))} - + option.instrument} + value={selectedTimeseries} + onChange={(_e, value) => setSelectedTimeseries(value)} + renderInput={(params) => } + options={timeseriesOptions} + />
handleClickAdd(e)} + saveIsDisabled={!selectedTimeseries} + onSave={handleClickAdd} onCancel={(e) => { e.preventDefault(); doModalClose(); diff --git a/src/app-pages/project/batch-plotting/batch-plotting.jsx b/src/app-pages/project/batch-plotting/batch-plotting.jsx index bce16db5..e9ec7a86 100644 --- a/src/app-pages/project/batch-plotting/batch-plotting.jsx +++ b/src/app-pages/project/batch-plotting/batch-plotting.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { connect } from 'redux-bundler-react'; +import { DateTime } from 'luxon'; import { Engineering } from '@mui/icons-material'; import { Link } from '@mui/material'; import { toast } from 'react-toastify'; @@ -14,7 +15,7 @@ import ProfilePlot from './chart-content/profile-plot.jsx'; import ScatterLinePlot from './chart-content/scatter-line-plot.jsx'; import { apiGet, buildQueryParams } from '../../../app-services/fetch-helpers.ts'; import { downloadFinalReport, useGetReportStatus, useInitializeReportDownload } from '../../../app-services/collections/report-configuration-download.ts'; -import { extractTimeseriesFrom, PlotTypeText } from './helper.js'; +import { determineDateRange, extractTimeseriesFrom, PlotTypeText } from './helper.js'; import { tUpdateSuccess } from '../../../common/helpers/toast-helpers'; import './batch-plotting.scss'; @@ -46,8 +47,9 @@ const BatchPlotting = connect( const crossSectionReady = import.meta.env.VITE_CROSS_SECTION === 'true'; const userConfigId = hashQuery ? hashQuery['c'] : ''; const activeConfig = batchPlotItems[batchPlotId]; - const { plot_type } = activeConfig || {}; + const { plot_type, date_range } = activeConfig || {}; const plotTimeseries = extractTimeseriesFrom(activeConfig, timeseries); + const currentDateRange = determineDateRange(date_range); const cwmsTimeseries = plotTimeseries.filter(el => el.type === 'cwms'); const instrumentIds = cwmsTimeseries.map(t => t.instrument_id); @@ -72,10 +74,12 @@ const BatchPlotting = connect( const { data: cwmsTimeseriesMeasurements } = useQueries({ queries: cwmsTimeseriesIds.map(ts => ({ - queryKey: ['cwmsTimeseriesMeasurementsPlotting', ts], + queryKey: ['cwmsTimeseriesMeasurementsPlotting', ts, currentDateRange[0]], queryFn: () => { const { cwms_timeseries_id, cwms_office_id, cwms_extent_earliest_time } = midasCwmsTimeseries.find(el => el.cwms_timeseries_id === ts); - const uri = `/timeseries${buildQueryParams({ name: cwms_timeseries_id, office: cwms_office_id, begin: cwms_extent_earliest_time })}`; + const beginDate = Math.max(DateTime.fromJSDate(currentDateRange[0]).toMillis(), DateTime.fromISO(cwms_extent_earliest_time).toMillis()); + + const uri = `/timeseries${buildQueryParams({ name: cwms_timeseries_id, office: cwms_office_id, begin: DateTime.fromMillis(beginDate).toUTC().toISO() })}`; return apiGet(uri, 'CWMS'); }, staleTime: Infinity, diff --git a/src/app-pages/project/batch-plotting/helper.js b/src/app-pages/project/batch-plotting/helper.js index e8ea06b7..4cb1dfe5 100644 --- a/src/app-pages/project/batch-plotting/helper.js +++ b/src/app-pages/project/batch-plotting/helper.js @@ -51,7 +51,7 @@ export const determineDateRange = date_range => { case '1 year': return [dateAgo(365), new Date()]; default: - return date_range.split(' ').map(date => DateTime.fromFormat(date, 'yyyy-MM-dd').toJSDate()); + return date_range?.split(' ').map(date => DateTime.fromFormat(date, 'yyyy-MM-dd').toJSDate()); }; }; @@ -88,7 +88,7 @@ const buildCwmsTraces = (cwmsData = [], cwmsTimeseries = [], plotConfig) => { return traces.map(trace => { const { timeseries_id } = trace; - const { cwms_timeseries_id, instrument, name, unit } = cwmsTimeseries.find(el => el.id === timeseries_id) || {}; + const { cwms_timeseries_id, instrument, name, unit } = cwmsTimeseries.find(el => el?.id === timeseries_id) || {}; const { values = [] } = cwmsData.find(el => el?.name === cwms_timeseries_id) || {}; const plotData = values.map(el => {