Skip to content

Commit

Permalink
feat: update patient demographics
Browse files Browse the repository at this point in the history
  • Loading branch information
mgramigna committed Dec 22, 2023
1 parent c1b7613 commit 93c123e
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 45 deletions.
41 changes: 38 additions & 3 deletions apps/expo/src/app/(app)/profile/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,17 @@ import { DemographicsInfo } from '@/components/organisms/DemographicsInfo';
import { usePatient } from '@/context/PatientContext';
import { getCodeFromConcept } from '@/fhirpath/utils';
import { palette } from '@/theme/colors';
import {
ETHNICITY_EXTENSION_URL,
GENDER_IDENTITY_EXTENSION_URL,
RACE_EXTENSION_URL,
} from '@/types/patient';
import { api } from '@/utils/api';
import { getEthnicityExtension, getGenderIdentityExtension, getRaceExtension } from '@/utils/fhir';
import { MaterialCommunityIcons } from '@expo/vector-icons';

import { type Extension } from '@careforge/canvas';

const About = () => {
const [goalModalOpen, setGoalModalOpen] = useState(false);
const [demographicsModalOpen, setDemographicsModalOpen] = useState(false);
Expand All @@ -41,9 +49,36 @@ const About = () => {
},
});

const handleDemographicsSave = useCallback((_form: DemographicsFormType) => {
Alert.alert('TODO: update patient resource');
}, []);
const handleDemographicsSave = useCallback(
(form: DemographicsFormType) => {
const raceExtension = getRaceExtension({ raceCodes: form.race });
const ethnicityExtension = getEthnicityExtension({ ethnicityCodes: form.ethnicity });
const genderIdentityExtension = getGenderIdentityExtension({
genderCode: form.genderIdentity,
});

const newPatientExtension: Extension[] = [
...(patient?.extension?.filter(
({ url }) =>
url !== RACE_EXTENSION_URL &&
url !== ETHNICITY_EXTENSION_URL &&
url !== GENDER_IDENTITY_EXTENSION_URL,
) ?? []),
...(raceExtension ? [raceExtension] : []),
...(ethnicityExtension ? [ethnicityExtension] : []),
...(genderIdentityExtension ? [genderIdentityExtension] : []),
];

updatePatientMutation.mutate({
id: patient!.id,
resource: {
...patient,
extension: newPatientExtension,
},
});
},
[patient, updatePatientMutation],
);

