Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: split Checklist into sub-components and hooks #4088

Merged
merged 39 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a70d00f
Refactor Checklist folder into subfolders
jamdelion Dec 4, 2024
32689a2
More refactor
jamdelion Dec 4, 2024
bf201d3
Refactor editor
jamdelion Dec 4, 2024
2aabdc6
refactor optionsEditor into shared component
jamdelion Dec 4, 2024
cc9fe24
retrigger checks
jamdelion Dec 5, 2024
db6fe7f
Remove redundant file
jamdelion Dec 5, 2024
63eb8f2
Refactor to use BaseOptionseditor instead
jamdelion Dec 5, 2024
dacbb0f
consistent renaming
jamdelion Dec 5, 2024
688bff6
Tidy up
jamdelion Dec 5, 2024
0683fd5
Add exclusive-or select input and validation
jamdelion Dec 9, 2024
2498c3c
Add or styling before exclusiveOr option, plus test
jamdelion Dec 9, 2024
155dd91
Add another test
jamdelion Dec 9, 2024
76fa41c
Reorder options when exclusive or option selected
jamdelion Dec 9, 2024
3491906
Only show dropdown when more than one option
jamdelion Dec 10, 2024
2bbf8ee
Some tidying
jamdelion Dec 10, 2024
60f159d
Use a list manager for exclusive or instead of inputSelect
jamdelion Dec 10, 2024
f434f3f
Restrict exclusiveOr to one option only
jamdelion Dec 10, 2024
9e582f3
Fix tests
jamdelion Dec 10, 2024
6ccff9f
Remove reorderOptions function
jamdelion Dec 10, 2024
9ba4336
Grey out other options if exclusive option is checked
jamdelion Dec 11, 2024
919b408
Add exclusive flag to Option type so that exclusive option shows in n…
jamdelion Dec 12, 2024
56e3e75
Delete exclusive option if no normal options
jamdelion Dec 12, 2024
f892565
Merge branch 'main' into jh/actual-or-work
jamdelion Dec 12, 2024
40e7427
Feature flag it
jamdelion Dec 12, 2024
1203531
Resolve some code nits
jamdelion Dec 17, 2024
4f27259
Disable drag and drop and tidy code slightly
jamdelion Dec 17, 2024
0b61774
Refactor to use getExclusiveOptions function
jamdelion Dec 17, 2024
687ad69
use lodash partition
jamdelion Dec 17, 2024
3d3c9c7
Use partition on public side
jamdelion Dec 17, 2024
9585660
Refactor changeCheckbox function
jamdelion Dec 17, 2024
ef50330
add validation for allRequired and maxItems
jamdelion Dec 18, 2024
7515cdd
Split out NonExclusiveChecklistItems
jamdelion Dec 18, 2024
e2bd43c
Split out visible checklist and exclusive checklist option
jamdelion Dec 18, 2024
c241b44
Add custom hooks
jamdelion Dec 18, 2024
55a2367
Refactor into more hooks
jamdelion Dec 18, 2024
79b9f86
sort imports
jamdelion Dec 18, 2024
3987fe4
Merge branch 'main' into jh/refactor-checklist-more
jamdelion Dec 18, 2024
9063715
fix imports
jamdelion Dec 18, 2024
5b3eaaf
Resolve nits
jamdelion Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 4 additions & 242 deletions editor.planx.uk/src/@planx/components/Checklist/Public/Public.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,10 @@
import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import { visuallyHidden } from "@mui/utils";
import {
checklistValidationSchema,
getFlatOptions,
getLayout,
} from "@planx/components/Checklist/model";
import ImageButton from "@planx/components/shared/Buttons/ImageButton";
import Card from "@planx/components/shared/Preview/Card";
import { CardHeader } from "@planx/components/shared/Preview/CardHeader/CardHeader";
import { getIn, useFormik } from "formik";
import { partition } from "lodash";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { useState } from "react";
import { ExpandableList, ExpandableListItem } from "ui/public/ExpandableList";
import FormWrapper from "ui/public/FormWrapper";
import FullWidthWrapper from "ui/public/FullWidthWrapper";
import ChecklistItem from "ui/shared/ChecklistItem/ChecklistItem";
import ErrorWrapper from "ui/shared/ErrorWrapper";
import { object } from "yup";
import React from "react";

import { Option } from "../../shared";
import { Props } from "../types";
jamdelion marked this conversation as resolved.
Show resolved Hide resolved
import { AutoAnsweredChecklist } from "./AutoAnsweredChecklist";
import {
getInitialExpandedGroups,
toggleInArray,
toggleNonExclusiveCheckbox,
} from "./helpers";

