Skip to content

Commit

Permalink
EES-5402 simplified form layout and updated reducer
Browse files Browse the repository at this point in the history
  • Loading branch information
bennettstuart committed Oct 15, 2024
1 parent 22bc82d commit 39ac02c
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,51 @@ import Effect from '@common/components/Effect';
import Form from '@common/components/form/Form';
import FormFieldSelect from '@common/components/form/FormFieldSelect';
import FormProvider from '@common/components/form/FormProvider';
import { MapDataSetConfig } from '@common/modules/charts/types/chart';
import {
AxisConfiguration,
MapConfig,
MapDataSetConfig,
} from '@common/modules/charts/types/chart';
import { LegendConfiguration } from '@common/modules/charts/types/legend';
import createDataSetCategories from '@common/modules/charts/util/createDataSetCategories';
import getDataSetCategoryConfigs from '@common/modules/charts/util/getDataSetCategoryConfigs';
import { FullTableMeta } from '@common/modules/table-tool/types/fullTable';
import { TableDataResult } from '@common/services/tableBuilderService';
import parseNumber from '@common/utils/number/parseNumber';
import Yup from '@common/validation/yup';
import { isEqual } from 'lodash';
import merge from 'lodash/merge';
import React, { ReactNode, useCallback } from 'react';
import React, { ReactNode, useCallback, useMemo } from 'react';
import { AnyObject, NumberSchema, ObjectSchema } from 'yup';
import ChartBoundaryLevelsDataSetConfiguration from './ChartBoundaryLevelsDataSetConfiguration';

const formId = 'chartBoundaryLevelsConfigurationForm';

export interface ChartBoundaryLevelsFormValues {
boundaryLevel: number;
dataSetConfigs: MapDataSetConfig[];
boundaryLevel?: number;
dataSetConfigs: Omit<MapDataSetConfig, 'dataGrouping'>[];
}

interface Props {
buttons?: ReactNode;
axisMajor: AxisConfiguration;
data: TableDataResult[];
legend: LegendConfiguration;
map?: MapConfig;
meta: FullTableMeta;
dataSetConfigs: MapDataSetConfig[];
boundaryLevel: ChartOptions['boundaryLevel'];
options: ChartOptions;
onChange: (values: ChartBoundaryLevelsFormValues) => void;
onSubmit: (values: ChartBoundaryLevelsFormValues) => void;
}

