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 {
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 (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:representation-from-an-agent-heading"
)}
</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>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-agent-heading"
)}
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={!hasTypeSelectionInline}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
optionsArr={appellantApplicantRepresentationArr}
/>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
@@ -242,62 +229,16 @@ const RepAgent = (props) => {
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj,
})}
>
{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>
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
)}
</>
);
@@ -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)
);
@@ -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 (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:representation-from-an-appellant-heading"
)}
</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>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:representation-from-an-appellant-heading"
)}
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={!hasTypeSelectionInline}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
optionsArr={appellantApplicantRepresentationArr}
/>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
@@ -232,62 +222,16 @@ const RepAppellant = (props) => {
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj,
})}
>
{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>
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
)}
</>
);
@@ -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)
);
@@ -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
</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 (
<>
<div className="govuk-grid-row">
<div className="govuk-form-group">
{typeof formObj.representationForm.values
.representationType == "undefined" &&
props.representationType != "Select..." ? (
<>
<h2 className="govuk-heading-m ">
{t(
"myrepresentations:consultation-from-an-interested-person-heading"
)}
</h2>
<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>
<RepresentationTypeSelectorBlock
t={t}
headingText={t(
"myrepresentations:consultation-from-an-interested-person-heading"
)}
kindLabelText={t("myrepresentations:kind-of-rep-label")}
isTypeSelected={!hasTypeSelectionInline}
selectedTypeDisplay={selectedRepresentationTypeDisplay}
optionsArr={interestedPersonRepresentationArr}
validate={[required]}
errorMsg={t("myrepresentations:select-an-option-label")}
/>
{(typeof props.representationType != "undefined" ||
typeof formObj.representationForm.values
.representationType != "undefined") && (
@@ -235,80 +220,16 @@ const RepInterestedPartyPerson = (props) => {
)}
{typeof formObj.representationForm.values.representationType !=
"undefined" && (
<div className="govuk-grid-row">
<div className="govuk-button-group">
<button
type="submit"
className="govuk-button"
data-module="govuk-button"
disabled={shouldDisableButton({
props,
formObj
})}
>
{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>
<RepresentationActionButtons
t={t}
locale={router.locale}
isSaving={savingStatus}
isContinueDisabled={shouldDisableButton({
props,
formObj
})}
onSaveExit={handleSaveExit}
/>
)}
</>
);
+24 -2
View File
@@ -6,8 +6,8 @@ Base branch: `refactor`
## Status
Current slice: Slice R3Journey Step Resolution Extraction
Status: IMPLEMENTED (manual validation pending)
Current slice: Slice R5Representation 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