Skip to content

Commit

Permalink
feature/reorder-collection-groups (#242)
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinJJackson authored Nov 21, 2024
1 parent 0215b93 commit 7343b82
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ rollup-analyze.*
.env.development.local
.env.test.local
.env.production.local
notes.txt

npm-debug.log*
yarn-debug.log*
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
27 changes: 23 additions & 4 deletions src/app-bundles/collection-group-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand All @@ -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 }) => {
Expand Down
1 change: 1 addition & 0 deletions src/app-pages/collection-group/collection-group.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export default connect(
<TimeseriesList
date={date}
project={project}
collectionGroup={detail}
items={detail.timeseries}
handleItemSaveValue={handleTimeseriesSaveValue}
handleItemDelete={(item) => {
Expand Down
144 changes: 125 additions & 19 deletions src/app-pages/collection-group/collectiongroup-timeseries-list.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -15,7 +18,7 @@ const TimeseriesListEntry = ({

return (
// Flex Wrapper
<div className='d-flex flex-row list-group-item border-0 bg-light my-1'>
<div className='d-flex flex-row list-group-item border-0 bg-light my-1 w-100'>
{/* Column 1 */}
<div className='d-flex flex-row' style={{ minWidth: '420px' }}>
<div className='d-flex flex-column my-2'>
Expand Down Expand Up @@ -89,23 +92,126 @@ const TimeseriesListEntry = ({
);
};

const CollectionGroupTimeseriesList = ({
items,
project,
handleItemDelete,
handleItemSaveValue,
}) => (
<div className='w-100 list-group'>
{items.map((item, idx) => (
<TimeseriesListEntry
key={idx}
item={item}
project={project}
handleItemDelete={handleItemDelete}
handleItemSaveValue={handleItemSaveValue}
/>
))}
</div>
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 (
<>
<IconButton
size='small'
color={isDirty ? 'success' : 'info'}
onClick={() => {
if (isDirty) {
handleSaveNewOrder();
}
setIsDraggingDisabled(prev => !prev);
}}>
{isDraggingDisabled ?
<Lock /> :
isDirty ?
<LockClock /> :
<LockOpen />
}
</IconButton>
<i className='ml-1'>
{isDraggingDisabled ? 'Dragging Disabled' : 'Dragging Enabled'}
{isDirty ? ' | Click lock to save changes.' :''}
</i>
<div className='w-100 list-group'>
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId='legend-order' direction='vertical'>
{(provided) => (
<List ref={provided.innerRef} {...provided.droppableProps} sx={{
border: '1px solid gray',
borderRadius: '3px',
width: '100%',
}}>
{sortedItems.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
isDragDisabled={isDraggingDisabled}
>
{(provided) => (
<ListItem
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
sx={{
margin: 0,
padding: 0,
width: '100%'
}}
>
<TimeseriesListEntry
key={index}
item={item}
project={project}
handleItemDelete={handleItemDelete}
handleItemSaveValue={handleItemSaveValue}
/>
</ListItem>
)}
</Draggable>
))}
{provided.placeholder}
</List>
)}
</Droppable>
</DragDropContext>
</div>
</>
);
},
);

export default CollectionGroupTimeseriesList;
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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 (
<div className='modal-content' style={{ overflow: 'visible' }}>
<ModalHeader title='Add Field' />
<section className='modal-body' style={{ overflow: 'visible' }}>
<Dropdown.Menu
buttonClasses={['btn-outline-primary', 'w-100']}
buttonContent={(
timeseriesSelected &&
timeseriesSelected.name &&
timeseriesSelected.instrument &&
`${timeseriesSelected.instrument} | ${timeseriesSelected.name}`
) || 'Select a Timeseries'}
>
{timeseries.map((t, idx) => (
<Dropdown.Item key={idx} onClick={() => setTimeseriesSelected(t)}>
{`${t.instrument} | ${t.name}`}
</Dropdown.Item>

))}
</Dropdown.Menu>
<Autocomplete
size='small'
groupBy={option => option.instrument}
value={selectedTimeseries}
onChange={(_e, value) => setSelectedTimeseries(value)}
renderInput={(params) => <TextField {...params} label='Timeseries' placeholder='Select Timeseries...' />}
options={timeseriesOptions}
/>
</section>
<ModalFooter
customClosingLogic
saveText='Add'
onSave={(e) => handleClickAdd(e)}
saveIsDisabled={!selectedTimeseries}
onSave={handleClickAdd}
onCancel={(e) => {
e.preventDefault();
doModalClose();
Expand Down
12 changes: 8 additions & 4 deletions src/app-pages/project/batch-plotting/batch-plotting.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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);

Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/app-pages/project/batch-plotting/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
};
};

Expand Down Expand Up @@ -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 => {
Expand Down

0 comments on commit 7343b82

Please sign in to comment.