diff --git a/src/adapters/person-attributes-adapter.ts b/src/adapters/person-attributes-adapter.ts new file mode 100644 index 00000000..0ff59046 --- /dev/null +++ b/src/adapters/person-attributes-adapter.ts @@ -0,0 +1,54 @@ +import { type PersonAttribute, type OpenmrsResource } from '@openmrs/esm-framework'; +import { type FormContextProps } from '../provider/form-provider'; +import { type FormField, type FormFieldValueAdapter, type FormProcessorContextProps } from '../types'; +import { clearSubmission } from '../utils/common-utils'; +import { isEmpty } from '../validators/form-validator'; + +export const PersonAttributesAdapter: FormFieldValueAdapter = { + transformFieldValue: function (field: FormField, value: any, context: FormContextProps) { + clearSubmission(field); + if (field.meta?.previousValue?.value === value || isEmpty(value)) { + return null; + } + field.meta.submission.newValue = { + value: value, + attributeType: field.questionOptions?.attributeType, + }; + return field.meta.submission.newValue; + }, + getInitialValue: function (field: FormField, sourceObject: OpenmrsResource, context: FormProcessorContextProps) { + const rendering = field.questionOptions.rendering; + + const personAttributeValue = context?.customDependencies.personAttributes.find( + (attribute: PersonAttribute) => attribute.attributeType.uuid === field.questionOptions.attributeType, + )?.value; + if (rendering === 'text') { + if (typeof personAttributeValue === 'string') { + return personAttributeValue; + } else if ( + personAttributeValue && + typeof personAttributeValue === 'object' && + 'display' in personAttributeValue + ) { + return personAttributeValue?.display; + } + } else if (rendering === 'ui-select-extended') { + if (personAttributeValue && typeof personAttributeValue === 'object' && 'uuid' in personAttributeValue) { + return personAttributeValue?.uuid; + } + } + return null; + }, + getPreviousValue: function (field: FormField, sourceObject: OpenmrsResource, context: FormProcessorContextProps) { + return null; + }, + getDisplayValue: function (field: FormField, value: any) { + if (value?.display) { + return value.display; + } + return value; + }, + tearDown: function (): void { + return; + }, +}; diff --git a/src/api/index.ts b/src/api/index.ts index 72fe0f44..cba2df90 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,4 +1,4 @@ -import { fhirBaseUrl, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; +import { fhirBaseUrl, openmrsFetch, type PersonAttribute, restBaseUrl } from '@openmrs/esm-framework'; import { encounterRepresentation } from '../constants'; import type { FHIRObsResource, OpenmrsForm, PatientIdentifier, PatientProgramPayload } from '../types'; import { isUuid } from '../utils/boolean-utils'; @@ -183,3 +183,36 @@ export function savePatientIdentifier(patientIdentifier: PatientIdentifier, pati body: JSON.stringify(patientIdentifier), }); } + +export function savePersonAttribute(personAttribute: PersonAttribute, personUuid: string) { + let url: string; + + if (personAttribute.uuid) { + url = `${restBaseUrl}/person/${personUuid}/attribute/${personAttribute.uuid}`; + } else { + url = `${restBaseUrl}/person/${personUuid}/attribute`; + } + + return openmrsFetch(url, { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify(personAttribute), + }); +} + +export async function getPersonAttributeTypeFormat(personAttributeTypeUuid: string) { + try { + const response = await openmrsFetch( + `${restBaseUrl}/personattributetype/${personAttributeTypeUuid}?v=custom:(format)`, + ); + if (response) { + const { data } = response; + return data?.format; + } + return null; + } catch (error) { + return null; + } +} diff --git a/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx b/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx index 026fa911..e3be16dc 100644 --- a/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx +++ b/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx @@ -106,6 +106,14 @@ jest.mock('../../../registry/registry', () => { }; }); +jest.mock('../../../hooks/usePersonAttributes', () => ({ + usePersonAttributes: jest.fn().mockReturnValue({ + personAttributes: [], + error: null, + isLoading: false, + }), +})); + const encounter = { uuid: 'encounter-uuid', obs: [ diff --git a/src/components/inputs/unspecified/unspecified.test.tsx b/src/components/inputs/unspecified/unspecified.test.tsx index d7635e55..daf37242 100644 --- a/src/components/inputs/unspecified/unspecified.test.tsx +++ b/src/components/inputs/unspecified/unspecified.test.tsx @@ -58,6 +58,14 @@ jest.mock('../../../hooks/useEncounter', () => ({ }), })); +jest.mock('../../../hooks/usePersonAttributes', () => ({ + usePersonAttributes: jest.fn().mockReturnValue({ + personAttributes: [], + error: null, + isLoading: false, + }), +})); + const renderForm = async (mode: SessionMode = 'enter') => { await act(async () => { render( diff --git a/src/datasources/person-attribute-datasource.ts b/src/datasources/person-attribute-datasource.ts new file mode 100644 index 00000000..a122b76f --- /dev/null +++ b/src/datasources/person-attribute-datasource.ts @@ -0,0 +1,16 @@ +import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; +import { BaseOpenMRSDataSource } from './data-source'; + +export class PersonAttributeLocationDataSource extends BaseOpenMRSDataSource { + constructor() { + super(null); + } + + async fetchData(searchTerm: string, config?: Record, uuid?: string): Promise { + const rep = 'v=custom:(uuid,display)'; + const url = `${restBaseUrl}/location?${rep}`; + const { data } = await openmrsFetch(searchTerm ? `${url}&q=${searchTerm}` : url); + + return data?.results; + } +} diff --git a/src/datasources/select-concept-answers-datasource.ts b/src/datasources/select-concept-answers-datasource.ts index 03504ac4..20894655 100644 --- a/src/datasources/select-concept-answers-datasource.ts +++ b/src/datasources/select-concept-answers-datasource.ts @@ -7,7 +7,7 @@ export class SelectConceptAnswersDatasource extends BaseOpenMRSDataSource { } fetchData(searchTerm: string, config?: Record): Promise { - const apiUrl = this.url.replace('conceptUuid', config.referencedValue || config.concept); + const apiUrl = this.url.replace('conceptUuid', config.concept || config.referencedValue); return openmrsFetch(apiUrl).then(({ data }) => { return data['setMembers'].length ? data['setMembers'] : data['answers']; }); diff --git a/src/hooks/useFormJson.ts b/src/hooks/useFormJson.ts index e52b1134..e5b4643b 100644 --- a/src/hooks/useFormJson.ts +++ b/src/hooks/useFormJson.ts @@ -118,16 +118,26 @@ function validateFormsArgs(formUuid: string, rawFormJson: any): Error { * @param {string} [formSessionIntent] - The optional form session intent. * @returns {FormSchema} - The refined form JSON object of type FormSchema. */ -function refineFormJson( +async function refineFormJson( formJson: any, schemaTransformers: FormSchemaTransformer[] = [], formSessionIntent?: string, -): FormSchema { - removeInlineSubForms(formJson, formSessionIntent); - // apply form schema transformers - schemaTransformers.reduce((draftForm, transformer) => transformer.transform(draftForm), formJson); - setEncounterType(formJson); - return applyFormIntent(formSessionIntent, formJson); +): Promise { + await removeInlineSubForms(formJson, formSessionIntent); + const transformedFormJson = await schemaTransformers.reduce(async (form, transformer) => { + const currentForm = await form; + if (isPromise(transformer.transform(currentForm))) { + return transformer.transform(currentForm); + } else { + return transformer.transform(currentForm); + } + }, Promise.resolve(formJson)); + setEncounterType(transformedFormJson); + return applyFormIntent(formSessionIntent, transformedFormJson); +} + +function isPromise(value: any): value is Promise { + return value && typeof value.then === 'function'; } /** @@ -144,7 +154,7 @@ function parseFormJson(formJson: any): FormSchema { * @param {FormSchema} formJson - The input form JSON object of type FormSchema. * @param {string} formSessionIntent - The form session intent. */ -function removeInlineSubForms(formJson: FormSchema, formSessionIntent: string): void { +async function removeInlineSubForms(formJson: FormSchema, formSessionIntent: string): Promise { for (let i = formJson.pages.length - 1; i >= 0; i--) { const page = formJson.pages[i]; if ( @@ -153,7 +163,7 @@ function removeInlineSubForms(formJson: FormSchema, formSessionIntent: string): page.subform?.form?.encounterType === formJson.encounterType ) { const nonSubformPages = page.subform.form.pages.filter((page) => !isTrue(page.isSubform)); - formJson.pages.splice(i, 1, ...refineFormJson(page.subform.form, [], formSessionIntent).pages); + formJson.pages.splice(i, 1, ...(await refineFormJson(page.subform.form, [], formSessionIntent)).pages); } } } diff --git a/src/hooks/usePersonAttributes.tsx b/src/hooks/usePersonAttributes.tsx new file mode 100644 index 00000000..4e00dc11 --- /dev/null +++ b/src/hooks/usePersonAttributes.tsx @@ -0,0 +1,31 @@ +import { openmrsFetch, type PersonAttribute, restBaseUrl } from '@openmrs/esm-framework'; +import { useEffect, useState } from 'react'; +import { type FormSchema } from '../types'; + +export const usePersonAttributes = (patientUuid: string, formJson: FormSchema) => { + const [personAttributes, setPersonAttributes] = useState>([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (formJson.meta?.personAttributes?.hasPersonAttributeFields && patientUuid) { + openmrsFetch(`${restBaseUrl}/patient/${patientUuid}?v=custom:(attributes)`) + .then((response) => { + setPersonAttributes(response.data?.attributes); + setIsLoading(false); + }) + .catch((error) => { + setError(error); + setIsLoading(false); + }); + } else { + setIsLoading(false); + } + }, [patientUuid]); + + return { + personAttributes, + error, + isLoading: isLoading, + }; +}; diff --git a/src/processors/encounter/encounter-form-processor.ts b/src/processors/encounter/encounter-form-processor.ts index 75c35167..3776dab5 100644 --- a/src/processors/encounter/encounter-form-processor.ts +++ b/src/processors/encounter/encounter-form-processor.ts @@ -7,9 +7,11 @@ import { prepareEncounter, preparePatientIdentifiers, preparePatientPrograms, + preparePersonAttributes, saveAttachments, savePatientIdentifiers, savePatientPrograms, + savePersonAttributes, } from './encounter-processor-helper'; import { type FormField, @@ -31,19 +33,24 @@ import { type FormContextProps } from '../../provider/form-provider'; import { useEncounter } from '../../hooks/useEncounter'; import { useEncounterRole } from '../../hooks/useEncounterRole'; import { usePatientPrograms } from '../../hooks/usePatientPrograms'; +import { usePersonAttributes } from '../../hooks/usePersonAttributes'; function useCustomHooks(context: Partial) { const [isLoading, setIsLoading] = useState(true); const { encounter, isLoading: isLoadingEncounter } = useEncounter(context.formJson); const { encounterRole, isLoading: isLoadingEncounterRole } = useEncounterRole(); const { isLoadingPatientPrograms, patientPrograms } = usePatientPrograms(context.patient?.id, context.formJson); + const { isLoading: isLoadingPersonAttributes, personAttributes } = usePersonAttributes( + context.patient?.id, + context.formJson, + ); useEffect(() => { - setIsLoading(isLoadingPatientPrograms || isLoadingEncounter || isLoadingEncounterRole); - }, [isLoadingPatientPrograms, isLoadingEncounter, isLoadingEncounterRole]); + setIsLoading(isLoadingPatientPrograms || isLoadingEncounter || isLoadingEncounterRole || isLoadingPersonAttributes); + }, [isLoadingPatientPrograms, isLoadingEncounter, isLoadingEncounterRole, isLoadingPersonAttributes]); return { - data: { encounter, patientPrograms, encounterRole }, + data: { encounter, patientPrograms, encounterRole, personAttributes }, isLoading, error: null, updateContext: (setContext: React.Dispatch>) => { @@ -56,6 +63,7 @@ function useCustomHooks(context: Partial) { ...context.customDependencies, patientPrograms: patientPrograms, defaultEncounterRole: encounterRole, + personAttributes: personAttributes, }, }; }); @@ -76,6 +84,7 @@ const contextInitializableTypes = [ 'patientIdentifier', 'encounterRole', 'programState', + 'personAttributes', ]; export class EncounterFormProcessor extends FormProcessor { @@ -159,6 +168,27 @@ export class EncounterFormProcessor extends FormProcessor { }); } + // save person attributes + try { + const personAttributes = preparePersonAttributes(context.formFields, context.location?.uuid); + const savedAttributes = await savePersonAttributes(context.patient, personAttributes); + if (savedAttributes?.length) { + showSnackbar({ + title: translateFn('personAttributesSaved', 'Person attribute(s) saved successfully'), + kind: 'success', + isLowContrast: true, + }); + } + } catch (error) { + const errorMessages = extractErrorMessagesFromResponse(error); + throw { + title: translateFn('errorSavingPersonAttributes', 'Error saving person attributes'), + description: errorMessages.join(', '), + kind: 'error', + critical: true, + }; + } + // save encounter try { const { data: savedEncounter } = await saveEncounter(abortController, encounter, encounter.uuid); diff --git a/src/processors/encounter/encounter-processor-helper.ts b/src/processors/encounter/encounter-processor-helper.ts index 7ccd0323..4851a64b 100644 --- a/src/processors/encounter/encounter-processor-helper.ts +++ b/src/processors/encounter/encounter-processor-helper.ts @@ -7,7 +7,7 @@ import { type PatientProgram, type PatientProgramPayload, } from '../../types'; -import { saveAttachment, savePatientIdentifier, saveProgramEnrollment } from '../../api'; +import { saveAttachment, savePatientIdentifier, savePersonAttribute, saveProgramEnrollment } from '../../api'; import { hasRendering, hasSubmission } from '../../utils/common-utils'; import dayjs from 'dayjs'; import { assignedObsIds, constructObs, voidObs } from '../../adapters/obs-adapter'; @@ -16,7 +16,7 @@ import { ConceptTrue } from '../../constants'; import { DefaultValueValidator } from '../../validators/default-value-validator'; import { cloneRepeatField } from '../../components/repeat/helpers'; import { assignedOrderIds } from '../../adapters/orders-adapter'; -import { type OpenmrsResource } from '@openmrs/esm-framework'; +import { type OpenmrsResource, type PersonAttribute } from '@openmrs/esm-framework'; import { assignedDiagnosesIds } from '../../adapters/encounter-diagnosis-adapter'; export function prepareEncounter( @@ -159,6 +159,10 @@ export function saveAttachments(fields: FormField[], encounter: OpenmrsEncounter }); } +export function savePersonAttributes(patient: fhir.Patient, attributes: PersonAttribute[]) { + return attributes.map((personAttribute) => savePersonAttribute(personAttribute, patient.id)); +} + export function getMutableSessionProps(context: FormContextProps) { const { formFields, @@ -373,3 +377,9 @@ function prepareDiagnosis(fields: FormField[]) { return diagnoses; } + +export function preparePersonAttributes(fields: FormField[], encounterLocation: string): PersonAttribute[] { + return fields + .filter((field) => field.type === 'personAttribute' && hasSubmission(field)) + .map((field) => field.meta.submission.newValue); +} diff --git a/src/registry/inbuilt-components/inbuiltControls.ts b/src/registry/inbuilt-components/inbuiltControls.ts index 4e4a8093..fa005a6a 100644 --- a/src/registry/inbuilt-components/inbuiltControls.ts +++ b/src/registry/inbuilt-components/inbuiltControls.ts @@ -94,6 +94,6 @@ export const inbuiltControls: Array ({ name: template.name, - component: templateToComponentMap.find((component) => component.name === template.name).baseControlComponent, + component: templateToComponentMap.find((component) => component.name === template.name)?.baseControlComponent, })), ]; diff --git a/src/registry/inbuilt-components/inbuiltDataSources.ts b/src/registry/inbuilt-components/inbuiltDataSources.ts index 0a1d9249..c437dc42 100644 --- a/src/registry/inbuilt-components/inbuiltDataSources.ts +++ b/src/registry/inbuilt-components/inbuiltDataSources.ts @@ -34,6 +34,10 @@ export const inbuiltDataSources: Array>> = [ name: 'encounter_role_datasource', component: new EncounterRoleDataSource(), }, + { + name: 'person-attribute-location', + component: new LocationDataSource(), + }, ]; export const validateInbuiltDatasource = (name: string) => { diff --git a/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts b/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts index 2a5632bf..8f3f330d 100644 --- a/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts +++ b/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts @@ -12,6 +12,7 @@ import { PatientIdentifierAdapter } from '../../adapters/patient-identifier-adap import { ProgramStateAdapter } from '../../adapters/program-state-adapter'; import { EncounterDiagnosisAdapter } from '../../adapters/encounter-diagnosis-adapter'; import { type FormFieldValueAdapter } from '../../types'; +import { PersonAttributesAdapter } from '../../adapters/person-attributes-adapter'; export const inbuiltFieldValueAdapters: RegistryItem[] = [ { @@ -66,4 +67,8 @@ export const inbuiltFieldValueAdapters: RegistryItem[] = type: 'diagnosis', component: EncounterDiagnosisAdapter, }, + { + type: 'personAttribute', + component: PersonAttributesAdapter, + }, ]; diff --git a/src/registry/inbuilt-components/inbuiltTransformers.ts b/src/registry/inbuilt-components/inbuiltTransformers.ts index a35c79cc..2f67bd74 100644 --- a/src/registry/inbuilt-components/inbuiltTransformers.ts +++ b/src/registry/inbuilt-components/inbuiltTransformers.ts @@ -1,3 +1,4 @@ +import { PersonAttributesTransformer } from '../../transformers/person-attributes-transformer'; import { DefaultFormSchemaTransformer } from '../../transformers/default-schema-transformer'; import { type FormSchemaTransformer } from '../../types'; import { type RegistryItem } from '../registry'; @@ -7,4 +8,8 @@ export const inbuiltFormTransformers: Array> name: 'DefaultFormSchemaTransformer', component: DefaultFormSchemaTransformer, }, + { + name: 'PersonAttributesTransformer', + component: PersonAttributesTransformer, + }, ]; diff --git a/src/registry/registry.ts b/src/registry/registry.ts index 1d96bb8b..09bcde4d 100644 --- a/src/registry/registry.ts +++ b/src/registry/registry.ts @@ -171,9 +171,10 @@ export async function getRegisteredFieldValueAdapter(type: string): Promise
{ - const transformers = []; + const transformers: Array = []; const cachedTransformers = registryCache.formSchemaTransformers; + if (Object.keys(cachedTransformers).length) { return Object.values(cachedTransformers); } @@ -186,14 +187,21 @@ export async function getRegisteredFormSchemaTransformers(): Promise transformer !== undefined)); - - transformers.push(...inbuiltFormTransformers.map((inbuiltTransformer) => inbuiltTransformer.component)); - + const inbuiltTransformersPromises = inbuiltFormTransformers.map(async (inbuiltTransformer) => { + const transformer = inbuiltTransformer.component; + if (transformer instanceof Promise) { + return await transformer; + } + return transformer; + }); + const resolvedInbuiltTransformers = await Promise.all(inbuiltTransformersPromises); + transformers.push(...resolvedInbuiltTransformers); transformers.forEach((transformer) => { const inbuiltTransformer = inbuiltFormTransformers.find((t) => t.component === transformer); - registryCache.formSchemaTransformers[inbuiltTransformer.name] = transformer; + if (inbuiltTransformer) { + registryCache.formSchemaTransformers[inbuiltTransformer.name] = transformer; + } }); - return transformers; } diff --git a/src/transformers/default-schema-transformer.test.ts b/src/transformers/default-schema-transformer.test.ts index 0a5ab3ec..045b1569 100644 --- a/src/transformers/default-schema-transformer.test.ts +++ b/src/transformers/default-schema-transformer.test.ts @@ -182,11 +182,11 @@ const expectedTransformedSchema = { }; describe('Default form schema transformer', () => { - it('should transform AFE schema to be compatible with RFE', () => { - expect(DefaultFormSchemaTransformer.transform(testForm as any)).toEqual(expectedTransformedSchema); + it('should transform AFE schema to be compatible with RFE', async () => { + expect(await DefaultFormSchemaTransformer.transform(testForm as any)).toEqual(expectedTransformedSchema); }); - it('should handle checkbox-searchable rendering', () => { + it('should handle checkbox-searchable rendering', async () => { // setup const form = { pages: [ @@ -209,7 +209,7 @@ describe('Default form schema transformer', () => { ], }; // exercise - const transformedForm = DefaultFormSchemaTransformer.transform(form as FormSchema); + const transformedForm = await DefaultFormSchemaTransformer.transform(form as FormSchema); const transformedQuestion = transformedForm.pages[0].sections[0].questions[0]; // verify expect(transformedQuestion.questionOptions.rendering).toEqual('checkbox'); diff --git a/src/transformers/default-schema-transformer.ts b/src/transformers/default-schema-transformer.ts index db5fd80a..d1c0a887 100644 --- a/src/transformers/default-schema-transformer.ts +++ b/src/transformers/default-schema-transformer.ts @@ -108,7 +108,7 @@ function sanitizeQuestion(question: FormField) { } } -function parseBooleanTokenIfPresent(node: any, token: any) { +export function parseBooleanTokenIfPresent(node: any, token: any) { if (node && typeof node[token] === 'string') { const trimmed = node[token].trim().toLowerCase(); if (trimmed === 'true' || trimmed === 'false') { diff --git a/src/transformers/person-attributes-transformer.ts b/src/transformers/person-attributes-transformer.ts new file mode 100644 index 00000000..e9545d6a --- /dev/null +++ b/src/transformers/person-attributes-transformer.ts @@ -0,0 +1,81 @@ +import { type FormField, type FormSchema, type FormSchemaTransformer, type RenderType, type FormPage } from '../types'; +import { getPersonAttributeTypeFormat } from '../api/'; +import { parseBooleanTokenIfPresent } from './default-schema-transformer'; + +export type RenderTypeExtended = 'multiCheckbox' | 'numeric' | RenderType; + +export const PersonAttributesTransformer: FormSchemaTransformer = { + transform: async (form: FormSchema): Promise => { + try { + parseBooleanTokenIfPresent(form, 'readonly'); + for (const [index, page] of form.pages.entries()) { + const label = page.label ?? ''; + page.id = `page-${label.replace(/\s/g, '')}-${index}`; + parseBooleanTokenIfPresent(page, 'readonly'); + if (page.sections) { + for (const section of page.sections) { + if (section.questions) { + const formMeta = form.meta ?? {}; + if (checkQuestions(section.questions)) { + formMeta.personAttributes = { hasPersonAttributeFields: true }; + } + form.meta = formMeta; + section.questions = await Promise.all( + section.questions.map((question) => handleQuestion(question, page, form)), + ); + } + } + } + } + } catch (error) { + console.error('Error in form transformation:', error); + throw error; + } + return form; + }, +}; + +async function handleQuestion(question: FormField, page: FormPage, form: FormSchema): Promise { + try { + await transformByType(question); + if (question.questions?.length) { + question.questions = await Promise.all( + question.questions.map((nestedQuestion) => handleQuestion(nestedQuestion, page, form)), + ); + } + question.meta.pageId = page.id; + return question; + } catch (error) { + console.error(error); + } +} + +async function transformByType(question: FormField) { + switch (question.type) { + case 'personAttribute': + await handlePersonAttributeType(question); + break; + } +} + +async function handlePersonAttributeType(question: FormField) { + const attributeTypeFormat = await getPersonAttributeTypeFormat(question.questionOptions.attributeType); + if (attributeTypeFormat === 'org.openmrs.Location') { + question.questionOptions.datasource = { + name: 'location_datasource', + }; + } else if (attributeTypeFormat === 'org.openmrs.Concept') { + question.questionOptions.datasource = { + name: 'select_concept_answers_datasource', + config: { + concept: question.questionOptions?.concept, + }, + }; + } else if (attributeTypeFormat === 'java.lang.String') { + question.questionOptions.rendering = 'text'; + } +} + +function checkQuestions(questions) { + return questions.some((question) => question.type === 'personAttribute'); +} diff --git a/src/types/index.ts b/src/types/index.ts index 39434ae2..2f58a510 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -102,7 +102,7 @@ export interface FormSchemaTransformer { /** * Transforms the raw schema to be compatible with the React Form Engine. */ - transform: (form: FormSchema) => FormSchema; + transform: (form: FormSchema) => Promise | FormSchema; } export interface PostSubmissionAction { diff --git a/src/types/schema.ts b/src/types/schema.ts index be00e315..db465f01 100644 --- a/src/types/schema.ts +++ b/src/types/schema.ts @@ -25,6 +25,10 @@ export interface FormSchema { hasProgramFields?: boolean; [anythingElse: string]: any; }; + personAttributes?: { + hasPersonAttributeFields?: boolean; + [anythingElse: string]: any; + }; }; } @@ -198,6 +202,7 @@ export interface FormQuestionOptions { conceptClasses?: Array; conceptSet?: string; }; + attributeType?: string; } export interface QuestionAnswerOption {