export default function ChartBoundaryLevelsConfiguration({
buttons,
axisMajor,
data,
legend,
map,
meta,
dataSetConfigs,
boundaryLevel,
options,
onChange,
onSubmit,
}: Props) {
Expand All @@ -42,14 +59,13 @@ export default function ChartBoundaryLevelsConfiguration({
(values: ChartBoundaryLevelsFormValues): ChartBoundaryLevelsFormValues => {
// Use `merge` as we want to avoid potential undefined
// values from overwriting existing values
const returnValue = merge({}, boundaryLevel, values, {
return merge({}, options.boundaryLevel, values, {
boundaryLevel: values.boundaryLevel
? parseNumber(values.boundaryLevel)
: undefined,
});
return returnValue;
},
[boundaryLevel],
[options],
);

const handleChange = useCallback(
Expand All @@ -59,35 +75,73 @@ export default function ChartBoundaryLevelsConfiguration({
[normalizeValues, onChange],
);

const validationSchema = useMemo<
ObjectSchema<ChartBoundaryLevelsFormValues>
>(() => {
return Yup.object({
boundaryLevel: Yup.number()
.transform(value => (Number.isNaN(value) ? undefined : value))
.nullable()
.oneOf(meta.boundaryLevels.map(level => level.id))
.required('Choose a boundary level'),
dataSetConfigs: Yup.array()
.of(
Yup.object({
boundaryLevel: Yup.mixed<number | ''>().test(
'dataset-boundary-is-number-or-empty',
'Must be a number or an empty string',
value => !Number.isNaN(value) || value === '',
) as NumberSchema<number, AnyObject, undefined, ''>,
dataSet: Yup.object({
filters: Yup.array().required(),
}).required(),
}),
)
.required(),
});
}, [meta.boundaryLevels]);

const initialValues = useMemo<ChartBoundaryLevelsFormValues>(() => {
const dataSetCategories = createDataSetCategories({
axisConfiguration: {
...axisMajor,
groupBy: 'locations',
},
data,
meta,
});

const dataSetCategoryConfigs = getDataSetCategoryConfigs({
dataSetCategories,
legendItems: legend.items,
meta,
deprecatedDataClassification: options.dataClassification,
deprecatedDataGroups: options.dataGroups,
});

return {
boundaryLevel: options.boundaryLevel,
dataSetConfigs: dataSetCategoryConfigs.map(({ rawDataSet }) => ({
dataSet: rawDataSet,
boundaryLevel: map?.dataSetConfigs.find(config =>
isEqual(config.dataSet, rawDataSet),
)?.boundaryLevel,
})),
};
}, [axisMajor, data, meta, legend.items, map, options]);

return (
<FormProvider<ChartBoundaryLevelsFormValues>
enableReinitialize
initialValues={{
boundaryLevel,
dataSetConfigs,
}}
/* validationSchema={Yup.object({
boundaryLevel: Yup.number()
.transform(value => (Number.isNaN(value) ? undefined : value))
.nullable()
.oneOf(meta.boundaryLevels.map(level => level.id))
.required('Choose a boundary level'),
// dataSetConfigs: Yup.array(), //TODO
})} */
initialValues={initialValues}
validationSchema={validationSchema}
>
{({ formState, watch, control }) => {
{({ formState, watch }) => {
const values = watch();
return (
<Form
id={formId}
onSubmit={async () => {
onSubmit(normalizeValues(values));
await submitForms();
}}
>
<Form id={formId} onSubmit={onSubmit}>
<Effect value={values} onChange={handleChange} />
<Effect
// update watcher of all forms for sibling validation
value={{
formKey: 'boundaryLevels',
isValid: formState.isValid,
Expand All @@ -98,7 +152,7 @@ export default function ChartBoundaryLevelsConfiguration({
/>
<FormFieldSelect<ChartBoundaryLevelsFormValues>
label="Default Boundary level"
hint="Select a version of geographical data to use across any data sets that don't have a specific one set for that dataset"
hint="Select a version of geographical data to use across any data sets that don't have a specific one set"
name="boundaryLevel"
order={[]}
options={[
Expand All @@ -114,14 +168,18 @@ export default function ChartBoundaryLevelsConfiguration({
/>

<ChartBoundaryLevelsDataSetConfiguration
control={control}
dataSetConfigs={values.dataSetConfigs}
meta={meta}
/>

<ChartBuilderSaveActions
formId={formId}
formKey="boundaryLevels"
disabled={formState.isSubmitting}
onClick={async () => {
onSubmit(values);
await submitForms();
}}
>
{buttons}
</ChartBuilderSaveActions>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,143 +1,70 @@
import Button from '@common/components/Button';
import ButtonGroup from '@common/components/ButtonGroup';
import ButtonText from '@common/components/ButtonText';
import FormFieldSelect from '@common/components/form/FormFieldSelect';
import Modal from '@common/components/Modal';
import { FormFieldSelect } from '@common/components/form';
import { MapDataSetConfig } from '@common/modules/charts/types/chart';
import expandDataSet from '@common/modules/charts/util/expandDataSet';
import generateDataSetKey from '@common/modules/charts/util/generateDataSetKey';
import { FullTableMeta } from '@common/modules/table-tool/types/fullTable';
import React, { useCallback, useState } from 'react';
import { useFieldArray, UseFormReturn } from 'react-hook-form';
import type { ChartBoundaryLevelsFormValues } from './ChartBoundaryLevelsConfiguration';
import React, { useMemo } from 'react';
import generateDataSetLabel from './utils/generateDataSetLabel';

interface Props {
control: UseFormReturn<ChartBoundaryLevelsFormValues>['control'];
meta: FullTableMeta;
}

export default function ChartBoundaryLevelsDataSetConfiguration({
control,
dataSetConfigs,
meta,
}: Props) {
const [updateIndex, setUpdateIndex] = useState<number>();
const [selectValue, setSelectValue] = useState<number>();
const { fields: dataSetConfigs, update } = useFieldArray<
ChartBoundaryLevelsFormValues,
'dataSetConfigs'
>({
control,
name: 'dataSetConfigs',
});

const openFormModal = useCallback(
(i: number) => {
setUpdateIndex(i);
setSelectValue(dataSetConfigs[i].boundaryLevel);
},
[setUpdateIndex, dataSetConfigs],
);

const closeFormModal = useCallback(() => {
setUpdateIndex(undefined);
setSelectValue(undefined);
}, [setUpdateIndex]);
}: {
dataSetConfigs: Omit<MapDataSetConfig, 'dataGrouping'>[];
meta: FullTableMeta;
}) {
const mappedDataSetConfigs = useMemo(() => {
return dataSetConfigs.map(dataSetConfig => {
const expandedDataSet = expandDataSet(dataSetConfig.dataSet, meta);
const dataSetLabel = generateDataSetLabel(expandedDataSet);
const key = generateDataSetKey(dataSetConfig.dataSet);

return {
dataSetLabel,
key,
};
});
}, [meta, dataSetConfigs]);
return (
<>
<h4>Set boundary levels per data set</h4>
{/* {error && <ErrorMessage id={`${id}-error`}>{error}</ErrorMessage>} */}

{!!dataSetConfigs && dataSetConfigs.length > 1 && (
<table data-testid="chart-boundary-level-selections">
<thead>
<tr>
<th>Data set</th>
<th>Boundary</th>
<th className="govuk-!-text-align-right">Actions</th>
</tr>
</thead>
<tbody>
{dataSetConfigs.map((dataSetConfig, index) => {
const expandedDataSet = expandDataSet(
dataSetConfig.dataSet,
meta,
);
const label = generateDataSetLabel(expandedDataSet);
const key = generateDataSetKey(dataSetConfig.dataSet);

const boundaryLabel = dataSetConfig.boundaryLevel
? meta.boundaryLevels.find(
({ id }) =>
String(id) === String(dataSetConfig.boundaryLevel),
)?.label
: 'Default';

{mappedDataSetConfigs.map(({ dataSetLabel, key }, index) => {
return (
<tr key={key}>
<td>{label}</td>
<td>{boundaryLabel}</td>
<td className="govuk-!-text-align-right">
<ButtonText
onClick={() => {
openFormModal(index);
}}
>
Edit
</ButtonText>
<td>{dataSetLabel}</td>
<td>
<FormFieldSelect
label={`Boundary level for dataset: ${dataSetLabel}`}
hideLabel
name={`dataSetConfigs.${index}.boundaryLevel`}
order={[]}
options={[
{
label: 'Use default',
value: '',
},
...meta.boundaryLevels.map(({ id, label }) => ({
value: Number(id),
label,
})),
]}
/>
</td>
</tr>
);
})}
</tbody>
</table>
)}

{updateIndex !== undefined && (
<Modal open title="Edit boundary" onExit={closeFormModal}>
<FormFieldSelect
label="Boundary level"
hint="Select a version of geographical data to use for this dataset"
name="dataSetBoundaryLevel"
order={[]}
options={[
{
label: 'Default',
value: '',
},
...meta.boundaryLevels.map(({ id, label }) => ({
value: id,
label,
})),
]}
onChange={({ target }) => {
// works but dodgy type?
const { value } = target;
setSelectValue(
Number.isNaN(Number(value)) ? undefined : Number(value),
);
}}
/>
<ButtonGroup>
<Button
onClick={() => {
const { dataSet, dataGrouping } = dataSetConfigs[updateIndex];
update(updateIndex, {
dataSet,
dataGrouping,
boundaryLevel: selectValue,
});
closeFormModal();
}}
>
Done
</Button>
<Button onClick={closeFormModal} variant="secondary">
Cancel
</Button>
</ButtonGroup>
</Modal>
)}
</>
);
}
Loading

0 comments on commit 39ac02c

Please sign in to comment.