Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
brandones committed May 25, 2022
1 parent 20e5082 commit 5dc7035
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 42 deletions.
30 changes: 29 additions & 1 deletion packages/esm-patient-registration-app/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = [
Expand Down Expand Up @@ -169,12 +174,35 @@ 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.',
},
},

// TODO: validate that
// - if a field has type 'obs', registrationObs.encounterTypeUuid is not null
// - all sections have been defined
// - all fields have been defined
};
1 change: 1 addition & 0 deletions packages/esm-patient-registration-app/src/offline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export async function syncPatientRegistration(
queuedPatient: PatientRegistration,
options: SyncProcessOptions<PatientRegistration>,
) {
// TODO: fix the arguments
await FormManager.savePatientFormOnline(
queuedPatient._patientRegistrationData.isNewPatient,
queuedPatient._patientRegistrationData.formValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
margin-left: $spacing-03;
}

.attributeField {
.customField {
margin-bottom: $spacing-05;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { InlineNotification } from 'carbon-components-react';
import { InlineNotification, Select, SelectItem } from 'carbon-components-react';
import { Field } from 'formik';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FieldDefinition } from '../../../config-schema';
import { useConcept } from '../field.resource';
import { Input } from '../../input/basic-input/input/input.component';
import { ConceptResponse } from '../../patient-registration-types';
import { useConcept, useConceptAnswers } from '../field.resource';
import styles from './../field.scss';

export interface ObsFieldProps {
fieldDefinition: FieldDefinition;
Expand All @@ -13,28 +18,95 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
if (isLoading) {
return null;
}
switch (concept.) {
case 'java.lang.String':
switch (concept.datatype.display) {
case 'Text':
return (
<TextPersonAttributeField
personAttributeType={personAttributeType}
<TextObsField
concept={concept}
validationRegex={fieldDefinition.validation.matches}
label={fieldDefinition.label}
/>
);
case 'org.openmrs.Concept':
case 'Coded':
return (
<CodedPersonAttributeField
personAttributeType={personAttributeType}
<CodedObsField
concept={concept}
answerConceptSetUuid={fieldDefinition.answerConceptSetUuid}
label={fieldDefinition.label}
/>
);
default:
return (
<InlineNotification kind="error" title="Error">
Patient attribute type has unknown format "{personAttributeType.format}"
Concept has unknown datatype "{concept.datatype.display}"
</InlineNotification>
);
}
}
}

interface TextObsFieldProps {
concept: ConceptResponse;
validationRegex: string;
label: string;
}

function TextObsField({ concept, validationRegex, label }: TextObsFieldProps) {
const { t } = useTranslation();

const validateInput = (value: string) => {
if (!value || !validationRegex || validationRegex === '' || typeof validationRegex !== 'string' || value === '') {
return;
}
const regex = new RegExp(validationRegex);
if (regex.test(value)) {
return;
} else {
return t('invalidInput', 'Invalid Input');
}
};

const fieldName = `obs.${concept.uuid}`;

return (
<div className={`${styles.customField} ${styles.halfWidthInDesktopView}`}>
<Field name={fieldName} validate={validateInput}>
{({ field, form: { touched, errors }, meta }) => {
return (
<Input
id={fieldName}
labelText={label ?? concept.display}
light
invalid={errors[fieldName] && touched[fieldName]}
{...field}
/>
);
}}
</Field>
</div>
);
}

interface CodedObsFieldProps {
concept: ConceptResponse;
answerConceptSetUuid: string;
label?: string;
}

function CodedObsField({ concept, answerConceptSetUuid, label }: CodedObsFieldProps) {
const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(answerConceptSetUuid);
const fieldName = `obs.${concept.uuid}`;

return (
<div className={`${styles.customField} ${styles.halfWidthInDesktopView}`}>
{!isLoadingConceptAnswers && conceptAnswers?.length ? (
<Select id={fieldName} name={fieldName} labelText={label ?? concept?.display} light>
{conceptAnswers.map((answer) => (
<SelectItem key={answer.uuid} value={answer.uuid} text={answer.display} />
))}
</Select>
) : (
<Input id={fieldName} labelText={label ?? concept?.display} name={fieldName} light />
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function CodedPersonAttributeField({
const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(answerConceptSetUuid);

return (
<div className={`${styles.attributeField} ${styles.halfWidthInDesktopView}`}>
<div className={`${styles.customField} ${styles.halfWidthInDesktopView}`}>
{!isLoadingConceptAnswers && conceptAnswers?.length ? (
<Select
id={`person-attribute-${personAttributeType.uuid}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function TextPersonAttributeField({
const fieldName = `attributes.${personAttributeType.uuid}`;

return (
<div className={`${styles.attributeField} ${styles.halfWidthInDesktopView}`}>
<div className={`${styles.customField} ${styles.halfWidthInDesktopView}`}>
<Field name={fieldName} validate={validateInput}>
{({ field, form: { touched, errors }, meta }) => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { queueSynchronizationItem } from '@openmrs/esm-framework';
import { FetchResponse, queueSynchronizationItem, Session } from '@openmrs/esm-framework';
import { patientRegistration } from '../constants';
import {
FormValues,
Expand All @@ -9,6 +9,7 @@ import {
PatientIdentifier,
PatientIdentifierValue,
PatientRegistration,
RelationshipValue,
} from './patient-registration-types';
import {
addPatientIdentifier,
Expand All @@ -23,6 +24,7 @@ import {
updatePatientIdentifier,
} from './patient-registration.resource';
import isEqual from 'lodash-es/isEqual';
import { RegistrationConfig } from '../config-schema';

export type SavePatientForm = (
isNewPatient: boolean,
Expand Down Expand Up @@ -76,8 +78,9 @@ export default class FormManager {
patientUuidMap: PatientUuidMapType,
initialAddressFieldValues: Record<string, any>,
capturePhotoProps: CapturePhotoProps,
patientPhotoConceptUuid: string,
currentLocation: string,
currentUser: Session,
config: RegistrationConfig,
abortController: AbortController,
): Promise<string> {
const patientIdentifiers: Array<PatientIdentifier> = await FormManager.savePatientIdentifiers(
Expand Down Expand Up @@ -106,38 +109,24 @@ export default class FormManager {
);

if (savePatientResponse.ok) {
await Promise.all(
values.relationships
.filter((m) => m.relationshipType)
.filter((relationship) => !!relationship.action)
.map(({ relatedPersonUuid, relationshipType, uuid: relationshipUuid, action }) => {
const [type, direction] = relationshipType.split('/');
const thisPatientUuid = savePatientResponse.data.uuid;
const isAToB = direction === 'aIsToB';
const relationshipToSave = {
personA: isAToB ? relatedPersonUuid : thisPatientUuid,
personB: isAToB ? thisPatientUuid : relatedPersonUuid,
relationshipType: type,
};

switch (action) {
case 'ADD':
return saveRelationship(abortController, relationshipToSave);
case 'UPDATE':
return updateRelationship(abortController, relationshipUuid, relationshipToSave);
case 'DELETE':
return deleteRelationship(abortController, relationshipUuid);
}
}),
await this.saveRelationships(values.relationships, savePatientResponse, abortController);

await this.saveObservations(
values.obs,
savePatientResponse,
currentLocation,
currentUser,
config,
abortController,
);

if (patientPhotoConceptUuid && capturePhotoProps?.imageData) {
if (config.concepts.patientPhotoUuid && capturePhotoProps?.imageData) {
await savePatientPhoto(
savePatientResponse.data.uuid,
capturePhotoProps.imageData,
'/ws/rest/v1/obs',
capturePhotoProps.dateTime || new Date().toISOString(),
patientPhotoConceptUuid,
config.concepts.patientPhotoUuid,
abortController,
);
}
Expand All @@ -146,6 +135,65 @@ export default class FormManager {
return savePatientResponse.data.uuid;
}

static async saveRelationships(
relationships: Array<RelationshipValue>,
savePatientResponse: FetchResponse,
abortController: AbortController,
) {
return Promise.all(
relationships
.filter((m) => m.relationshipType)
.filter((relationship) => !!relationship.action)
.map(({ relatedPersonUuid, relationshipType, uuid: relationshipUuid, action }) => {
const [type, direction] = relationshipType.split('/');
const thisPatientUuid = savePatientResponse.data.uuid;
const isAToB = direction === 'aIsToB';
const relationshipToSave = {
personA: isAToB ? relatedPersonUuid : thisPatientUuid,
personB: isAToB ? thisPatientUuid : relatedPersonUuid,
relationshipType: type,
};

switch (action) {
case 'ADD':
return saveRelationship(abortController, relationshipToSave);
case 'UPDATE':
return updateRelationship(abortController, relationshipUuid, relationshipToSave);
case 'DELETE':
return deleteRelationship(abortController, relationshipUuid);
}
}),
);
}

static async saveObservations(
obss: Record<string, string>,
savePatientResponse: FetchResponse,
currentLocation: string,
currentUser: Session,
config: RegistrationConfig,
abortController: AbortController,
) {
if (Object.keys(obss).length > 0 && config.registrationObs.encounterTypeUuid) {
const encounterToSave = {
encounterDatetime: new Date(),
patient: savePatientResponse.data.uuid,
encounterType: config.registrationObs.encounterTypeUuid,
location: currentLocation,
encounterProviders: [
{
provider: currentUser.currentProvider.uuid,
encounterRole: config.registrationObs.encounterProviderRoleUuid,
},
],
form: config.registrationObs.registrationFormUuid,
obs: null, // TODO: transform `obss` into here
};

// TODO: fill this in
}
}

static async savePatientIdentifiers(
isNewPatient: boolean,
patientUuid: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ export interface FormValues {
attributes?: {
[attributeTypeUuid: string]: string;
};
obs?: {
[conceptUuid: string]: string;
};
}

export interface PatientUuidMapType {
Expand Down Expand Up @@ -225,7 +228,12 @@ export interface PersonAttributeResponse {
export interface ConceptResponse {
uuid: string;
display: string;
datatype: {
uuid: string;
display: string;
};
answers: Array<ConceptAnswers>;
setMembers: Array<ConceptAnswers>;
}

export interface ConceptAnswers {
Expand Down

0 comments on commit 5dc7035

Please sign in to comment.