diff --git a/model/src/components/component-types.ts b/model/src/components/component-types.ts index fd4c1a7406..f79e28738d 100644 --- a/model/src/components/component-types.ts +++ b/model/src/components/component-types.ts @@ -10,6 +10,15 @@ export const ComponentTypes: ComponentDef[] = [ options: {}, schema: {}, }, + { + name: "TextFieldCustom", + type: "TextFieldCustom", + title: "Text field", + subType: "field", + hint: "", + options: {}, + schema: {}, + }, { name: "MultilineTextField", type: "MultilineTextField", diff --git a/model/src/components/types.ts b/model/src/components/types.ts index 8357d643e7..2fe51cef12 100644 --- a/model/src/components/types.ts +++ b/model/src/components/types.ts @@ -1,5 +1,6 @@ export enum ComponentTypeEnum { TextField = "TextField", + TextFieldCustom = "TextFieldCustom", MultilineTextField = "MultilineTextField", YesNoField = "YesNoField", DateField = "DateField", @@ -28,6 +29,7 @@ export enum ComponentTypeEnum { export type ComponentType = | "TextField" + | "TextFieldCustom" | "MultilineTextField" | "YesNoField" | "DateField" @@ -178,6 +180,17 @@ export interface TextFieldComponent extends TextFieldBase { }; } +export interface TextFieldCustomComponent extends TextFieldBase { + type: "TextFieldCustom"; + options: TextFieldBase["options"] & { + customValidationMessage?: string; + conditionalTextField?: { + dependsOn: string; + }; + fieldName?: string; + }; +} + export interface EmailAddressFieldComponent extends TextFieldBase { type: "EmailAddressField"; } @@ -345,6 +358,7 @@ export type ComponentDef = | SelectFieldComponent | TelephoneNumberFieldComponent | TextFieldComponent + | TextFieldCustomComponent | TimeFieldComponent | UkAddressFieldComponent | YesNoFieldComponent diff --git a/runner/src/server/forms/ReportAnOutbreak.json b/runner/src/server/forms/ReportAnOutbreak.json index 833721bc82..074c8d3cf7 100644 --- a/runner/src/server/forms/ReportAnOutbreak.json +++ b/runner/src/server/forms/ReportAnOutbreak.json @@ -33,7 +33,7 @@ "title": "Acute Respiratory Infections (ARI) - COVID-19, Flu or unknown infection in Adult Social Care settings", "components": [ { - "name": "S0Q4", + "name": "S0Q1", "options": { "customValidationMessages": { "string.regex.base": "Enter a full UK postcode", @@ -41,53 +41,79 @@ } }, "type": "TextField", - "title": "Please enter your setting Postcode", + "title": "Your setting postcode", "schema": { "regex": "^([a-zA-Z]{1,2}[0-9][a-zA-Z0-9]? *[0-9][a-zA-Z]{2}|[a-zA-Z]{1,2}[0-9][a-zA-Z0-9]?)$" }, "nameHasError": false, - "hint": "Please provide the postcode of the specific setting affected (i.e. not the overarching management company if applicable). If there is no one specific setting affected e.g. Domiciliary or Home Care, then please provide the postcode of the care provider company" + "hint": "Give the postcode of your care setting, not the management company. If you provide domiciliary or home care, enter the postcode of the care provider company." }, { "name": "S0Q2", - "options": { - "required":false, - "customValidationMessages": { - "string.regex.base": "Enter a valid CQC Location ID", - "string.pattern.base": "Enter a valid CQC Location ID" - } - }, - "type": "TextField", - "title": "Please enter your CQC Location ID", + "options": { "required": true }, + "type": "SelectField", + "title": "Your local UKHSA health protection team", + "list": "sjgMDe", "nameHasError": false, - "schema": { "regex": "^(1-\\d{1,9}|[a-zA-Z0-9]{3,7})$" } + "schema": {}, + "values": { + "type": "listRef" + } }, { - "name": "VMOkqi", + "name": "cstZQG", "options": {}, "type": "Para", - "content": "If you don’t know your CQC Location ID then please visit Using CQC data - Care Quality Commission and scroll about half way down the page to CQC care directory - csv. You will find your CQC Location ID in this look up table.\n

", + "content": "Use your postcode to find your local health protection team (opens in a new tab)", "schema": {} }, { "name": "S0Q3", - "options": { "required": true }, - "type": "SelectField", - "title": "Please enter your local HPT here", - "list": "sjgMDe", + "options": { + "required": false, + "optionalText": false, + "conditionalTextField": { + "dependsOn": "S0Q4" + }, + "fieldName": "cqc" + }, + "type": "TextFieldCustom", + "title": "Your Care Quality Commission (CQC) location ID", "nameHasError": false, - "schema": {}, - "values": { - "type": "listRef" + "schema": { + "regex": "^(1-\\d{1,9}|[a-zA-Z0-9]{3,7})$" + } + }, + { + "name": "rIpMWq", + "options": {}, + "type": "Para", + "content": "
or
" + }, + { + "name": "S0Q4", + "options": { + "hideTitle": true, + "required": false }, - "hint": "Please make sure you've selected the correct local health protection team (HPT) to guarantee you receive the appropriate support" + "type": "CheckboxesField", + "title": "My setting is not registered with the CQC", + "nameHasError": false, + "list": "OZDejp", + "schema": {} }, { - "name": "cstZQG", + "name": "VMOkqi", "options": {}, "type": "Para", - "content": "Click on the link to Find your local health protection team in England - GOV.UK (www.gov.uk) [Enter the postcode of your care setting]", + "content": "Find your CQC location ID in the care directory on the CQC website (opens in a new tab).", "schema": {} + }, + { + "name": "rIpMWr", + "options": {}, + "type": "Html", + "content": "" } ], "next": [ @@ -1661,6 +1687,17 @@ } ], "lists": [ + { + "title": "care-setting-not-registered", + "name": "OZDejp", + "type": "string", + "items": [ + { + "text": "My setting is not registered with the CQC", + "value": "My setting is not registered with the CQC" + } + ] + }, { "title": "social-care-provider-type", "name": "MpSRIP", diff --git a/runner/src/server/plugins/engine/components/CheckboxesField.ts b/runner/src/server/plugins/engine/components/CheckboxesField.ts index 73c3c7ba11..057858f771 100644 --- a/runner/src/server/plugins/engine/components/CheckboxesField.ts +++ b/runner/src/server/plugins/engine/components/CheckboxesField.ts @@ -1,4 +1,9 @@ -import { FormData, FormSubmissionErrors, FormSubmissionState } from "../types"; +import { + FormData, + FormPayload, + FormSubmissionErrors, + FormSubmissionState, +} from "../types"; import { ListComponentsDef } from "@xgovformbuilder/model"; import { FormModel } from "../models"; import joi from "joi"; @@ -31,6 +36,10 @@ export class CheckboxesField extends SelectionControlField { this.stateSchema = schema; } + getStateValueFromValidForm(payload: FormPayload) { + return payload[this.name]; + } + getDisplayStringFromState(state: FormSubmissionState) { return state?.[this.name] ?.map( diff --git a/runner/src/server/plugins/engine/components/TextFieldCustom.ts b/runner/src/server/plugins/engine/components/TextFieldCustom.ts new file mode 100644 index 0000000000..ee532b5ed7 --- /dev/null +++ b/runner/src/server/plugins/engine/components/TextFieldCustom.ts @@ -0,0 +1,65 @@ +import { TextFieldCustomComponent } from "@xgovformbuilder/model"; +import { FormModel } from "../models"; +import joi from "joi"; +import * as helpers from "./helpers"; +import { FormPayload } from "../types"; +import { FormComponent } from "./FormComponent"; + +// Currently only used on the ReportAnOutbreak/uon-and-cqc page to handle textfield and checkbox comparisons +export class TextFieldCustom extends FormComponent { + formSchema; + stateSchema; + pattern; + dependentField; + fieldName; + + constructor(def: TextFieldCustomComponent, model: FormModel) { + super(def, model); + + const { options, schema = {} } = def; + this.options = options; + this.schema = schema; + + if (schema.regex) { + this.pattern = new RegExp(schema.regex); + } + + if (options.conditionalTextField) { + this.dependentField = options.conditionalTextField.dependsOn; + } + + if (options.fieldName) { + this.fieldName = options.fieldName; + } + + let componentSchema = joi.optional().allow(null).allow(""); + + this.formSchema = componentSchema; + } + + getStateSchemaKeys() { + let schema: any = this.formSchema; + + if (this.fieldName) { + schema = schema.custom( + helpers.customTextCheckboxValidator(this.fieldName) + ); + } + + this.schema = schema; + return { [this.name]: schema }; + } + + getStateValueFromValidForm(payload: FormPayload) { + const currentFieldValue = payload[this.name].trim(); + const dependentFieldValue = payload[this.dependentField]; + + if (currentFieldValue === "" && !dependentFieldValue) { + return false; + } + if (currentFieldValue && !this.pattern.test(currentFieldValue)) { + return "regex"; + } + return currentFieldValue; + } +} diff --git a/runner/src/server/plugins/engine/components/helpers.ts b/runner/src/server/plugins/engine/components/helpers.ts index d922a1f6e5..75655b1eed 100644 --- a/runner/src/server/plugins/engine/components/helpers.ts +++ b/runner/src/server/plugins/engine/components/helpers.ts @@ -149,6 +149,22 @@ export function getCustomDateValidator( }; } +export function customTextCheckboxValidator(fieldName: string) { + return (value: string, helpers: joi.CustomHelpers) => { + if (!value) { + return helpers.error(`string.${fieldName}`, { + label: helpers.state.key, + }); + } + if (value == "regex") { + return helpers.error(`string.${fieldName}.regex`, { + label: helpers.state.key, + }); + } + return value; + }; +} + export function internationalPhoneValidator( value: string, _helpers: joi.CustomHelpers diff --git a/runner/src/server/plugins/engine/components/index.ts b/runner/src/server/plugins/engine/components/index.ts index fab35d4651..986e435821 100644 --- a/runner/src/server/plugins/engine/components/index.ts +++ b/runner/src/server/plugins/engine/components/index.ts @@ -28,6 +28,7 @@ export { RadiosField } from "./RadiosField"; export { SelectField } from "./SelectField"; export { TelephoneNumberField } from "./TelephoneNumberField"; export { TextField } from "./TextField"; +export { TextFieldCustom } from "./TextFieldCustom"; export { TimeField } from "./TimeField"; export { UkAddressField } from "./UkAddressField"; export { WebsiteField } from "./WebsiteField"; diff --git a/runner/src/server/plugins/engine/pageControllers/validationOptions.ts b/runner/src/server/plugins/engine/pageControllers/validationOptions.ts index 9fceb9497e..3cc44a8d1d 100644 --- a/runner/src/server/plugins/engine/pageControllers/validationOptions.ts +++ b/runner/src/server/plugins/engine/pageControllers/validationOptions.ts @@ -29,6 +29,9 @@ const messageTemplate = { dateDayYear: "{{#label}} must include a day and a year", dateDayMonth: "{{#label}} must include a day and a month", dateYear4digits: "The year must include 4 numbers", + cqc: + "Enter your CQC location ID or select 'My setting is not registered with the CQC'", + cqcRegex: "Enter a valid CQC Location ID", }; export const messages: ValidationOptions["messages"] = { @@ -39,6 +42,8 @@ export const messages: ValidationOptions["messages"] = { "string.email": messageTemplate.email, "string.regex.base": messageTemplate.format, "string.maxWords": messageTemplate.maxWords, + "string.cqc": messageTemplate.cqc, + "string.cqc.regex": messageTemplate.cqcRegex, "date.base": messageTemplate.date, "date.empty": messageTemplate.required, diff --git a/runner/src/server/plugins/engine/views/components/textfieldcustom.html b/runner/src/server/plugins/engine/views/components/textfieldcustom.html new file mode 100644 index 0000000000..2a04118f48 --- /dev/null +++ b/runner/src/server/plugins/engine/views/components/textfieldcustom.html @@ -0,0 +1,5 @@ +{% from "input/macro.njk" import govukInput %} + +{% macro TextFieldCustom(component) %} + {{ govukInput(component.model) }} +{% endmacro %} diff --git a/runner/src/server/plugins/engine/views/partials/form.html b/runner/src/server/plugins/engine/views/partials/form.html index 01809ff5f9..f252d37ee2 100644 --- a/runner/src/server/plugins/engine/views/partials/form.html +++ b/runner/src/server/plugins/engine/views/partials/form.html @@ -1,7 +1,7 @@ {% from "button/macro.njk" import govukButton %} {% from "summary-list/macro.njk" import govukSummaryList -%} -
+ {{ componentList(components) }}