From 363cc941a371d1d0cee1efd6caae57cbde6f061c Mon Sep 17 00:00:00 2001 From: Jo Humphrey <31373245+jamdelion@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:02:31 +0000 Subject: [PATCH 1/2] feat: feedback editor component (#3866) --- .../docs/adding-a-new-component.md | 20 +- editor.planx.uk/package.json | 2 +- editor.planx.uk/pnpm-lock.yaml | 85 ++- .../@planx/components/Calculate/logic.test.ts | 7 +- .../@planx/components/Checklist/Public.tsx | 9 +- .../Feedback/Editor/Editor.test.tsx | 18 + .../components/Feedback/Editor/Editor.tsx | 120 +++ .../Editor/FeedbackEditor.stories.tsx | 13 + .../{ => Public}/Feedback.stories.tsx | 2 +- .../Feedback/{ => Public}/Public.test.tsx | 6 +- .../Feedback/{ => Public}/Public.tsx | 68 +- .../Feedback/components/placeholders.tsx | 15 + .../src/@planx/components/Feedback/model.ts | 32 + .../src/@planx/components/Feedback/types.ts | 11 - .../components/Question/Public.test.tsx | 39 +- .../components/Question/Public/Question.tsx | 4 +- .../components/shared/Preview/SummaryList.tsx | 1 + .../src/@planx/components/shared/icons.tsx | 2 + editor.planx.uk/src/lib/featureFlags.ts | 2 +- .../components/Flow/components/Node.tsx | 2 + .../components/Team/TeamMembers.tsx | 3 +- .../Team/components/UserUpsertModal.tsx | 5 +- .../Team/queries/createAndAddUserToTeam.tsx | 7 +- ...TeamMembers.addNewMember.demoTeam.test.tsx | 8 +- .../tests/TeamMembers.addNewMember.test.tsx | 3 +- .../FlowEditor/components/forms/FormModal.tsx | 4 + .../FlowEditor/components/forms/index.ts | 2 + .../src/pages/FlowEditor/data/types.ts | 1 + .../lib/__tests__/automations.blanks.test.ts | 684 +++++++++--------- .../__tests__/automations.parentChild.test.ts | 273 +++---- ...automations.planningConstraintNots.test.ts | 226 +++--- .../__tests__/automations.setValue.test.ts | 473 ++++++------ .../FlowEditor/lib/__tests__/filters.test.ts | 438 ++++++----- .../preview/autoAnswerableFlag.test.ts | 71 +- .../preview/autoAnswerableOptions.test.ts | 495 ++++++------- .../pages/FlowEditor/lib/__tests__/utils.ts | 1 + editor.planx.uk/src/pages/Preview/Node.tsx | 5 +- editor.planx.uk/src/ui/public/InputLabel.tsx | 2 +- 38 files changed, 1790 insertions(+), 1369 deletions(-) create mode 100644 editor.planx.uk/src/@planx/components/Feedback/Editor/Editor.test.tsx create mode 100644 editor.planx.uk/src/@planx/components/Feedback/Editor/Editor.tsx create mode 100644 editor.planx.uk/src/@planx/components/Feedback/Editor/FeedbackEditor.stories.tsx rename editor.planx.uk/src/@planx/components/Feedback/{ => Public}/Feedback.stories.tsx (87%) rename editor.planx.uk/src/@planx/components/Feedback/{ => Public}/Public.test.tsx (86%) rename editor.planx.uk/src/@planx/components/Feedback/{ => Public}/Public.tsx (66%) create mode 100644 editor.planx.uk/src/@planx/components/Feedback/components/placeholders.tsx create mode 100644 editor.planx.uk/src/@planx/components/Feedback/model.ts delete mode 100644 editor.planx.uk/src/@planx/components/Feedback/types.ts diff --git a/editor.planx.uk/docs/adding-a-new-component.md b/editor.planx.uk/docs/adding-a-new-component.md index 3ec886133e..6335e1ccd2 100644 --- a/editor.planx.uk/docs/adding-a-new-component.md +++ b/editor.planx.uk/docs/adding-a-new-component.md @@ -2,7 +2,7 @@ Let's add a `SetValue` component -## Core directory & files +## Core directory typing 1. `planx-core/src/types/component.ts` @@ -12,9 +12,11 @@ Add type to enum in `planx-core` repository SetValue = 380, ``` -2. `mkdir src/@planx/components/SetValue` +## `planx-new` component files -3. `model.ts` +1. `mkdir src/@planx/components/SetValue` + +2. `model.ts` ```typescript import { BaseNodeData, parseBaseNodeData } from "../shared"; @@ -31,7 +33,7 @@ export const parseContent = ( }); ``` -4. `Editor.tsx` +3. `Editor.tsx` ```typescript type Props = EditorProps; @@ -60,7 +62,7 @@ function SetValueComponent(props: Props) { 5. `Public.tsx` ```typescript -import { PublicProps } from "@planx/components/ui"; +import { PublicProps } from "@planx/components/shared/types"; type Props = PublicProps; @@ -86,7 +88,7 @@ function SetValueComponent(props: Props) { ## Editor configurations -1. `src/@planx/components/ui.tsx` +1. `src/@planx/components/shared/icons.tsx` ```typescript import PlaylistAdd from "@mui/icons-material/PlaylistAdd"; @@ -135,13 +137,13 @@ case TYPES.SetValue: 2. `src/@planx/components/shared/Preview/SummaryList` -If/how should this component appear on the Review page? +If/how should this component appear in a Review component: ```typescript [TYPES.SetValue]: undefined, ``` -3. `src/@planx/components/Send/bops/index` +3. In `planx-core` - `src/export/bops/index` If/how should this component be formatted in Send data formats such as BOPS? @@ -154,4 +156,4 @@ function isTypeForBopsPayload(type?: TYPES) { // ... } } -``` +``` \ No newline at end of file diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json index 28beeb4d1a..4ec4c918a9 100644 --- a/editor.planx.uk/package.json +++ b/editor.planx.uk/package.json @@ -15,7 +15,7 @@ "@mui/material": "^5.15.10", "@mui/utils": "^5.15.11", "@opensystemslab/map": "1.0.0-alpha.4", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#54be9e0", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#b4ca5a9", "@tiptap/core": "^2.4.0", "@tiptap/extension-bold": "^2.0.3", "@tiptap/extension-bubble-menu": "^2.1.13", diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml index 87ae1a3022..603a2812c4 100644 --- a/editor.planx.uk/pnpm-lock.yaml +++ b/editor.planx.uk/pnpm-lock.yaml @@ -47,8 +47,8 @@ dependencies: specifier: 1.0.0-alpha.4 version: 1.0.0-alpha.4 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#54be9e0 - version: github.com/theopensystemslab/planx-core/54be9e0(@types/react@18.2.45) + specifier: git+https://github.com/theopensystemslab/planx-core#b4ca5a9 + version: github.com/theopensystemslab/planx-core/b4ca5a9(@types/react@18.2.45) '@tiptap/core': specifier: ^2.4.0 version: 2.4.0(@tiptap/pm@2.0.3) @@ -2789,30 +2789,30 @@ packages: react: 18.2.0 dev: true - /@formatjs/ecma402-abstract@2.2.0: - resolution: {integrity: sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==} + /@formatjs/ecma402-abstract@2.2.1: + resolution: {integrity: sha512-O4ywpkdJybrjFc9zyL8qK5aklleIAi5O4nYhBVJaOFtCkNrnU+lKFeJOFC48zpsZQmR8Aok2V79hGpHnzbmFpg==} dependencies: - '@formatjs/fast-memoize': 2.2.1 - '@formatjs/intl-localematcher': 0.5.5 + '@formatjs/fast-memoize': 2.2.2 + '@formatjs/intl-localematcher': 0.5.6 tslib: 2.7.0 dev: false - /@formatjs/fast-memoize@2.2.1: - resolution: {integrity: sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==} + /@formatjs/fast-memoize@2.2.2: + resolution: {integrity: sha512-mzxZcS0g1pOzwZTslJOBTmLzDXseMLLvnh25ymRilCm8QLMObsQ7x/rj9GNrH0iUhZMlFisVOD6J1n6WQqpKPQ==} dependencies: tslib: 2.7.0 dev: false - /@formatjs/intl-listformat@7.5.9: - resolution: {integrity: sha512-HqtGkxUh2Uz0oGVTxHAvPZ3EGxc8+ol5+Bx7S9xB97d4PEJJd9oOgHrerIghHA0gtIjsNKBFUae3P0My+F6YUA==} + /@formatjs/intl-listformat@7.7.1: + resolution: {integrity: sha512-bjBxWaUhYAbJFUlFSMWZGn3r2mglXwk+BLyGRu8dY8Q83ZPsqmmVQzjQKENHE3lV6eoQGHT2oZHxUaVndJlk6Q==} dependencies: - '@formatjs/ecma402-abstract': 2.2.0 - '@formatjs/intl-localematcher': 0.5.5 + '@formatjs/ecma402-abstract': 2.2.1 + '@formatjs/intl-localematcher': 0.5.6 tslib: 2.7.0 dev: false - /@formatjs/intl-localematcher@0.5.5: - resolution: {integrity: sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==} + /@formatjs/intl-localematcher@0.5.6: + resolution: {integrity: sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA==} dependencies: tslib: 2.7.0 dev: false @@ -3441,9 +3441,9 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@mui/base@5.0.0-beta.40(@types/react@18.2.45)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} - engines: {node: '>=12.0.0'} + /@mui/base@5.0.0-beta.60(@types/react@18.2.45)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-w8twR3qCUI+uJHO5xDOuc1yB5l46KFbvNsTwIvEW9tQkKxVaiEFf2GAXHuvFJiHfZLqjzett6drZjghy8D1Z1A==} + engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -3454,8 +3454,8 @@ packages: dependencies: '@babel/runtime': 7.25.7 '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1)(react@18.3.1) - '@mui/types': 7.2.17(@types/react@18.2.45) - '@mui/utils': 5.16.6(@types/react@18.2.45)(react@18.3.1) + '@mui/types': 7.2.18(@types/react@18.2.45) + '@mui/utils': 6.1.5(@types/react@18.2.45)(react@18.3.1) '@popperjs/core': 2.11.8 '@types/react': 18.2.45 clsx: 2.1.1 @@ -3739,6 +3739,17 @@ packages: '@types/react': 18.2.45 dev: false + /@mui/types@7.2.18(@types/react@18.2.45): + resolution: {integrity: sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.45 + dev: false + /@mui/utils@5.15.11(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==} engines: {node: '>=12.0.0'} @@ -3815,6 +3826,26 @@ packages: react-is: 18.3.1 dev: false + /@mui/utils@6.1.5(@types/react@18.2.45)(react@18.3.1): + resolution: {integrity: sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.25.7 + '@mui/types': 7.2.18(@types/react@18.2.45) + '@types/prop-types': 15.7.13 + '@types/react': 18.2.45 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 18.3.1 + dev: false + /@nicolo-ribaudo/semver-v6@6.3.3: resolution: {integrity: sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==} hasBin: true @@ -14723,8 +14754,8 @@ packages: dev: false optional: true - /uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + /uuid@11.0.2: + resolution: {integrity: sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==} hasBin: true dev: false @@ -15359,9 +15390,9 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: false - github.com/theopensystemslab/planx-core/54be9e0(@types/react@18.2.45): - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/54be9e0} - id: github.com/theopensystemslab/planx-core/54be9e0 + github.com/theopensystemslab/planx-core/b4ca5a9(@types/react@18.2.45): + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/b4ca5a9} + id: github.com/theopensystemslab/planx-core/b4ca5a9 name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true @@ -15369,8 +15400,8 @@ packages: dependencies: '@emotion/react': 11.13.3(@types/react@18.2.45)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.45)(react@18.3.1) - '@formatjs/intl-listformat': 7.5.9 - '@mui/base': 5.0.0-beta.40(@types/react@18.2.45)(react-dom@18.3.1)(react@18.3.1) + '@formatjs/intl-listformat': 7.7.1 + '@mui/base': 5.0.0-beta.60(@types/react@18.2.45)(react-dom@18.3.1)(react@18.3.1) '@mui/material': 5.15.10(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.45)(react-dom@18.3.1)(react@18.3.1) ajv: 8.17.1 ajv-formats: 2.1.1(ajv@8.17.1) @@ -15388,7 +15419,7 @@ packages: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) type-fest: 4.26.1 - uuid: 10.0.0 + uuid: 11.0.2 zod: 3.23.8 transitivePeerDependencies: - '@types/react' diff --git a/editor.planx.uk/src/@planx/components/Calculate/logic.test.ts b/editor.planx.uk/src/@planx/components/Calculate/logic.test.ts index 3194ae5a77..dad78d34fa 100644 --- a/editor.planx.uk/src/@planx/components/Calculate/logic.test.ts +++ b/editor.planx.uk/src/@planx/components/Calculate/logic.test.ts @@ -1,5 +1,8 @@ import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; -import { clickContinue, visitedNodes } from "pages/FlowEditor/lib/__tests__/utils"; +import { + clickContinue, + visitedNodes, +} from "pages/FlowEditor/lib/__tests__/utils"; import { Store, useStore } from "pages/FlowEditor/lib/store"; const { getState, setState } = useStore; @@ -19,7 +22,7 @@ test("When formatOutputForAutomations is true, Calculate writes an array and fut // The Question can be auto-answered expect(visitedNodes()).toEqual(["Calculate"]); - expect(upcomingCardIds()).toEqual(["Question"]) + expect(upcomingCardIds()).toEqual(["Question"]); expect(autoAnswerableOptions("Question")).toEqual(["Group2Response"]); }); diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx index 3291b9b988..c52f64c15b 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx @@ -42,11 +42,11 @@ const ChecklistComponent: React.FC = (props) => { const autoAnswerableOptions = useStore( (state) => state.autoAnswerableOptions, ); - + if (props.neverAutoAnswer) { return ; } - + let idsThatCanBeAutoAnswered: string[] | undefined; if (props.id) idsThatCanBeAutoAnswered = autoAnswerableOptions(props.id); if (idsThatCanBeAutoAnswered) { @@ -203,8 +203,9 @@ const VisibleChecklist: React.FC = (props) => { pb={2} aria-labelledby={`group-${index}-heading`} id={`group-${index}-content`} - data-testid={`group-${index}${isExpanded ? "-expanded" : "" - }`} + data-testid={`group-${index}${ + isExpanded ? "-expanded" : "" + }`} > {group.children.map((option) => ( { + it("does not throw an error", () => { + setup( + + + , + ); + expect(screen.getByText("Feedback")).toBeInTheDocument(); + }); +}); diff --git a/editor.planx.uk/src/@planx/components/Feedback/Editor/Editor.tsx b/editor.planx.uk/src/@planx/components/Feedback/Editor/Editor.tsx new file mode 100644 index 0000000000..633359c209 --- /dev/null +++ b/editor.planx.uk/src/@planx/components/Feedback/Editor/Editor.tsx @@ -0,0 +1,120 @@ +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import { ComponentType as TYPES } from "@opensystemslab/planx-core/types"; +import { ICONS } from "@planx/components/shared/icons"; +import { EditorProps } from "@planx/components/shared/types"; +import { useFormik } from "formik"; +import React from "react"; +import InputGroup from "ui/editor/InputGroup"; +import InputLabel from "ui/editor/InputLabel"; +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 Input from "ui/shared/Input/Input"; +import InputRow from "ui/shared/InputRow"; + +import { + descriptionPlaceholder, + disclaimerPlaceholder, + freeformQuestionPlaceholder, + ratingQuestionPlaceholder, + titlePlaceholder, +} from "../components/placeholders"; +import { Feedback, parseFeedback } from "../model"; + +type FeedbackEditorProps = EditorProps; + +export const FeedbackEditor = (props: FeedbackEditorProps) => { + const formik = useFormik({ + initialValues: parseFeedback(props.node?.data), + onSubmit: (newValues) => { + if (props.handleSubmit) { + props.handleSubmit({ + type: TYPES.Feedback, + data: newValues, + }); + } + }, + }); + + return ( +