@digihmis/esm-outpatient-app 1.0.0
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/README.md +4 -0
- package/dist/117.js +1 -0
- package/dist/117.js.map +1 -0
- package/dist/132.js +1 -0
- package/dist/132.js.map +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/209.js +1 -0
- package/dist/209.js.map +1 -0
- package/dist/317.js +1 -0
- package/dist/317.js.map +1 -0
- package/dist/349.js +1 -0
- package/dist/349.js.map +1 -0
- package/dist/371.js +1 -0
- package/dist/371.js.map +1 -0
- package/dist/378.js +1 -0
- package/dist/378.js.map +1 -0
- package/dist/395.js +12 -0
- package/dist/395.js.map +1 -0
- package/dist/442.js +1 -0
- package/dist/442.js.map +1 -0
- package/dist/45.js +1 -0
- package/dist/45.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/508.js +1 -0
- package/dist/508.js.map +1 -0
- package/dist/564.js +1 -0
- package/dist/564.js.map +1 -0
- package/dist/61.js +1 -0
- package/dist/61.js.map +1 -0
- package/dist/640.js +1 -0
- package/dist/640.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/709.js +1 -0
- package/dist/709.js.map +1 -0
- package/dist/712.js +1 -0
- package/dist/712.js.map +1 -0
- package/dist/720.js +1 -0
- package/dist/720.js.map +1 -0
- package/dist/742.js +1 -0
- package/dist/742.js.map +1 -0
- package/dist/759.js +15 -0
- package/dist/759.js.map +1 -0
- package/dist/771.js +1 -0
- package/dist/771.js.map +1 -0
- package/dist/797.js +1 -0
- package/dist/797.js.map +1 -0
- package/dist/806.js +1 -0
- package/dist/806.js.map +1 -0
- package/dist/912.js +1 -0
- package/dist/912.js.map +1 -0
- package/dist/913.js +1 -0
- package/dist/913.js.map +1 -0
- package/dist/940.js +1 -0
- package/dist/940.js.map +1 -0
- package/dist/974.js +1 -0
- package/dist/974.js.map +1 -0
- package/dist/digihmis-esm-outpatient-app.js +6 -0
- package/dist/digihmis-esm-outpatient-app.js.buildmanifest.json +827 -0
- package/dist/digihmis-esm-outpatient-app.js.map +1 -0
- package/dist/main.js +17 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/rspack.config.js +1 -0
- package/src/config-schema.ts +347 -0
- package/src/dashboard.meta.ts +16 -0
- package/src/declarations.d.ts +6 -0
- package/src/icon-wrapper/icon-wrapper.extension.tsx +17 -0
- package/src/index.ts +27 -0
- package/src/left-panel/left-panel.component.tsx +100 -0
- package/src/left-panel/left-panel.scss +13 -0
- package/src/main-dashboard/outpatient-dashboard.component.tsx +264 -0
- package/src/main-dashboard/outpatient-dashboard.resource.ts +71 -0
- package/src/main-dashboard/outpatient-dashboard.scss +166 -0
- package/src/main-dashboard/type/index.ts +162 -0
- package/src/page-header/page-header.component.tsx +35 -0
- package/src/page-header/page-header.scss +76 -0
- package/src/patient-management/components/auto-suggest/autosuggest.component.tsx +149 -0
- package/src/patient-management/components/auto-suggest/autosuggest.scss +61 -0
- package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.component.tsx +131 -0
- package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.scss +42 -0
- package/src/patient-management/patient-banner/patient-banner.component.tsx +60 -0
- package/src/patient-management/patient-banner/patient-banner.scss +72 -0
- package/src/patient-management/patient-management-workspace/billable-services-selector.component.tsx +150 -0
- package/src/patient-management/patient-management-workspace/patient-management-schema/index.ts +261 -0
- package/src/patient-management/patient-management-workspace/patient-management-workspace.scss +304 -0
- package/src/patient-management/patient-management-workspace/patient-management-workspace.tsx +1581 -0
- package/src/patient-management/patient-management-workspace/patient-management.resource.ts +182 -0
- package/src/patient-management/patient-management-workspace/type/index.ts +193 -0
- package/src/patient-management/patient-management-workspace/utils/index.ts +172 -0
- package/src/patient-management/patient-management.component.tsx +97 -0
- package/src/patient-management/patient-management.scss +30 -0
- package/src/patient-management/utils/index.ts +5 -0
- package/src/queue-room-table/queue-room-table.component.tsx +313 -0
- package/src/queue-room-table/queue-room-table.resource.ts +73 -0
- package/src/queue-room-table/queue-room-table.scss +193 -0
- package/src/queue-room-table/type/index.ts +34 -0
- package/src/queue-room-table/useQueueActions.ts +24 -0
- package/src/routes.json +55 -0
- package/src/setup-tests.ts +1 -0
- package/translations/am.json +57 -0
- package/translations/ar.json +57 -0
- package/translations/en.json +57 -0
- package/translations/es.json +57 -0
- package/translations/fr.json +57 -0
- package/translations/he.json +57 -0
- package/translations/km.json +57 -0
- package/translations/pt.json +57 -0
- package/translations/sw.json +57 -0
- package/translations/vi.json +57 -0
- package/translations/zh.json +57 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { FetchResponse, openmrsFetch, restBaseUrl, showSnackbar, useConfig, useVisit } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { useCallback, useMemo } from 'react';
|
|
4
|
+
import { Patient } from '../../main-dashboard/type';
|
|
5
|
+
import {
|
|
6
|
+
BillableItem,
|
|
7
|
+
BillableItemsResponse,
|
|
8
|
+
ConceptAnswers,
|
|
9
|
+
ConceptResponse,
|
|
10
|
+
DiagnosisResponse,
|
|
11
|
+
PaymentInfo,
|
|
12
|
+
TransformedBillableItem,
|
|
13
|
+
} from './type';
|
|
14
|
+
import { ConfigObject } from '../../config-schema';
|
|
15
|
+
import { useCurrencyFormatting } from './utils';
|
|
16
|
+
import useSWRImmutable from 'swr/immutable';
|
|
17
|
+
|
|
18
|
+
export const usePerson = (uuid: string) => {
|
|
19
|
+
const customRepresentation = `custom:(uuid,display,gender,birthdate,dead,age,deathDate,causeOfDeath:(uuid,display),identifiers:(uuid,identifier,identifierType:(uuid,display),preferred),person:(uuid`;
|
|
20
|
+
const url = `${restBaseUrl}/person/${uuid}?v=${customRepresentation}`;
|
|
21
|
+
const { isLoading, error, data } = useSWR<FetchResponse<Patient['person']>>(url, openmrsFetch);
|
|
22
|
+
const person = data?.data;
|
|
23
|
+
return { isLoading, error, person };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const useDiagnosis = (searchQuery: string) => {
|
|
27
|
+
const customRepresentation = `custom:(uuid,display)`;
|
|
28
|
+
const ICD11_SOURCE_UUID = '39ADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD';
|
|
29
|
+
const url = `${restBaseUrl}/concept?v=${customRepresentation}&q=${searchQuery}&source=${ICD11_SOURCE_UUID}`;
|
|
30
|
+
|
|
31
|
+
const { isLoading, error, data } = useSWR<FetchResponse<DiagnosisResponse>>(
|
|
32
|
+
searchQuery && searchQuery.length >= 3 ? url : null,
|
|
33
|
+
openmrsFetch,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const diagnoses = data?.data?.results;
|
|
37
|
+
return { isLoading, error, diagnoses };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const usePatientPaymentType = (patientUuid: string): PaymentInfo => {
|
|
41
|
+
const { activeVisit } = useVisit(patientUuid);
|
|
42
|
+
const { visitAttributeTypes, insurancePaymentMethod } = useConfig<ConfigObject>();
|
|
43
|
+
|
|
44
|
+
const paymentInfo = useMemo<PaymentInfo>(() => {
|
|
45
|
+
if (!activeVisit?.attributes) {
|
|
46
|
+
return {
|
|
47
|
+
paymentType: null,
|
|
48
|
+
isInsurance: false,
|
|
49
|
+
isCash: false,
|
|
50
|
+
insuranceScheme: null,
|
|
51
|
+
policyNumber: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const paymentMethodAttr = activeVisit?.attributes.find(
|
|
56
|
+
(attr) => attr?.attributeType?.uuid === visitAttributeTypes?.paymentMethods,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const insuranceSchemeAttr = activeVisit?.attributes.find(
|
|
60
|
+
(attr) => attr?.attributeType?.uuid === visitAttributeTypes?.insuranceScheme,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const policyNumberAttr = activeVisit?.attributes.find(
|
|
64
|
+
(attr) => attr?.attributeType?.uuid === visitAttributeTypes?.policyNumber,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (!paymentMethodAttr) {
|
|
68
|
+
return {
|
|
69
|
+
paymentType: null,
|
|
70
|
+
isInsurance: false,
|
|
71
|
+
isCash: false,
|
|
72
|
+
insuranceScheme: null,
|
|
73
|
+
policyNumber: null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const paymentMethodValue =
|
|
78
|
+
typeof paymentMethodAttr?.value === 'object' ? paymentMethodAttr?.value?.uuid : paymentMethodAttr?.value;
|
|
79
|
+
|
|
80
|
+
const isInsurance = paymentMethodValue === insurancePaymentMethod;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
paymentType: isInsurance ? ('insurance' as const) : ('cash' as const),
|
|
84
|
+
isInsurance,
|
|
85
|
+
isCash: !isInsurance,
|
|
86
|
+
insuranceScheme: insuranceSchemeAttr?.value || null,
|
|
87
|
+
policyNumber: policyNumberAttr?.value || null,
|
|
88
|
+
};
|
|
89
|
+
}, [activeVisit, visitAttributeTypes, insurancePaymentMethod]);
|
|
90
|
+
|
|
91
|
+
return paymentInfo;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const useBillableServicesByType = (serviceTypeUuid: string) => {
|
|
95
|
+
const customRepresentation =
|
|
96
|
+
'custom:(uuid,name,shortName,serviceStatus,serviceType:(uuid,display),servicePrices:(uuid,name,price,paymentMode:(uuid,name,description)),concept:(uuid))';
|
|
97
|
+
|
|
98
|
+
const { data, error, isLoading, mutate } = useSWR<{ data: BillableItemsResponse }, Error>(
|
|
99
|
+
serviceTypeUuid ? `${restBaseUrl}/cashier/billableService?v=${customRepresentation}` : null,
|
|
100
|
+
openmrsFetch,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const lineItems = useMemo<BillableItem[]>(() => {
|
|
104
|
+
if (!data?.data?.results || !serviceTypeUuid) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return data.data.results.filter((item) => item.serviceType?.uuid === serviceTypeUuid);
|
|
109
|
+
}, [data, serviceTypeUuid]);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
lineItems,
|
|
113
|
+
isLoading,
|
|
114
|
+
error,
|
|
115
|
+
mutate,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const useBillableServicesWithPricing = (serviceTypeUuid: string) => {
|
|
120
|
+
const { formatSimple } = useCurrencyFormatting();
|
|
121
|
+
const { lineItems, isLoading, error, mutate } = useBillableServicesByType(serviceTypeUuid);
|
|
122
|
+
|
|
123
|
+
const transformItems = useCallback(
|
|
124
|
+
(items: BillableItem[], isInsurance: boolean): TransformedBillableItem[] => {
|
|
125
|
+
if (!items || items.length === 0) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return items.map((item) => {
|
|
130
|
+
const priceForPaymentMode = isInsurance
|
|
131
|
+
? item.servicePrices?.find((p) => p.paymentMode?.name?.toLowerCase().includes('insurance'))
|
|
132
|
+
: item.servicePrices?.find((p) => p.paymentMode?.name?.toLowerCase().includes('cash'));
|
|
133
|
+
|
|
134
|
+
const finalPrice = priceForPaymentMode || item?.servicePrices?.[0];
|
|
135
|
+
const price = finalPrice ? parseFloat(finalPrice.price.toString()) : 0;
|
|
136
|
+
const conceptUuid = item?.concept?.uuid || item?.uuid;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
id: conceptUuid,
|
|
140
|
+
text: `${item.name} - ${formatSimple(price, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`,
|
|
141
|
+
name: item.name,
|
|
142
|
+
price: price,
|
|
143
|
+
paymentModeUuid: finalPrice?.paymentMode?.uuid || '',
|
|
144
|
+
paymentModeName: finalPrice?.paymentMode?.name || 'Cash',
|
|
145
|
+
billableServiceUuid: item.uuid,
|
|
146
|
+
conceptUuid: conceptUuid,
|
|
147
|
+
priceUuid: finalPrice?.uuid || '',
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
[formatSimple],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
lineItems,
|
|
156
|
+
isLoading,
|
|
157
|
+
error,
|
|
158
|
+
mutate,
|
|
159
|
+
transformItems,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export function useConceptAnswers(conceptUuid: string): {
|
|
164
|
+
data: Array<ConceptAnswers>;
|
|
165
|
+
isLoading: boolean;
|
|
166
|
+
error: Error;
|
|
167
|
+
} {
|
|
168
|
+
const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== '';
|
|
169
|
+
const { data, error, isLoading } = useSWRImmutable<FetchResponse<ConceptResponse>, Error>(
|
|
170
|
+
shouldFetch ? `${restBaseUrl}/concept/${conceptUuid}` : null,
|
|
171
|
+
openmrsFetch,
|
|
172
|
+
);
|
|
173
|
+
if (error) {
|
|
174
|
+
showSnackbar({
|
|
175
|
+
title: error.name,
|
|
176
|
+
subtitle: error.message,
|
|
177
|
+
kind: 'error',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const results = useMemo(() => ({ data: data?.data?.answers, isLoading, error }), [isLoading, error, data]);
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { FetchResponse } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export interface DiagnosisResponse {
|
|
4
|
+
results: Array<{ uuid: string; display: string }>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface DiagnosisItem {
|
|
8
|
+
id: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ConceptAnswers {
|
|
13
|
+
display: string;
|
|
14
|
+
uuid: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ConceptResponse {
|
|
18
|
+
uuid: string;
|
|
19
|
+
display: string;
|
|
20
|
+
datatype: {
|
|
21
|
+
uuid: string;
|
|
22
|
+
display: string;
|
|
23
|
+
};
|
|
24
|
+
answers: Array<ConceptAnswers>;
|
|
25
|
+
setMembers: Array<ConceptAnswers>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface OpenmrsObject {
|
|
29
|
+
uuid: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type BaseOpenmrsObject = OpenmrsObject;
|
|
33
|
+
|
|
34
|
+
export interface SessionPriviledge {
|
|
35
|
+
uuid: string;
|
|
36
|
+
name: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Person {
|
|
40
|
+
uuid: string;
|
|
41
|
+
display: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Role {
|
|
45
|
+
role: string;
|
|
46
|
+
display: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface User {
|
|
50
|
+
uuid: string;
|
|
51
|
+
display: string;
|
|
52
|
+
givenName: string;
|
|
53
|
+
familyName: string;
|
|
54
|
+
firstName: string;
|
|
55
|
+
lastName: string;
|
|
56
|
+
person?: Person;
|
|
57
|
+
roles?: Array<Role>;
|
|
58
|
+
privileges: Array<SessionPriviledge>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Auditable extends OpenmrsObject {
|
|
62
|
+
creator: User;
|
|
63
|
+
dateCreated: Date;
|
|
64
|
+
changedBy: User;
|
|
65
|
+
dateChanged: Date;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Retireable extends OpenmrsObject {
|
|
69
|
+
retired: boolean;
|
|
70
|
+
dateRetired: Date;
|
|
71
|
+
retiredBy: User;
|
|
72
|
+
retireReason: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ConceptName extends BaseOpenmrsObject {
|
|
76
|
+
conceptNameId: number;
|
|
77
|
+
concept: Concept;
|
|
78
|
+
name: string;
|
|
79
|
+
localePreferred: boolean;
|
|
80
|
+
short: boolean;
|
|
81
|
+
preferred: boolean;
|
|
82
|
+
indexTerm: boolean;
|
|
83
|
+
synonym: boolean;
|
|
84
|
+
fullySpecifiedName: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface Concept extends BaseOpenmrsObject, Auditable, Retireable {
|
|
88
|
+
conceptId: number;
|
|
89
|
+
display: string;
|
|
90
|
+
set: boolean;
|
|
91
|
+
version: string;
|
|
92
|
+
names: Array<ConceptName>;
|
|
93
|
+
name: ConceptName;
|
|
94
|
+
numeric: boolean;
|
|
95
|
+
complex: boolean;
|
|
96
|
+
shortNames: Array<ConceptName>;
|
|
97
|
+
indexTerms: Array<ConceptName>;
|
|
98
|
+
synonyms: Array<ConceptName>;
|
|
99
|
+
setMembers: Array<Concept>;
|
|
100
|
+
possibleValues: Array<Concept>;
|
|
101
|
+
preferredName: ConceptName;
|
|
102
|
+
shortName: ConceptName;
|
|
103
|
+
fullySpecifiedName: ConceptName;
|
|
104
|
+
answers: Array<Concept>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type ConceptResult = FetchResponse<Concept>;
|
|
108
|
+
export type ConceptResults = FetchResponse<{ setMembers: Array<Concept> }>;
|
|
109
|
+
|
|
110
|
+
export interface ProceduresType {
|
|
111
|
+
label: string;
|
|
112
|
+
conceptUuid: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface UseProceduresType {
|
|
116
|
+
testTypes: Array<ProceduresType>;
|
|
117
|
+
isLoading: boolean;
|
|
118
|
+
error: Error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface PaymentMode {
|
|
122
|
+
uuid: string;
|
|
123
|
+
name: string;
|
|
124
|
+
description: string;
|
|
125
|
+
retired: boolean;
|
|
126
|
+
retireReason: string | null;
|
|
127
|
+
attributeTypes: Array<any>;
|
|
128
|
+
sortOrder: number | null;
|
|
129
|
+
resourceVersion: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ServicePrice {
|
|
133
|
+
uuid: string;
|
|
134
|
+
name: string;
|
|
135
|
+
price: number;
|
|
136
|
+
paymentMode: PaymentMode;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ServiceType {
|
|
140
|
+
uuid: string;
|
|
141
|
+
display: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface BillableItem {
|
|
145
|
+
uuid: string;
|
|
146
|
+
name: string;
|
|
147
|
+
shortName: string;
|
|
148
|
+
serviceStatus: string;
|
|
149
|
+
serviceType: ServiceType;
|
|
150
|
+
servicePrices: Array<ServicePrice>;
|
|
151
|
+
concept: {
|
|
152
|
+
uuid: string;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface BillableItemsResponse {
|
|
157
|
+
results: Array<BillableItem>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface ProcedureWithBillingDetails {
|
|
161
|
+
id: string;
|
|
162
|
+
text: string;
|
|
163
|
+
name: string;
|
|
164
|
+
price: number;
|
|
165
|
+
paymentModeUuid: string;
|
|
166
|
+
paymentModeName: string;
|
|
167
|
+
billableServiceUuid: string;
|
|
168
|
+
conceptUuid: string;
|
|
169
|
+
priceUuid: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type TransformedBillableItem = ProcedureWithBillingDetails;
|
|
173
|
+
|
|
174
|
+
export interface PaymentInfo {
|
|
175
|
+
paymentType: 'insurance' | 'cash' | null;
|
|
176
|
+
isInsurance: boolean;
|
|
177
|
+
isCash: boolean;
|
|
178
|
+
insuranceScheme: string | null;
|
|
179
|
+
policyNumber: string | null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface GenericItem {
|
|
183
|
+
id: string;
|
|
184
|
+
text: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface BillableServicesHookResult {
|
|
188
|
+
lineItems: Array<BillableItem>;
|
|
189
|
+
isLoading: boolean;
|
|
190
|
+
error: Error | undefined;
|
|
191
|
+
mutate: () => void;
|
|
192
|
+
transformItems: (items: Array<BillableItem>, isInsurance: boolean) => Array<TransformedBillableItem>;
|
|
193
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
2
|
+
import { ConfigObject } from '../../../config-schema';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_LOCALE_CURRENCY_MAP: Record<string, string> = {
|
|
5
|
+
en: 'KSH', // English - US Dollar
|
|
6
|
+
sw: 'KES', // Swahili - Kenyan Shilling
|
|
7
|
+
am: 'ETB', // Amharic - Ethiopian Birr
|
|
8
|
+
ar: 'SAR', // Arabic - Saudi Riyal
|
|
9
|
+
es: 'EUR', // Spanish - Euro
|
|
10
|
+
fr: 'EUR', // French - Euro
|
|
11
|
+
he: 'ILS', // Hebrew - Israeli Shekel
|
|
12
|
+
km: 'KHR', // Khmer - Cambodian Riel
|
|
13
|
+
pt: 'EUR', // Portuguese - Euro
|
|
14
|
+
vi: 'VND', // Vietnamese - Vietnamese Dong
|
|
15
|
+
zh: 'CNY', // Chinese - Chinese Yuan
|
|
16
|
+
'en-KE': 'KES', // English Kenya
|
|
17
|
+
'sw-KE': 'KES', // Swahili Kenya
|
|
18
|
+
'am-ET': 'ETB', // Amharic Ethiopia
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gets the currency code for the current locale
|
|
23
|
+
* @returns The currency code (e.g., 'KES', 'ETB', 'USD')
|
|
24
|
+
*/
|
|
25
|
+
export const getCurrencyForLocale = (): string => {
|
|
26
|
+
const currentLocale = localStorage.getItem('i18nextLng') ?? 'en';
|
|
27
|
+
|
|
28
|
+
// Try exact match first
|
|
29
|
+
if (DEFAULT_LOCALE_CURRENCY_MAP[currentLocale]) {
|
|
30
|
+
return DEFAULT_LOCALE_CURRENCY_MAP[currentLocale];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Try base language (e.g., 'en-US' → 'en')
|
|
34
|
+
const baseLanguage = currentLocale.split('-')[0];
|
|
35
|
+
if (DEFAULT_LOCALE_CURRENCY_MAP[baseLanguage]) {
|
|
36
|
+
return DEFAULT_LOCALE_CURRENCY_MAP[baseLanguage];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Final fallback
|
|
40
|
+
return 'USD';
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gets the currency code for the current locale with config support
|
|
45
|
+
* @param config - The billing configuration object
|
|
46
|
+
* @returns The currency code (e.g., 'KES', 'ETB', 'USD')
|
|
47
|
+
*/
|
|
48
|
+
export const getCurrencyForLocaleWithConfig = (config?: ConfigObject): string => {
|
|
49
|
+
const currentLocale = localStorage.getItem('i18nextLng') ?? 'en';
|
|
50
|
+
|
|
51
|
+
// Use config mapping if available, otherwise fall back to default
|
|
52
|
+
const currencyMap = config?.localeCurrencyMapping || DEFAULT_LOCALE_CURRENCY_MAP;
|
|
53
|
+
|
|
54
|
+
// Try exact match first
|
|
55
|
+
if (currencyMap[currentLocale]) {
|
|
56
|
+
return currencyMap[currentLocale];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Try base language (e.g., 'en-US' → 'en')
|
|
60
|
+
const baseLanguage = currentLocale.split('-')[0];
|
|
61
|
+
if (currencyMap[baseLanguage]) {
|
|
62
|
+
return currencyMap[baseLanguage];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Final fallback
|
|
66
|
+
return 'USD';
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets the current locale from localStorage
|
|
71
|
+
* @returns The current locale (e.g., 'en', 'sw', 'am')
|
|
72
|
+
*/
|
|
73
|
+
export const getCurrentLocale = (): string => {
|
|
74
|
+
return localStorage.getItem('i18nextLng') ?? 'en';
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Formats a number as currency based on the current locale
|
|
79
|
+
* @param amount - The amount to format
|
|
80
|
+
* @param options - Additional Intl.NumberFormat options
|
|
81
|
+
* @returns Formatted currency string
|
|
82
|
+
*/
|
|
83
|
+
export const formatCurrency = (amount: number, options: Intl.NumberFormatOptions = {}): string => {
|
|
84
|
+
const currentLocale = getCurrentLocale();
|
|
85
|
+
const currency = getCurrencyForLocale();
|
|
86
|
+
|
|
87
|
+
const formatter = new Intl.NumberFormat(currentLocale, {
|
|
88
|
+
style: 'currency',
|
|
89
|
+
currency: currency,
|
|
90
|
+
minimumFractionDigits: 2,
|
|
91
|
+
...options,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let formattedAmount = formatter.format(Math.abs(amount));
|
|
95
|
+
|
|
96
|
+
if (amount < 0) {
|
|
97
|
+
formattedAmount = `- ${formattedAmount}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return formattedAmount;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Formats a number as currency without negative sign handling
|
|
105
|
+
* @param amount - The amount to format
|
|
106
|
+
* @param options - Additional Intl.NumberFormat options
|
|
107
|
+
* @returns Formatted currency string
|
|
108
|
+
*/
|
|
109
|
+
export const formatCurrencySimple = (amount: number, options: Intl.NumberFormatOptions = {}): string => {
|
|
110
|
+
const currentLocale = getCurrentLocale();
|
|
111
|
+
const currency = getCurrencyForLocale();
|
|
112
|
+
|
|
113
|
+
const formatter = new Intl.NumberFormat(currentLocale, {
|
|
114
|
+
style: 'currency',
|
|
115
|
+
currency: currency,
|
|
116
|
+
...options,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return formatter.format(amount);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Hook to get currency formatting with config support
|
|
124
|
+
* @returns Object with currency formatting functions
|
|
125
|
+
*/
|
|
126
|
+
export const useCurrencyFormatting = () => {
|
|
127
|
+
const config = useConfig<ConfigObject>();
|
|
128
|
+
|
|
129
|
+
const getCurrency = () => getCurrencyForLocaleWithConfig(config);
|
|
130
|
+
|
|
131
|
+
const getLocale = () => getCurrentLocale();
|
|
132
|
+
|
|
133
|
+
const format = (amount: number, options: Intl.NumberFormatOptions = {}) => {
|
|
134
|
+
const currentLocale = getLocale();
|
|
135
|
+
const currency = getCurrency();
|
|
136
|
+
|
|
137
|
+
const formatter = new Intl.NumberFormat(currentLocale, {
|
|
138
|
+
style: 'currency',
|
|
139
|
+
currency: currency,
|
|
140
|
+
minimumFractionDigits: 2,
|
|
141
|
+
...options,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
let formattedAmount = formatter.format(Math.abs(amount));
|
|
145
|
+
|
|
146
|
+
if (amount < 0) {
|
|
147
|
+
formattedAmount = `- ${formattedAmount}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return formattedAmount;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const formatSimple = (amount: number, options: Intl.NumberFormatOptions = {}) => {
|
|
154
|
+
const currentLocale = getLocale();
|
|
155
|
+
const currency = getCurrency();
|
|
156
|
+
|
|
157
|
+
const formatter = new Intl.NumberFormat(currentLocale, {
|
|
158
|
+
style: 'currency',
|
|
159
|
+
currency: currency,
|
|
160
|
+
...options,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return formatter.format(amount);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
getCurrency,
|
|
168
|
+
getLocale,
|
|
169
|
+
format,
|
|
170
|
+
formatSimple,
|
|
171
|
+
};
|
|
172
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import styles from './patient-management.scss';
|
|
4
|
+
import { getPatientUuidFromUrl } from './utils';
|
|
5
|
+
import { ExtensionSlot, launchWorkspace, launchWorkspace2, usePatient } from '@openmrs/esm-framework';
|
|
6
|
+
import {
|
|
7
|
+
Tabs,
|
|
8
|
+
TabsVertical,
|
|
9
|
+
TabListVertical,
|
|
10
|
+
TextInput,
|
|
11
|
+
Stack,
|
|
12
|
+
Layer,
|
|
13
|
+
Tab,
|
|
14
|
+
RadioButtonGroup,
|
|
15
|
+
RadioButton,
|
|
16
|
+
Checkbox,
|
|
17
|
+
TabList,
|
|
18
|
+
TabPanel,
|
|
19
|
+
TabPanels,
|
|
20
|
+
Button,
|
|
21
|
+
} from '@carbon/react';
|
|
22
|
+
import { AssetMovement } from '@carbon/react/icons';
|
|
23
|
+
import PatientBanner from './patient-banner/patient-banner.component';
|
|
24
|
+
import { CardHeader } from '@openmrs/esm-patient-common-lib/src';
|
|
25
|
+
|
|
26
|
+
const CarePanel: React.FC = () => {
|
|
27
|
+
const { t } = useTranslation();
|
|
28
|
+
const patientUuid = getPatientUuidFromUrl();
|
|
29
|
+
const { patient, isLoading, error } = usePatient(patientUuid);
|
|
30
|
+
const [selectedTabIndex, setSelectedTabIndex] = React.useState(0);
|
|
31
|
+
|
|
32
|
+
const handleConsultationStart = (patientUuid: string) => {
|
|
33
|
+
launchWorkspace('patient-consultation', { patientUuid });
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<div className={styles.header}>
|
|
39
|
+
{patient && <PatientBanner patientUuid={patientUuid} patient={patient} hideActionsOverflow={true} />}
|
|
40
|
+
<ExtensionSlot name="patient-vitals-header-slot" state={{ patientUuid }} />
|
|
41
|
+
<Button
|
|
42
|
+
size="sm"
|
|
43
|
+
kind="primary"
|
|
44
|
+
renderIcon={AssetMovement}
|
|
45
|
+
className={styles.buttonStartConsultation}
|
|
46
|
+
onClick={() => handleConsultationStart(patientUuid!)}>
|
|
47
|
+
{t('startConsultation', 'Start Consultation')}
|
|
48
|
+
</Button>
|
|
49
|
+
</div>
|
|
50
|
+
{/* <TabsVertical height="3">
|
|
51
|
+
<TabListVertical>
|
|
52
|
+
<Tab>{t('clinicalEncounter', 'Clinical Encounter')}</Tab>
|
|
53
|
+
<Tab>{t('labResults', 'Laboratory Results')}</Tab>
|
|
54
|
+
<Tab>{t('procedureResults', 'Procedure Results')}</Tab>
|
|
55
|
+
<Tab>{t('imagingResults', 'Imaging Results')}</Tab>
|
|
56
|
+
<Tab>{t('prescriptions', 'Prescriptions')}</Tab>
|
|
57
|
+
<Tab>{t('appointments', 'Appointments')}</Tab>
|
|
58
|
+
</TabListVertical>
|
|
59
|
+
<TabPanels>
|
|
60
|
+
<TabPanel>Tab Panel 1</TabPanel>
|
|
61
|
+
<TabPanel>
|
|
62
|
+
<Layer>
|
|
63
|
+
<form
|
|
64
|
+
style={{
|
|
65
|
+
margin: '2em',
|
|
66
|
+
}}>
|
|
67
|
+
<Stack gap={7}>
|
|
68
|
+
<TextInput id="one" labelText="First Name" />
|
|
69
|
+
<TextInput id="three" labelText="Middle Initial" />
|
|
70
|
+
<TextInput id="two" labelText="Last Name" />
|
|
71
|
+
<RadioButtonGroup
|
|
72
|
+
defaultSelected="radio-1"
|
|
73
|
+
legendText="Radio button heading"
|
|
74
|
+
name="formgroup-default-radio-button-group">
|
|
75
|
+
<RadioButton id="radio-1" labelText="Option 1" value="radio-1" />
|
|
76
|
+
<RadioButton id="radio-2" labelText="Option 2" value="radio-2" />
|
|
77
|
+
<RadioButton id="radio-3" labelText="Option 3" value="radio-3" />
|
|
78
|
+
</RadioButtonGroup>
|
|
79
|
+
<Checkbox id="checkbox-label-1" labelText="Checkbox one" />
|
|
80
|
+
<Checkbox id="checkbox-label-2" labelText="Checkbox two" />
|
|
81
|
+
<Button>Submit</Button>
|
|
82
|
+
</Stack>
|
|
83
|
+
</form>
|
|
84
|
+
</Layer>
|
|
85
|
+
</TabPanel>
|
|
86
|
+
<TabPanel>Tab Panel 3</TabPanel>
|
|
87
|
+
<TabPanel>Tab Panel 4</TabPanel>
|
|
88
|
+
<TabPanel>Tab Panel 5</TabPanel>
|
|
89
|
+
<TabPanel>Tab Panel 6</TabPanel>
|
|
90
|
+
<TabPanel>Tab Panel 7</TabPanel>
|
|
91
|
+
</TabPanels>
|
|
92
|
+
</TabsVertical> */}
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default CarePanel;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.header {
|
|
6
|
+
margin-top: layout.$spacing-04;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:global(.cds--css-grid) {
|
|
10
|
+
--cds-grid-gutter-start: calc(var(--cds-grid-gutter) / 2);
|
|
11
|
+
--cds-grid-gutter-end: calc(var(--cds-grid-gutter) / 2);
|
|
12
|
+
--cds-grid-column-hang: calc(var(--cds-grid-gutter) / 2);
|
|
13
|
+
grid-template-columns: repeat(var(--cds-grid-columns), minmax(0, 1fr));
|
|
14
|
+
width: 100%;
|
|
15
|
+
max-width: 150rem;
|
|
16
|
+
padding: 0;
|
|
17
|
+
margin-left: 0;
|
|
18
|
+
margin-right: 0;
|
|
19
|
+
display: grid;
|
|
20
|
+
}
|
|
21
|
+
:global(.-esm-outpatient__card-header__desktopHeader___Qe2fu),
|
|
22
|
+
:global(.-esm-outpatient__card-header__tabletHeader___gFxMA) {
|
|
23
|
+
// background-color: #f4f4f4 !important; // Your desired color
|
|
24
|
+
padding-bottom: 1rem;
|
|
25
|
+
}
|
|
26
|
+
:global(.cds--tabs.cds--tabs--vertical .cds--tabs__nav-item.cds--tabs__nav-item--selected) {
|
|
27
|
+
box-shadow: inset 3px 0 0 0 var(--cds-border-interactive, #4a90b8);
|
|
28
|
+
border-left: none;
|
|
29
|
+
border-right: none;
|
|
30
|
+
}
|