Skip to content

Commit

Permalink
enhancement/formula-list-filtering (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinJJackson authored Nov 6, 2024
1 parent 7e269dd commit 6b1cfd7
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 38 deletions.
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.17.5",
"version": "0.17.6",
"private": true,
"dependencies": {
"@ag-grid-community/client-side-row-model": "^30.0.3",
Expand Down
2 changes: 1 addition & 1 deletion src/app-components/navigation/secondaryNavBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const CustomTabPanel = props => {
)}
</div>
);
}
};

const SecondaryNavBar = connect(
'selectHashStripQuery',
Expand Down
90 changes: 77 additions & 13 deletions src/app-pages/instrument/formula/formula.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';
import { connect } from 'redux-bundler-react';
import { Accordion, AccordionDetails, AccordionSummary, Autocomplete, TextField } from '@mui/material';
import { ArrowDropDown } from '@mui/icons-material';

import Button from '../../../app-components/button';
import FormulaCard from './formulaCard';

const generateTimeseriesOptions = timeseries => {
return timeseries.map(ts => ({
label: `${ts.instrument} - ${ts.name}`,
value: ts.id,
variable: ts.variable,
instrumentName: ts.instrument,
}));
};

const groupTimeseries = (timeseries = []) => {
return timeseries.reduce((accum, current) => {
const { instrument_id, instrument } = current;


return {
...accum,
[instrument_id]: {
name: instrument,
timeseries: [...(accum[instrument_id]?.timeseries || []), current],
},
};
}, {});
};

export default connect(
'doModalOpen',
'doInstrumentFormulasSave',
Expand All @@ -19,7 +45,11 @@ export default connect(
nonComputedTimeseriesItemsByRoute: timeseries,
}) => {
const [activeFormula, setActiveFormula] = useState(null);
const sortedTimeseries = timeseries.sort((a, b) => String(a.variable).localeCompare(b.variable));
const [searchString, setSeachString] = useState(undefined);

const groupedTs = groupTimeseries(timeseries);
const groupKeys = Object.keys(groupedTs).sort((a, b) => groupedTs[a].name.localeCompare(groupedTs[b].name));
const options = useMemo(() => generateTimeseriesOptions(timeseries).sort((a, b) => -b.label.localeCompare(a.label)), [timeseries]);

const createNewFormula = () => {
doInstrumentFormulasSave({
Expand All @@ -30,6 +60,7 @@ export default connect(
};

const insertParam = (param) => {
if (!activeFormula) return;
const { input, formula, setFormula } = activeFormula;

const start = input.current.selectionStart;
Expand All @@ -51,17 +82,50 @@ export default connect(
<i className='ml-3'>Available parameters</i>
<div className='row mt-2'>
<div className='col-3'>
<ul className='list-group limit-item-list'>
{sortedTimeseries.map((ts, i) => (
<li
key={i}
className='list-group-item list-group-item-action noselect'
onDoubleClick={() => insertParam(`[${ts.variable}]`)}
>
{ts.variable}
</li>
))}
</ul>
<Autocomplete
size='small'
sx={{ marginBottom: 2 }}
groupBy={opt => opt.instrumentName}
getOptionLabel={opt => opt.label}
options={options}
value={undefined}
onChange={(e, val) => {
if (val) {
insertParam(`[${val.variable}]`);
}
setSeachString(undefined);
}}
renderInput={(params) => (
<TextField
{...params}
value={searchString}
onChange={e => setSeachString(e.target.value)}
variant='outlined'
label='Select a timeseries...'
/>
)}
/>
{
groupKeys.map(group => (
<Accordion key={group}>
<AccordionSummary expandIcon={<ArrowDropDown />}>{groupedTs[group].name}</AccordionSummary>
<AccordionDetails>
<ul className='list-group limit-item-list'>
{groupedTs[group].timeseries.sort((a, b) => String(a.variable).localeCompare(b.variable)).map((ts, i) => (
<li
key={i}
className='list-group-item list-group-item-action noselect'
onDoubleClick={() => insertParam(`[${ts.variable}]`)}
>
{ts.variable}
</li>
))}
</ul>
</AccordionDetails>
</Accordion>
))
}

</div>
<div className='col'>
{instrumentFormulasItems.length ? (
Expand Down
104 changes: 81 additions & 23 deletions src/app-pages/instrument/settings.jsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,122 @@
import React from 'react';
import React, { useState } from 'react';
import { connect } from 'redux-bundler-react';
import { Box, Tab, Tabs, tabsClasses } from '@mui/material';

import AlertEditor from './alert/alert-editor';
import Card from '../../app-components/card';
import Chart from './chart/chart';
import Constants from './constants/constants';
import CwmsTimeseries from './cwms-timeseries/cwmsTimeseries';
import FormulaEditor from './formula/formula';
import TabContainer from '../../app-components/tab';
import Timeseries from './timeseries/timeseries';
import Sensors from './sensors/sensors';

const CustomTabPanel = props => {
const { children, value, active } = props;

return (
<div
role='tabpanel'
hidden={value !== active}
id={`tabpanel-${value}`}
>
{value === active && (
<Box sx={{ p: 3 }}>
{children}
</Box>
)}
</div>
);
};

export default connect(
'selectTimeseriesMeasurementsItemsObject',
'selectInstrumentsByRoute',
({
timeseriesMeasurementsItemsObject: measurements,
instrumentsByRoute: instrument,
}) => {
const { type, show_cwms_tab } = instrument || {};

// const alertsReady = import.meta.env.VITE_ALERT_EDITOR === 'true';
const alertsReady = false;
// const alertsReady = import.meta.env.VITE_ALERT_EDITOR === 'true';
const forumlaReady = import.meta.env.VITE_FORMULA_EDITOR === 'true';
const chartReady = import.meta.env.VITE_INSTRUMENT_CHART === 'true';

const { type, show_cwms_tab } = instrument || {};
const [activeTab, setActiveTab] = useState(alertsReady ? 'Alerts' : 'Constants');

const isShapeArray = type === 'SAA';
const isIPI = type === 'IPI';

const tabs = [
alertsReady && {
{
title: 'Alerts',
content: <AlertEditor />
isVisible: alertsReady,
}, {
title: 'Constants',
content: <Constants />
isVisible: true,
}, {
title: 'Timeseries',
content: <Timeseries data={measurements[instrument.id]} />,
}, show_cwms_tab && {
isVisible: true,
}, {
title: 'CWMS Timeseries',
content: <CwmsTimeseries />,
}, (isShapeArray || isIPI) && {
isVisible: show_cwms_tab,
}, {
title: 'Sensors',
content: <Sensors type={isShapeArray ? 'saa' : 'ipi'} />,
}, forumlaReady && {
isVisible: (isShapeArray || isIPI),
}, {
title: 'Formula Editor',
content: <FormulaEditor />,
}, chartReady && {
isVisible: forumlaReady,
}, {
title: 'Chart',
content: <Chart />,
isVisible: chartReady,
}
].filter(e => e);
];

return (
<Card>
<TabContainer
tabs={tabs}
tabListClass='card-header pb-0'
contentClass='card-body'
/>
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={activeTab}
onChange={(_e, val) => setActiveTab(val)}
variant='scrollable'
scrollButtons='auto'
sx={{
[`& .${tabsClasses.scrollButtons}`]: {
'&.Mui-disabled': { opacity: 0.25 },
},
}}
>
{tabs.map(tab => {
const { title, isVisible } = tab;

return isVisible ? <Tab key={title} label={title} value={title} /> : null;
}).filter(e => e)}
</Tabs>
</Box>
{/* Mapping over these doesnt work... interesting */}
<CustomTabPanel value={'Alerts'} active={activeTab}>
<AlertEditor />
</CustomTabPanel>
<CustomTabPanel value={'Constants'} active={activeTab}>
<Constants />
</CustomTabPanel>
<CustomTabPanel value={'Timeseries'} active={activeTab}>
<Timeseries data={measurements[instrument.id]} />
</CustomTabPanel>
<CustomTabPanel value={'CWMS Timeseries'} active={activeTab}>
<CwmsTimeseries />
</CustomTabPanel>
<CustomTabPanel value={'Sensors'} active={activeTab}>
<Sensors type={isShapeArray ? 'saa' : 'ipi'} />
</CustomTabPanel>
<CustomTabPanel value={'Formula Editor'} active={activeTab}>
<FormulaEditor />
</CustomTabPanel>
<CustomTabPanel value={'Chart'} active={activeTab}>
<Chart />
</CustomTabPanel>
</Box>
</Card>
);
}
Expand Down

0 comments on commit 6b1cfd7

Please sign in to comment.