Skip to content

Commit 8e130a6

Browse files
committed
Refactor placeOfEvent to use single HIDDEN field instead of array of field references
1 parent 09c8b57 commit 8e130a6

File tree

13 files changed

+229
-97
lines changed

13 files changed

+229
-97
lines changed

packages/client/src/v2-events/components/forms/FormFieldGenerator/FormSectionComponent.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,27 @@ export function FormSectionComponent({
240240
)
241241

242242
const firstNonFalsyValue = compact(
243-
referencesToOtherFields.map((reference) =>
244-
resolveFieldReferenceValue(reference, fieldValues)
245-
)
243+
referencesToOtherFields.map((reference) => {
244+
const referenceFieldConfig = allFieldsWithDotSeparator.find(
245+
(field: FieldConfig) => field.id === reference.$$field
246+
)
247+
248+
if (!referenceFieldConfig) {
249+
return undefined
250+
}
251+
252+
const isReferenceVisible = isFieldVisible(
253+
referenceFieldConfig,
254+
makeFormikFieldIdsOpenCRVSCompatible(fieldValues),
255+
validatorContext
256+
)
257+
258+
if (!isReferenceVisible || !listenerFieldConfig) {
259+
return undefined
260+
}
261+
262+
return resolveFieldReferenceValue(reference, fieldValues)
263+
})
246264
)[0]
247265

248266
if (firstNonFalsyValue) {
@@ -261,7 +279,7 @@ export function FormSectionComponent({
261279

262280
return
263281
},
264-
[allFieldsWithDotSeparator, systemVariables]
282+
[allFieldsWithDotSeparator, systemVariables, validatorContext]
265283
)
266284

267285
const onFieldValueChange = useCallback(

packages/client/src/v2-events/components/forms/FormFieldGenerator/GeneratedInputField.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
isIdReaderFieldType,
6363
isQrReaderFieldType,
6464
isLoaderFieldType,
65+
isHiddenFieldType,
6566
isAgeFieldType
6667
} from '@opencrvs/commons/client'
6768
import { TextArea } from '@opencrvs/components/lib/TextArea'
@@ -100,6 +101,7 @@ import { IdReader } from '@client/v2-events/features/events/registered-fields/Id
100101
import { QrReader } from '@client/v2-events/features/events/registered-fields/QrReader'
101102
import { QueryParamReader } from '@client/v2-events/features/events/registered-fields/QueryParamReader'
102103
import { Loader } from '@client/v2-events/features/events/registered-fields/Loader'
104+
import { Hidden } from '@client/v2-events/features/events/registered-fields/Hidden'
103105
import {
104106
makeFormFieldIdFormikCompatible,
105107
makeFormikFieldIdOpenCRVSCompatible
@@ -800,6 +802,15 @@ export const GeneratedInputField = React.memo(
800802
)
801803
}
802804

805+
if (isHiddenFieldType(field)) {
806+
return (
807+
<Hidden.Input
808+
{...inputProps}
809+
value={field.value as string | undefined}
810+
/>
811+
)
812+
}
813+
803814
throw new Error(`Unsupported field ${JSON.stringify(fieldDefinition)}`)
804815
}
805816
)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* OpenCRVS is also distributed under the terms of the Civil Registration
7+
* & Healthcare Disclaimer located at http://opencrvs.org/license.
8+
*
9+
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
10+
*/
11+
import type { Meta, StoryObj } from '@storybook/react'
12+
import React from 'react'
13+
import styled from 'styled-components'
14+
import { expect, within } from '@storybook/test'
15+
import { TRPCProvider } from '@client/v2-events/trpc'
16+
import { Hidden } from './Hidden'
17+
18+
const meta: Meta<typeof Hidden.Input> = {
19+
title: 'Inputs/Hidden',
20+
component: Hidden.Input,
21+
args: {},
22+
decorators: [
23+
(Story) => (
24+
<TRPCProvider>
25+
<Story />
26+
</TRPCProvider>
27+
)
28+
]
29+
}
30+
31+
export default meta
32+
33+
const Box = styled.div`
34+
border: 2px solid #000;
35+
padding: 1rem;
36+
`
37+
38+
function HiddenComponentBox({ children }: { children: React.ReactNode }) {
39+
return (
40+
<div>
41+
<h2>
42+
{
43+
'Inside the box there is a hidden component, which is not rendered in the UI, but is present in the DOM'
44+
}
45+
</h2>
46+
<Box>{children}</Box>
47+
</div>
48+
)
49+
}
50+
51+
export const HiddenInput: StoryObj<typeof Hidden.Input> = {
52+
args: {
53+
id: 'hidden-input',
54+
value: 'hidden value'
55+
},
56+
render: (args) => (
57+
<HiddenComponentBox>
58+
<Hidden.Input {...args} />
59+
</HiddenComponentBox>
60+
),
61+
play: async ({ canvasElement }) => {
62+
const canvas = within(canvasElement)
63+
const hiddenInput = await canvas.findByTestId('text__hidden-input')
64+
await expect(hiddenInput).toBeInTheDocument()
65+
await expect(hiddenInput).toHaveValue('hidden value')
66+
}
67+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* OpenCRVS is also distributed under the terms of the Civil Registration
7+
* & Healthcare Disclaimer located at http://opencrvs.org/license.
8+
*
9+
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
10+
*/
11+
import * as React from 'react'
12+
13+
function HiddenInput({ id, value }: { id: string; value?: string }) {
14+
return (
15+
<input
16+
data-testid={`text__${id}`}
17+
disabled={true}
18+
id={id}
19+
name={id}
20+
type="hidden"
21+
value={value}
22+
/>
23+
)
24+
}
25+
26+
export const Hidden = {
27+
Input: HiddenInput,
28+
stringify: (value: string | undefined) => {
29+
return value?.toString() || ''
30+
}
31+
}

packages/commons/src/events/EventConfig.ts

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { AdvancedSearchConfig, EventFieldId } from './AdvancedSearchConfig'
1616
import { findAllFields, getDeclarationFields } from './utils'
1717
import { DeclarationFormConfig } from './FormConfig'
1818
import { FieldType } from './FieldType'
19-
import { FieldReference, LOCATIONS_FIELD_TYPES } from './FieldConfig'
19+
import { FieldReference } from './FieldConfig'
2020
import { FlagConfig, InherentFlags } from './Flag'
2121

2222
/**
@@ -34,12 +34,9 @@ export const EventConfig = z
3434
dateOfEvent: FieldReference.optional().describe(
3535
'Reference to the field capturing the date of the event (e.g. date of birth). Defaults to the event creation date if unspecified.'
3636
),
37-
placeOfEvent: z
38-
.array(FieldReference)
39-
.optional()
40-
.describe(
41-
'Reference to the field capturing the place of the event (e.g. place of birth). Defaults to the meta.createdAtLocation if unspecified.'
42-
),
37+
placeOfEvent: FieldReference.optional().describe(
38+
'Reference to the field capturing the place of the event (e.g. place of birth). Defaults to the meta.createdAtLocation if unspecified.'
39+
),
4340
title: TranslationConfig.describe(
4441
'Title template for the singular event, supporting variables (e.g. "{applicant.name.firstname} {applicant.name.surname}").'
4542
),
@@ -139,28 +136,17 @@ export const EventConfig = z
139136
}
140137
}
141138

142-
if (Array.isArray(event.placeOfEvent)) {
143-
const eventPlaceFieldId = getDeclarationFields(event).find(({ id }) =>
144-
event.placeOfEvent?.find((config) => config.$$field === id)
139+
if (event.placeOfEvent) {
140+
const eventPlaceFieldId = getDeclarationFields(event).find(
141+
({ id }) => id === event.placeOfEvent?.$$field
145142
)
146143
if (!eventPlaceFieldId) {
147144
ctx.addIssue({
148145
code: 'custom',
149-
message: `Place of event field id must match a field id in fields array.
150-
Invalid place of event field ID for event ${event.id}: ${event.placeOfEvent.map((x) => x.$$field).toString()}`,
146+
message: `Place of event field id must match a field id in the event.declaration fields.
147+
Invalid place of event field ID for event ${event.id}: ${event.placeOfEvent.$$field}`,
151148
path: ['placeOfEvent']
152149
})
153-
} else if (
154-
!(LOCATIONS_FIELD_TYPES as readonly FieldType[]).includes(
155-
eventPlaceFieldId.type
156-
)
157-
) {
158-
ctx.addIssue({
159-
code: 'custom',
160-
message: `Field specified for place of event is of type: ${eventPlaceFieldId.type},
161-
but it needs to be any one of these type: ${LOCATIONS_FIELD_TYPES.join(', ')}`,
162-
path: ['placeOfEvent.fieldType']
163-
})
164150
}
165151
}
166152

packages/commons/src/events/FieldConfig.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,14 @@ const LoaderField = BaseField.extend({
844844

845845
export type LoaderField = z.infer<typeof LoaderField>
846846

847+
const HiddenField = BaseField.extend({
848+
type: z.literal(FieldType.HIDDEN)
849+
}).describe(
850+
'A non-interactive, hidden field that only hold a value in the form'
851+
)
852+
853+
export type HiddenField = z.infer<typeof HiddenField>
854+
847855
export const FieldConfig = z
848856
.discriminatedUnion('type', [
849857
Address,
@@ -884,7 +892,8 @@ export const FieldConfig = z
884892
IdReaderField,
885893
QueryParamReaderField,
886894
LoaderField,
887-
SearchField
895+
SearchField,
896+
HiddenField
888897
])
889898
.meta({
890899
description: 'Form field configuration',
@@ -930,12 +939,3 @@ export type FieldTypeToFieldConfig<T extends FieldType> = Extract<
930939
FieldConfigInput,
931940
{ type: T }
932941
>
933-
934-
export const LOCATIONS_FIELD_TYPES = [
935-
FieldType.ADMINISTRATIVE_AREA,
936-
FieldType.FACILITY,
937-
FieldType.OFFICE,
938-
FieldType.LOCATION,
939-
FieldType.COUNTRY,
940-
FieldType.ADDRESS
941-
] as const

packages/commons/src/events/FieldType.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export const FieldType = {
5050
QUERY_PARAM_READER: 'QUERY_PARAM_READER',
5151
QR_READER: 'QR_READER',
5252
ID_READER: 'ID_READER',
53-
LOADER: 'LOADER'
53+
LOADER: 'LOADER',
54+
HIDDEN: 'HIDDEN'
5455
} as const
5556

5657
/**

packages/commons/src/events/FieldTypeMapping.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ import {
5050
QrReaderField,
5151
IdReaderField,
5252
LoaderField,
53-
AgeField
53+
AgeField,
54+
HiddenField
5455
} from './FieldConfig'
5556
import { FieldType } from './FieldType'
5657
import {
@@ -159,6 +160,7 @@ export function mapFieldTypeToZod(field: FieldConfig, actionType?: ActionType) {
159160
case FieldType.VERIFICATION_STATUS:
160161
case FieldType.ID:
161162
case FieldType.LOADER:
163+
case FieldType.HIDDEN:
162164
schema = field.required ? NonEmptyTextValue : TextValue
163165
break
164166
case FieldType.NUMBER:
@@ -250,6 +252,7 @@ export function mapFieldTypeToEmptyValue(field: FieldConfig) {
250252
case FieldType.QR_READER:
251253
case FieldType.ID_READER:
252254
case FieldType.LOADER:
255+
case FieldType.HIDDEN:
253256
return null
254257
case FieldType.ADDRESS:
255258
return {
@@ -555,6 +558,13 @@ export const isLoaderFieldType = (field: {
555558
return field.config.type === FieldType.LOADER
556559
}
557560

561+
export const isHiddenFieldType = (field: {
562+
config: FieldConfig
563+
value: FieldValue | FieldUpdateValue
564+
}): field is { value: undefined; config: HiddenField } => {
565+
return field.config.type === FieldType.HIDDEN
566+
}
567+
558568
export type NonInteractiveFieldType =
559569
| Divider
560570
| PageHeader

packages/commons/src/events/FieldValue.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
*/
3838

3939
export const TextValue = z.string()
40+
export const HiddenFieldValue = z.string()
4041
export const NonEmptyTextValue = TextValue.min(1)
4142

4243
export const DateValue = z.iso.date().describe('Date in the format YYYY-MM-DD')
@@ -229,6 +230,7 @@ export type FieldUpdateValue =
229230
| z.infer<typeof NameFieldUpdateValue>
230231
| z.infer<typeof HttpFieldUpdateValue>
231232
| z.infer<typeof QueryParamReaderFieldUpdateValue>
233+
| z.infer<typeof HiddenFieldValue>
232234

233235
// All schemas are tagged using .describe() so we can identify them later
234236
// inside safeUnion(). The tag name should match PRIORITY_ORDER.
@@ -247,7 +249,8 @@ export const FieldUpdateValue: z.ZodType<FieldUpdateValue> = safeUnion([
247249
DataFieldValue.describe('DataFieldValue'),
248250
NameFieldUpdateValue.describe('NameFieldUpdateValue'),
249251
HttpFieldUpdateValue.describe('HttpFieldUpdateValue'),
250-
QueryParamReaderFieldUpdateValue.describe('QueryParamReaderFieldUpdateValue')
252+
QueryParamReaderFieldUpdateValue.describe('QueryParamReaderFieldUpdateValue'),
253+
HiddenFieldValue.describe('HiddenFieldValue')
251254
])
252255

253256
/**

0 commit comments

Comments
 (0)