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

feat: exclusive 'Or' option in checklists #4056

Merged
merged 31 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 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
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
35 changes: 25 additions & 10 deletions editor.planx.uk/src/@planx/components/Checklist/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ import { ModalFooter } from "ui/editor/ModalFooter";
import ModalSection from "ui/editor/ModalSection";
import ModalSectionContent from "ui/editor/ModalSectionContent";
import RichTextInput from "ui/editor/RichTextInput/RichTextInput";
import ErrorWrapper from "ui/shared/ErrorWrapper";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";
import { Switch } from "ui/shared/Switch";

import { parseBaseNodeData } from "../../shared";
import { Option, parseBaseNodeData } from "../../shared";
import { ICONS } from "../../shared/icons";
import type { Checklist } from "../model";
import { toggleExpandableChecklist } from "../model";
import { ChecklistProps } from "../types";
import { Options } from "./Options";

export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
export const ChecklistEditor: React.FC<ChecklistProps> = (props) => {
const type = TYPES.Checklist;

const formik = useFormik<Checklist>({
Expand All @@ -39,7 +40,7 @@ export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
: groupedOptions?.flatMap((group) => group.children);

const filteredOptions = (sourceOptions || []).filter(
(option) => option.data.text,
(option) => option.data.text
);

const processedOptions = filteredOptions.map((option) => ({
Expand All @@ -66,21 +67,33 @@ export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
}),
},
},
processedOptions,
processedOptions
);
} else {
alert(JSON.stringify({ type, ...values, options }, null, 2));
}
},
validate: ({ options, groupedOptions, ...values }) => {
validate: ({ options, groupedOptions, allRequired, ...values }) => {
const errors: FormikErrors<FormikValues> = {};

const exclusiveOptions: Option[] | undefined = options?.filter(
(option) => option.data.exclusive
);
if (allRequired && exclusiveOptions && exclusiveOptions.length > 0) {
errors.allRequired =
'Cannot configure exclusive "or" option alongside "all required" setting';
}
// Account for flat or expandable Checklist options
options =
options || groupedOptions?.map((group) => group.children)?.flat();
if (values.fn && !options?.some((option) => option.data.val)) {
errors.fn =
"At least one option must set a data value when the checklist has a data field";
}
if (exclusiveOptions && exclusiveOptions.length > 1) {
errors.options =
"There should be a maximum of one exclusive option configured";
}
return errors;
},
});
Expand Down Expand Up @@ -160,33 +173,35 @@ export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
onChange={() =>
formik.setFieldValue(
"allRequired",
!formik.values.allRequired,
!formik.values.allRequired
)
}
label="All required"
/>
</InputRow>

<InputRow>
<Switch
checked={formik.values.neverAutoAnswer}
onChange={() =>
formik.setFieldValue(
"neverAutoAnswer",
!formik.values.neverAutoAnswer,
!formik.values.neverAutoAnswer
)
}
label="Always put to user (forgo automation)"
/>
</InputRow>
</InputGroup>
</ModalSectionContent>

<Options formik={formik} />
<ErrorWrapper error={formik.errors.options}>
<Options formik={formik} />
</ErrorWrapper>
</ModalSection>

<ModalFooter formik={formik} />
</form>
);
};

export default ChecklistComponent;
export default ChecklistEditor;
72 changes: 61 additions & 11 deletions editor.planx.uk/src/@planx/components/Checklist/Editor/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import Delete from "@mui/icons-material/Delete";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import { BaseOptionsEditor } from "@planx/components/shared/BaseOptionsEditor";
import { hasFeatureFlag } from "lib/featureFlags";
import { partition } from "lodash";
import adjust from "ramda/src/adjust";
import compose from "ramda/src/compose";
import remove from "ramda/src/remove";
import React from "react";
import { FormikHookReturn } from "types";
import ListManager from "ui/editor/ListManager/ListManager";
import ModalSectionContent from "ui/editor/ModalSectionContent";
import ErrorWrapper from "ui/shared/ErrorWrapper";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";

Expand All @@ -17,6 +21,14 @@ import type { Group } from "../model";
import ChecklistOptionsEditor from "./OptionsEditor";

export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
const [exclusiveOptions, nonExclusiveOptions]: Option[][] = partition(
formik.values.options,
(option) => option.data.exclusive
);

const exclusiveOrOptionManagerShouldRender =
hasFeatureFlag("EXCLUSIVE_OR") && nonExclusiveOptions.length;

return (
<ModalSectionContent subtitle="Options">
{formik.values.groupedOptions ? (
Expand All @@ -42,7 +54,7 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
onClick={() => {
formik.setFieldValue(
`groupedOptions`,
remove(groupIndex, 1, formik.values.groupedOptions),
remove(groupIndex, 1, formik.values.groupedOptions)
);
}}
size="large"
Expand All @@ -57,7 +69,7 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
onChange={(newOptions) => {
formik.setFieldValue(
`groupedOptions[${groupIndex}].children`,
newOptions,
newOptions
);
}}
newValue={() =>
Expand All @@ -76,7 +88,7 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
showValueField: !!formik.values.fn,
onMoveToGroup: (
movedItemIndex: number,
moveToGroupIndex: number,
moveToGroupIndex: number
) => {
const item = groupedOption.children[movedItemIndex];
formik.setFieldValue(
Expand All @@ -87,27 +99,27 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
(option: Group<Option>) => ({
...option,
children: [...option.children, item],
}),
})
),
adjust(groupIndex, (option: Group<Option>) => ({
...option,
children: remove(
movedItemIndex,
1,
option.children,
option.children
),
})),
)(formik.values.groupedOptions),
}))
)(formik.values.groupedOptions)
);
},
groups: formik.values.groupedOptions.map(
(opt: Group<Option>) => opt.title,
(opt: Group<Option>) => opt.title
),
}}
/>
</Box>
</Box>
),
)
)}
<Box mt={1}>
<Button
Expand All @@ -128,9 +140,14 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
</Box>
) : (
<ListManager
values={formik.values.options || []}
values={nonExclusiveOptions || []}
onChange={(newOptions) => {
formik.setFieldValue("options", newOptions);
const newCombinedOptions =
newOptions.length === 0
? []
: [...exclusiveOptions, ...newOptions];

formik.setFieldValue("options", newCombinedOptions);
}}
newValueLabel="add new option"
newValue={() =>
Expand All @@ -146,6 +163,39 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
editorExtraProps={{ showValueField: !!formik.values.fn }}
/>
)}
{exclusiveOrOptionManagerShouldRender ? (
<Box mt={1}>
<ErrorWrapper error={formik.errors.allRequired as string}>
<ListManager
values={exclusiveOptions || []}
onChange={(newExclusiveOptions) => {
const newCombinedOptions = [
...nonExclusiveOptions,
...newExclusiveOptions,
];
formik.setFieldValue("options", newCombinedOptions);
}}
newValueLabel='add "or" option'
maxItems={1}
disableDragAndDrop
newValue={() =>
({
data: {
text: "",
description: "",
val: "",
exclusive: true,
},
}) as Option
}
Editor={BaseOptionsEditor}
editorExtraProps={{ showValueField: !!formik.values.fn }}
/>
</ErrorWrapper>
</Box>
) : (
<></>
)}
</ModalSectionContent>
);
};
Loading
Loading