@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.
Files changed (116) hide show
  1. package/README.md +4 -0
  2. package/dist/117.js +1 -0
  3. package/dist/117.js.map +1 -0
  4. package/dist/132.js +1 -0
  5. package/dist/132.js.map +1 -0
  6. package/dist/152.js +1 -0
  7. package/dist/152.js.map +1 -0
  8. package/dist/209.js +1 -0
  9. package/dist/209.js.map +1 -0
  10. package/dist/317.js +1 -0
  11. package/dist/317.js.map +1 -0
  12. package/dist/349.js +1 -0
  13. package/dist/349.js.map +1 -0
  14. package/dist/371.js +1 -0
  15. package/dist/371.js.map +1 -0
  16. package/dist/378.js +1 -0
  17. package/dist/378.js.map +1 -0
  18. package/dist/395.js +12 -0
  19. package/dist/395.js.map +1 -0
  20. package/dist/442.js +1 -0
  21. package/dist/442.js.map +1 -0
  22. package/dist/45.js +1 -0
  23. package/dist/45.js.map +1 -0
  24. package/dist/466.js +1 -0
  25. package/dist/466.js.map +1 -0
  26. package/dist/508.js +1 -0
  27. package/dist/508.js.map +1 -0
  28. package/dist/564.js +1 -0
  29. package/dist/564.js.map +1 -0
  30. package/dist/61.js +1 -0
  31. package/dist/61.js.map +1 -0
  32. package/dist/640.js +1 -0
  33. package/dist/640.js.map +1 -0
  34. package/dist/689.js +1 -0
  35. package/dist/689.js.map +1 -0
  36. package/dist/709.js +1 -0
  37. package/dist/709.js.map +1 -0
  38. package/dist/712.js +1 -0
  39. package/dist/712.js.map +1 -0
  40. package/dist/720.js +1 -0
  41. package/dist/720.js.map +1 -0
  42. package/dist/742.js +1 -0
  43. package/dist/742.js.map +1 -0
  44. package/dist/759.js +15 -0
  45. package/dist/759.js.map +1 -0
  46. package/dist/771.js +1 -0
  47. package/dist/771.js.map +1 -0
  48. package/dist/797.js +1 -0
  49. package/dist/797.js.map +1 -0
  50. package/dist/806.js +1 -0
  51. package/dist/806.js.map +1 -0
  52. package/dist/912.js +1 -0
  53. package/dist/912.js.map +1 -0
  54. package/dist/913.js +1 -0
  55. package/dist/913.js.map +1 -0
  56. package/dist/940.js +1 -0
  57. package/dist/940.js.map +1 -0
  58. package/dist/974.js +1 -0
  59. package/dist/974.js.map +1 -0
  60. package/dist/digihmis-esm-outpatient-app.js +6 -0
  61. package/dist/digihmis-esm-outpatient-app.js.buildmanifest.json +827 -0
  62. package/dist/digihmis-esm-outpatient-app.js.map +1 -0
  63. package/dist/main.js +17 -0
  64. package/dist/main.js.map +1 -0
  65. package/dist/routes.json +1 -0
  66. package/jest.config.js +26 -0
  67. package/package.json +51 -0
  68. package/rspack.config.js +1 -0
  69. package/src/config-schema.ts +347 -0
  70. package/src/dashboard.meta.ts +16 -0
  71. package/src/declarations.d.ts +6 -0
  72. package/src/icon-wrapper/icon-wrapper.extension.tsx +17 -0
  73. package/src/index.ts +27 -0
  74. package/src/left-panel/left-panel.component.tsx +100 -0
  75. package/src/left-panel/left-panel.scss +13 -0
  76. package/src/main-dashboard/outpatient-dashboard.component.tsx +264 -0
  77. package/src/main-dashboard/outpatient-dashboard.resource.ts +71 -0
  78. package/src/main-dashboard/outpatient-dashboard.scss +166 -0
  79. package/src/main-dashboard/type/index.ts +162 -0
  80. package/src/page-header/page-header.component.tsx +35 -0
  81. package/src/page-header/page-header.scss +76 -0
  82. package/src/patient-management/components/auto-suggest/autosuggest.component.tsx +149 -0
  83. package/src/patient-management/components/auto-suggest/autosuggest.scss +61 -0
  84. package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.component.tsx +131 -0
  85. package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.scss +42 -0
  86. package/src/patient-management/patient-banner/patient-banner.component.tsx +60 -0
  87. package/src/patient-management/patient-banner/patient-banner.scss +72 -0
  88. package/src/patient-management/patient-management-workspace/billable-services-selector.component.tsx +150 -0
  89. package/src/patient-management/patient-management-workspace/patient-management-schema/index.ts +261 -0
  90. package/src/patient-management/patient-management-workspace/patient-management-workspace.scss +304 -0
  91. package/src/patient-management/patient-management-workspace/patient-management-workspace.tsx +1581 -0
  92. package/src/patient-management/patient-management-workspace/patient-management.resource.ts +182 -0
  93. package/src/patient-management/patient-management-workspace/type/index.ts +193 -0
  94. package/src/patient-management/patient-management-workspace/utils/index.ts +172 -0
  95. package/src/patient-management/patient-management.component.tsx +97 -0
  96. package/src/patient-management/patient-management.scss +30 -0
  97. package/src/patient-management/utils/index.ts +5 -0
  98. package/src/queue-room-table/queue-room-table.component.tsx +313 -0
  99. package/src/queue-room-table/queue-room-table.resource.ts +73 -0
  100. package/src/queue-room-table/queue-room-table.scss +193 -0
  101. package/src/queue-room-table/type/index.ts +34 -0
  102. package/src/queue-room-table/useQueueActions.ts +24 -0
  103. package/src/routes.json +55 -0
  104. package/src/setup-tests.ts +1 -0
  105. package/translations/am.json +57 -0
  106. package/translations/ar.json +57 -0
  107. package/translations/en.json +57 -0
  108. package/translations/es.json +57 -0
  109. package/translations/fr.json +57 -0
  110. package/translations/he.json +57 -0
  111. package/translations/km.json +57 -0
  112. package/translations/pt.json +57 -0
  113. package/translations/sw.json +57 -0
  114. package/translations/vi.json +57 -0
  115. package/translations/zh.json +57 -0
  116. 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
+ }
@@ -0,0 +1,5 @@
1
+ export function getPatientUuidFromUrl(): string {
2
+ const match = /\/patient\/([a-zA-Z0-9\-]+)\/?/.exec(location.pathname);
3
+ const patientUuidFromUrl = match && match[1];
4
+ return patientUuidFromUrl || '';
5
+ }