From 8e21ec1dd724390c7588e1d838e1ec4b064e490a Mon Sep 17 00:00:00 2001 From: Robert Bond Date: Fri, 17 Apr 2026 09:22:36 +0000 Subject: [PATCH] Merged PR 2254: refactor(representations): normalise shared type-selector and action-row frag... refactor(representations): normalise shared type-selector and action-row fragments in main representation variants (Slice R5, behaviour-preserving) Related work items: #22441 --- .../elements/RepresentationActionButtons.js | 42 ++++ .../RepresentationTypeSelectorBlock.js | 49 +++++ .../representation/representationAgent.js | 165 +++++----------- .../representation/representationAppellant.js | 162 +++++----------- .../representationInterestedPartyPerson.js | 181 +++++------------- context/representations-refactor-tracker.md | 26 ++- 6 files changed, 272 insertions(+), 353 deletions(-) create mode 100644 components/case/representation/elements/RepresentationActionButtons.js create mode 100644 components/case/representation/elements/RepresentationTypeSelectorBlock.js diff --git a/components/case/representation/elements/RepresentationActionButtons.js b/components/case/representation/elements/RepresentationActionButtons.js new file mode 100644 index 00000000..8bc5ee9c --- /dev/null +++ b/components/case/representation/elements/RepresentationActionButtons.js @@ -0,0 +1,42 @@ +const RepresentationActionButtons = ({ + t, + locale, + isSaving, + isContinueDisabled, + onSaveExit +}) => { + return ( +
+
+ + + {isSaving ? ( + + {locale == "cy" ? "Nôl data" : "Saving data"} + {locale + + ) : ( + + )} +
+
+ ); +}; + +export default RepresentationActionButtons; diff --git a/components/case/representation/elements/RepresentationTypeSelectorBlock.js b/components/case/representation/elements/RepresentationTypeSelectorBlock.js new file mode 100644 index 00000000..38bde62b --- /dev/null +++ b/components/case/representation/elements/RepresentationTypeSelectorBlock.js @@ -0,0 +1,49 @@ +import { Field } from "redux-form"; + +import { RenderPickList } from "../representationElements"; + +const RepresentationTypeSelectorBlock = ({ + t, + headingText, + kindLabelText, + isTypeSelected, + selectedTypeDisplay, + optionsArr, + validate, + errorMsg +}) => { + return ( +
+
+ {!isTypeSelected ? ( + <> +

{headingText}

+ + + + + + ) : ( + + )} +
+
+ ); +}; + +export default RepresentationTypeSelectorBlock; diff --git a/components/case/representation/representationAgent.js b/components/case/representation/representationAgent.js index 14368697..6773b6aa 100644 --- a/components/case/representation/representationAgent.js +++ b/components/case/representation/representationAgent.js @@ -8,16 +8,17 @@ import { bytesToSize, getThumbnailIconByExtension } from "../../utils"; import { FileUploadField, RenderCondtionalRadioList, - RenderPickList, RenderRichMultiline, - shouldDisableButton, + shouldDisableButton } from "./representationElements"; +import RepresentationActionButtons from "./elements/RepresentationActionButtons"; +import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock"; import { useDropzone } from "react-dropzone"; import { setRepresentationCapacity, setRepresentationSubmit, - setFilesForRepresentations, + setFilesForRepresentations } from "../../../store/currentView/action"; const RepAgent = (props) => { @@ -35,7 +36,7 @@ const RepAgent = (props) => { updateRepresentation, setSavingStatus, savingStatus, - setFilesForRepresentations, + setFilesForRepresentations } = props; const { acceptedFiles, getRootProps, getInputProps } = useDropzone(); @@ -63,58 +64,44 @@ const RepAgent = (props) => { )); const required = (value) => (value ? undefined : "Required"); + const hasTypeSelectionInline = + typeof formObj.representationForm.values.representationType == + "undefined" && props.representationType != "Select..."; + + const selectedRepresentationTypeDisplay = + router.locale == "cy" + ? formObj.representationForm.values.representationType == + "Questionnaire" + ? "Holiadur" + : formObj.representationForm.values.representationType == + "Statement" + ? "Datganiad" + : formObj.representationForm.values.representationType == + "Final comments" + ? "Sylwadau terfynol" + : formObj.representationForm.values.representationType + : formObj.representationForm.values.representationType; + + const handleSaveExit = () => { + let valuesObj = props.props.props.form[props.form].values || {}; + + delete valuesObj["_pinswg_appellant_value"]; + + updateRepresentation(valuesObj, false, true); + }; + return ( <> -
-
- {typeof formObj.representationForm.values - .representationType == "undefined" && - props.representationType != "Select..." ? ( - <> -

- {t( - "myrepresentations:representation-from-an-agent-heading" - )} -

- - - - - - ) : ( - - )} -
-
+ {(typeof props.representationType != "undefined" || typeof formObj.representationForm.values .representationType != "undefined") && ( @@ -242,62 +229,16 @@ const RepAgent = (props) => { )} {typeof formObj.representationForm.values.representationType != "undefined" && ( -
-
- - - {savingStatus ? ( - - {router.locale == "cy" - ? "Nôl data" - : "Saving data"} - { - - ) : ( - - )} -
-
+ )} ); @@ -310,7 +251,7 @@ const mapDispatchToProps = (dispatch) => ({ setFilesForRepresentations: (fileList) => { dispatch(setFilesForRepresentations(fileList)); - }, + } }); export default connect( @@ -320,6 +261,6 @@ export default connect( reduxForm({ form: "representationForm", enableReinitialize: true, - destroyOnUnmount: false, + destroyOnUnmount: false })(RepAgent) ); diff --git a/components/case/representation/representationAppellant.js b/components/case/representation/representationAppellant.js index 59b51e25..290697f2 100644 --- a/components/case/representation/representationAppellant.js +++ b/components/case/representation/representationAppellant.js @@ -9,14 +9,16 @@ import { FileUploadField, RenderPickList, RenderRichMultiline, - shouldDisableButton, + shouldDisableButton } from "./representationElements"; +import RepresentationActionButtons from "./elements/RepresentationActionButtons"; +import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock"; import { useDropzone } from "react-dropzone"; import { setRepresentationCapacity, setRepresentationSubmit, - setFilesForRepresentations, + setFilesForRepresentations } from "../../../store/currentView/action"; const RepAppellant = (props) => { @@ -34,7 +36,7 @@ const RepAppellant = (props) => { updateRepresentation, setSavingStatus, savingStatus, - setFilesForRepresentations, + setFilesForRepresentations } = props; const { acceptedFiles, getRootProps, getInputProps } = useDropzone(); @@ -62,56 +64,44 @@ const RepAppellant = (props) => { index === self.findIndex((t) => t.name === value.name) ); + const hasTypeSelectionInline = + typeof formObj.representationForm.values.representationType == + "undefined" && props.representationType != "Select..."; + + const selectedRepresentationTypeDisplay = + router.locale == "cy" + ? formObj.representationForm.values.representationType == + "Questionnaire" + ? "Holiadur" + : formObj.representationForm.values.representationType == + "Statement" + ? "Datganiad" + : formObj.representationForm.values.representationType == + "Final comments" + ? "Sylwadau terfynol" + : formObj.representationForm.values.representationType + : formObj.representationForm.values.representationType; + + const handleSaveExit = () => { + let valuesObj = props.props.props.form[props.form].values || {}; + + delete valuesObj["_pinswg_appellant_value"]; + + updateRepresentation(valuesObj, false, true); + }; + return ( <> -
-
- {typeof formObj.representationForm.values - .representationType == "undefined" && - props.representationType != "Select..." ? ( - <> -

- {t( - "myrepresentations:representation-from-an-appellant-heading" - )} -

- - - - ) : ( - - )} -
-
+ {(typeof props.representationType != "undefined" || typeof formObj.representationForm.values .representationType != "undefined") && ( @@ -232,62 +222,16 @@ const RepAppellant = (props) => { {typeof formObj.representationForm.values.representationType != "undefined" && ( -
-
- - - {savingStatus ? ( - - {router.locale == "cy" - ? "Nôl data" - : "Saving data"} - { - - ) : ( - - )} -
-
+ )} ); @@ -303,7 +247,7 @@ const mapDispatchToProps = (dispatch) => { setFilesForRepresentations: (fileList) => { dispatch(setFilesForRepresentations(fileList)); - }, + } }; }; @@ -314,6 +258,6 @@ export default connect( reduxForm({ form: "representationForm", enableReinitialize: true, - destroyOnUnmount: false, + destroyOnUnmount: false })(RepAppellant) ); diff --git a/components/case/representation/representationInterestedPartyPerson.js b/components/case/representation/representationInterestedPartyPerson.js index 63c21ab7..4f55b433 100644 --- a/components/case/representation/representationInterestedPartyPerson.js +++ b/components/case/representation/representationInterestedPartyPerson.js @@ -8,10 +8,11 @@ import { bytesToSize, getThumbnailIconByExtension } from "../../utils"; import { FileUploadField, RenderCondtionalRadioList, - RenderPickList, RenderRichMultiline, shouldDisableButton } from "./representationElements"; +import RepresentationActionButtons from "./elements/RepresentationActionButtons"; +import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock"; import { useDropzone } from "react-dropzone"; import { @@ -62,63 +63,47 @@ const RepInterestedPartyPerson = (props) => { {file.path} - {file.size} bytes )); + + const hasTypeSelectionInline = + typeof formObj.representationForm.values.representationType == + "undefined" && props.representationType != "Select..."; + + const selectedRepresentationTypeDisplay = + router.locale == "cy" + ? formObj.representationForm.values.representationType == + "Questionnaire" + ? "Holiadur" + : formObj.representationForm.values.representationType == + "Statement" + ? "Datganiad" + : formObj.representationForm.values.representationType == + "Final comments" + ? "Sylwadau terfynol" + : formObj.representationForm.values.representationType + : formObj.representationForm.values.representationType; + + const handleSaveExit = () => { + let valuesObj = props.props.props.form[props.form].values || {}; + + delete valuesObj["_pinswg_appellant_value"]; + + updateRepresentation(valuesObj, false, true); + }; + return ( <> -
-
- {typeof formObj.representationForm.values - .representationType == "undefined" && - props.representationType != "Select..." ? ( - <> -

- {t( - "myrepresentations:consultation-from-an-interested-person-heading" - )} -

- - - - - - ) : ( - - )} -
-
+ {(typeof props.representationType != "undefined" || typeof formObj.representationForm.values .representationType != "undefined") && ( @@ -235,80 +220,16 @@ const RepInterestedPartyPerson = (props) => { )} {typeof formObj.representationForm.values.representationType != "undefined" && ( -
-
- - {/* - { - setRepresentationCapacity(); - props.updateField( - "representationForm", - "representationCapacity", - "" - ); - props.updateField( - "representationForm", - "representationType", - "" - ); - }} - className="govuk-link" - > - {t("case:summary-back-button-label")} - */} - {savingStatus ? ( - - {router.locale == "cy" - ? "Nôl data" - : "Saving data"} - { - - ) : ( - - )} -
-
+ )} ); diff --git a/context/representations-refactor-tracker.md b/context/representations-refactor-tracker.md index da70991a..0e48d46a 100644 --- a/context/representations-refactor-tracker.md +++ b/context/representations-refactor-tracker.md @@ -6,8 +6,8 @@ Base branch: `refactor` ## Status -Current slice: Slice R3 — Journey Step Resolution Extraction -Status: IMPLEMENTED (manual validation pending) +Current slice: Slice R5 — Representation Elements Normalisation +Status: COMPLETE --- @@ -146,6 +146,28 @@ Clean up shared UI helpers and duplicated patterns. - file upload handling (structure only, not behaviour) - shared UI logic extraction +**Completion notes (this slice):** + +- Implemented on feature branch created from `refactor`: `rep-slice-r5-elements-normalisation`. +- Added render-only shared fragments under `components/case/representation/elements/`: + - `RepresentationTypeSelectorBlock.js` + - `RepresentationActionButtons.js` +- Integrated these fragments into main representation variants only: + - `representationAppellant.js` + - `representationAgent.js` + - `representationInterestedPartyPerson.js` +- Preserved scope constraints for Slice R5: + - kept parent components as logic owners + - no submission/update side-effect refactor + - no upload service behaviour changes + - no payload/route/query/Redux/validation business-rule changes + - consultation variants intentionally not normalised in this slice +- Validation evidence: + - `npm run lint` completed (warnings only, no new errors). + - `npm run test:reps` completed: **7 passed**. + - Manual APP/IP/Agent/LPA checks: **passed**. + - Manual EN/CY parity checks: **passed**. + --- ### Slice R6 — Async Data / Service Layer Cleanup