-
Notifications
You must be signed in to change notification settings - Fork 232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
O3-283 Configurable Support for Recording Registration Observations #221
Changes from all commits
df90113
1cefeed
139e396
c44190e
addfbb5
a2f73c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,14 +9,14 @@ export interface SectionDefinition { | |
export interface FieldDefinition { | ||
id: string; | ||
type: string; | ||
label: string; | ||
label: string | null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can also use Just a suggestion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional and "possibly null" are distinct. Optional is more or less the same as |
||
uuid: string; | ||
placeholder: string; | ||
validation: { | ||
required: boolean; | ||
matches: string; | ||
matches: string | null; | ||
}; | ||
answerConceptSetUuid: string; | ||
answerConceptSetUuid: string | null; | ||
} | ||
|
||
export interface RegistrationConfig { | ||
|
@@ -38,6 +38,11 @@ export interface RegistrationConfig { | |
patientPhotoUuid: string; | ||
}; | ||
defaultPatientIdentifierTypes: Array<string>; | ||
registrationObs: { | ||
encounterTypeUuid: string | null; | ||
encounterProviderRoleUuid: string; | ||
registrationFormUuid: string | null; | ||
}; | ||
} | ||
|
||
export const builtInSections: Array<SectionDefinition> = [ | ||
|
@@ -96,6 +101,11 @@ export const esmPatientRegistrationSchema = { | |
_description: | ||
'How this field will be referred to in the `fields` element of the `sectionDefinitions` configuration.', | ||
}, | ||
type: { | ||
_type: Type.String, | ||
_description: "How this field's data will be stored—a person attribute or an obs.", | ||
_validators: [validators.oneOf(['person attribute', 'obs'])], | ||
}, | ||
uuid: { | ||
_type: Type.UUID, | ||
_description: "Person attribute type UUID that this field's data should be saved to.", | ||
|
@@ -162,12 +172,84 @@ export const esmPatientRegistrationSchema = { | |
_default: '736e8771-e501-4615-bfa7-570c03f4bef5', | ||
}, | ||
}, | ||
|
||
defaultPatientIdentifierTypes: { | ||
_type: Type.Array, | ||
_elements: { | ||
_type: Type.PatientIdentifierTypeUuid, | ||
}, | ||
_default: [], | ||
}, | ||
registrationObs: { | ||
encounterTypeUuid: { | ||
_type: Type.UUID, | ||
_default: null, | ||
_description: | ||
'Obs created during registration will be associated with an encounter of this type. This must be set in order to use fields of type `obs`.', | ||
}, | ||
encounterProviderRoleUuid: { | ||
_type: Type.UUID, | ||
_default: 'a0b03050-c99b-11e0-9572-0800200c9a66', | ||
_description: "The provider role to use for the registration encounter. Default is 'Unkown'.", | ||
}, | ||
registrationFormUuid: { | ||
_type: Type.UUID, | ||
_default: null, | ||
_description: | ||
'The form UUID to associate with the registration encounter. By default no form will be associated.', | ||
}, | ||
}, | ||
_validators: [ | ||
validator( | ||
(config: RegistrationConfig) => | ||
!config.fieldDefinitions.some((d) => d.type == 'obs') || config.registrationObs.encounterTypeUuid != null, | ||
"If fieldDefinitions contains any fields of type 'obs', `registrationObs.encounterTypeUuid` must be specified.", | ||
), | ||
validator( | ||
(config: RegistrationConfig) => | ||
config.sections.every((s) => | ||
[...builtInSections, ...config.sectionDefinitions].map((sDef) => sDef.id).includes(s), | ||
), | ||
(config: RegistrationConfig) => { | ||
const allowedSections = [...builtInSections, ...config.sectionDefinitions].map((sDef) => sDef.id); | ||
const badSection = config.sections.find((s) => !allowedSections.includes(s)); | ||
return ( | ||
`'${badSection}' is not a valid section ID. Valid section IDs include the built-in sections ${stringifyDefinitions( | ||
builtInSections, | ||
)}` + | ||
(config.sectionDefinitions.length | ||
? `; and the defined sections ${stringifyDefinitions(config.sectionDefinitions)}.` | ||
: '.') | ||
); | ||
}, | ||
), | ||
validator( | ||
(config: RegistrationConfig) => | ||
config.sectionDefinitions.every((sectionDefinition) => | ||
sectionDefinition.fields.every((f) => | ||
[...builtInFields, ...config.fieldDefinitions.map((fDef) => fDef.id)].includes(f), | ||
), | ||
), | ||
(config: RegistrationConfig) => { | ||
const allowedFields = [...builtInFields, ...config.fieldDefinitions.map((fDef) => fDef.id)]; | ||
const badSection = config.sectionDefinitions.find((sectionDefinition) => | ||
sectionDefinition.fields.some((f) => !allowedFields.includes(f)), | ||
); | ||
const badField = badSection.fields.find((f) => !allowedFields.includes(f)); | ||
return ( | ||
`The section definition '${ | ||
badSection.id | ||
}' contains an invalid field '${badField}'. 'fields' can only contain the built-in fields '${builtInFields.join( | ||
"', '", | ||
)}'` + | ||
(config.fieldDefinitions.length | ||
? `; or the defined fields ${stringifyDefinitions(config.fieldDefinitions)}.` | ||
: '.') | ||
); | ||
}, | ||
), | ||
], | ||
}; | ||
|
||
function stringifyDefinitions(sectionDefinitions: Array<SectionDefinition | FieldDefinition>) { | ||
return `'${sectionDefinitions.map((s) => s.id).join("', '")}'`; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { ConceptResponse } from '../../patient-registration-types'; | ||
|
||
export const useConcept = jest.fn(function mockUseConceptImplementation(uuid: string): { | ||
data: ConceptResponse; | ||
isLoading: boolean; | ||
} { | ||
let data; | ||
if (uuid == 'weight-uuid') { | ||
data = { | ||
uuid: 'weight-uuid', | ||
display: 'Weight (kg)', | ||
datatype: { display: 'Numeric', uuid: 'num' }, | ||
answers: [], | ||
setMembers: [], | ||
}; | ||
} else if (uuid == 'chief-complaint-uuid') { | ||
data = { | ||
uuid: 'chief-complaint-uuid', | ||
display: 'Chief Complaint', | ||
datatype: { display: 'Text', uuid: 'txt' }, | ||
answers: [], | ||
setMembers: [], | ||
}; | ||
} else if (uuid == 'nationality-uuid') { | ||
data = { | ||
uuid: 'nationality-uuid', | ||
display: 'Nationality', | ||
datatype: { display: 'Coded', uuid: 'cdd' }, | ||
answers: [ | ||
{ display: 'USA', uuid: 'usa' }, | ||
{ display: 'Mexico', uuid: 'mex' }, | ||
], | ||
setMembers: [], | ||
}; | ||
} | ||
return { | ||
data: data ?? null, | ||
isLoading: !data, | ||
}; | ||
}); | ||
|
||
export const useConceptAnswers = jest.fn((uuid: string) => { | ||
if (uuid == 'nationality-uuid') { | ||
return { | ||
data: [ | ||
{ display: 'USA', uuid: 'usa' }, | ||
{ display: 'Mexico', uuid: 'mex' }, | ||
], | ||
isLoading: false, | ||
}; | ||
} else if (uuid == 'other-countries-uuid') { | ||
return { | ||
data: [ | ||
{ display: 'Kenya', uuid: 'ke' }, | ||
{ display: 'Uganda', uuid: 'ug' }, | ||
], | ||
isLoading: false, | ||
}; | ||
} | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { FetchResponse, openmrsFetch, showToast } from '@openmrs/esm-framework'; | ||
import useSWRImmutable from 'swr/immutable'; | ||
import { ConceptAnswers, ConceptResponse } from '../patient-registration-types'; | ||
|
||
export function useConcept(conceptUuid: string): { data: ConceptResponse; isLoading: boolean } { | ||
const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== ''; | ||
const { data, error } = useSWRImmutable<FetchResponse<ConceptResponse>, Error>( | ||
shouldFetch ? `/ws/rest/v1/concept/${conceptUuid}` : null, | ||
openmrsFetch, | ||
); | ||
if (error) { | ||
showToast({ | ||
title: error.name, | ||
description: error.message, | ||
kind: 'error', | ||
}); | ||
} | ||
return { data: data?.data, isLoading: !data && !error }; | ||
} | ||
|
||
export function useConceptAnswers(conceptUuid: string): { data: Array<ConceptAnswers>; isLoading: boolean } { | ||
const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== ''; | ||
const { data, error } = useSWRImmutable<FetchResponse<ConceptResponse>, Error>( | ||
shouldFetch ? `/ws/rest/v1/concept/${conceptUuid}` : null, | ||
openmrsFetch, | ||
); | ||
if (error) { | ||
showToast({ | ||
title: error.name, | ||
description: error.message, | ||
kind: 'error', | ||
}); | ||
} | ||
return { data: data?.data?.answers, isLoading: !data && !error }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,7 +83,7 @@ | |
margin-left: $spacing-03; | ||
} | ||
|
||
.attributeField { | ||
.customField { | ||
margin-bottom: $spacing-05; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes warnings about needing unique
key
s when creating elements withmap
.