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
This commit is contained in:
Robert Bond
2026-04-17 09:22:36 +00:00
parent d05e54c020
commit 8e21ec1dd7
6 changed files with 272 additions and 353 deletions
@@ -0,0 +1,42 @@
const RepresentationActionButtons = ({
t,
locale,
isSaving,
isContinueDisabled,
onSaveExit
}) => {
return (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={isContinueDisabled}
>
{t("common:continue-button")}
</button>
{isSaving ? (
<span className=" progress-save-active">
{locale == "cy" ? "Nôl data" : "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={locale == "cy" ? "Nôl data" : "Saving data"}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={onSaveExit}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
);
};
export default RepresentationActionButtons;
@@ -0,0 +1,49 @@
import { Field } from "redux-form";
import { RenderPickList } from "../representationElements";
const RepresentationTypeSelectorBlock = ({
t,
headingText,
kindLabelText,
isTypeSelected,
selectedTypeDisplay,
optionsArr,
validate,
errorMsg
}) => {
return (
<div className="govuk-grid-row">
<div className="govuk-form-group">
{!isTypeSelected ? (
<>
<h2 className="govuk-heading-m ">{headingText}</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{kindLabelText}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
validate={validate}
optionsArr={optionsArr}
errorMsg={errorMsg}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{selectedTypeDisplay}
</label>
)}
</div>
</div>
);
};
export default RepresentationTypeSelectorBlock;
@@ -8,16 +8,17 @@ import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import { import {
FileUploadField, FileUploadField,
RenderCondtionalRadioList, RenderCondtionalRadioList,
RenderPickList,
RenderRichMultiline, RenderRichMultiline,
shouldDisableButton, shouldDisableButton
} from "./representationElements"; } from "./representationElements";
import RepresentationActionButtons from "./elements/RepresentationActionButtons";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { import {
setRepresentationCapacity, setRepresentationCapacity,
setRepresentationSubmit, setRepresentationSubmit,
setFilesForRepresentations, setFilesForRepresentations
} from "../../../store/currentView/action"; } from "../../../store/currentView/action";
const RepAgent = (props) => { const RepAgent = (props) => {
@@ -35,7 +36,7 @@ const RepAgent = (props) => {
updateRepresentation, updateRepresentation,
setSavingStatus, setSavingStatus,
savingStatus, savingStatus,
setFilesForRepresentations, setFilesForRepresentations
} = props; } = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone(); const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
@@ -63,58 +64,44 @@ const RepAgent = (props) => {
)); ));
const required = (value) => (value ? undefined : "Required"); 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 ( return (
<> <>
<div className="govuk-grid-row"> <RepresentationTypeSelectorBlock
<div className="govuk-form-group"> t={t}
{typeof formObj.representationForm.values headingText={t(
.representationType == "undefined" && "myrepresentations:representation-from-an-agent-heading"
props.representationType != "Select..." ? ( )}
<> kindLabelText={t("myrepresentations:kind-of-rep-label")}
<h2 className="govuk-heading-m "> isTypeSelected={!hasTypeSelectionInline}
{t( selectedTypeDisplay={selectedRepresentationTypeDisplay}
"myrepresentations:representation-from-an-agent-heading" optionsArr={appellantApplicantRepresentationArr}
)} />
</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t("myrepresentations:kind-of-rep-label")}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
optionsArr={
appellantApplicantRepresentationArr
}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{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}
</label>
)}
</div>
</div>
{(typeof props.representationType != "undefined" || {(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values typeof formObj.representationForm.values
.representationType != "undefined") && ( .representationType != "undefined") && (
@@ -242,62 +229,16 @@ const RepAgent = (props) => {
)} )}
{typeof formObj.representationForm.values.representationType != {typeof formObj.representationForm.values.representationType !=
"undefined" && ( "undefined" && (
<div className="govuk-grid-row"> <RepresentationActionButtons
<div className="govuk-button-group"> t={t}
<button locale={router.locale}
type="submit" isSaving={savingStatus}
className="govuk-button" isContinueDisabled={shouldDisableButton({
data-module="govuk-button" props,
disabled={shouldDisableButton({ formObj
props, })}
formObj, onSaveExit={handleSaveExit}
})} />
>
{t("common:continue-button")}
</button>
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)} )}
</> </>
); );
@@ -310,7 +251,7 @@ const mapDispatchToProps = (dispatch) => ({
setFilesForRepresentations: (fileList) => { setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList)); dispatch(setFilesForRepresentations(fileList));
}, }
}); });
export default connect( export default connect(
@@ -320,6 +261,6 @@ export default connect(
reduxForm({ reduxForm({
form: "representationForm", form: "representationForm",
enableReinitialize: true, enableReinitialize: true,
destroyOnUnmount: false, destroyOnUnmount: false
})(RepAgent) })(RepAgent)
); );
@@ -9,14 +9,16 @@ import {
FileUploadField, FileUploadField,
RenderPickList, RenderPickList,
RenderRichMultiline, RenderRichMultiline,
shouldDisableButton, shouldDisableButton
} from "./representationElements"; } from "./representationElements";
import RepresentationActionButtons from "./elements/RepresentationActionButtons";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { import {
setRepresentationCapacity, setRepresentationCapacity,
setRepresentationSubmit, setRepresentationSubmit,
setFilesForRepresentations, setFilesForRepresentations
} from "../../../store/currentView/action"; } from "../../../store/currentView/action";
const RepAppellant = (props) => { const RepAppellant = (props) => {
@@ -34,7 +36,7 @@ const RepAppellant = (props) => {
updateRepresentation, updateRepresentation,
setSavingStatus, setSavingStatus,
savingStatus, savingStatus,
setFilesForRepresentations, setFilesForRepresentations
} = props; } = props;
const { acceptedFiles, getRootProps, getInputProps } = useDropzone(); const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
@@ -62,56 +64,44 @@ const RepAppellant = (props) => {
index === self.findIndex((t) => t.name === value.name) 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 ( return (
<> <>
<div className="govuk-grid-row"> <RepresentationTypeSelectorBlock
<div className="govuk-form-group"> t={t}
{typeof formObj.representationForm.values headingText={t(
.representationType == "undefined" && "myrepresentations:representation-from-an-appellant-heading"
props.representationType != "Select..." ? ( )}
<> kindLabelText={t("myrepresentations:kind-of-rep-label")}
<h2 className="govuk-heading-m "> isTypeSelected={!hasTypeSelectionInline}
{t( selectedTypeDisplay={selectedRepresentationTypeDisplay}
"myrepresentations:representation-from-an-appellant-heading" optionsArr={appellantApplicantRepresentationArr}
)} />
</h2>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t("myrepresentations:kind-of-rep-label")}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
optionsArr={
appellantApplicantRepresentationArr
}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{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}
</label>
)}
</div>
</div>
{(typeof props.representationType != "undefined" || {(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values typeof formObj.representationForm.values
.representationType != "undefined") && ( .representationType != "undefined") && (
@@ -232,62 +222,16 @@ const RepAppellant = (props) => {
{typeof formObj.representationForm.values.representationType != {typeof formObj.representationForm.values.representationType !=
"undefined" && ( "undefined" && (
<div className="govuk-grid-row"> <RepresentationActionButtons
<div className="govuk-button-group"> t={t}
<button locale={router.locale}
type="submit" isSaving={savingStatus}
className="govuk-button" isContinueDisabled={shouldDisableButton({
data-module="govuk-button" props,
disabled={shouldDisableButton({ formObj
props, })}
formObj, onSaveExit={handleSaveExit}
})} />
>
{t("common:continue-button")}
</button>
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)} )}
</> </>
); );
@@ -303,7 +247,7 @@ const mapDispatchToProps = (dispatch) => {
setFilesForRepresentations: (fileList) => { setFilesForRepresentations: (fileList) => {
dispatch(setFilesForRepresentations(fileList)); dispatch(setFilesForRepresentations(fileList));
}, }
}; };
}; };
@@ -314,6 +258,6 @@ export default connect(
reduxForm({ reduxForm({
form: "representationForm", form: "representationForm",
enableReinitialize: true, enableReinitialize: true,
destroyOnUnmount: false, destroyOnUnmount: false
})(RepAppellant) })(RepAppellant)
); );
@@ -8,10 +8,11 @@ import { bytesToSize, getThumbnailIconByExtension } from "../../utils";
import { import {
FileUploadField, FileUploadField,
RenderCondtionalRadioList, RenderCondtionalRadioList,
RenderPickList,
RenderRichMultiline, RenderRichMultiline,
shouldDisableButton shouldDisableButton
} from "./representationElements"; } from "./representationElements";
import RepresentationActionButtons from "./elements/RepresentationActionButtons";
import RepresentationTypeSelectorBlock from "./elements/RepresentationTypeSelectorBlock";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { import {
@@ -62,63 +63,47 @@ const RepInterestedPartyPerson = (props) => {
{file.path} - {file.size} bytes {file.path} - {file.size} bytes
</li> </li>
)); ));
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 ( return (
<> <>
<div className="govuk-grid-row"> <RepresentationTypeSelectorBlock
<div className="govuk-form-group"> t={t}
{typeof formObj.representationForm.values headingText={t(
.representationType == "undefined" && "myrepresentations:consultation-from-an-interested-person-heading"
props.representationType != "Select..." ? ( )}
<> kindLabelText={t("myrepresentations:kind-of-rep-label")}
<h2 className="govuk-heading-m "> isTypeSelected={!hasTypeSelectionInline}
{t( selectedTypeDisplay={selectedRepresentationTypeDisplay}
"myrepresentations:consultation-from-an-interested-person-heading" optionsArr={interestedPersonRepresentationArr}
)} validate={[required]}
</h2> errorMsg={t("myrepresentations:select-an-option-label")}
/>
<label
className="govuk-label govuk-body-m"
htmlFor="representationType"
>
{t("myrepresentations:kind-of-rep-label")}
</label>
<Field
name="representationType"
component={RenderPickList}
datafieldname="representationType"
validate={[required]}
optionsArr={
interestedPersonRepresentationArr
}
errorMsg={t(
"myrepresentations:select-an-option-label"
)}
/>
</>
) : (
<label className="govuk-label govuk-body-m">
{t("myrepresentations:representation-type")}:{" "}
{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}
</label>
)}
</div>
</div>
{(typeof props.representationType != "undefined" || {(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values typeof formObj.representationForm.values
.representationType != "undefined") && ( .representationType != "undefined") && (
@@ -235,80 +220,16 @@ const RepInterestedPartyPerson = (props) => {
)} )}
{typeof formObj.representationForm.values.representationType != {typeof formObj.representationForm.values.representationType !=
"undefined" && ( "undefined" && (
<div className="govuk-grid-row"> <RepresentationActionButtons
<div className="govuk-button-group"> t={t}
<button locale={router.locale}
type="submit" isSaving={savingStatus}
className="govuk-button" isContinueDisabled={shouldDisableButton({
data-module="govuk-button" props,
disabled={shouldDisableButton({ formObj
props, })}
formObj onSaveExit={handleSaveExit}
})} />
>
{t("common:continue-button")}
</button>
{/*
<a
onClick={() => {
setRepresentationCapacity();
props.updateField(
"representationForm",
"representationCapacity",
""
);
props.updateField(
"representationForm",
"representationType",
""
);
}}
className="govuk-link"
>
{t("case:summary-back-button-label")}
</a> */}
{savingStatus ? (
<span className=" progress-save-active">
{router.locale == "cy"
? "Nôl data"
: "Saving data"}
<img
className="data-loading-icon"
src="/assets/images/loading.gif"
alt={
router.locale == "cy"
? "Nôl data"
: "Saving data"
}
/>
</span>
) : (
<button
className="govuk-button progress-save "
onClick={() => {
let valuesObj =
props.props.props.form[props.form]
.values || {};
//console.log("valuesobj:", valuesObj);
delete valuesObj[
"_pinswg_appellant_value"
];
//console.log("on save:", valuesObj);
updateRepresentation(
valuesObj,
false,
true
);
}}
>
{t("common:save-exit-button")}
</button>
)}
</div>
</div>
)} )}
</> </>
); );
+24 -2
View File
@@ -6,8 +6,8 @@ Base branch: `refactor`
## Status ## Status
Current slice: Slice R3Journey Step Resolution Extraction Current slice: Slice R5Representation Elements Normalisation
Status: IMPLEMENTED (manual validation pending) Status: COMPLETE
--- ---
@@ -146,6 +146,28 @@ Clean up shared UI helpers and duplicated patterns.
- file upload handling (structure only, not behaviour) - file upload handling (structure only, not behaviour)
- shared UI logic extraction - 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 ### Slice R6 — Async Data / Service Layer Cleanup