if (isLoading) {
return (
Expand Down
114 changes: 108 additions & 6 deletions apps/expo/src/components/organisms/DemographicsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import React from 'react';
import { View } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { getEthnicityCodes, getGenderIdentityCode, getRaceCodes } from '@/fhirpath/patient';
import {
ETHNICITY_DISPLAY_TO_CODE,
EthnicityCodeSchema,
ethnicityOptions,
GENDER_DISPLAY_TO_CODE,
GenderCodeSchema,
genderIdentityOptions,
RACE_DISPLAY_TO_CODE,
RaceCodeSchema,
raceOptions,
} from '@/types/patient';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import { z } from 'zod';

import { type Patient } from '@careforge/canvas';

import { Button } from '../atoms/Button';
import { Checkbox } from '../atoms/Checkbox';
import { RadioButton } from '../atoms/RadioButton';
import { Text } from '../atoms/Text';

const DemographicsFormSchema = z.object({});
const DemographicsFormSchema = z.object({
genderIdentity: GenderCodeSchema.optional(),
race: RaceCodeSchema.array().optional(),
ethnicity: EthnicityCodeSchema.array().optional(),
});

export type DemographicsFormType = z.infer<typeof DemographicsFormSchema>;

export const DemographicsForm = ({
patient: _patient,
patient,
onSubmit,
onCancel,
isMutating,
Expand All @@ -25,23 +43,107 @@ export const DemographicsForm = ({
onCancel: () => void;
isMutating?: boolean;
}) => {
const genderCode = getGenderIdentityCode(patient);
const raceCodes = getRaceCodes(patient);
const ethnicityCodes = getEthnicityCodes(patient);

const {
formState: { isValid },
handleSubmit,
control,
watch,
} = useForm<DemographicsFormType>({
resolver: zodResolver(DemographicsFormSchema),
defaultValues: {},
defaultValues: {
genderIdentity: genderCode,
race: raceCodes,
ethnicity: ethnicityCodes,
},
});

const currentGender = watch('genderIdentity');

return (
<KeyboardAwareScrollView showsVerticalScrollIndicator={false} className="h-full">
<Text className="text-3xl" weight="bold">
Edit Demographics
</Text>
<View className="mt-8 flex gap-8">
<Text>TODO</Text>
<Text className="text-xl">Gender</Text>
<View className="flex flex-row flex-wrap gap-4">
{genderIdentityOptions.map((option) => (
<Controller
key={option}
control={control}
name="genderIdentity"
render={({ field: { onChange } }) => (
<RadioButton
label={option}
selected={currentGender === GENDER_DISPLAY_TO_CODE[option]}
onPress={() => {
onChange(GENDER_DISPLAY_TO_CODE[option]);
}}
/>
)}
/>
))}
</View>
</View>
<View className="mt-8 flex gap-8">
<Text className="text-xl">Race</Text>
{raceOptions.map((option) => (
<Controller
key={option}
control={control}
name="race"
render={({ field: { onChange, value } }) => (
<Checkbox
label={option}
checked={value?.includes(RACE_DISPLAY_TO_CODE[option])}
onPress={() => {
const code = RACE_DISPLAY_TO_CODE[option];
if (value?.includes(code)) {
const newValue = value ? [...value] : [];
newValue.splice(newValue.indexOf(code), 1);

onChange(newValue);
} else {
onChange([...(value ?? []), code]);
}
}}
/>
)}
/>
))}
</View>
<View className="mt-8 flex gap-8">
<Text className="text-xl">Ethnicity</Text>
{ethnicityOptions.map((option) => (
<Controller
key={option}
control={control}
name="ethnicity"
render={({ field: { onChange, value } }) => (
<Checkbox
label={option}
checked={value?.includes(ETHNICITY_DISPLAY_TO_CODE[option])}
onPress={() => {
const code = ETHNICITY_DISPLAY_TO_CODE[option];
if (value?.includes(code)) {
const newValue = value ? [...value] : [];
newValue.splice(newValue.indexOf(code), 1);

onChange(newValue);
} else {
onChange([...(value ?? []), code]);
}
}}
/>
)}
/>
))}
</View>
<View className="mt-8 flex flex-row gap-8">
<View className="mt-8 flex flex-row gap-8 pb-24">
<View className="flex-1">
<Button text="Cancel" variant="secondary" onPress={onCancel} />
</View>
Expand Down
52 changes: 30 additions & 22 deletions apps/expo/src/components/organisms/DemographicsInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
import { Fragment } from 'react';
import { View } from 'react-native';
import { getEthnicities, getGenderIdentity, getRaces } from '@/fhirpath/patient';
import {
getEthnicityDisplays,
getGenderIdentityDisplay,
getRaceDisplays,
} from '@/fhirpath/patient';

import { type Patient } from '@careforge/canvas';

import { Chip } from '../atoms/Chip';
import { Text } from '../atoms/Text';

export const DemographicsInfo = ({ patient }: { patient: Patient }) => {
const races = getRaces(patient);
const ethnicities = getEthnicities(patient);
const genderIdentity = getGenderIdentity(patient);
const races = getRaceDisplays(patient);
const ethnicities = getEthnicityDisplays(patient);
const genderIdentity = getGenderIdentityDisplay(patient);

return (
<>
<View className="flex gap-8">
<View>
<Text className="mb-2 pl-1 text-2xl">Race</Text>
<View className="flex flex-row flex-wrap gap-2">
{races.map((race) => (
<Fragment key={race}>
<Chip text={race} />
</Fragment>
))}
{races.length > 0 && (
<View>
<Text className="mb-2 pl-1 text-2xl">Race</Text>
<View className="flex flex-row flex-wrap gap-2">
{races.map((race) => (
<Fragment key={race}>
<Chip text={race} />
</Fragment>
))}
</View>
</View>
</View>
<View>
<Text className="mb-2 pl-1 text-2xl">Ethnicity</Text>
<View className="flex flex-row flex-wrap gap-2">
{ethnicities.map((ethnicity) => (
<Fragment key={ethnicity}>
<Chip text={ethnicity} />
</Fragment>
))}
)}
{ethnicities.length > 0 && (
<View>
<Text className="mb-2 pl-1 text-2xl">Ethnicity</Text>
<View className="flex flex-row flex-wrap gap-2">
{ethnicities.map((ethnicity) => (
<Fragment key={ethnicity}>
<Chip text={ethnicity} />
</Fragment>
))}
</View>
</View>
</View>
)}
{genderIdentity && (
<View>
<Text className="mb-2 pl-1 text-2xl">Gender Identity</Text>
Expand Down
48 changes: 37 additions & 11 deletions apps/expo/src/fhirpath/patient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { GENDER_CODE_TO_DISPLAY, type GenderCode, type GenderIdentity } from '@/types/patient';
import {
ETHNICITY_EXTENSION_URL,
GENDER_CODE_TO_DISPLAY,
GENDER_IDENTITY_EXTENSION_URL,
RACE_EXTENSION_URL,
type EthnicityCode,
type GenderCode,
type GenderIdentity,
type RaceCode,
} from '@/types/patient';
import fhirpath from 'fhirpath';

import { type Patient } from '@careforge/canvas';
Expand Down Expand Up @@ -48,12 +57,7 @@ export function getPhone(patient: Patient): string | undefined {
return phone;
}

const RACE_EXTENSION_URL = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race';
const ETHNICITY_EXTENSION_URL = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity';
const GENDER_IDENTITY_EXTENSION_URL =
'http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity';

export function getRaces(patient: Patient): string[] {
export function getRaceDisplays(patient: Patient): string[] {
const races = fhirpath.evaluate(
patient,
`extension.where(url='${RACE_EXTENSION_URL}').extension.where(url='ombCategory').valueCoding.display`,
Expand All @@ -62,7 +66,7 @@ export function getRaces(patient: Patient): string[] {
return races;
}

export function getEthnicities(patient: Patient): string[] {
export function getEthnicityDisplays(patient: Patient): string[] {
const races = fhirpath.evaluate(
patient,
`extension.where(url='${ETHNICITY_EXTENSION_URL}').extension.where(url='ombCategory').valueCoding.display`,
Expand All @@ -71,13 +75,35 @@ export function getEthnicities(patient: Patient): string[] {
return races;
}

export function getGenderIdentity(patient: Patient): GenderIdentity | undefined {
export function getGenderIdentityCode(patient: Patient): GenderCode | undefined {
const [code] = fhirpath.evaluate(
patient,
`extension.where(url='${GENDER_IDENTITY_EXTENSION_URL}').valueCodeableConcept.coding.first().code`,
) as GenderCode[];

if (!code) return undefined;
return code;
}

export function getRaceCodes(patient: Patient): RaceCode[] {
const races = fhirpath.evaluate(
patient,
`extension.where(url='${RACE_EXTENSION_URL}').extension.where(url='ombCategory').valueCoding.code`,
) as RaceCode[];

return races;
}

export function getEthnicityCodes(patient: Patient): EthnicityCode[] {
const races = fhirpath.evaluate(
patient,
`extension.where(url='${ETHNICITY_EXTENSION_URL}').extension.where(url='ombCategory').valueCoding.code`,
) as EthnicityCode[];

return races;
}

export function getGenderIdentityDisplay(patient: Patient): GenderIdentity | undefined {
const code = getGenderIdentityCode(patient);

return GENDER_CODE_TO_DISPLAY[code];
return code ? GENDER_CODE_TO_DISPLAY[code] : undefined;
}
Loading

0 comments on commit 93c123e

Please sign in to comment.