@digihmis/esm-outpatient-app 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/371.js +1 -1
- package/dist/371.js.map +1 -1
- package/dist/409.js +1 -0
- package/dist/409.js.map +1 -0
- package/dist/594.js +1 -1
- package/dist/594.js.map +1 -1
- package/dist/digihmis-esm-outpatient-app.js +2 -2
- package/dist/digihmis-esm-outpatient-app.js.buildmanifest.json +33 -33
- package/dist/main.js +5 -5
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +7 -0
- package/src/dashboard.meta.ts +3 -1
- package/src/patient-management/clinical-encounters.component.tsx +16 -9
- package/src/patient-management/patient-management-workspace/patient-management-schema/index.ts +2 -2
- package/src/patient-management/patient-management-workspace/patient-management-workspace.tsx +3 -0
- package/src/patient-management/patient-management.component.tsx +43 -26
- package/src/patient-management/patient-management.scss +33 -5
- package/src/patient-management/results/investigation-results.component.tsx +210 -0
- package/src/patient-management/results/investigation-results.scss +75 -0
- package/src/patient-management/results/results.resource.ts +150 -0
- package/src/queue-room-table/queue-room-table.component.tsx +18 -7
- package/src/queue-room-table/queue-room-table.resource.ts +26 -8
- package/src/queue-room-table/queue-room-table.scss +17 -0
- package/src/queue-room-table/type/index.ts +2 -0
- package/src/routes.json +1 -1
- package/dist/752.js +0 -1
- package/dist/752.js.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
|
|
4
|
+
/** A single named observation captured against a lab order (concept → value). */
|
|
5
|
+
export interface LabResultValue {
|
|
6
|
+
concept?: { uuid: string; display: string };
|
|
7
|
+
value?: string | number | { uuid: string; display: string };
|
|
8
|
+
obsDatetime?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** One completed lab test order together with its result observations. */
|
|
12
|
+
export interface LabResultRecord {
|
|
13
|
+
orderUuid: string;
|
|
14
|
+
orderNumber?: string;
|
|
15
|
+
testName: string;
|
|
16
|
+
date?: string;
|
|
17
|
+
results: Array<LabResultValue>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A radiology/procedure study record (shared Procedure entity in the orders module). */
|
|
21
|
+
export interface ProcedureRecord {
|
|
22
|
+
uuid: string;
|
|
23
|
+
status?: string;
|
|
24
|
+
startDatetime?: string;
|
|
25
|
+
orderUuid?: string;
|
|
26
|
+
concept?: { uuid: string; display: string };
|
|
27
|
+
preliminaryReport?: string;
|
|
28
|
+
procedureReport?: string;
|
|
29
|
+
impressions?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const PROCEDURE_REP =
|
|
33
|
+
'custom:(uuid,status,startDatetime,orderUuid,concept:(uuid,display),preliminaryReport,procedureReport,impressions)';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* All radiology/procedure studies for a patient (both Imaging and Procedure — the caller splits them
|
|
37
|
+
* by concept-set membership). Newest first.
|
|
38
|
+
*/
|
|
39
|
+
export function useProcedureRecords(patientUuid: string) {
|
|
40
|
+
const url = patientUuid
|
|
41
|
+
? `${restBaseUrl}/digihmisorders/procedure?patient=${patientUuid}&v=${PROCEDURE_REP}`
|
|
42
|
+
: null;
|
|
43
|
+
const { data, error, isLoading, mutate } = useSWR<{ data: { results: Array<ProcedureRecord> } }>(url, openmrsFetch);
|
|
44
|
+
const procedures = (data?.data?.results ?? [])
|
|
45
|
+
.slice()
|
|
46
|
+
.sort((a, b) => (b.startDatetime ?? '').localeCompare(a.startDatetime ?? ''));
|
|
47
|
+
return { procedures, error, isLoading, mutate };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The flattened set of concept uuids belonging to a concept set (recursing up to three levels of
|
|
52
|
+
* nested sets — enough for the curated radiology set). Used to decide Imaging vs Procedure.
|
|
53
|
+
*/
|
|
54
|
+
export function useConceptSetMembers(setUuid: string): Set<string> {
|
|
55
|
+
const url = setUuid
|
|
56
|
+
? `${restBaseUrl}/concept/${setUuid}?v=custom:(setMembers:(uuid,setMembers:(uuid,setMembers:(uuid))))`
|
|
57
|
+
: null;
|
|
58
|
+
const { data } = useSWR<{ data: { setMembers: Array<NestedConcept> } }>(url, openmrsFetch);
|
|
59
|
+
const members = new Set<string>();
|
|
60
|
+
collectMembers(data?.data?.setMembers, members);
|
|
61
|
+
return members;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface NestedConcept {
|
|
65
|
+
uuid: string;
|
|
66
|
+
setMembers?: Array<NestedConcept>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function collectMembers(nodes: Array<NestedConcept> | undefined, acc: Set<string>): void {
|
|
70
|
+
for (const node of nodes ?? []) {
|
|
71
|
+
if (node?.uuid) {
|
|
72
|
+
acc.add(node.uuid);
|
|
73
|
+
}
|
|
74
|
+
if (node?.setMembers?.length) {
|
|
75
|
+
collectMembers(node.setMembers, acc);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The patient's completed lab test orders with their result observations. Fetches the patient's
|
|
82
|
+
* orders, keeps the completed Test Orders, then pulls each order's results from the labresult
|
|
83
|
+
* endpoint. Newest first.
|
|
84
|
+
*/
|
|
85
|
+
export function useLabResults(patientUuid: string) {
|
|
86
|
+
const ordersUrl = patientUuid
|
|
87
|
+
? `${restBaseUrl}/order?patient=${patientUuid}&v=custom:(uuid,orderNumber,dateActivated,fulfillerStatus,action,concept:(display),orderType:(uuid,display))`
|
|
88
|
+
: null;
|
|
89
|
+
|
|
90
|
+
const { data, error, isLoading, mutate } = useSWR<Array<LabResultRecord>>(
|
|
91
|
+
ordersUrl,
|
|
92
|
+
async (url: string) => {
|
|
93
|
+
const response = await openmrsFetch<{ results: Array<LabOrder> }>(url);
|
|
94
|
+
const orders = response?.data?.results ?? [];
|
|
95
|
+
const testOrders = orders.filter(
|
|
96
|
+
(order) =>
|
|
97
|
+
order?.orderType?.display?.toLowerCase().includes('test') &&
|
|
98
|
+
order?.fulfillerStatus === 'COMPLETED' &&
|
|
99
|
+
order?.action !== 'DISCONTINUE',
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const records = await Promise.all(
|
|
103
|
+
testOrders.map(async (order): Promise<LabResultRecord> => {
|
|
104
|
+
const base: LabResultRecord = {
|
|
105
|
+
orderUuid: order.uuid,
|
|
106
|
+
orderNumber: order.orderNumber,
|
|
107
|
+
testName: order?.concept?.display ?? order?.orderNumber ?? 'Test',
|
|
108
|
+
date: order.dateActivated,
|
|
109
|
+
results: [],
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
const res = await openmrsFetch<{ results: Array<LabResultValue> }>(
|
|
113
|
+
`${restBaseUrl}/digihmisorders/labresult/${order.uuid}`,
|
|
114
|
+
);
|
|
115
|
+
return { ...base, results: res?.data?.results ?? [] };
|
|
116
|
+
} catch {
|
|
117
|
+
// A single order's results failing must not blank the whole tab.
|
|
118
|
+
return base;
|
|
119
|
+
}
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return records.sort((a, b) => (b.date ?? '').localeCompare(a.date ?? ''));
|
|
124
|
+
},
|
|
125
|
+
{ revalidateOnFocus: false },
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return { labResults: data ?? [], error, isLoading, mutate };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface LabOrder {
|
|
132
|
+
uuid: string;
|
|
133
|
+
orderNumber?: string;
|
|
134
|
+
dateActivated?: string;
|
|
135
|
+
fulfillerStatus?: string;
|
|
136
|
+
action?: string;
|
|
137
|
+
concept?: { display: string };
|
|
138
|
+
orderType?: { uuid: string; display: string };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Render an obs value that may be a scalar or a coded {uuid,display}. */
|
|
142
|
+
export function formatLabValue(value: LabResultValue['value']): string {
|
|
143
|
+
if (value == null) {
|
|
144
|
+
return '—';
|
|
145
|
+
}
|
|
146
|
+
if (typeof value === 'object') {
|
|
147
|
+
return value.display ?? '—';
|
|
148
|
+
}
|
|
149
|
+
return String(value);
|
|
150
|
+
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { CheckmarkFilled, Time } from '@carbon/react/icons';
|
|
19
19
|
import { useTranslation } from 'react-i18next';
|
|
20
20
|
import { isDesktop, launchWorkspace, navigate, showSnackbar, useConfig, useLayoutType } from '@openmrs/esm-framework';
|
|
21
|
-
import { useQueueEntries } from './queue-room-table.resource';
|
|
21
|
+
import { useQueueEntries, isReturnedEntry } from './queue-room-table.resource';
|
|
22
22
|
import { useQueueActions } from './useQueueActions';
|
|
23
23
|
import styles from './queue-room-table.scss';
|
|
24
24
|
import { type QueueEntry } from './type';
|
|
@@ -204,8 +204,14 @@ export const QueueRoomTable: React.FC<QueueRoomTableProps> = ({ queueRoomUuid, q
|
|
|
204
204
|
</TableRow>
|
|
205
205
|
</TableHead>
|
|
206
206
|
<TableBody>
|
|
207
|
-
{rows.map((row) =>
|
|
208
|
-
|
|
207
|
+
{rows.map((row) => {
|
|
208
|
+
const rowEntry = row.cells.find((c) => c.info.header === 'entry')?.value as QueueEntry;
|
|
209
|
+
const returned = isReturnedEntry(rowEntry);
|
|
210
|
+
return (
|
|
211
|
+
<TableRow
|
|
212
|
+
key={row.id}
|
|
213
|
+
{...getRowProps({ row })}
|
|
214
|
+
className={returned ? styles.returnedRow : undefined}>
|
|
209
215
|
{row.cells.map((cell) => {
|
|
210
216
|
if (
|
|
211
217
|
['statusUuid', 'patientUuid', 'entryUuid', 'priorityColor', 'entry'].includes(cell.info.header)
|
|
@@ -226,9 +232,13 @@ export const QueueRoomTable: React.FC<QueueRoomTableProps> = ({ queueRoomUuid, q
|
|
|
226
232
|
if (cell.info.header === 'priority') {
|
|
227
233
|
return (
|
|
228
234
|
<TableCell key={cell.id}>
|
|
229
|
-
|
|
230
|
-
{
|
|
231
|
-
|
|
235
|
+
{returned ? (
|
|
236
|
+
<Tag type="teal">{t('returned', 'Returned')}</Tag>
|
|
237
|
+
) : (
|
|
238
|
+
<Tag type={row.cells.find((c) => c.info.header === 'priorityColor')?.value}>
|
|
239
|
+
{cell.value}
|
|
240
|
+
</Tag>
|
|
241
|
+
)}
|
|
232
242
|
</TableCell>
|
|
233
243
|
);
|
|
234
244
|
}
|
|
@@ -292,7 +302,8 @@ export const QueueRoomTable: React.FC<QueueRoomTableProps> = ({ queueRoomUuid, q
|
|
|
292
302
|
return <TableCell key={cell.id}>{cell.value}</TableCell>;
|
|
293
303
|
})}
|
|
294
304
|
</TableRow>
|
|
295
|
-
|
|
305
|
+
);
|
|
306
|
+
})}
|
|
296
307
|
</TableBody>
|
|
297
308
|
</Table>
|
|
298
309
|
</TableContainer>
|
|
@@ -2,9 +2,19 @@ import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework
|
|
|
2
2
|
import useSWR from 'swr';
|
|
3
3
|
import { type QueueEntry } from './type';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A "returned" entry is a patient sent back to the consultation queue after all their ordered
|
|
7
|
+
* investigations completed. The backend stamps such entries with a priorityComment of the form
|
|
8
|
+
* "All N investigations complete - Results ready for review" and a queueComingFrom pointing at the
|
|
9
|
+
* department they just finished. We surface these distinctly (own colour, ranked right after
|
|
10
|
+
* Emergency) so the clinician picks up results-ready patients first.
|
|
11
|
+
*/
|
|
12
|
+
export const isReturnedEntry = (entry: Pick<QueueEntry, 'priorityComment'>): boolean =>
|
|
13
|
+
!!entry?.priorityComment?.toLowerCase().includes('investigations complete');
|
|
14
|
+
|
|
5
15
|
export const useQueueEntries = (queueRoomUuid: string, queueUuid: string) => {
|
|
6
16
|
const customRepresentation =
|
|
7
|
-
'custom:(uuid,display,patient:(uuid,display,person:(display,age,gender)),priority:(uuid,display),status:(uuid,display),visit:(uuid),startedAt,sortWeight,queueRoom:(uuid,display),queueComingFrom:(uuid,display),queueRoomComingFrom:(uuid,display))';
|
|
17
|
+
'custom:(uuid,display,patient:(uuid,display,person:(display,age,gender)),priority:(uuid,display),status:(uuid,display),visit:(uuid),startedAt,sortWeight,priorityComment,queueRoom:(uuid,display),queueComingFrom:(uuid,display),queueRoomComingFrom:(uuid,display))';
|
|
8
18
|
|
|
9
19
|
const params = new URLSearchParams();
|
|
10
20
|
params.append('v', customRepresentation);
|
|
@@ -24,18 +34,26 @@ export const useQueueEntries = (queueRoomUuid: string, queueUuid: string) => {
|
|
|
24
34
|
);
|
|
25
35
|
|
|
26
36
|
const sortedEntries = data?.data?.results?.sort((a, b) => {
|
|
37
|
+
// Emergency first, then results-ready "Returned" patients, then the usual priorities.
|
|
27
38
|
const priorityOrder: Record<string, number> = {
|
|
28
39
|
emergency: 0,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
returned: 1,
|
|
41
|
+
urgent: 2,
|
|
42
|
+
priority: 3,
|
|
43
|
+
normal: 4,
|
|
32
44
|
};
|
|
33
45
|
|
|
34
|
-
const
|
|
35
|
-
|
|
46
|
+
const rank = (entry: QueueEntry) => {
|
|
47
|
+
const p = entry.priority?.display?.toLowerCase() || 'normal';
|
|
48
|
+
// A returned patient outranks everything except a true emergency.
|
|
49
|
+
if (isReturnedEntry(entry) && p !== 'emergency') {
|
|
50
|
+
return priorityOrder.returned;
|
|
51
|
+
}
|
|
52
|
+
return priorityOrder[p] ?? priorityOrder.normal;
|
|
53
|
+
};
|
|
36
54
|
|
|
37
|
-
const aOrder =
|
|
38
|
-
const bOrder =
|
|
55
|
+
const aOrder = rank(a);
|
|
56
|
+
const bOrder = rank(b);
|
|
39
57
|
|
|
40
58
|
if (aOrder !== bOrder) {
|
|
41
59
|
return aOrder - bOrder;
|
|
@@ -139,6 +139,23 @@
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/* Results-ready "Returned" patients: a distinct teal wash + left accent so they stand out from
|
|
143
|
+
ordinary waiting rows, even under Carbon zebra striping. Ranked right after Emergency. */
|
|
144
|
+
.returnedRow {
|
|
145
|
+
&,
|
|
146
|
+
td {
|
|
147
|
+
background-color: colors.$teal-10 !important;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
td:first-child {
|
|
151
|
+
box-shadow: inset 3px 0 0 0 colors.$teal-50;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
&:hover td {
|
|
155
|
+
background-color: colors.$teal-20 !important;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
142
159
|
.longWait {
|
|
143
160
|
display: flex;
|
|
144
161
|
align-items: center;
|
package/src/routes.json
CHANGED
package/dist/752.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(globalThis.webpackChunk_digihmis_esm_outpatient_app=globalThis.webpackChunk_digihmis_esm_outpatient_app||[]).push([["752"],{79214(e){e.exports=JSON.parse('{"actions":"Actions","age":"Age","avgWaitTime":"Avg. Wait Time","carePanel":"Care panel","changeStation":"Change Station","clockedInTo":"Clocked in to","clockedOutFromCurrent":"Clocked out from current station","clockIn":"Clock In","clockingIn":"Clocking in","clockingOut":"Clocking out","clockInPrompt":"Click the \\"Clock In\\" button above to start managing patients","clockOut":"Clock Out","clockOutError":"Clock Out Failed","clockOutFailed":"Failed to clock out","clockOutSuccess":"Clock Out Successful","clockOutSuccessMessage":"You have been clocked out successfully","comingFrom":"Coming From","completedToday":"Completed Today","direct":"Direct Entry","error":"Error","female":"Female","filterPatients":"Filter patients","gender":"Gender","loading":"Loading...","loadingQueue":"Loading queue...","loadingServices":"Loading services...","male":"Male","myQueueInservice":"My Queue - In Service","myQueueWaiting":"My Queue - Waiting","noActiveSession":"No active session found","noBillableServices":"No billable services","noBillableServicesDescription":"Please configure billable services to continue.","noDiagnosisFound":"No diagnosis codes found. Try different keywords.","noPatients":"No patients in queue","noPatientsDescription":"There are currently no patients waiting in this queue room.","normal":"Normal","outpatient":"Outpatient","outpatientQueue":"Outpatient Queue","patient":"Patient","patientServed":"Patient Served","patientServedMessage":"","pleaseClockIn":"Please clock in to view the queue","priority":"Priority","searchingDiagnosis":"Searching...","servePatient":"Serve Patient","servePatientError":"Failed to serve patient. Please try again.","servePatientLoading":"Serving Patient...","since":"since","startConsultation":"Start Consultation","status":"Status","switchingStation":"Switching Station","switchStationError":"Switch Station Failed","switchStationFailed":"Failed to switch station","typeMoreCharacters":"Type at least 3 characters to search","waitTime":"Wait Time"}')}}]);
|
package/dist/752.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"752.js","sources":["webpack://@digihmis/esm-outpatient-app/json|/home/kios09/Desktop/DigiHMIS/backend/DigiHMIS-apps/packages/esm-outpatient-app/translations/vi.json"],"names":["JSON"],"mappings":"mJAAA,EAAO,OAAO,CAAGA,KAAK,KAAK,CAAC,s9D"}
|