Skip to content

Commit 5361b7f

Browse files
committed
Add generic ResourceList component
Ref: #6
1 parent 45acfbb commit 5361b7f

File tree

5 files changed

+446
-309
lines changed

5 files changed

+446
-309
lines changed

src/containers/App/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { menuLayout } from './layout';
88
import { PatientDetails } from '../PatientDetails';
99
import { EncounterPage } from '../PatientDetails/encounter';
1010
import { PatientResourceList } from '../PatientResourceList';
11+
import { ResourceList } from '../PatientDetails/ResourceList';
1112
import { SignIn } from '../SignIn';
1213

1314
export function App() {
@@ -29,6 +30,7 @@ export function App() {
2930
<Route path="/patients" element={<PatientResourceList />} />
3031
<Route path="/patients/:id/encounter/:encounter/*" element={<EncounterPage />} />
3132
<Route path="/patients/:id/*" element={<PatientDetails />} />
33+
<Route path="/patients/:id/resources/:resourceType/*" element={<ResourceList />} />
3234
</>
3335
}
3436
/>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Resource } from "fhir/r4b";
2+
import { useParams } from 'react-router-dom';
3+
4+
import { ResourceListPage } from '@beda.software/emr/dist/uberComponents/ResourceListPage/index';
5+
6+
import { AvailableResourceTypesStr } from '../types';
7+
import { getResourceConfigData } from '../utils';
8+
9+
10+
export function ResourceList() {
11+
const params = useParams();
12+
const resourceType = params?.resourceType as AvailableResourceTypesStr
13+
const patientId = params?.id || ''
14+
const { title, columns } = getResourceConfigData(resourceType, 'uberList')
15+
16+
return (
17+
<ResourceListPage<Resource>
18+
resourceType={resourceType}
19+
headerTitle={title}
20+
searchParams={{'patient': patientId}}
21+
getTableColumns={() => columns}
22+
/>
23+
);
24+
};
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { ColumnType } from 'antd/lib/table';
2+
import {
3+
AllergyIntolerance,
4+
Condition,
5+
Extension,
6+
Immunization,
7+
MedicationStatement,
8+
Observation,
9+
ObservationComponent,
10+
Procedure,
11+
RelatedPerson,
12+
MedicationRequest,
13+
Resource,
14+
Bundle
15+
} from 'fhir/r4b';
16+
import { extractExtension } from 'sdc-qrf';
17+
18+
import {
19+
formatHumanDate,
20+
formatHumanDateTime,
21+
formatPeriodDateTime,
22+
renderHumanName,
23+
} from '@beda.software/emr/dist/utils/index';
24+
25+
26+
type RecordType<R extends Resource> = { resource: R; bundle: Bundle };
27+
28+
29+
export function makeRenderer<T extends Resource, RCM extends 'uberList' | 'dashboard'>(
30+
fn: (r: T) => React.ReactNode,
31+
renderColumnMode: RCM,
32+
): typeof renderColumnMode extends 'uberList' ? ColumnType<RecordType<T>>['render'] : (resource: T) => React.ReactNode {
33+
return (renderColumnMode === 'uberList' ? (_text: any, { resource }: { resource: T }) => fn(resource) : (resource: T) => fn(resource)) as any; // @ts-ignore
34+
}
35+
36+
function getComponentValue(c: ObservationComponent) {
37+
if (c.dataAbsentReason) {
38+
return [c.dataAbsentReason.text];
39+
}
40+
return [`${c.valueQuantity?.value} ${c.valueQuantity?.unit}`];
41+
}
42+
43+
function getAbsentReason(extension?: Array<Extension>) {
44+
return extension?.find((e) => e.url === 'http://hl7.org/fhir/StructureDefinition/data-absent-reason');
45+
}
46+
47+
export const isUberList = (renderType: 'uberList' | 'dashboard') => renderType == 'uberList';
48+
export const allergyName = (r: AllergyIntolerance): string => r.code?.text ?? r.code?.coding?.[0]?.display ?? 'Unknown';
49+
export const allergyDate = (r: AllergyIntolerance): string => {
50+
const createdAt = extractExtension(r.meta?.extension, 'ex:createdAt');
51+
const date = r.recordedDate || createdAt || r.meta?.lastUpdated;
52+
53+
return date ? formatHumanDate(date) : 'Unknown';
54+
};
55+
export const conditionName = (r: Condition): string =>
56+
r.code?.text ?? r.code?.coding?.[0]?.display ?? r.code?.coding?.[0]?.code ?? 'unknown';
57+
export const conditionDate = (r: Condition): string => {
58+
const date = r.recordedDate || r.onsetDateTime;
59+
return date ? formatHumanDate(date) : 'Unknown';
60+
};
61+
export const observationName = (r: Observation): string => r.code.text ?? r.code.coding?.[0]?.display ?? 'Unknown';
62+
export const observationDate = (r: Observation): string => {
63+
const createdAt = extractExtension(r.meta?.extension, 'ex:createdAt');
64+
const date = r.effectiveDateTime || r.issued || createdAt;
65+
if (date) {
66+
return formatHumanDate(date);
67+
}
68+
69+
const masked = getAbsentReason(r._effectiveDateTime?.extension);
70+
return masked?.valueCode ?? 'Unknown';
71+
};
72+
export const observationValue = (r: Observation): string | React.ReactElement => {
73+
if (r.dataAbsentReason) {
74+
return r.dataAbsentReason.text ?? r.dataAbsentReason.coding?.[0]?.display ?? 'unknown';
75+
} else if (r.valueQuantity) {
76+
const masked = getAbsentReason(r.valueQuantity.extension);
77+
if (masked) {
78+
return masked.valueCode ?? 'Unknown';
79+
}
80+
return `${r.valueQuantity.value} ${r.valueQuantity.unit}`;
81+
} else if (r.valueCodeableConcept) {
82+
return r.valueCodeableConcept.text ?? r.valueCodeableConcept.coding?.[0]?.display ?? 'Unknown';
83+
} else if (r.component) {
84+
return (
85+
<>
86+
{r.component
87+
.map((c) => [...[c.code.coding?.[0]?.display], ...getComponentValue(c)].join(': '))
88+
.map((v) => (
89+
<div key={v}>{v}</div>
90+
))}
91+
</>
92+
);
93+
}
94+
return 'Unknown';
95+
};
96+
export const immunizationVaccine = (r: Immunization) => r.vaccineCode.text ?? 'Unknown';
97+
export const immunizationDate = (r: Immunization) =>
98+
r.occurrenceDateTime ? formatHumanDate(r.occurrenceDateTime) : 'Unknown';
99+
export const msMedication = (r: MedicationStatement) =>
100+
r.medicationCodeableConcept?.text ?? r.medicationCodeableConcept?.coding?.[0].display ?? 'Unknown';
101+
export const msDosage = (r: MedicationStatement) => {
102+
const dosageItem = r.dosage?.find((item) => item.text !== undefined);
103+
104+
return dosageItem?.text ?? 'Unknown';
105+
};
106+
export const msDate = (r: MedicationStatement) => (r.dateAsserted ? formatHumanDate(r.dateAsserted) : 'Unknown');
107+
export const procedureTitle = (r: Procedure) => r.code?.coding?.[0].display ?? r.code?.text ?? 'Unknown';
108+
export const procedureDate = (r: Procedure) => {
109+
if (r.performedPeriod) {
110+
return formatPeriodDateTime(r.performedPeriod) ?? 'Unknown';
111+
} else if (r.performedDateTime) {
112+
return formatHumanDateTime(r.performedDateTime) ?? 'Unknown';
113+
} else {
114+
return 'Unknown';
115+
}
116+
};
117+
export const rpName = (r: RelatedPerson) => renderHumanName(r.name?.[0]);
118+
export const rpRelationShip = (r: RelatedPerson) => r.relationship?.[0].coding?.[0].display ?? 'Unknown';
119+
export const mrName = (r: MedicationRequest) =>
120+
r.medicationCodeableConcept?.coding?.[0]?.display ?? r.medicationCodeableConcept?.text ?? 'Unknown';
121+
export const mrReason = (r: MedicationRequest) => r.reasonCode?.[0]?.coding?.[0]?.display ?? 'Unknown';
122+
export const mrDosage = (r: MedicationRequest) =>
123+
r.dosageInstruction?.[0]?.text ? r.dosageInstruction?.[0]?.text : '';
124+
export const mrStatus = (r: MedicationRequest) => r.status;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { ColumnsType } from 'antd/lib/table';
2+
import {
3+
Resource,
4+
Bundle,
5+
AllergyIntolerance,
6+
Condition,
7+
Observation,
8+
Immunization,
9+
MedicationStatement,
10+
MedicationRequest,
11+
Procedure,
12+
RelatedPerson,
13+
} from 'fhir/r4b';
14+
15+
export type AvailableResourceTypesStr =
16+
| 'AllergyIntolerance'
17+
| 'Condition'
18+
| 'Observation'
19+
| 'Immunization'
20+
| 'MedicationStatement'
21+
| 'MedicationRequest'
22+
| 'Procedure'
23+
| 'RelatedPerson';
24+
export type AvailableResourceTypes =
25+
| AllergyIntolerance
26+
| Condition
27+
| Observation
28+
| Immunization
29+
| MedicationStatement
30+
| MedicationRequest
31+
| Procedure
32+
| RelatedPerson;
33+
34+
type RecordType<R extends Resource> = { resource: R; bundle: Bundle };
35+
36+
export interface UberListRT<T extends Resource> {
37+
title: string;
38+
icon: JSX.Element;
39+
columns: ColumnsType<RecordType<T>>;
40+
}
41+
42+
export interface DashboardRT<T extends Resource> {
43+
title: string;
44+
icon: JSX.Element;
45+
columns: Array<{
46+
title: string;
47+
key: string;
48+
render: (resource: T) => React.ReactNode;
49+
width?: number;
50+
}>;
51+
}
52+
53+
export type MapResourceConfigType = {
54+
AllergyIntolerance: UberListRT<AllergyIntolerance> | DashboardRT<AllergyIntolerance>;
55+
Condition: UberListRT<Condition> | DashboardRT<Condition>;
56+
Observation: UberListRT<Observation> | DashboardRT<Observation>;
57+
Immunization: UberListRT<Immunization> | DashboardRT<Immunization>;
58+
MedicationStatement: UberListRT<MedicationStatement> | DashboardRT<MedicationStatement>;
59+
MedicationRequest: UberListRT<MedicationRequest> | DashboardRT<MedicationRequest>;
60+
Procedure: UberListRT<Procedure> | DashboardRT<Procedure>;
61+
RelatedPerson: UberListRT<RelatedPerson> | DashboardRT<RelatedPerson>;
62+
};

0 commit comments

Comments
 (0)