From 3314c57c5bd40570899c1ee0fb5f7d22dab2b960 Mon Sep 17 00:00:00 2001 From: norda-gunni <161026627+norda-gunni@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:12:04 +0000 Subject: [PATCH] feat(application-system): Add phone and email fields to tablerepeater (#17219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial field and validation * feat(ExampleForm): Update table repeater field and validation schema - Renamed 'rentalHousingLandlordInfoTable' to 'tableRepeaterField' in ExampleForm. - Updated ExampleSchema to include validation for the new 'tableRepeaterField' structure. - Enhanced NationalIdWithName component to accept phone and email default values. - Refactored error handling in NationalIdWithName for better validation feedback. - Cleaned up unused code in TableRepeaterFormField for improved readability. * fix lint * cleanup * Error message cleanup * cleanup * Refine dataschema * PR comment * Fix field layout * Cleanup types * Revert example form changes * Update libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx Add suggestion from jonni Co-authored-by: Jónas G. Sigurðsson --------- Co-authored-by: Jónas G. Sigurðsson Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../reference-template/src/lib/dataSchema.ts | 14 ++ libs/application/types/src/lib/Fields.ts | 6 + .../NationalIdWithName/NationalIdWithName.tsx | 202 ++++++++++++------ 3 files changed, 153 insertions(+), 69 deletions(-) diff --git a/libs/application/templates/reference-template/src/lib/dataSchema.ts b/libs/application/templates/reference-template/src/lib/dataSchema.ts index faa3b728cff4..e4945ef27981 100644 --- a/libs/application/templates/reference-template/src/lib/dataSchema.ts +++ b/libs/application/templates/reference-template/src/lib/dataSchema.ts @@ -15,6 +15,20 @@ const careerHistoryCompaniesValidation = (data: any) => { } export const ExampleSchema = z.object({ approveExternalData: z.boolean().refine((v) => v), + tableRepeaterField: z.array( + z.object({ + nationalIdWithName: z.object({ + name: z.string().min(1).max(256), + nationalId: z.string().refine((n) => n && kennitala.isValid(n), { + params: m.dataSchemeNationalId, + }), + phone: z.string().refine(isValidNumber, { + params: m.dataSchemePhoneNumber, + }), + email: z.string().email(), + }), + }), + ), person: z.object({ name: z.string().min(1).max(256), age: z.string().refine((x) => { diff --git a/libs/application/types/src/lib/Fields.ts b/libs/application/types/src/lib/Fields.ts index fafd82964b7c..24a2c9ac8884 100644 --- a/libs/application/types/src/lib/Fields.ts +++ b/libs/application/types/src/lib/Fields.ts @@ -89,6 +89,8 @@ export type RepeaterItem = { */ displayInTable?: boolean label?: StaticText + phoneLabel?: StaticText + emailLabel?: StaticText placeholder?: StaticText options?: TableRepeaterOptions backgroundColor?: 'blue' | 'white' @@ -99,6 +101,10 @@ export type RepeaterItem = { activeField?: Record, ) => boolean dataTestId?: string + showPhoneField?: boolean + phoneRequired?: boolean + showEmailField?: boolean + emailRequired?: boolean readonly?: | boolean | (( diff --git a/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx b/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx index fb42f7b19422..e2786a44a3bd 100644 --- a/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx +++ b/libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx @@ -1,11 +1,7 @@ import { FC, useEffect, useState } from 'react' import { GridRow, GridColumn } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' -import { - coreErrorMessages, - getErrorViaPath, - getValueViaPath, -} from '@island.is/application/core' +import { coreErrorMessages, getValueViaPath } from '@island.is/application/core' import { Application, StaticText } from '@island.is/application/types' import { gql, useLazyQuery } from '@apollo/client' import { @@ -13,7 +9,10 @@ import { Query, RskCompanyInfoInput, } from '@island.is/api/schema' -import { InputController } from '@island.is/shared/form-fields' +import { + InputController, + PhoneInputController, +} from '@island.is/shared/form-fields' import { useFormContext } from 'react-hook-form' import * as kennitala from 'kennitala' import debounce from 'lodash/debounce' @@ -27,14 +26,23 @@ interface NationalIdWithNameProps { customId?: string customNationalIdLabel?: StaticText customNameLabel?: StaticText + phoneLabel?: StaticText + emailLabel?: StaticText + phoneRequired?: boolean + emailRequired?: boolean onNationalIdChange?: (s: string) => void onNameChange?: (s: string) => void nationalIdDefaultValue?: string nameDefaultValue?: string + phoneDefaultValue?: string + emailDefaultValue?: string errorMessage?: string minAgePerson?: number searchPersons?: boolean searchCompanies?: boolean + showPhoneField?: boolean + showEmailField?: boolean + error?: string } export const NationalIdWithName: FC< @@ -46,19 +54,30 @@ export const NationalIdWithName: FC< required, customId = '', customNationalIdLabel = '', + phoneLabel = '', + emailLabel = '', + phoneRequired = false, + emailRequired = false, customNameLabel = '', onNationalIdChange, onNameChange, nationalIdDefaultValue, nameDefaultValue, + phoneDefaultValue, + emailDefaultValue, errorMessage, minAgePerson, searchPersons = true, searchCompanies = false, + showPhoneField = false, + showEmailField = false, + error, }) => { const fieldId = customId.length > 0 ? customId : id const nameField = `${fieldId}.name` const nationalIdField = `${fieldId}.nationalId` + const emailField = `${fieldId}.email` + const phoneField = `${fieldId}.phone` const { formatMessage } = useLocale() const { @@ -69,12 +88,18 @@ export const NationalIdWithName: FC< const [personName, setPersonName] = useState('') const [companyName, setCompanyName] = useState('') - // get name validation errors - const nameFieldErrors = errorMessage - ? nameDefaultValue?.length === 0 - ? errorMessage - : undefined - : getErrorViaPath(errors, nameField) + const getFieldErrorString = ( + error: unknown, + id: string, + ): string | undefined => { + if (!error || typeof error !== 'object') return undefined + + const errorList = error as Record[] + if (!Array.isArray(errorList)) return undefined + + const fieldError = errorList[id as any] + return typeof fieldError === 'string' ? fieldError : undefined + } // get national id validation errors let nationalIdFieldErrors: string | undefined @@ -91,7 +116,7 @@ export const NationalIdWithName: FC< { minAge: minAgePerson }, ) } else if (!errorMessage) { - nationalIdFieldErrors = getErrorViaPath(errors, nationalIdField) + nationalIdFieldErrors = getFieldErrorString(error, 'nationalId') } // get default values @@ -101,6 +126,12 @@ export const NationalIdWithName: FC< const defaultName = nameDefaultValue ? nameDefaultValue : getValueViaPath(application.answers, nameField, '') + const defaultPhone = phoneDefaultValue + ? phoneDefaultValue + : getValueViaPath(application.answers, phoneField, '') + const defaultEmail = emailDefaultValue + ? emailDefaultValue + : getValueViaPath(application.answers, emailField, '') // query to get name by national id const [getIdentity, { data, loading: queryLoading, error: queryError }] = @@ -180,62 +211,95 @@ export const NationalIdWithName: FC< }, [personName, companyName, setValue, nameField, application.answers]) return ( - - - { - setNationalIdInput(v.target.value.replace(/\W/g, '')) - onNationalIdChange && - onNationalIdChange(v.target.value.replace(/\W/g, '')) - })} - loading={searchPersons ? queryLoading : companyQueryLoading} - error={nationalIdFieldErrors} - disabled={disabled} - /> - - - + + + { + setNationalIdInput(v.target.value.replace(/\W/g, '')) + onNationalIdChange && + onNationalIdChange(v.target.value.replace(/\W/g, '')) + })} + loading={searchPersons ? queryLoading : companyQueryLoading} + error={nationalIdFieldErrors} + disabled={disabled} + /> + + + - - + } + disabled + /> + + + {(showPhoneField || showEmailField) && ( + + {showPhoneField && ( + + + + )} + {showEmailField && ( + + + + )} + + )} + ) }