From 15ab3e569033e0303b33b89b3bb984cf93a2d815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 20 Jun 2024 14:49:22 +0100 Subject: [PATCH 01/12] feat: Make checklist validation shared --- .../@planx/components/Checklist/Public.tsx | 69 +++++-------------- .../src/@planx/components/Checklist/model.ts | 46 ++++++++++++- .../src/@planx/components/List/model.ts | 25 ++++++- 3 files changed, 84 insertions(+), 56 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx index 768bc86f73..1d911a8603 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx @@ -1,7 +1,7 @@ import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; import { visuallyHidden } from "@mui/utils"; -import type { Checklist, Group } from "@planx/components/Checklist/model"; +import { checklistValidationSchema, getFlatOptions, type Checklist, type Group } 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"; @@ -12,10 +12,10 @@ import FormWrapper from "ui/public/FormWrapper"; import FullWidthWrapper from "ui/public/FullWidthWrapper"; import ChecklistItem from "ui/shared/ChecklistItem"; import ErrorWrapper from "ui/shared/ErrorWrapper"; -import { array, object } from "yup"; import { Option } from "../shared"; import type { PublicProps } from "../ui"; +import { object } from "yup"; export type Props = PublicProps; @@ -31,36 +31,21 @@ function toggleInArray(value: T, arr: Array): Array { : [...arr, value]; } -function getFlatOptions({ - options, - groupedOptions, -}: { - options: Checklist["options"]; - groupedOptions: Checklist["groupedOptions"]; -}) { - if (options) { - return options; - } - if (groupedOptions) { - return groupedOptions.flatMap((group) => group.children); - } - return []; -} +const ChecklistComponent: React.FC = (props) => { + const { + description = "", + groupedOptions, + handleSubmit, + howMeasured, + info, + options, + policyRef, + text, + img, + previouslySubmittedData, + id, + } = props; -const ChecklistComponent: React.FC = ({ - allRequired, - description = "", - groupedOptions, - handleSubmit, - howMeasured, - info, - options, - policyRef, - text, - img, - previouslySubmittedData, - id, -}) => { const formik = useFormik<{ checked: Array }>({ initialValues: { checked: previouslySubmittedData?.answers || [], @@ -71,27 +56,7 @@ const ChecklistComponent: React.FC = ({ validateOnBlur: false, validateOnChange: false, validationSchema: object({ - checked: array() - .required() - .test({ - name: "atLeastOneChecked", - message: "Select at least one option", - test: (checked?: Array) => { - return Boolean(checked && checked.length > 0); - }, - }) - .test({ - name: "notAllChecked", - message: "All options must be checked", - test: (checked?: Array) => { - if (!allRequired) { - return true; - } - const flatOptions = getFlatOptions({ options, groupedOptions }); - const allChecked = checked && checked.length === flatOptions.length; - return Boolean(allChecked); - }, - }), + checked: checklistValidationSchema(props), }), }); diff --git a/editor.planx.uk/src/@planx/components/Checklist/model.ts b/editor.planx.uk/src/@planx/components/Checklist/model.ts index 843be32901..7f330c35c1 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/model.ts +++ b/editor.planx.uk/src/@planx/components/Checklist/model.ts @@ -1,4 +1,5 @@ import { MoreInformation, Option } from "../shared"; +import { array } from "yup"; export interface Group { title: string; @@ -21,7 +22,7 @@ interface ChecklistExpandableProps { } export const toggleExpandableChecklist = ( - checklist: ChecklistExpandableProps, + checklist: ChecklistExpandableProps ): ChecklistExpandableProps => { if (checklist.options !== undefined && checklist.options.length > 0) { return { @@ -56,3 +57,46 @@ export const toggleExpandableChecklist = ( }; } }; + +export const getFlatOptions = ({ + options, + groupedOptions, +}: { + options: Checklist["options"]; + groupedOptions: Checklist["groupedOptions"]; +}) => { + if (options) { + return options; + } + if (groupedOptions) { + return groupedOptions.flatMap((group) => group.children); + } + return []; +}; + +export const checklistValidationSchema = ({ + allRequired, + options, + groupedOptions, +}: Checklist) => + array() + .required() + .test({ + name: "atLeastOneChecked", + message: "Select at least one option", + test: (checked?: Array) => { + return Boolean(checked && checked.length > 0); + }, + }) + .test({ + name: "notAllChecked", + message: "All options must be checked", + test: (checked?: Array) => { + if (!allRequired) { + return true; + } + const flatOptions = getFlatOptions({ options, groupedOptions }); + const allChecked = checked && checked.length === flatOptions.length; + return Boolean(allChecked); + }, + }); diff --git a/editor.planx.uk/src/@planx/components/List/model.ts b/editor.planx.uk/src/@planx/components/List/model.ts index 65adc60975..dc94adc51b 100644 --- a/editor.planx.uk/src/@planx/components/List/model.ts +++ b/editor.planx.uk/src/@planx/components/List/model.ts @@ -8,6 +8,7 @@ import { userDataSchema as textInputValidationSchema, } from "../TextInput/model"; import { SCHEMAS } from "./Editor"; +import { checklistValidationSchema } from "../Checklist/model"; /** * Simplified custom QuestionInput @@ -20,6 +21,12 @@ interface QuestionInput { options: Option[]; } +interface ChecklistInput { + title: string; + description?: string; + options: Option[]; +} + /** * As above, we need a simplified validation schema for QuestionsInputs */ @@ -42,12 +49,17 @@ export type QuestionField = { type: "question"; data: QuestionInput & { fn: string }; }; +export type ChecklistField = { + type: "checklist"; + required?: true; + data: ChecklistInput & { fn: string }; +}; /** * Represents the input types available in the List component * Existing models are used to allow to us to re-use existing components, maintaining consistend UX/UI */ -export type Field = TextField | NumberField | QuestionField; +export type Field = TextField | NumberField | QuestionField | ChecklistField; /** * Models the form displayed to the user @@ -59,7 +71,7 @@ export interface Schema { max?: number; } -export type UserResponse = Record; +export type UserResponse = Record; export type UserData = { userData: UserResponse[] }; @@ -100,6 +112,9 @@ const generateValidationSchemaForFields = ( case "question": fieldSchemas[data.fn] = questionInputValidationSchema(data); break; + case "checklist": + fieldSchemas[data.fn] = checklistValidationSchema(data); + break; } }); @@ -125,6 +140,10 @@ export const generateValidationSchema = (schema: Schema) => { export const generateInitialValues = (schema: Schema): UserResponse => { const initialValues: UserResponse = {}; - schema.fields.forEach((field) => (initialValues[field.data.fn] = "")); + schema.fields.forEach((field) => { + field.type === "checklist" + ? (initialValues[field.data.fn] = []) + : (initialValues[field.data.fn] = ""); + }); return initialValues; }; From 36b6f875eece4df21d1dcc4c4d73821ecbdf9be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 20 Jun 2024 14:51:21 +0100 Subject: [PATCH 02/12] feat: Import first schema with checklists for testing --- .../@planx/components/Checklist/Public.tsx | 9 +++- .../src/@planx/components/Checklist/model.ts | 5 ++- .../src/@planx/components/List/Editor.tsx | 6 ++- .../src/@planx/components/List/model.ts | 2 +- .../schemas/ResidentialUnits/GLA/Removed.ts | 44 +++++++++---------- 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx index 1d911a8603..6383d8abcd 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx @@ -1,7 +1,12 @@ import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; import { visuallyHidden } from "@mui/utils"; -import { checklistValidationSchema, getFlatOptions, type Checklist, type Group } from "@planx/components/Checklist/model"; +import { + type Checklist, + checklistValidationSchema, + getFlatOptions, + type Group, +} 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"; @@ -12,10 +17,10 @@ import FormWrapper from "ui/public/FormWrapper"; import FullWidthWrapper from "ui/public/FullWidthWrapper"; import ChecklistItem from "ui/shared/ChecklistItem"; import ErrorWrapper from "ui/shared/ErrorWrapper"; +import { object } from "yup"; import { Option } from "../shared"; import type { PublicProps } from "../ui"; -import { object } from "yup"; export type Props = PublicProps; diff --git a/editor.planx.uk/src/@planx/components/Checklist/model.ts b/editor.planx.uk/src/@planx/components/Checklist/model.ts index 7f330c35c1..7287fe9364 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/model.ts +++ b/editor.planx.uk/src/@planx/components/Checklist/model.ts @@ -1,6 +1,7 @@ -import { MoreInformation, Option } from "../shared"; import { array } from "yup"; +import { MoreInformation, Option } from "../shared"; + export interface Group { title: string; children: Array; @@ -22,7 +23,7 @@ interface ChecklistExpandableProps { } export const toggleExpandableChecklist = ( - checklist: ChecklistExpandableProps + checklist: ChecklistExpandableProps, ): ChecklistExpandableProps => { if (checklist.options !== undefined && checklist.options.length > 0) { return { diff --git a/editor.planx.uk/src/@planx/components/List/Editor.tsx b/editor.planx.uk/src/@planx/components/List/Editor.tsx index 6b85d49b87..4e6d6f5544 100644 --- a/editor.planx.uk/src/@planx/components/List/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/List/Editor.tsx @@ -20,10 +20,10 @@ import { CommunalSpaceGLA } from "./schemas/GLA/CommunalSpace"; import { ExistingAndProposedUsesGLA } from "./schemas/GLA/ExistingAndProposedUses"; import { OpenSpaceGLA } from "./schemas/GLA/OpenSpace"; import { ProtectedSpaceGLA } from "./schemas/GLA/ProtectedSpace"; -import { Zoo } from "./schemas/mocks/Zoo"; import { ResidentialUnitsExisting } from "./schemas/ResidentialUnits/Existing"; import { ResidentialUnitsGLAGained } from "./schemas/ResidentialUnits/GLA/Gained"; import { ResidentialUnitsGLALost } from "./schemas/ResidentialUnits/GLA/Lost"; +import { ResidentialUnitsGLARemoved } from "./schemas/ResidentialUnits/GLA/Removed"; import { ResidentialUnitsProposed } from "./schemas/ResidentialUnits/Proposed"; type Props = EditorProps; @@ -36,6 +36,10 @@ export const SCHEMAS = [ schema: ResidentialUnitsGLAGained, }, { name: "Residential units (GLA) - Lost", schema: ResidentialUnitsGLALost }, + { + name: "Residential units (GLA) - Removed", + schema: ResidentialUnitsGLARemoved, + }, { name: "Non-residential floorspace", schema: NonResidentialFloorspace }, { name: "Existing and proposed uses (GLA)", diff --git a/editor.planx.uk/src/@planx/components/List/model.ts b/editor.planx.uk/src/@planx/components/List/model.ts index dc94adc51b..759aae527b 100644 --- a/editor.planx.uk/src/@planx/components/List/model.ts +++ b/editor.planx.uk/src/@planx/components/List/model.ts @@ -1,6 +1,7 @@ import { cloneDeep } from "lodash"; import { array, BaseSchema, object, ObjectSchema, string } from "yup"; +import { checklistValidationSchema } from "../Checklist/model"; import { NumberInput, numberInputValidationSchema } from "../NumberInput/model"; import { MoreInformation, Option, parseMoreInformation } from "../shared"; import { @@ -8,7 +9,6 @@ import { userDataSchema as textInputValidationSchema, } from "../TextInput/model"; import { SCHEMAS } from "./Editor"; -import { checklistValidationSchema } from "../Checklist/model"; /** * Simplified custom QuestionInput diff --git a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts index 4c5d4ecd94..f8f16974ba 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts @@ -69,28 +69,28 @@ export const ResidentialUnitsGLARemoved: Schema = { ], }, }, - // { - // type: "checklist", // @todo - // data: { - // title: "Is this unit compliant with any of the following?", - // fn: "compliance", - // options: [ - // { - // id: "m42", - // data: { text: "Part M4(2) of the Building Regulations 2010" }, - // }, - // { - // id: "m432a", - // data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, - // }, - // { - // id: "m432b", - // data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, - // }, - // { id: "none", data: { text: "None of these" } }, - // ], - // }, - // }, + { + type: "checklist", + data: { + title: "Is this unit compliant with any of the following?", + fn: "compliance", + options: [ + { + id: "m42", + data: { text: "Part M4(2) of the Building Regulations 2010" }, + }, + { + id: "m432a", + data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, + }, + { + id: "m432b", + data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, + }, + { id: "none", data: { text: "None of these" } }, + ], + }, + }, { type: "question", data: { From fdbec2e096840f209d0d4469a6dc3231523c7679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 20 Jun 2024 14:51:51 +0100 Subject: [PATCH 03/12] feat: Handle arrays of responses (slightly crudely for now) --- .../src/@planx/components/List/utils.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/List/utils.ts b/editor.planx.uk/src/@planx/components/List/utils.ts index 0e0d0dcdc3..c9af6b1f0a 100644 --- a/editor.planx.uk/src/@planx/components/List/utils.ts +++ b/editor.planx.uk/src/@planx/components/List/utils.ts @@ -6,7 +6,7 @@ import { QuestionField, Schema, UserResponse } from "./model"; * @param schema - the Schema object * @returns string - the `text` for the given value `val`, or the original value */ -export function formatSchemaDisplayValue(value: string, schema: Schema) { +export function formatSchemaDisplayValue(value: string | string[], schema: Schema) { const questionFields = schema.fields.filter( (field) => field.type === "question", ) as QuestionField[]; @@ -32,7 +32,11 @@ export function sumIdenticalUnits( passportData: Record, ): number { let sum = 0; - passportData[`${fn}`].map((item) => (sum += parseInt(item?.identicalUnits))); + passportData[`${fn}`].map((item) => { + if (typeof item?.identicalUnits === "string") { + sum += parseInt(item?.identicalUnits) + } + }); return sum; } @@ -59,8 +63,11 @@ export function sumIdenticalUnitsByDevelopmentType( notKnown: 0, }; passportData[`${fn}`].map( - (item) => - (baseSums[`${item?.development}`] += parseInt(item?.identicalUnits)), + (item) => { + if (typeof item?.identicalUnits === "string") { + (baseSums[`${item?.development}`] += parseInt(item?.identicalUnits)) + } + } ); // Format property names for passport, and filter out any entries with default sum = 0 From 727f5f93c28df705c3ed1aef4fa5b755ccadfb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 20 Jun 2024 14:54:00 +0100 Subject: [PATCH 04/12] feat: Basic ChecklistFieldInput setup - Still getting buggy behaviours with checkboxes, but not labels --- .../@planx/components/List/Public/Fields.tsx | 64 ++++++++++++++++++- .../@planx/components/List/Public/index.tsx | 3 + 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx b/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx index a5313d3cfb..b9d359636c 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx @@ -3,7 +3,6 @@ import FormControl from "@mui/material/FormControl"; import FormLabel from "@mui/material/FormLabel"; import MenuItem from "@mui/material/MenuItem"; import RadioGroup from "@mui/material/RadioGroup"; -import { Option } from "@planx/components/shared"; import React from "react"; import SelectInput from "ui/editor/SelectInput"; import InputLabel from "ui/public/InputLabel"; @@ -13,9 +12,12 @@ import InputRowLabel from "ui/shared/InputRowLabel"; import { DESCRIPTION_TEXT, ERROR_MESSAGE } from "../../shared/constants"; import BasicRadio from "../../shared/Radio/BasicRadio"; -import type { NumberField, QuestionField, TextField } from "../model"; +import type { ChecklistField, NumberField, QuestionField, TextField } from "../model"; import { useListContext } from "./Context"; import { get } from "lodash"; +import Grid from "@mui/material/Grid"; +import { visuallyHidden } from "@mui/utils"; +import ChecklistItem from "ui/shared/ChecklistItem"; type Props = T & { id: string }; @@ -183,3 +185,61 @@ export const SelectFieldInput: React.FC> = (props) => { ); }; + +export const ChecklistFieldInput: React.FC> = (props) => { + const { formik, activeIndex } = useListContext(); + const { id, data: { options, title, fn } } = props; + + const changeCheckbox = + (id: string) => + async (event: React.MouseEvent | undefined) => { + + // console.log("Clicked id: ", id) + + // console.log("Before: ", formik.values.userData[activeIndex][fn]) + let newCheckedIds; + + if (formik.values.userData[activeIndex][fn].includes(id)) { + // console.log("Already in list") + newCheckedIds = (formik.values.userData[activeIndex][fn] as string[]).filter((x) => x !== id); + // console.log("After: ", newCheckedIds) + } else { + // console.log("New") + newCheckedIds = [...formik.values.userData[activeIndex][fn], id]; + // console.log("After: ", newCheckedIds) + } + + await formik.setFieldValue( + `userData[${activeIndex}]['${fn}']`, + newCheckedIds + ); + + // console.log(formik.values.userData[activeIndex][fn]) + }; + + return ( + + + + {title} + { + options.map((option) => + + ) + } + + + + ) +} \ No newline at end of file diff --git a/editor.planx.uk/src/@planx/components/List/Public/index.tsx b/editor.planx.uk/src/@planx/components/List/Public/index.tsx index 66efdcd9d9..0f7cbbdf91 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/index.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/index.tsx @@ -20,6 +20,7 @@ import type { Field, List } from "../model"; import { formatSchemaDisplayValue } from "../utils"; import { ListProvider, useListContext } from "./Context"; import { + ChecklistFieldInput, NumberFieldInput, RadioFieldInput, SelectFieldInput, @@ -59,6 +60,8 @@ const InputField: React.FC = (props) => { return ; } return ; + case "checklist": + return ; } }; From d32745187efc2d8b3b31a8819f4f5bd2ef4f6bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 20 Jun 2024 14:56:54 +0100 Subject: [PATCH 05/12] fix: White backgrounds for checkboxes --- editor.planx.uk/src/ui/shared/Checkbox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor.planx.uk/src/ui/shared/Checkbox.tsx b/editor.planx.uk/src/ui/shared/Checkbox.tsx index 5637178092..37969b1e90 100644 --- a/editor.planx.uk/src/ui/shared/Checkbox.tsx +++ b/editor.planx.uk/src/ui/shared/Checkbox.tsx @@ -11,7 +11,7 @@ const Root = styled(Box)(({ theme }) => ({ height: 40, borderColor: theme.palette.text.primary, border: "2px solid", - background: "transparent", + backgroundColor: theme.palette.common.white, "&:focus-within": borderedFocusStyle, })); From 36d3674a24b156d45c6056dbbeb1b0c92be23930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Tue, 25 Jun 2024 11:53:29 +0100 Subject: [PATCH 06/12] test: Fix existing test cases - Revisit this when taking a closer look at how best to handle checklist responses --- editor.planx.uk/src/@planx/components/List/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/List/utils.ts b/editor.planx.uk/src/@planx/components/List/utils.ts index c9af6b1f0a..347741ddbe 100644 --- a/editor.planx.uk/src/@planx/components/List/utils.ts +++ b/editor.planx.uk/src/@planx/components/List/utils.ts @@ -33,7 +33,7 @@ export function sumIdenticalUnits( ): number { let sum = 0; passportData[`${fn}`].map((item) => { - if (typeof item?.identicalUnits === "string") { + if (!Array.isArray(item?.identicalUnits)) { sum += parseInt(item?.identicalUnits) } }); @@ -64,7 +64,7 @@ export function sumIdenticalUnitsByDevelopmentType( }; passportData[`${fn}`].map( (item) => { - if (typeof item?.identicalUnits === "string") { + if (!Array.isArray(item?.identicalUnits)) { (baseSums[`${item?.development}`] += parseInt(item?.identicalUnits)) } } From d0e1a82a36e1a424005238f26c73c5fdec1cc97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Tue, 25 Jun 2024 15:15:48 +0100 Subject: [PATCH 07/12] feat: Handle list display in InactiveCard --- .../@planx/components/List/Public/Fields.tsx | 103 ++++++++++-------- .../@planx/components/List/Public/index.tsx | 2 +- .../components/List/{utils.ts => utils.tsx} | 62 +++++++---- 3 files changed, 95 insertions(+), 72 deletions(-) rename editor.planx.uk/src/@planx/components/List/{utils.ts => utils.tsx} (74%) diff --git a/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx b/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx index b9d359636c..7139498643 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx @@ -1,23 +1,29 @@ import Box from "@mui/material/Box"; import FormControl from "@mui/material/FormControl"; import FormLabel from "@mui/material/FormLabel"; +import Grid from "@mui/material/Grid"; import MenuItem from "@mui/material/MenuItem"; import RadioGroup from "@mui/material/RadioGroup"; +import { visuallyHidden } from "@mui/utils"; +import { getIn } from "formik"; import React from "react"; import SelectInput from "ui/editor/SelectInput"; import InputLabel from "ui/public/InputLabel"; +import ChecklistItem from "ui/shared/ChecklistItem"; import ErrorWrapper from "ui/shared/ErrorWrapper"; import Input from "ui/shared/Input"; import InputRowLabel from "ui/shared/InputRowLabel"; import { DESCRIPTION_TEXT, ERROR_MESSAGE } from "../../shared/constants"; import BasicRadio from "../../shared/Radio/BasicRadio"; -import type { ChecklistField, NumberField, QuestionField, TextField } from "../model"; +import type { + ChecklistField, + NumberField, + QuestionField, + TextField, +} from "../model"; import { useListContext } from "./Context"; import { get } from "lodash"; -import Grid from "@mui/material/Grid"; -import { visuallyHidden } from "@mui/utils"; -import ChecklistItem from "ui/shared/ChecklistItem"; type Props = T & { id: string }; @@ -188,58 +194,61 @@ export const SelectFieldInput: React.FC> = (props) => { export const ChecklistFieldInput: React.FC> = (props) => { const { formik, activeIndex } = useListContext(); - const { id, data: { options, title, fn } } = props; + const { + id, + data: { options, title, fn }, + } = props; const changeCheckbox = (id: string) => - async (event: React.MouseEvent | undefined) => { - - // console.log("Clicked id: ", id) - - // console.log("Before: ", formik.values.userData[activeIndex][fn]) - let newCheckedIds; - - if (formik.values.userData[activeIndex][fn].includes(id)) { - // console.log("Already in list") - newCheckedIds = (formik.values.userData[activeIndex][fn] as string[]).filter((x) => x !== id); - // console.log("After: ", newCheckedIds) - } else { - // console.log("New") - newCheckedIds = [...formik.values.userData[activeIndex][fn], id]; - // console.log("After: ", newCheckedIds) - } - - await formik.setFieldValue( - `userData[${activeIndex}]['${fn}']`, - newCheckedIds - ); - - // console.log(formik.values.userData[activeIndex][fn]) - }; + async ( + event: React.MouseEvent | undefined, + ) => { + // console.log("Clicked id: ", id) + + // console.log("Before: ", formik.values.userData[activeIndex][fn]) + let newCheckedIds; + + if (formik.values.userData[activeIndex][fn].includes(id)) { + // console.log("Already in list") + newCheckedIds = ( + formik.values.userData[activeIndex][fn] as string[] + ).filter((x) => x !== id); + // console.log("After: ", newCheckedIds) + } else { + // console.log("New") + newCheckedIds = [...formik.values.userData[activeIndex][fn], id]; + // console.log("After: ", newCheckedIds) + } + + await formik.setFieldValue( + `userData[${activeIndex}]['${fn}']`, + newCheckedIds, + ); + + // console.log(formik.values.userData[activeIndex][fn]) + }; return ( - - - + - {title} - { - options.map((option) => + + {title} + {options.map((option) => ( - ) - } - - + ))} + + - ) -} \ No newline at end of file + ); +}; diff --git a/editor.planx.uk/src/@planx/components/List/Public/index.tsx b/editor.planx.uk/src/@planx/components/List/Public/index.tsx index 0f7cbbdf91..c0d3136c29 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/index.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/index.tsx @@ -129,7 +129,7 @@ const InactiveListCard: React.FC<{ {formatSchemaDisplayValue( formik.values.userData[i][field.data.fn], - schema, + schema.fields[j], )} diff --git a/editor.planx.uk/src/@planx/components/List/utils.ts b/editor.planx.uk/src/@planx/components/List/utils.tsx similarity index 74% rename from editor.planx.uk/src/@planx/components/List/utils.ts rename to editor.planx.uk/src/@planx/components/List/utils.tsx index 347741ddbe..db3d58b2ba 100644 --- a/editor.planx.uk/src/@planx/components/List/utils.ts +++ b/editor.planx.uk/src/@planx/components/List/utils.tsx @@ -1,24 +1,40 @@ -import { QuestionField, Schema, UserResponse } from "./model"; +import React from "react"; + +import { Field, UserResponse } from "./model"; /** - * In the case of "question" fields, ensure the displayed value reflects option "text", rather than "val" as recorded in passport + * In the case of "question" and "checklist" fields, ensure the displayed value reflects option "text", rather than "val" as recorded in passport * @param value - the `val` or `text` of an Option defined in the schema's fields - * @param schema - the Schema object - * @returns string - the `text` for the given value `val`, or the original value + * @param field - the Field object + * @returns string | React.JSX.Element - the `text` for the given value `val`, or the original value */ -export function formatSchemaDisplayValue(value: string | string[], schema: Schema) { - const questionFields = schema.fields.filter( - (field) => field.type === "question", - ) as QuestionField[]; - const matchingField = questionFields?.find((field) => - field.data.options.some((option) => option.data.val === value), - ); - const matchingOption = matchingField?.data.options.find( - (option) => option.data.val === value, - ); - - // If we found a "val" match, return its text, else just return the value as passed in - return matchingOption?.data?.text || value; +export function formatSchemaDisplayValue( + value: string | string[], + field: Field, +) { + switch (field.type) { + case "number": + case "text": + return value; + case "checklist": { + const matchingOptions = field.data.options.filter((option) => + (value as string[]).includes(option.id), + ); + return ( +
    + {matchingOptions.map((option) => ( +
  • {option.data.text}
  • + ))} +
+ ); + } + case "question": { + const matchingOption = field.data.options.find( + (option) => option.data.val === value, + ); + return matchingOption?.data.text; + } + } } /** @@ -34,7 +50,7 @@ export function sumIdenticalUnits( let sum = 0; passportData[`${fn}`].map((item) => { if (!Array.isArray(item?.identicalUnits)) { - sum += parseInt(item?.identicalUnits) + sum += parseInt(item?.identicalUnits); } }); return sum; @@ -62,13 +78,11 @@ export function sumIdenticalUnitsByDevelopmentType( newBuild: 0, notKnown: 0, }; - passportData[`${fn}`].map( - (item) => { - if (!Array.isArray(item?.identicalUnits)) { - (baseSums[`${item?.development}`] += parseInt(item?.identicalUnits)) - } + passportData[`${fn}`].map((item) => { + if (!Array.isArray(item?.identicalUnits)) { + baseSums[`${item?.development}`] += parseInt(item?.identicalUnits); } - ); + }); // Format property names for passport, and filter out any entries with default sum = 0 const formattedSums: Record = {}; From ec69004d76782f67813b7bed574c76f4c58d696f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Tue, 25 Jun 2024 16:43:46 +0100 Subject: [PATCH 08/12] test: Add checklist and basic test coverage to existing Zoo schema --- .../src/@planx/components/List/Public/Fields.tsx | 11 +---------- .../@planx/components/List/Public/index.test.tsx | 5 +++++ .../@planx/components/List/schemas/mocks/Zoo.ts | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx b/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx index 7139498643..8fad6c35db 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/Fields.tsx @@ -202,31 +202,22 @@ export const ChecklistFieldInput: React.FC> = (props) => { const changeCheckbox = (id: string) => async ( - event: React.MouseEvent | undefined, + _checked: React.MouseEvent | undefined, ) => { - // console.log("Clicked id: ", id) - - // console.log("Before: ", formik.values.userData[activeIndex][fn]) let newCheckedIds; if (formik.values.userData[activeIndex][fn].includes(id)) { - // console.log("Already in list") newCheckedIds = ( formik.values.userData[activeIndex][fn] as string[] ).filter((x) => x !== id); - // console.log("After: ", newCheckedIds) } else { - // console.log("New") newCheckedIds = [...formik.values.userData[activeIndex][fn], id]; - // console.log("After: ", newCheckedIds) } await formik.setFieldValue( `userData[${activeIndex}]['${fn}']`, newCheckedIds, ); - - // console.log(formik.values.userData[activeIndex][fn]) }; return ( diff --git a/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx b/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx index 5bdaa44f8b..bb1ea3ee86 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx @@ -651,6 +651,11 @@ const fillInResponse = async (user: UserEvent) => { const cuteRadio = screen.getAllByRole("radio")[0]; await user.click(cuteRadio); + const eatCheckboxes = screen.getAllByRole("checkbox"); + await user.click(eatCheckboxes[0]); + await user.click(eatCheckboxes[1]); + await user.click(eatCheckboxes[2]); + const saveButton = screen.getByRole("button", { name: /Save/, }); diff --git a/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts b/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts index fe94d763e4..2e7a8122c3 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts @@ -64,6 +64,20 @@ export const Zoo: Schema = { ], }, }, + // Checklist + { + type: "checklist", + data: { + title: "What do they eat?", + fn: "food", + options: [ + { id: "meat", data: { text: "Meat" } }, + { id: "leaves", data: { text: "Leaves" } }, + { id: "bamboo", data: { text: "Bamboo" } }, + { id: "fruit", data: { text: "fruit" } }, + ], + }, + }, ], min: 1, max: 3, @@ -86,6 +100,7 @@ export const mockZooPayload = { "email.address": "richard.parker@pi.com", name: "Richard Parker", size: "Medium", + food: ["meat", "leaves", "bamboo"], }, { age: 10, @@ -93,6 +108,7 @@ export const mockZooPayload = { "email.address": "richard.parker@pi.com", name: "Richard Parker", size: "Medium", + food: ["meat", "leaves", "bamboo"], }, ], "mockFn.one.age": 10, From 2e940ae32d1dc95a597d76f683b0e7e0bea175af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Tue, 25 Jun 2024 16:44:26 +0100 Subject: [PATCH 09/12] chore: Update all schemas with checklists - Also replace questions for checklists --- .../src/@planx/components/List/Editor.tsx | 10 +++ .../schemas/ResidentialUnits/GLA/Gained.ts | 68 +++++-------------- .../List/schemas/ResidentialUnits/GLA/Lost.ts | 68 +++++-------------- .../List/schemas/ResidentialUnits/GLA/New.ts | 68 +++++-------------- .../schemas/ResidentialUnits/GLA/Rebuilt.ts | 68 +++++-------------- .../schemas/ResidentialUnits/GLA/Removed.ts | 36 ---------- 6 files changed, 74 insertions(+), 244 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/List/Editor.tsx b/editor.planx.uk/src/@planx/components/List/Editor.tsx index 4e6d6f5544..bfa3e99ae8 100644 --- a/editor.planx.uk/src/@planx/components/List/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/List/Editor.tsx @@ -23,6 +23,8 @@ import { ProtectedSpaceGLA } from "./schemas/GLA/ProtectedSpace"; import { ResidentialUnitsExisting } from "./schemas/ResidentialUnits/Existing"; import { ResidentialUnitsGLAGained } from "./schemas/ResidentialUnits/GLA/Gained"; import { ResidentialUnitsGLALost } from "./schemas/ResidentialUnits/GLA/Lost"; +import { ResidentialUnitsGLANew } from "./schemas/ResidentialUnits/GLA/New"; +import { ResidentialUnitsGLARebuilt } from "./schemas/ResidentialUnits/GLA/Rebuilt"; import { ResidentialUnitsGLARemoved } from "./schemas/ResidentialUnits/GLA/Removed"; import { ResidentialUnitsProposed } from "./schemas/ResidentialUnits/Proposed"; @@ -36,6 +38,14 @@ export const SCHEMAS = [ schema: ResidentialUnitsGLAGained, }, { name: "Residential units (GLA) - Lost", schema: ResidentialUnitsGLALost }, + { + name: "Residential units (GLA) - New", + schema: ResidentialUnitsGLANew, + }, + { + name: "Residential units (GLA) - Rebuilt", + schema: ResidentialUnitsGLARebuilt, + }, { name: "Residential units (GLA) - Removed", schema: ResidentialUnitsGLARemoved, diff --git a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Gained.ts b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Gained.ts index b08d3bbc6f..f403091c5c 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Gained.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Gained.ts @@ -70,61 +70,25 @@ export const ResidentialUnitsGLAGained: Schema = { ], }, }, - // { - // type: "checklist", // @todo - // data: { - // title: "Is this unit compliant with any of the following?", - // fn: "compliance", - // options: [ - // { - // id: "m42", - // data: { text: "Part M4(2) of the Building Regulations 2010" }, - // }, - // { - // id: "m432a", - // data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, - // }, - // { - // id: "m432b", - // data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, - // }, - // { id: "none", data: { text: "None of these" } }, - // ], - // }, - // }, { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(2) of the Building Regulations 2010?", - fn: "compliance.m42", - options: [ - { id: "true", data: { text: "Yes" } }, - { id: "false", data: { text: "No" } }, - ], - }, - }, - { - type: "question", + type: "checklist", data: { - title: - "Is this unit compliant with Part M4(3)(2a) of the Building Regulations 2010?", - fn: "compliance.m432a", + title: "Is this unit compliant with any of the following?", + fn: "compliance", options: [ - { id: "true", data: { text: "Yes" } }, - { id: "false", data: { text: "No" } }, - ], - }, - }, - { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(3)(2b) of the Building Regulations 2010?", - fn: "compliance.m432b", - options: [ - { id: "true", data: { text: "Yes" } }, - { id: "false", data: { text: "No" } }, + { + id: "m42", + data: { text: "Part M4(2) of the Building Regulations 2010" }, + }, + { + id: "m432a", + data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, + }, + { + id: "m432b", + data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, + }, + { id: "none", data: { text: "None of these" } }, ], }, }, diff --git a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Lost.ts b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Lost.ts index 5c4d126916..bed690a2e7 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Lost.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Lost.ts @@ -53,61 +53,25 @@ export const ResidentialUnitsGLALost: Schema = { ], }, }, - // { - // type: "checklist", // @todo - // data: { - // title: "Is this unit compliant with any of the following?", - // fn: "compliance", - // options: [ - // { - // id: "m42", - // data: { text: "Part M4(2) of the Building Regulations 2010" }, - // }, - // { - // id: "m432a", - // data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, - // }, - // { - // id: "m432b", - // data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, - // }, - // { id: "none", data: { text: "None of these" } }, - // ], - // }, - // }, { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(2) of the Building Regulations 2010?", - fn: "compliance.m42", - options: [ - { id: "true", data: { text: "Yes" } }, - { id: "false", data: { text: "No" } }, - ], - }, - }, - { - type: "question", + type: "checklist", data: { - title: - "Is this unit compliant with Part M4(3)(2a) of the Building Regulations 2010?", - fn: "compliance.m432a", + title: "Is this unit compliant with any of the following?", + fn: "compliance", options: [ - { id: "true", data: { text: "Yes" } }, - { id: "false", data: { text: "No" } }, - ], - }, - }, - { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(3)(2b) of the Building Regulations 2010?", - fn: "compliance.m432b", - options: [ - { id: "true", data: { text: "Yes" } }, - { id: "false", data: { text: "No" } }, + { + id: "m42", + data: { text: "Part M4(2) of the Building Regulations 2010" }, + }, + { + id: "m432a", + data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, + }, + { + id: "m432b", + data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, + }, + { id: "none", data: { text: "None of these" } }, ], }, }, diff --git a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/New.ts b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/New.ts index 67a67ff4d0..fe8f9a2a78 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/New.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/New.ts @@ -90,61 +90,25 @@ export const ResidentialUnitsGLANew: Schema = { ], }, }, - // { - // type: "checklist", // @todo - // data: { - // title: "Is this unit compliant with any of the following?", - // fn: "compliance", - // options: [ - // { - // id: "m42", - // data: { text: "Part M4(2) of the Building Regulations 2010" }, - // }, - // { - // id: "m432a", - // data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, - // }, - // { - // id: "m432b", - // data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, - // }, - // { id: "none", data: { text: "None of these" } }, - // ], - // }, - // }, { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(2) of the Building Regulations 2010?", - fn: "compliance.m42", - options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, - ], - }, - }, - { - type: "question", + type: "checklist", data: { - title: - "Is this unit compliant with Part M4(3)(2a) of the Building Regulations 2010?", - fn: "compliance.m432a", + title: "Is this unit compliant with any of the following?", + fn: "compliance", options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, - ], - }, - }, - { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(3)(2b) of the Building Regulations 2010?", - fn: "compliance.m432b", - options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, + { + id: "m42", + data: { text: "Part M4(2) of the Building Regulations 2010" }, + }, + { + id: "m432a", + data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, + }, + { + id: "m432b", + data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, + }, + { id: "none", data: { text: "None of these" } }, ], }, }, diff --git a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Rebuilt.ts b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Rebuilt.ts index 31953e66b6..b108866be2 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Rebuilt.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Rebuilt.ts @@ -90,61 +90,25 @@ export const ResidentialUnitsGLARebuilt: Schema = { ], }, }, - // { - // type: "checklist", // @todo - // data: { - // title: "Is this unit compliant with any of the following?", - // fn: "compliance", - // options: [ - // { - // id: "m42", - // data: { text: "Part M4(2) of the Building Regulations 2010" }, - // }, - // { - // id: "m432a", - // data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, - // }, - // { - // id: "m432b", - // data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, - // }, - // { id: "none", data: { text: "None of these" } }, - // ], - // }, - // }, { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(2) of the Building Regulations 2010?", - fn: "compliance.m42", - options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, - ], - }, - }, - { - type: "question", + type: "checklist", data: { - title: - "Is this unit compliant with Part M4(3)(2a) of the Building Regulations 2010?", - fn: "compliance.m432a", + title: "Is this unit compliant with any of the following?", + fn: "compliance", options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, - ], - }, - }, - { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(3)(2b) of the Building Regulations 2010?", - fn: "compliance.m432b", - options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, + { + id: "m42", + data: { text: "Part M4(2) of the Building Regulations 2010" }, + }, + { + id: "m432a", + data: { text: "Part M4(3)(2a) of the Building Regulations 2010" }, + }, + { + id: "m432b", + data: { text: "Part M4(3)(2b) of the Building Regulations 2010" }, + }, + { id: "none", data: { text: "None of these" } }, ], }, }, diff --git a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts index f8f16974ba..b5fde1a983 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/ResidentialUnits/GLA/Removed.ts @@ -91,42 +91,6 @@ export const ResidentialUnitsGLARemoved: Schema = { ], }, }, - { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(2) of the Building Regulations 2010?", - fn: "compliance.m42", - options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, - ], - }, - }, - { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(3)(2a) of the Building Regulations 2010?", - fn: "compliance.m432a", - options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, - ], - }, - }, - { - type: "question", - data: { - title: - "Is this unit compliant with Part M4(3)(2b) of the Building Regulations 2010?", - fn: "compliance.m432b", - options: [ - { id: "true", data: { text: "Yes", val: "true" } }, - { id: "false", data: { text: "No", val: "false" } }, - ], - }, - }, { type: "question", data: { From 96e74b37d6eb123726892995da31b2aac45791f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Wed, 26 Jun 2024 09:43:01 +0100 Subject: [PATCH 10/12] feat: Only flatten payload to required depth, in order to preserve checklist answers --- .../src/@planx/components/List/Public/Context.tsx | 2 +- .../@planx/components/List/schemas/mocks/Zoo.ts | 6 ++---- .../src/@planx/components/List/utils.test.ts | 6 +++++- .../src/@planx/components/List/utils.tsx | 15 ++++++++++----- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/List/Public/Context.tsx b/editor.planx.uk/src/@planx/components/List/Public/Context.tsx index bca7c02f9e..38bb5b1ec4 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/Context.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/Context.tsx @@ -171,7 +171,7 @@ export const ListProvider: React.FC = (props) => { const defaultPassportData = makeData(props, values.userData)?.["data"]; // flattenedPassportData makes individual list items compatible with Calculate components - const flattenedPassportData = flatten(defaultPassportData); + const flattenedPassportData = flatten(defaultPassportData, { depth: 2 }); // basic example of general summary stats we can add onSubmit: // 1. count of items/responses diff --git a/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts b/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts index 2e7a8122c3..59394a6f31 100644 --- a/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts +++ b/editor.planx.uk/src/@planx/components/List/schemas/mocks/Zoo.ts @@ -3,10 +3,6 @@ import { TextInputType } from "@planx/components/TextInput/model"; import { Schema } from "../../model"; import { Props } from "../../Public"; -/** - * Temp simple example to build out UI - * Can be re-used as mock for testing - */ export const Zoo: Schema = { type: "Animal", fields: [ @@ -116,11 +112,13 @@ export const mockZooPayload = { "mockFn.one.email.address": "richard.parker@pi.com", "mockFn.one.name": "Richard Parker", "mockFn.one.size": "Medium", + "mockFn.one.food": ["meat", "leaves", "bamboo"], "mockFn.two.age": 10, "mockFn.two.cuteness.amount": "Very", "mockFn.two.email.address": "richard.parker@pi.com", "mockFn.two.name": "Richard Parker", "mockFn.two.size": "Medium", + "mockFn.two.food": ["meat", "leaves", "bamboo"], "mockFn.total.listItems": 2, }, }; diff --git a/editor.planx.uk/src/@planx/components/List/utils.test.ts b/editor.planx.uk/src/@planx/components/List/utils.test.ts index c34dbf20d8..72e8996e19 100644 --- a/editor.planx.uk/src/@planx/components/List/utils.test.ts +++ b/editor.planx.uk/src/@planx/components/List/utils.test.ts @@ -15,6 +15,7 @@ describe("passport data shape", () => { "email.address": "richard.parker@pi.com", name: "Richard Parker", size: "Medium", + food: ["bamboo", "leaves"], }, { age: 10, @@ -22,21 +23,24 @@ describe("passport data shape", () => { "email.address": "richard.parker@pi.com", name: "Richard Parker", size: "Medium", + food: ["meat", "bamboo", "leaves"], }, ], }; - expect(flatten(defaultPassportData)).toEqual({ + expect(flatten(defaultPassportData, { depth: 2 })).toEqual({ "mockFn.one.age": 10, "mockFn.one.cuteness.amount": "Very", "mockFn.one.email.address": "richard.parker@pi.com", "mockFn.one.name": "Richard Parker", "mockFn.one.size": "Medium", + "mockFn.one.food": ["bamboo", "leaves"], "mockFn.two.age": 10, "mockFn.two.cuteness.amount": "Very", "mockFn.two.email.address": "richard.parker@pi.com", "mockFn.two.name": "Richard Parker", "mockFn.two.size": "Medium", + "mockFn.two.food": ["meat", "bamboo", "leaves"], }); }); diff --git a/editor.planx.uk/src/@planx/components/List/utils.tsx b/editor.planx.uk/src/@planx/components/List/utils.tsx index db3d58b2ba..30c3281c51 100644 --- a/editor.planx.uk/src/@planx/components/List/utils.tsx +++ b/editor.planx.uk/src/@planx/components/List/utils.tsx @@ -95,14 +95,19 @@ export function sumIdenticalUnitsByDevelopmentType( return formattedSums; } +interface FlattenOptions { + depth?: number; + path?: string | null; + separator?: string; +}; + /** * Flattens nested object so we can output passport variables like `{listFn}.{itemIndexAsText}.{fieldFn}` * Adapted from https://gist.github.com/penguinboy/762197 */ export function flatten>( - object: T, - path: string | null = null, - separator = ".", + object: T, + { depth = Infinity, path = null, separator = ".", }: FlattenOptions = {} ): T { return Object.keys(object).reduce((acc: T, key: string): T => { const value = object[key]; @@ -121,8 +126,8 @@ export function flatten>( !(Array.isArray(value) && value.length === 0), ].every(Boolean); - return isObject - ? { ...acc, ...flatten(value, newPath, separator) } + return (isObject && depth > 0) + ? { ...acc, ...flatten(value, { depth: depth - 1, path: newPath, separator }) } : { ...acc, [newPath]: value }; }, {} as T); } From cea11f8dc725c1cbbbfa6bac456931b7f84983fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Thu, 27 Jun 2024 17:21:05 +0100 Subject: [PATCH 11/12] fix: Update list style to match `SummaryList` --- editor.planx.uk/src/@planx/components/List/utils.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/List/utils.tsx b/editor.planx.uk/src/@planx/components/List/utils.tsx index 30c3281c51..7b26eee627 100644 --- a/editor.planx.uk/src/@planx/components/List/utils.tsx +++ b/editor.planx.uk/src/@planx/components/List/utils.tsx @@ -1,6 +1,13 @@ import React from "react"; import { Field, UserResponse } from "./model"; +import { styled } from "@mui/material/styles"; + +const List = styled("ul")(() => ({ + listStylePosition: "inside", + padding: 0, + margin: 0, +})) /** * In the case of "question" and "checklist" fields, ensure the displayed value reflects option "text", rather than "val" as recorded in passport @@ -21,11 +28,11 @@ export function formatSchemaDisplayValue( (value as string[]).includes(option.id), ); return ( -
    + {matchingOptions.map((option) => (
  • {option.data.text}
  • ))} -
+ ); } case "question": { From 3685748cb46a26ab05a4053639bd6c7f21000589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Fri, 28 Jun 2024 12:18:01 +0100 Subject: [PATCH 12/12] test: Add validation test --- .../src/@planx/components/List/Public/index.test.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx b/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx index bb1ea3ee86..b2c2d6ec73 100644 --- a/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx +++ b/editor.planx.uk/src/@planx/components/List/Public/index.test.tsx @@ -453,7 +453,15 @@ describe("Form validation and error handling", () => { expect(cuteInputErrorMessage).toHaveTextContent(/Select your answer before continuing/); }); - test.todo("checklist fields") + test("checklist fields", async () => { + const { user, getByRole, getByTestId } = setup(); + + await user.click(getByRole("button", { name: /Save/ })); + + const foodInputErrorMessage = getByTestId(/error-message-input-checklist-food/); + + expect(foodInputErrorMessage).toHaveTextContent(/Select at least one option/); + }) }); test("an error displays if the minimum number of items is not met", async () => {