export enum ChecklistLayout {
Basic,
Grouped,
Images,
}
import { AutoAnsweredChecklist } from "./components/AutoAnsweredChecklist";
import { VisibleChecklist } from "./components/VisibleChecklist";

const ChecklistComponent: React.FC<Props> = (props) => {
const autoAnswerableOptions = useStore(
Expand All @@ -56,212 +26,4 @@ const ChecklistComponent: React.FC<Props> = (props) => {
return <VisibleChecklist {...props} />;
};

const VisibleChecklist: React.FC<Props> = (props) => {
const {
description = "",
groupedOptions,
handleSubmit,
howMeasured,
info,
options,
policyRef,
text,
img,
previouslySubmittedData,
id,
} = props;

const formik = useFormik<{ checked: Array<string> }>({
initialValues: {
checked: previouslySubmittedData?.answers || [],
},
onSubmit: (values) => {
handleSubmit?.({ answers: values.checked });
},
validateOnBlur: false,
validateOnChange: false,
validationSchema: object({
checked: checklistValidationSchema(props),
}),
});

const setCheckedFieldValue = (optionIds: string[]) => {
const sortedCheckedIds = sortCheckedIds(optionIds);
formik.setFieldValue("checked", sortedCheckedIds);
};

const initialExpandedGroups = getInitialExpandedGroups(
groupedOptions,
previouslySubmittedData
);

const [expandedGroups, setExpandedGroups] = useState<Array<number>>(
initialExpandedGroups
);

const layout = getLayout({ options, groupedOptions });
const flatOptions = getFlatOptions({ options, groupedOptions });

const sortCheckedIds = (ids: string[]): string[] => {
const originalIds = flatOptions.map((cb) => cb.id);
return ids.sort((a, b) => originalIds.indexOf(a) - originalIds.indexOf(b));
};

const [exclusiveOptions, nonExclusiveOptions]: Option[][] = partition(
options,
(option) => option.data.exclusive
);

const exclusiveOrOption = exclusiveOptions[0];

const exclusiveOptionIsChecked =
exclusiveOrOption && formik.values.checked.includes(exclusiveOrOption.id);

const toggleExclusiveCheckbox = (checkboxId: string) => {
return exclusiveOptionIsChecked ? [] : [checkboxId];
};

const changeCheckbox = (id: string) => () => {
const currentCheckedIds = formik.values.checked;

const currentCheckboxIsExclusiveOption =
exclusiveOrOption && id === exclusiveOrOption.id;

if (currentCheckboxIsExclusiveOption) {
const newCheckedIds = toggleExclusiveCheckbox(id);
setCheckedFieldValue(newCheckedIds);
return;
}
const newCheckedIds = toggleNonExclusiveCheckbox(
id,
currentCheckedIds,
exclusiveOrOption
);
setCheckedFieldValue(newCheckedIds);
};

return (
<Card handleSubmit={formik.handleSubmit} isValid>
<CardHeader
title={text}
description={description}
info={info}
policyRef={policyRef}
howMeasured={howMeasured}
img={img}
/>
<FullWidthWrapper>
<ErrorWrapper error={getIn(formik.errors, "checked")} id={id}>
<Grid
container
spacing={layout === ChecklistLayout.Images ? 2 : 0}
component="fieldset"
>
<legend style={visuallyHidden}>{text}</legend>
{nonExclusiveOptions.map((option) =>
layout === ChecklistLayout.Basic ? (
<FormWrapper key={option.id}>
<Grid item xs={12} key={option.data.text}>
<ChecklistItem
onChange={changeCheckbox(option.id)}
label={option.data.text}
id={option.id}
checked={
formik.values.checked.includes(option.id) &&
!exclusiveOptionIsChecked
}
/>
</Grid>
</FormWrapper>
) : (
<Grid
item
xs={12}
sm={6}
contentWrap={4}
key={option.data.text}
>
<ImageButton
title={option.data.text}
id={option.id}
img={option.data.img}
selected={formik.values.checked.includes(option.id)}
onClick={changeCheckbox(option.id)}
checkbox
/>
</Grid>
)
)}
{exclusiveOrOption && (
<FormWrapper key={exclusiveOrOption.id}>
<Grid item xs={12} key={exclusiveOrOption.data.text}>
<Typography width={36} display="flex" justifyContent="center">
or
</Typography>

<ChecklistItem
onChange={changeCheckbox(exclusiveOrOption.id)}
label={exclusiveOrOption.data.text}
id={exclusiveOrOption.id}
checked={formik.values.checked.includes(
exclusiveOrOption.id
)}
/>
</Grid>
</FormWrapper>
)}

{groupedOptions && (
<FormWrapper>
<Grid item xs={12}>
<ExpandableList>
{groupedOptions.map((group, index) => {
const isExpanded = expandedGroups.includes(index);
return (
<ExpandableListItem
key={index}
expanded={isExpanded}
onToggle={() => {
setExpandedGroups((previous) =>
toggleInArray(index, previous)
);
}}
headingId={`group-${index}-heading`}
groupId={`group-${index}-content`}
title={group.title}
>
<Box
pt={0.5}
pb={2}
aria-labelledby={`group-${index}-heading`}
id={`group-${index}-content`}
data-testid={`group-${index}${
isExpanded ? "-expanded" : ""
}`}
>
{group.children.map((option) => (
<ChecklistItem
onChange={changeCheckbox(option.id)}
key={option.data.text}
label={option.data.text}
id={option.id}
checked={formik.values.checked.includes(
option.id
)}
/>
))}
</Box>
</ExpandableListItem>
);
})}
</ExpandableList>
</Grid>
</FormWrapper>
)}
</Grid>
</ErrorWrapper>
</FullWidthWrapper>
</Card>
);
};
export default ChecklistComponent;
export default ChecklistComponent;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from "react";

import { Props } from "../types";
import { Props } from "../../types";

// An auto-answered Checklist won't be seen by the user, but still leaves a breadcrumb
export const AutoAnsweredChecklist: React.FC<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Grid from "@mui/material/Grid";
import { Option } from "@planx/components/shared";
import ImageButton from "@planx/components/shared/Buttons/ImageButton";
import { FormikProps } from "formik";
import React from "react";
import FormWrapper from "ui/public/FormWrapper";
import ChecklistItem from "ui/shared/ChecklistItem/ChecklistItem";

import { ChecklistLayout } from "./VisibleChecklist";

interface NonExclusiveChecklistItemsProps {
jamdelion marked this conversation as resolved.
Show resolved Hide resolved
nonExclusiveOptions: Option[];
layout: ChecklistLayout;
changeCheckbox: (id: string) => () => void;
formik: FormikProps<{ checked: Array<string> }>;
exclusiveOptionIsChecked: boolean;
}

export const NonExclusiveChecklistItems = ({
jamdelion marked this conversation as resolved.
Show resolved Hide resolved
nonExclusiveOptions,
layout,
changeCheckbox,
formik,
exclusiveOptionIsChecked,
}: NonExclusiveChecklistItemsProps) => (
<>
{nonExclusiveOptions.map((option: Option) =>
layout === ChecklistLayout.Basic ? (
<FormWrapper key={option.id}>
<Grid item xs={12} key={option.data.text}>
<ChecklistItem
onChange={changeCheckbox(option.id)}
label={option.data.text}
id={option.id}
checked={
formik.values.checked.includes(option.id) &&
!exclusiveOptionIsChecked
}
/>
</Grid>
</FormWrapper>
) : (
<Grid item xs={12} sm={6} contentWrap={4} key={option.data.text}>
<ImageButton
title={option.data.text}
id={option.id}
img={option.data.img}
selected={formik.values.checked.includes(option.id)}
onClick={changeCheckbox(option.id)}
checkbox
/>
</Grid>
)
)}
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import { FormikProps } from "formik";
import React from "react";
import FormWrapper from "ui/public/FormWrapper";
import ChecklistItem from "ui/shared/ChecklistItem/ChecklistItem";

import { Option } from "../../../shared";

export const ExclusiveChecklistItem = ({
exclusiveOrOption,
changeCheckbox,
formik,
}: {
exclusiveOrOption: Option;
changeCheckbox: (id: string) => () => void;
formik: FormikProps<{ checked: Array<string> }>;
}) => (
<FormWrapper key={exclusiveOrOption.id}>
<Grid item xs={12} key={exclusiveOrOption.data.text}>
<Typography width={36} display="flex" justifyContent="center">
or
</Typography>

jamdelion marked this conversation as resolved.
Show resolved Hide resolved
<ChecklistItem
onChange={changeCheckbox(exclusiveOrOption.id)}
label={exclusiveOrOption.data.text}
id={exclusiveOrOption.id}
checked={formik.values.checked.includes(exclusiveOrOption.id)}
/>
</Grid>
</FormWrapper>
);
Loading
Loading