diff --git a/frontend/src/component/impact-metrics/ChartConfigModal.tsx b/frontend/src/component/impact-metrics/ChartConfigModal.tsx index 83188fac8e..40a480aa77 100644 --- a/frontend/src/component/impact-metrics/ChartConfigModal.tsx +++ b/frontend/src/component/impact-metrics/ChartConfigModal.tsx @@ -1,5 +1,4 @@ import type { FC } from 'react'; -import { useState, useEffect } from 'react'; import { Dialog, DialogTitle, @@ -12,7 +11,7 @@ import { } from '@mui/material'; import { ImpactMetricsControls } from './ImpactMetricsControls/ImpactMetricsControls.tsx'; import { ImpactMetricsChartPreview } from './ImpactMetricsChartPreview.tsx'; -import { useImpactMetricsData } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; +import { useChartFormState } from './hooks/useChartFormState.ts'; import type { ChartConfig } from './types.ts'; import type { ImpactMetricsSeries } from 'hooks/api/getters/useImpactMetricsMetadata/useImpactMetricsMetadata'; @@ -58,67 +57,19 @@ export const ChartConfigModal: FC = ({ metricSeries, loading = false, }) => { - const [title, setTitle] = useState(initialConfig?.title || ''); - const [selectedSeries, setSelectedSeries] = useState( - initialConfig?.selectedSeries || '', - ); - const [selectedRange, setSelectedRange] = useState< - 'hour' | 'day' | 'week' | 'month' - >(initialConfig?.selectedRange || 'day'); - const [beginAtZero, setBeginAtZero] = useState( - initialConfig?.beginAtZero || false, - ); - const [selectedLabels, setSelectedLabels] = useState< - Record - >(initialConfig?.selectedLabels || {}); - - const { - data: { labels: currentAvailableLabels }, - } = useImpactMetricsData( - selectedSeries - ? { - series: selectedSeries, - range: selectedRange, - } - : undefined, - ); - - useEffect(() => { - if (open && initialConfig) { - setTitle(initialConfig.title || ''); - setSelectedSeries(initialConfig.selectedSeries); - setSelectedRange(initialConfig.selectedRange); - setBeginAtZero(initialConfig.beginAtZero); - setSelectedLabels(initialConfig.selectedLabels); - } else if (open && !initialConfig) { - setTitle(''); - setSelectedSeries(''); - setSelectedRange('day'); - setBeginAtZero(false); - setSelectedLabels({}); - } - }, [open, initialConfig]); + const { formData, actions, isValid, currentAvailableLabels } = + useChartFormState({ + open, + initialConfig, + }); const handleSave = () => { - if (!selectedSeries) return; + if (!isValid) return; - onSave({ - title: title || undefined, - selectedSeries, - selectedRange, - beginAtZero, - selectedLabels, - }); + onSave(actions.getConfigToSave()); onClose(); }; - const handleSeriesChange = (series: string) => { - setSelectedSeries(series); - setSelectedLabels({}); - }; - - const isValid = selectedSeries.length > 0; - return ( = ({ setTitle(e.target.value)} + value={formData.title} + onChange={(e) => actions.setTitle(e.target.value)} fullWidth variant='outlined' size='small' /> diff --git a/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx b/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx index 206efe8ca7..1c166e9be2 100644 --- a/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx +++ b/frontend/src/component/impact-metrics/ImpactMetricsControls/ImpactMetricsControls.tsx @@ -1,29 +1,33 @@ import type { FC } from 'react'; -import { Box, Typography } from '@mui/material'; +import { Box, Typography, FormControlLabel, Checkbox } from '@mui/material'; import type { ImpactMetricsSeries } from 'hooks/api/getters/useImpactMetricsMetadata/useImpactMetricsMetadata'; import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; import { SeriesSelector } from './components/SeriesSelector.tsx'; -import { RangeSelector, type TimeRange } from './components/RangeSelector.tsx'; -import { BeginAtZeroToggle } from './components/BeginAtZeroToggle.tsx'; +import { RangeSelector } from './components/RangeSelector.tsx'; import { LabelsFilter } from './components/LabelsFilter.tsx'; +import type { ChartFormState } from '../hooks/useChartFormState.ts'; export type ImpactMetricsControlsProps = { - selectedSeries: string; - onSeriesChange: (series: string) => void; - selectedRange: TimeRange; - onRangeChange: (range: TimeRange) => void; - beginAtZero: boolean; - onBeginAtZeroChange: (beginAtZero: boolean) => void; + formData: ChartFormState['formData']; + actions: Pick< + ChartFormState['actions'], + | 'handleSeriesChange' + | 'setSelectedRange' + | 'setBeginAtZero' + | 'setSelectedLabels' + >; metricSeries: (ImpactMetricsSeries & { name: string })[]; loading?: boolean; - selectedLabels: Record; - onLabelsChange: (labels: Record) => void; availableLabels?: ImpactMetricsLabels; }; -export const ImpactMetricsControls: FC = ( - props, -) => ( +export const ImpactMetricsControls: FC = ({ + formData, + actions, + metricSeries, + loading, + availableLabels, +}) => ( ({ display: 'flex', @@ -39,27 +43,32 @@ export const ImpactMetricsControls: FC = ( - actions.setBeginAtZero(e.target.checked)} + /> + } + label='Begin at zero' /> - {props.availableLabels && ( + {availableLabels && ( )} diff --git a/frontend/src/component/impact-metrics/ImpactMetricsControls/components/BeginAtZeroToggle.tsx b/frontend/src/component/impact-metrics/ImpactMetricsControls/components/BeginAtZeroToggle.tsx deleted file mode 100644 index 7fb884f4ba..0000000000 --- a/frontend/src/component/impact-metrics/ImpactMetricsControls/components/BeginAtZeroToggle.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { FC } from 'react'; -import { FormControlLabel, Checkbox } from '@mui/material'; - -export type BeginAtZeroToggleProps = { - value: boolean; - onChange: (beginAtZero: boolean) => void; -}; - -export const BeginAtZeroToggle: FC = ({ - value, - onChange, -}) => ( - onChange(e.target.checked)} - /> - } - label='Begin at zero' - /> -); diff --git a/frontend/src/component/impact-metrics/hooks/useChartFormState.ts b/frontend/src/component/impact-metrics/hooks/useChartFormState.ts new file mode 100644 index 0000000000..662c5003fe --- /dev/null +++ b/frontend/src/component/impact-metrics/hooks/useChartFormState.ts @@ -0,0 +1,112 @@ +import { useState, useEffect } from 'react'; +import { useImpactMetricsData } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; +import type { ChartConfig } from '../types.ts'; +import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; + +type UseChartConfigParams = { + open: boolean; + initialConfig?: ChartConfig; +}; + +export type ChartFormState = { + formData: { + title: string; + selectedSeries: string; + selectedRange: 'hour' | 'day' | 'week' | 'month'; + beginAtZero: boolean; + selectedLabels: Record; + }; + actions: { + setTitle: (title: string) => void; + setSelectedSeries: (series: string) => void; + setSelectedRange: (range: 'hour' | 'day' | 'week' | 'month') => void; + setBeginAtZero: (beginAtZero: boolean) => void; + setSelectedLabels: (labels: Record) => void; + handleSeriesChange: (series: string) => void; + getConfigToSave: () => Omit; + }; + isValid: boolean; + currentAvailableLabels: ImpactMetricsLabels | undefined; +}; + +export const useChartFormState = ({ + open, + initialConfig, +}: UseChartConfigParams): ChartFormState => { + const [title, setTitle] = useState(initialConfig?.title || ''); + const [selectedSeries, setSelectedSeries] = useState( + initialConfig?.selectedSeries || '', + ); + const [selectedRange, setSelectedRange] = useState< + 'hour' | 'day' | 'week' | 'month' + >(initialConfig?.selectedRange || 'day'); + const [beginAtZero, setBeginAtZero] = useState( + initialConfig?.beginAtZero || false, + ); + const [selectedLabels, setSelectedLabels] = useState< + Record + >(initialConfig?.selectedLabels || {}); + + const { + data: { labels: currentAvailableLabels }, + } = useImpactMetricsData( + selectedSeries + ? { + series: selectedSeries, + range: selectedRange, + } + : undefined, + ); + + useEffect(() => { + if (open && initialConfig) { + setTitle(initialConfig.title || ''); + setSelectedSeries(initialConfig.selectedSeries); + setSelectedRange(initialConfig.selectedRange); + setBeginAtZero(initialConfig.beginAtZero); + setSelectedLabels(initialConfig.selectedLabels); + } else if (open && !initialConfig) { + setTitle(''); + setSelectedSeries(''); + setSelectedRange('day'); + setBeginAtZero(false); + setSelectedLabels({}); + } + }, [open, initialConfig]); + + const handleSeriesChange = (series: string) => { + setSelectedSeries(series); + setSelectedLabels({}); + }; + + const getConfigToSave = (): Omit => ({ + title: title || undefined, + selectedSeries, + selectedRange, + beginAtZero, + selectedLabels, + }); + + const isValid = selectedSeries.length > 0; + + return { + formData: { + title, + selectedSeries, + selectedRange, + beginAtZero, + selectedLabels, + }, + actions: { + setTitle, + setSelectedSeries, + setSelectedRange, + setBeginAtZero, + setSelectedLabels, + handleSeriesChange, + getConfigToSave, + }, + isValid, + currentAvailableLabels, + }; +};