diff --git a/src/data/repositories/consts/DiseaseOutbreakConstants.ts b/src/data/repositories/consts/DiseaseOutbreakConstants.ts index fcb3db5b..501b31fc 100644 --- a/src/data/repositories/consts/DiseaseOutbreakConstants.ts +++ b/src/data/repositories/consts/DiseaseOutbreakConstants.ts @@ -132,21 +132,11 @@ export function getValueFromDiseaseOutbreak( } export function getHazardTypeByCode(hazardTypeCode: string): HazardType { - switch (hazardTypeCode) { - case "BIOLOGICAL_ANIMAL": - return "Biological:Animal"; - case "BIOLOGICAL_HUMAN": - return "Biological:Human"; - case "BIOLOGICAL_HUM_ANM": - return "Biological:HumanAndAnimal"; - case "CHEMICAL": - return "Chemical"; - case "ENVIRONMENTAL": - return "Environmental"; - case "UNKNOWN": - default: - return "Unknown"; - } + return ( + (Object.keys(hazardTypeCodeMap) as HazardType[]).find( + key => hazardTypeCodeMap[key] === hazardTypeCode + ) || "Unknown" + ); } function getOUTextFromList(OUs: string[]): string { diff --git a/src/webapp/components/form/FieldWidget.tsx b/src/webapp/components/form/FieldWidget.tsx index 0b5d61de..c41accb0 100644 --- a/src/webapp/components/form/FieldWidget.tsx +++ b/src/webapp/components/form/FieldWidget.tsx @@ -9,7 +9,7 @@ import { RadioButtonsGroup } from "../radio-buttons-group/RadioButtonsGroup"; import { TextArea } from "../text-input/TextArea"; import { DatePicker } from "../date-picker/DatePicker"; import { Checkbox } from "../checkbox/Checkbox"; -import { FormFieldState, updateFieldState } from "./FormState"; +import { FormFieldState, updateFieldState } from "./FormFieldsState"; export type FieldWidgetProps = { onChange: (updatedField: FormFieldState) => void; diff --git a/src/webapp/components/form/Form.tsx b/src/webapp/components/form/Form.tsx index 377c21ca..a88478ec 100644 --- a/src/webapp/components/form/Form.tsx +++ b/src/webapp/components/form/Form.tsx @@ -1,10 +1,11 @@ import React from "react"; import { useForm } from "./useForm"; -import { FormState, FormFieldState } from "./FormState"; +import { FormState } from "./FormState"; import { FormLayout } from "./FormLayout"; import { FormSection } from "./FormSection"; import { Layout } from "../layout/Layout"; +import { FormFieldState } from "./FormFieldsState"; export type FormProps = { formState: FormState; diff --git a/src/webapp/components/form/FormFieldsState.ts b/src/webapp/components/form/FormFieldsState.ts new file mode 100644 index 00000000..3d34d4d4 --- /dev/null +++ b/src/webapp/components/form/FormFieldsState.ts @@ -0,0 +1,200 @@ +import { Maybe } from "../../../utils/ts-utils"; +import { validateFieldRequired, validateFieldRequiredWithNotApplicable } from "./validations"; +import { UserOption } from "../user-selector/UserSelector"; +import { Option } from "../utils/option"; +import { ValidationError, ValidationErrorKey } from "../../../domain/entities/ValidationError"; +import { FormSectionState } from "./FormSectionsState"; + +export type FieldType = "text" | "boolean" | "select" | "radio" | "date" | "user"; + +type FormFieldStateBase = { + id: string; + label?: string; + placeholder?: string; + helperText?: string; + errors: string[]; + required?: boolean; + showIsRequired?: boolean; + disabled?: boolean; + isVisible?: boolean; + hasNotApplicable?: boolean; + notApplicableFieldId?: string; + width?: string; + maxWidth?: string; + value: T; + type: FieldType; +}; + +export type FormTextFieldState = FormFieldStateBase & { + type: "text"; + multiline?: boolean; +}; + +export type FormBooleanFieldState = FormFieldStateBase & { + type: "boolean"; +}; + +export type FormMultipleOptionsFieldState = FormFieldStateBase & { + type: "select"; + options: Option[]; + multiple: true; +}; + +export type FormOptionsFieldState = FormFieldStateBase & { + type: "select" | "radio"; + options: Option[]; + multiple: false; +}; + +export type FormDateFieldState = FormFieldStateBase & { + type: "date"; +}; + +export type FormAvatarFieldState = FormFieldStateBase> & { + type: "user"; + options: UserOption[]; +}; + +export type FormFieldState = + | FormTextFieldState + | FormOptionsFieldState + | FormMultipleOptionsFieldState + | FormBooleanFieldState + | FormDateFieldState + | FormAvatarFieldState; + +// HELPERS: + +export function getAllFieldsFromSections(formSections: FormSectionState[]): FormFieldState[] { + return formSections.reduce( + (acc: FormFieldState[], section: FormSectionState): FormFieldState[] => { + if (section.subsections) { + return [...acc, ...getAllFieldsFromSections(section.subsections)]; + } else { + return [...acc, ...section.fields]; + } + }, + [] + ); +} + +export function getStringFieldValue(id: string, allFields: FormFieldState[]): string { + return ( + getFieldValueById( + id, + allFields + ) || "" + ); +} + +export function getDateFieldValue(id: string, allFields: FormFieldState[]): Date | null { + return getFieldValueById(id, allFields) || null; +} + +export function getBooleanFieldValue(id: string, allFields: FormFieldState[]): boolean { + return !!getFieldValueById(id, allFields); +} + +export function getMultipleOptionsFieldValue(id: string, allFields: FormFieldState[]): string[] { + return getFieldValueById(id, allFields) || []; +} + +export function getFieldValueById( + id: string, + fields: FormFieldState[] +): F["value"] | undefined { + const field = fields.find(field => field.id === id); + if (field) { + return getFieldValue(field); + } +} + +export function getFieldIdFromIdsDictionary>( + key: keyof T, + fieldIdsDictionary: T +): string { + return fieldIdsDictionary[key] as string; +} + +export function getEmptyValueForField(field: FormFieldState): F["value"] { + switch (field.type) { + case "text": + return ""; + case "boolean": + return false; + case "select": + return field.multiple ? [] : ""; + case "radio": + return ""; + case "date": + return null; + case "user": + return undefined; + } +} + +export function getFieldValue(field: FormFieldState): F["value"] { + return field.value; +} + +export function isFieldInSection(section: FormSectionState, field: FormFieldState): boolean { + return section.fields.some(f => f.id === field.id); +} + +// UPDATES: + +export function updateFields( + formFields: FormFieldState[], + updatedField: FormFieldState, + fieldValidationErrors?: ValidationError[] +): FormFieldState[] { + return formFields.map(field => { + if (field.id === updatedField.id) { + return { + ...updatedField, + errors: + fieldValidationErrors?.find(error => error.property === updatedField.id) + ?.errors || [], + }; + } else { + return field; + } + }); +} + +export function updateFieldState( + fieldToUpdate: F, + newValue: F["value"] +): F { + return { ...fieldToUpdate, value: newValue }; +} + +// VALIDATIONS: + +export function validateField( + field: FormFieldState, + allFields: FormFieldState[] = [] +): ValidationError | undefined { + if (field.notApplicableFieldId || !field.isVisible) return; + + const errors: ValidationErrorKey[] = [ + ...(field.required && !field.hasNotApplicable + ? validateFieldRequired(field.value, field.type) + : []), + ...(field.hasNotApplicable + ? validateFieldRequiredWithNotApplicable( + field.value, + field.type, + !!allFields.find(f => f.notApplicableFieldId === field.id)?.value + ) + : []), + ]; + + return errors.length > 0 + ? { + property: field.id, + errors: errors, + value: field.value, + } + : undefined; +} diff --git a/src/webapp/components/form/FormSection.tsx b/src/webapp/components/form/FormSection.tsx index a4421aae..34aee588 100644 --- a/src/webapp/components/form/FormSection.tsx +++ b/src/webapp/components/form/FormSection.tsx @@ -5,7 +5,8 @@ import { IconInfo24 } from "@dhis2/ui"; import { Separator } from "../separator/Separator"; import { IconButton } from "../icon-button/IconButton"; import { FieldWidget } from "./FieldWidget"; -import { FormFieldState, FormSectionState } from "./FormState"; +import { FormFieldState } from "./FormFieldsState"; +import { FormSectionState } from "./FormSectionsState"; type FormSectionProps = { id: string; diff --git a/src/webapp/components/form/FormSectionsState.ts b/src/webapp/components/form/FormSectionsState.ts new file mode 100644 index 00000000..7fc8e530 --- /dev/null +++ b/src/webapp/components/form/FormSectionsState.ts @@ -0,0 +1,157 @@ +import { ValidationError } from "../../../domain/entities/ValidationError"; +import { + FormFieldState, + getAllFieldsFromSections, + getEmptyValueForField, + getFieldValue, + isFieldInSection, + updateFields, + validateField, +} from "./FormFieldsState"; +import { FormState } from "./FormState"; + +export type FormSectionState = { + id: string; + title?: string; + isVisible?: boolean; + required?: boolean; + direction?: "row" | "column"; + fields: FormFieldState[]; + subsections?: FormSectionState[]; + onClickInfo?: (id: string) => void; +}; + +// HELPERS: + +function hasSectionAFieldWithNotApplicable(sectionsState: FormSectionState) { + return sectionsState.fields.some(field => field.hasNotApplicable); +} + +// UPDATES: + +export function applyEffectNotApplicableFieldUpdatedInSections( + sectionsState: FormSectionState[] +): FormSectionState[] { + return sectionsState.map(section => { + if (section?.subsections) { + const maybeAppliedEffect = hasSectionAFieldWithNotApplicable(section) + ? applyEffectNotApplicableFieldUpdatedInSection(section) + : section; + return { + ...maybeAppliedEffect, + subsections: applyEffectNotApplicableFieldUpdatedInSections(section?.subsections), + }; + } else { + return hasSectionAFieldWithNotApplicable(section) + ? applyEffectNotApplicableFieldUpdatedInSection(section) + : section; + } + }); +} + +function applyEffectNotApplicableFieldUpdatedInSection( + sectionState: FormSectionState +): FormSectionState { + return { + ...sectionState, + fields: sectionState.fields.map(field => { + if (field.hasNotApplicable) { + const notApplicableField = sectionState.fields.find( + f => f.notApplicableFieldId === field.id + ); + + // TODO: FIXME TypeScript error returning the corresponding fieldValue type + const fieldValue = ( + notApplicableField?.value ? getEmptyValueForField(field) : getFieldValue(field) + ) as any; + + return { + ...field, + value: fieldValue, + disabled: !!notApplicableField?.value, + }; + } + return field; + }), + }; +} + +export function updateSections( + formSectionsState: FormSectionState[], + updatedField: FormFieldState, + fieldValidationErrors?: ValidationError[] +): FormSectionState[] { + return formSectionsState.map(section => { + if (section?.subsections) { + const maybeUpdatedSection = isFieldInSection(section, updatedField) + ? updateSectionState(section, updatedField, fieldValidationErrors) + : section; + return { + ...maybeUpdatedSection, + subsections: updateSections( + section?.subsections, + updatedField, + fieldValidationErrors + ), + }; + } else { + return isFieldInSection(section, updatedField) + ? updateSectionState(section, updatedField, fieldValidationErrors) + : section; + } + }); +} + +function updateSectionState( + formSectionState: FormSectionState, + updatedField: FormFieldState, + fieldValidationErrors?: ValidationError[] +): FormSectionState { + if (isFieldInSection(formSectionState, updatedField)) { + return { + ...formSectionState, + fields: updateFields(formSectionState.fields, updatedField, fieldValidationErrors), + }; + } else { + return formSectionState; + } +} + +// VALIDATIONS: + +export function validateSections( + sections: FormSectionState[], + updatedField: FormFieldState, + newState: FormState +): ValidationError[] { + return sections.flatMap(section => { + if (section?.subsections) { + const maybeValidatedSection = isFieldInSection(section, updatedField) + ? validateSection(section, updatedField, newState) + : []; + return [ + ...maybeValidatedSection, + ...validateSections(section?.subsections, updatedField, newState), + ]; + } else { + return isFieldInSection(section, updatedField) + ? validateSection(section, updatedField, newState) + : []; + } + }); +} + +function validateSection( + section: FormSectionState, + updatedField: FormFieldState, + newState: FormState +): ValidationError[] { + const allFields: FormFieldState[] = getAllFieldsFromSections(newState.sections); + + return section.fields.flatMap(field => { + if (field.id === updatedField.id) { + return validateField(updatedField, allFields) || []; + } + return []; + }); +} diff --git a/src/webapp/components/form/FormState.ts b/src/webapp/components/form/FormState.ts index d49754bb..b63ca25d 100644 --- a/src/webapp/components/form/FormState.ts +++ b/src/webapp/components/form/FormState.ts @@ -1,8 +1,11 @@ -import { Maybe } from "../../../utils/ts-utils"; -import { validateFieldRequired, validateFieldRequiredWithNotApplicable } from "./validations"; -import { UserOption } from "../user-selector/UserSelector"; -import { Option } from "../utils/option"; -import { ValidationError, ValidationErrorKey } from "../../../domain/entities/ValidationError"; +import { ValidationError } from "../../../domain/entities/ValidationError"; +import { FormFieldState, getAllFieldsFromSections, validateField } from "./FormFieldsState"; +import { + applyEffectNotApplicableFieldUpdatedInSections, + FormSectionState, + updateSections, + validateSections, +} from "./FormSectionsState"; export type FormState = { id: string; @@ -16,147 +19,6 @@ export type FormState = { isValid: boolean; }; -export type FormSectionState = { - id: string; - title?: string; - isVisible?: boolean; - required?: boolean; - direction?: "row" | "column"; - fields: FormFieldState[]; - subsections?: FormSectionState[]; - onClickInfo?: (id: string) => void; -}; - -export type FieldType = "text" | "boolean" | "select" | "radio" | "date" | "user"; - -type FormFieldStateBase = { - id: string; - label?: string; - placeholder?: string; - helperText?: string; - errors: string[]; - required?: boolean; - showIsRequired?: boolean; - disabled?: boolean; - isVisible?: boolean; - hasNotApplicable?: boolean; - notApplicableFieldId?: string; - width?: string; - maxWidth?: string; - value: T; - type: FieldType; -}; - -export type FormTextFieldState = FormFieldStateBase & { - type: "text"; - multiline?: boolean; -}; - -export type FormBooleanFieldState = FormFieldStateBase & { - type: "boolean"; -}; - -export type FormMultipleOptionsFieldState = FormFieldStateBase & { - type: "select"; - options: Option[]; - multiple: true; -}; - -export type FormOptionsFieldState = FormFieldStateBase & { - type: "select" | "radio"; - options: Option[]; - multiple: false; -}; - -export type FormDateFieldState = FormFieldStateBase & { - type: "date"; -}; - -export type FormAvatarFieldState = FormFieldStateBase> & { - type: "user"; - options: UserOption[]; -}; - -export type FormFieldState = - | FormTextFieldState - | FormOptionsFieldState - | FormMultipleOptionsFieldState - | FormBooleanFieldState - | FormDateFieldState - | FormAvatarFieldState; - -export function getAllFieldsFromSections(formSections: FormSectionState[]): FormFieldState[] { - return formSections.reduce( - (acc: FormFieldState[], section: FormSectionState): FormFieldState[] => { - if (section.subsections) { - return [...acc, ...getAllFieldsFromSections(section.subsections)]; - } else { - return [...acc, ...section.fields]; - } - }, - [] - ); -} - -export function getStringFieldValue(id: string, allFields: FormFieldState[]): string { - return ( - getFieldValueById( - id, - allFields - ) || "" - ); -} - -export function getDateFieldValue(id: string, allFields: FormFieldState[]): Date | null { - return getFieldValueById(id, allFields) || null; -} - -export function getBooleanFieldValue(id: string, allFields: FormFieldState[]): boolean { - return !!getFieldValueById(id, allFields); -} - -export function getMultipleOptionsFieldValue(id: string, allFields: FormFieldState[]): string[] { - return getFieldValueById(id, allFields) || []; -} - -export function getFieldValueById( - id: string, - fields: FormFieldState[] -): F["value"] | undefined { - const field = fields.find(field => field.id === id); - if (field) { - return getFieldValue(field); - } -} - -export function getFieldIdFromIdsDictionary>( - key: keyof T, - fieldIdsDictionary: T -): string { - return fieldIdsDictionary[key] as string; -} - -export function getEmptyValueForField(field: FormFieldState): F["value"] { - switch (field.type) { - case "text": - return ""; - case "boolean": - return false; - case "select": - return field.multiple ? [] : ""; - case "radio": - return ""; - case "date": - return null; - case "user": - return undefined; - } -} - -function getFieldValue(field: FormFieldState): F["value"] { - return field.value; -} - // UPDATES: export function updateFormStateAndApplySideEffects( @@ -185,52 +47,10 @@ function applyEffectNotApplicableFieldUpdated(formState: FormState): FormState { }; } -function applyEffectNotApplicableFieldUpdatedInSections( - sectionsState: FormSectionState[] -): FormSectionState[] { - return sectionsState.map(section => { - if (section?.subsections) { - return { - ...applyEffectNotApplicableFieldUpdatedInSection(section), - subsections: applyEffectNotApplicableFieldUpdatedInSections(section?.subsections), - }; - } else { - return applyEffectNotApplicableFieldUpdatedInSection(section); - } - }); -} - -function applyEffectNotApplicableFieldUpdatedInSection( - sectionState: FormSectionState -): FormSectionState { - return { - ...sectionState, - fields: sectionState.fields.map(field => { - if (field.hasNotApplicable) { - const notApplicableField = sectionState.fields.find( - f => f.notApplicableFieldId === field.id - ); - - // TODO: FIXME TypeScript error returning the corresponding fieldValue type - const fieldValue = ( - notApplicableField?.value ? getEmptyValueForField(field) : getFieldValue(field) - ) as any; - - return { - ...field, - value: fieldValue, - disabled: !!notApplicableField?.value, - }; - } - return field; - }), - }; -} - -export function updateFormState(prevFormState: FormState, updatedField: FormFieldState): FormState { +export function updateFormState(formState: FormState, updatedField: FormFieldState): FormState { return { - ...prevFormState, - sections: updateSectionsState(prevFormState.sections, updatedField), + ...formState, + sections: updateSections(formState.sections, updatedField), }; } @@ -241,68 +61,10 @@ export function updateFormStateWithFieldErrors( ): FormState { return { ...formState, - sections: updateSectionsState(formState.sections, updatedField, fieldValidationErrors), + sections: updateSections(formState.sections, updatedField, fieldValidationErrors), }; } -function updateSectionsState( - prevFormSectionsState: FormSectionState[], - updatedField: FormFieldState, - fieldValidationErrors?: ValidationError[] -): FormSectionState[] { - return prevFormSectionsState.map(section => { - if (section?.subsections) { - return { - ...updateSectionState(section, updatedField, fieldValidationErrors), - subsections: updateSectionsState( - section?.subsections, - updatedField, - fieldValidationErrors - ), - }; - } else { - return updateSectionState(section, updatedField, fieldValidationErrors); - } - }); -} - -function updateSectionState( - prevFormSectionState: FormSectionState, - updatedField: FormFieldState, - fieldValidationErrors?: ValidationError[] -): FormSectionState { - return { - ...prevFormSectionState, - fields: updateFieldsState(prevFormSectionState.fields, updatedField, fieldValidationErrors), - }; -} - -function updateFieldsState( - prevFormFields: FormFieldState[], - updatedField: FormFieldState, - fieldValidationErrors?: ValidationError[] -): FormFieldState[] { - return prevFormFields.map(field => { - if (field.id === updatedField.id) { - return { - ...updatedField, - errors: - fieldValidationErrors?.find(error => error.property === updatedField.id) - ?.errors || [], - }; - } else { - return field; - } - }); -} - -export function updateFieldState( - fieldToUpdate: F, - newValue: F["value"] -): F { - return { ...fieldToUpdate, value: newValue }; -} - // VALIDATIONS: export function isValidForm(formSections: FormSectionState[]): boolean { @@ -321,63 +83,3 @@ export function validateForm( ): ValidationError[] { return validateSections(formState.sections, updatedField, formState); } - -function validateSections( - sections: FormSectionState[], - updatedField: FormFieldState, - newState: FormState -): ValidationError[] { - return sections.flatMap(section => { - if (section?.subsections) { - return [ - ...validateSection(section, updatedField, newState), - ...validateSections(section?.subsections, updatedField, newState), - ]; - } else { - return validateSection(section, updatedField, newState); - } - }); -} - -function validateSection( - section: FormSectionState, - updatedField: FormFieldState, - newState: FormState -): ValidationError[] { - const allFields: FormFieldState[] = getAllFieldsFromSections(newState.sections); - - return section.fields.flatMap(field => { - if (field.id === updatedField.id) { - return validateField(updatedField, allFields) || []; - } - return []; - }); -} - -function validateField( - field: FormFieldState, - allFields: FormFieldState[] = [] -): ValidationError | undefined { - if (field.notApplicableFieldId || !field.isVisible) return; - - const errors: ValidationErrorKey[] = [ - ...(field.required && !field.hasNotApplicable - ? validateFieldRequired(field.value, field.type) - : []), - ...(field.hasNotApplicable - ? validateFieldRequiredWithNotApplicable( - field.value, - field.type, - !!allFields.find(f => f.notApplicableFieldId === field.id)?.value - ) - : []), - ]; - - return errors.length > 0 - ? { - property: field.id, - errors: errors, - value: field.value, - } - : undefined; -} diff --git a/src/webapp/components/form/__tests__/Form.spec.tsx b/src/webapp/components/form/__tests__/Form.spec.tsx index 50e1edec..3a2cd228 100644 --- a/src/webapp/components/form/__tests__/Form.spec.tsx +++ b/src/webapp/components/form/__tests__/Form.spec.tsx @@ -1,6 +1,6 @@ import { getReactComponent } from "../../../../utils/tests"; import { FormProps, Form } from "../Form"; -import { FormFieldState } from "../FormState"; +import { FormFieldState } from "../FormFieldsState"; describe("Given Form component", () => { describe("when render form and its layout", () => { diff --git a/src/webapp/components/form/useForm.ts b/src/webapp/components/form/useForm.ts index 6d7f9afd..02e2821a 100644 --- a/src/webapp/components/form/useForm.ts +++ b/src/webapp/components/form/useForm.ts @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from "react"; -import { updateFormState, FormState, FormFieldState } from "./FormState"; +import { updateFormState, FormState } from "./FormState"; +import { FormFieldState } from "./FormFieldsState"; type State = { formLocalState: FormState; diff --git a/src/webapp/components/form/validations.ts b/src/webapp/components/form/validations.ts index cd800ee5..5b7cc873 100644 --- a/src/webapp/components/form/validations.ts +++ b/src/webapp/components/form/validations.ts @@ -1,5 +1,5 @@ import { ValidationErrorKey } from "../../../domain/entities/ValidationError"; -import { FieldType } from "./FormState"; +import { FieldType } from "./FormFieldsState"; export function validateFieldRequired(value: any, fieldType: FieldType): ValidationErrorKey[] { if (fieldType === "date") { diff --git a/src/webapp/pages/form-page/disease-outbreak-event/useDiseaseOutbreakEventForm.ts b/src/webapp/pages/form-page/disease-outbreak-event/useDiseaseOutbreakEventForm.ts index 3f9082d6..9b4f5eae 100644 --- a/src/webapp/pages/form-page/disease-outbreak-event/useDiseaseOutbreakEventForm.ts +++ b/src/webapp/pages/form-page/disease-outbreak-event/useDiseaseOutbreakEventForm.ts @@ -4,17 +4,16 @@ import { Maybe } from "../../../../utils/ts-utils"; import i18n from "../../../../utils/i18n"; import { useAppContext } from "../../../contexts/app-context"; import { Id } from "../../../../domain/entities/Ref"; -import { FormFieldState, FormState } from "../../../components/form/FormState"; +import { FormState } from "../../../components/form/FormState"; import { RouteName, useRoutes } from "../../../hooks/useRoutes"; import { DiseaseOutbreakEventLables, DiseaseOutbreakEventWithOptions, } from "../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventWithOptions"; -import { - mapEntityToInitialFormState, - mapFormStateToEntityData, -} from "./utils/diseaseOutbreakEventFormMapper"; +import { mapFormStateToEntityData } from "./utils/mapFormStateToEntityData"; import { updateDiseaseOutbreakEventFormState } from "./utils/updateDiseaseOutbreakEventFormState"; +import { mapEntityToInitialFormState } from "./utils/mapEntityToInitialFormState"; +import { FormFieldState } from "../../../components/form/FormFieldsState"; export type GlobalMessage = { text: string; diff --git a/src/webapp/pages/form-page/disease-outbreak-event/utils/applyRulesInFormState.ts b/src/webapp/pages/form-page/disease-outbreak-event/utils/applyRulesInFormState.ts index 6b88ec80..a430510e 100644 --- a/src/webapp/pages/form-page/disease-outbreak-event/utils/applyRulesInFormState.ts +++ b/src/webapp/pages/form-page/disease-outbreak-event/utils/applyRulesInFormState.ts @@ -1,10 +1,7 @@ import { Rule } from "../../../../../domain/entities/Rule"; -import { - FormFieldState, - FormSectionState, - FormState, - getEmptyValueForField, -} from "../../../../components/form/FormState"; +import { FormFieldState, getEmptyValueForField } from "../../../../components/form/FormFieldsState"; +import { FormSectionState } from "../../../../components/form/FormSectionsState"; +import { FormState } from "../../../../components/form/FormState"; export function applyRulesInFormState( currentFormState: FormState, diff --git a/src/webapp/pages/form-page/disease-outbreak-event/utils/diseaseOutbreakEventFormMapper.ts b/src/webapp/pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState.ts similarity index 83% rename from src/webapp/pages/form-page/disease-outbreak-event/utils/diseaseOutbreakEventFormMapper.ts rename to src/webapp/pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState.ts index fccd127d..7d94e664 100644 --- a/src/webapp/pages/form-page/disease-outbreak-event/utils/diseaseOutbreakEventFormMapper.ts +++ b/src/webapp/pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState.ts @@ -1,21 +1,7 @@ -import { - DataSource, - DiseaseOutbreakEventBaseAttrs, - HazardType, - IncidentStatusType, -} from "../../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent"; import { DiseaseOutbreakEventWithOptions } from "../../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventWithOptions"; -import { - FormFieldState, - FormState, - getAllFieldsFromSections, - getDateFieldValue, - getMultipleOptionsFieldValue, - getBooleanFieldValue, - getStringFieldValue, -} from "../../../../components/form/FormState"; import { Option } from "../../../../../domain/entities/Ref"; -import { getFieldIdFromIdsDictionary } from "../../../../components/form/FormState"; +import { getFieldIdFromIdsDictionary } from "../../../../components/form/FormFieldsState"; +import { FormState } from "../../../../components/form/FormState"; import { UserOption } from "../../../../components/user-selector/UserSelector"; import { Option as PresentationOption } from "../../../../components/utils/option"; @@ -51,6 +37,7 @@ export const diseaseOutbreakEventFieldIds = { notes: "notes", } as const; +// TODO: Thinking for the future about generate this FormState by iterating over Object.Keys(diseaseOutbreakEvent) export function mapEntityToInitialFormState( diseaseOutbreakEventWithOptions: DiseaseOutbreakEventWithOptions ): FormState { @@ -703,143 +690,3 @@ function mapToPresentationOptions(options: Option[]): PresentationOption[] { }) ); } - -export function mapFormStateToEntityData( - formState: FormState, - currentUserName: string, - diseaseOutbreakEventWithOptions: DiseaseOutbreakEventWithOptions -): DiseaseOutbreakEventBaseAttrs { - const { diseaseOutbreakEvent } = diseaseOutbreakEventWithOptions; - - const allFields: FormFieldState[] = getAllFieldsFromSections(formState.sections); - - const diseaseOutbreakEventEditableData = { - name: getStringFieldValue(diseaseOutbreakEventFieldIds.name, allFields), - dataSource: getStringFieldValue( - diseaseOutbreakEventFieldIds.dataSource, - allFields - ) as DataSource, - hazardType: getStringFieldValue( - diseaseOutbreakEventFieldIds.hazardType, - allFields - ) as HazardType, - mainSyndromeCode: getStringFieldValue( - diseaseOutbreakEventFieldIds.mainSyndromeCode, - allFields - ), - suspectedDiseaseCode: getStringFieldValue( - diseaseOutbreakEventFieldIds.suspectedDiseaseCode, - allFields - ), - notificationSourceCode: getStringFieldValue( - diseaseOutbreakEventFieldIds.notificationSourceCode, - allFields - ), - areasAffectedProvinceIds: getMultipleOptionsFieldValue( - diseaseOutbreakEventFieldIds.areasAffectedProvinceIds, - allFields - ), - areasAffectedDistrictIds: getMultipleOptionsFieldValue( - diseaseOutbreakEventFieldIds.areasAffectedDistrictIds, - allFields - ), - incidentStatus: getStringFieldValue( - diseaseOutbreakEventFieldIds.incidentStatus, - allFields - ) as IncidentStatusType, - emerged: { - date: getDateFieldValue(diseaseOutbreakEventFieldIds.emergedDate, allFields) as Date, - narrative: getStringFieldValue( - diseaseOutbreakEventFieldIds.emergedNarrative, - allFields - ), - }, - detected: { - date: getDateFieldValue(diseaseOutbreakEventFieldIds.detectedDate, allFields) as Date, - narrative: getStringFieldValue( - diseaseOutbreakEventFieldIds.detectedNarrative, - allFields - ), - }, - notified: { - date: getDateFieldValue(diseaseOutbreakEventFieldIds.notifiedDate, allFields) as Date, - narrative: getStringFieldValue( - diseaseOutbreakEventFieldIds.notifiedNarrative, - allFields - ), - }, - earlyResponseActions: { - initiateInvestigation: getDateFieldValue( - diseaseOutbreakEventFieldIds.initiateInvestigation, - allFields - ) as Date, - conductEpidemiologicalAnalysis: getDateFieldValue( - diseaseOutbreakEventFieldIds.conductEpidemiologicalAnalysis, - allFields - ) as Date, - laboratoryConfirmation: { - date: getDateFieldValue( - diseaseOutbreakEventFieldIds.laboratoryConfirmationDate, - allFields - ) as Date, - na: getBooleanFieldValue( - diseaseOutbreakEventFieldIds.laboratoryConfirmationNA, - allFields - ), - }, - appropriateCaseManagement: { - date: getDateFieldValue( - diseaseOutbreakEventFieldIds.appropriateCaseManagementDate, - allFields - ) as Date, - na: getBooleanFieldValue( - diseaseOutbreakEventFieldIds.appropriateCaseManagementNA, - allFields - ), - }, - initiatePublicHealthCounterMeasures: { - date: getDateFieldValue( - diseaseOutbreakEventFieldIds.initiatePublicHealthCounterMeasuresDate, - allFields - ) as Date, - na: getBooleanFieldValue( - diseaseOutbreakEventFieldIds.initiatePublicHealthCounterMeasuresNA, - allFields - ), - }, - initiateRiskCommunication: { - date: getDateFieldValue( - diseaseOutbreakEventFieldIds.initiateRiskCommunicationDate, - allFields - ) as Date, - na: getBooleanFieldValue( - diseaseOutbreakEventFieldIds.initiateRiskCommunicationNA, - allFields - ), - }, - establishCoordination: getDateFieldValue( - diseaseOutbreakEventFieldIds.establishCoordination, - allFields - ) as Date, - responseNarrative: getStringFieldValue( - diseaseOutbreakEventFieldIds.responseNarrative, - allFields - ), - }, - incidentManagerName: getStringFieldValue( - diseaseOutbreakEventFieldIds.incidentManagerName, - allFields - ), - notes: getStringFieldValue(diseaseOutbreakEventFieldIds.notes, allFields), - }; - - const diseaseOutbreakEventBase: DiseaseOutbreakEventBaseAttrs = { - id: diseaseOutbreakEvent?.id || "", - created: diseaseOutbreakEvent?.created || new Date(), - lastUpdated: diseaseOutbreakEvent?.lastUpdated || new Date(), - createdByName: diseaseOutbreakEvent?.createdByName || currentUserName, - ...diseaseOutbreakEventEditableData, - }; - - return diseaseOutbreakEventBase; -} diff --git a/src/webapp/pages/form-page/disease-outbreak-event/utils/mapFormStateToEntityData.ts b/src/webapp/pages/form-page/disease-outbreak-event/utils/mapFormStateToEntityData.ts new file mode 100644 index 00000000..6c86721b --- /dev/null +++ b/src/webapp/pages/form-page/disease-outbreak-event/utils/mapFormStateToEntityData.ts @@ -0,0 +1,157 @@ +import { + DataSource, + DiseaseOutbreakEventBaseAttrs, + HazardType, + IncidentStatusType, +} from "../../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent"; +import { DiseaseOutbreakEventWithOptions } from "../../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventWithOptions"; +import { FormState } from "../../../../components/form/FormState"; +import { diseaseOutbreakEventFieldIds } from "./mapEntityToInitialFormState"; +import { + FormFieldState, + getAllFieldsFromSections, + getBooleanFieldValue, + getDateFieldValue, + getMultipleOptionsFieldValue, + getStringFieldValue, +} from "../../../../components/form/FormFieldsState"; + +export function mapFormStateToEntityData( + formState: FormState, + currentUserName: string, + diseaseOutbreakEventWithOptions: DiseaseOutbreakEventWithOptions +): DiseaseOutbreakEventBaseAttrs { + const { diseaseOutbreakEvent } = diseaseOutbreakEventWithOptions; + + const allFields: FormFieldState[] = getAllFieldsFromSections(formState.sections); + + const diseaseOutbreakEventEditableData = { + name: getStringFieldValue(diseaseOutbreakEventFieldIds.name, allFields), + dataSource: getStringFieldValue( + diseaseOutbreakEventFieldIds.dataSource, + allFields + ) as DataSource, + hazardType: getStringFieldValue( + diseaseOutbreakEventFieldIds.hazardType, + allFields + ) as HazardType, + mainSyndromeCode: getStringFieldValue( + diseaseOutbreakEventFieldIds.mainSyndromeCode, + allFields + ), + suspectedDiseaseCode: getStringFieldValue( + diseaseOutbreakEventFieldIds.suspectedDiseaseCode, + allFields + ), + notificationSourceCode: getStringFieldValue( + diseaseOutbreakEventFieldIds.notificationSourceCode, + allFields + ), + areasAffectedProvinceIds: getMultipleOptionsFieldValue( + diseaseOutbreakEventFieldIds.areasAffectedProvinceIds, + allFields + ), + areasAffectedDistrictIds: getMultipleOptionsFieldValue( + diseaseOutbreakEventFieldIds.areasAffectedDistrictIds, + allFields + ), + incidentStatus: getStringFieldValue( + diseaseOutbreakEventFieldIds.incidentStatus, + allFields + ) as IncidentStatusType, + emerged: { + date: getDateFieldValue(diseaseOutbreakEventFieldIds.emergedDate, allFields) as Date, + narrative: getStringFieldValue( + diseaseOutbreakEventFieldIds.emergedNarrative, + allFields + ), + }, + detected: { + date: getDateFieldValue(diseaseOutbreakEventFieldIds.detectedDate, allFields) as Date, + narrative: getStringFieldValue( + diseaseOutbreakEventFieldIds.detectedNarrative, + allFields + ), + }, + notified: { + date: getDateFieldValue(diseaseOutbreakEventFieldIds.notifiedDate, allFields) as Date, + narrative: getStringFieldValue( + diseaseOutbreakEventFieldIds.notifiedNarrative, + allFields + ), + }, + earlyResponseActions: { + initiateInvestigation: getDateFieldValue( + diseaseOutbreakEventFieldIds.initiateInvestigation, + allFields + ) as Date, + conductEpidemiologicalAnalysis: getDateFieldValue( + diseaseOutbreakEventFieldIds.conductEpidemiologicalAnalysis, + allFields + ) as Date, + laboratoryConfirmation: { + date: getDateFieldValue( + diseaseOutbreakEventFieldIds.laboratoryConfirmationDate, + allFields + ) as Date, + na: getBooleanFieldValue( + diseaseOutbreakEventFieldIds.laboratoryConfirmationNA, + allFields + ), + }, + appropriateCaseManagement: { + date: getDateFieldValue( + diseaseOutbreakEventFieldIds.appropriateCaseManagementDate, + allFields + ) as Date, + na: getBooleanFieldValue( + diseaseOutbreakEventFieldIds.appropriateCaseManagementNA, + allFields + ), + }, + initiatePublicHealthCounterMeasures: { + date: getDateFieldValue( + diseaseOutbreakEventFieldIds.initiatePublicHealthCounterMeasuresDate, + allFields + ) as Date, + na: getBooleanFieldValue( + diseaseOutbreakEventFieldIds.initiatePublicHealthCounterMeasuresNA, + allFields + ), + }, + initiateRiskCommunication: { + date: getDateFieldValue( + diseaseOutbreakEventFieldIds.initiateRiskCommunicationDate, + allFields + ) as Date, + na: getBooleanFieldValue( + diseaseOutbreakEventFieldIds.initiateRiskCommunicationNA, + allFields + ), + }, + establishCoordination: getDateFieldValue( + diseaseOutbreakEventFieldIds.establishCoordination, + allFields + ) as Date, + responseNarrative: getStringFieldValue( + diseaseOutbreakEventFieldIds.responseNarrative, + allFields + ), + }, + incidentManagerName: getStringFieldValue( + diseaseOutbreakEventFieldIds.incidentManagerName, + allFields + ), + notes: getStringFieldValue(diseaseOutbreakEventFieldIds.notes, allFields), + }; + + const diseaseOutbreakEventBase: DiseaseOutbreakEventBaseAttrs = { + id: diseaseOutbreakEvent?.id || "", + created: diseaseOutbreakEvent?.created || new Date(), + lastUpdated: diseaseOutbreakEvent?.lastUpdated || new Date(), + createdByName: diseaseOutbreakEvent?.createdByName || currentUserName, + ...diseaseOutbreakEventEditableData, + }; + + return diseaseOutbreakEventBase; +} diff --git a/src/webapp/pages/form-page/disease-outbreak-event/utils/updateDiseaseOutbreakEventFormState.ts b/src/webapp/pages/form-page/disease-outbreak-event/utils/updateDiseaseOutbreakEventFormState.ts index bf48335f..2b8cd650 100644 --- a/src/webapp/pages/form-page/disease-outbreak-event/utils/updateDiseaseOutbreakEventFormState.ts +++ b/src/webapp/pages/form-page/disease-outbreak-event/utils/updateDiseaseOutbreakEventFormState.ts @@ -1,8 +1,8 @@ import { DiseaseOutbreakEvent } from "../../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent"; import { DiseaseOutbreakEventWithOptions } from "../../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventWithOptions"; import { ValidationError } from "../../../../../domain/entities/ValidationError"; +import { FormFieldState } from "../../../../components/form/FormFieldsState"; import { - FormFieldState, FormState, isValidForm, updateFormStateAndApplySideEffects, @@ -10,7 +10,7 @@ import { validateForm, } from "../../../../components/form/FormState"; import { applyRulesInFormState } from "./applyRulesInFormState"; -import { mapFormStateToEntityData } from "./diseaseOutbreakEventFormMapper"; +import { mapFormStateToEntityData } from "./mapFormStateToEntityData"; export function updateDiseaseOutbreakEventFormState( prevFormState: FormState,