@openmrs/esm-billing-app 1.0.1-pre.98 → 1.0.2-pre.58

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 (224) hide show
  1. package/.eslintignore +0 -1
  2. package/.eslintrc +33 -24
  3. package/.husky/pre-commit +1 -1
  4. package/.turbo.json +1 -1
  5. package/.tx/config +11 -0
  6. package/README.md +111 -1
  7. package/dist/1119.js +1 -0
  8. package/dist/1197.js +1 -0
  9. package/dist/1362.js +1 -0
  10. package/dist/1362.js.map +1 -0
  11. package/dist/2146.js +1 -0
  12. package/dist/2690.js +1 -0
  13. package/dist/3029.js +2 -0
  14. package/dist/3029.js.LICENSE.txt +7 -0
  15. package/dist/3029.js.map +1 -0
  16. package/dist/3099.js +1 -0
  17. package/dist/3511.js +1 -0
  18. package/dist/3511.js.map +1 -0
  19. package/dist/3584.js +1 -0
  20. package/dist/4055.js +1 -0
  21. package/dist/4132.js +1 -0
  22. package/dist/4225.js +1 -0
  23. package/dist/4225.js.map +1 -0
  24. package/dist/4300.js +1 -0
  25. package/dist/4335.js +1 -0
  26. package/dist/4618.js +1 -0
  27. package/dist/4652.js +1 -0
  28. package/dist/4817.js +2 -0
  29. package/dist/4817.js.LICENSE.txt +77 -0
  30. package/dist/4817.js.map +1 -0
  31. package/dist/4944.js +1 -0
  32. package/dist/4993.js +1 -0
  33. package/dist/4993.js.map +1 -0
  34. package/dist/5173.js +1 -0
  35. package/dist/5241.js +1 -0
  36. package/dist/5442.js +1 -0
  37. package/dist/5661.js +1 -0
  38. package/dist/6022.js +1 -0
  39. package/dist/6468.js +1 -0
  40. package/dist/6540.js +2 -0
  41. package/dist/6540.js.map +1 -0
  42. package/dist/6606.js +2 -0
  43. package/dist/{591.js.LICENSE.txt → 6606.js.LICENSE.txt} +2 -2
  44. package/dist/6606.js.map +1 -0
  45. package/dist/6679.js +1 -0
  46. package/dist/6840.js +1 -0
  47. package/dist/6859.js +1 -0
  48. package/dist/6941.js +1 -0
  49. package/dist/6941.js.map +1 -0
  50. package/dist/7097.js +1 -0
  51. package/dist/7159.js +1 -0
  52. package/dist/723.js +1 -0
  53. package/dist/7255.js +1 -0
  54. package/dist/7255.js.map +1 -0
  55. package/dist/7617.js +1 -0
  56. package/dist/763.js +1 -0
  57. package/dist/763.js.map +1 -0
  58. package/dist/8163.js +1 -0
  59. package/dist/8349.js +1 -0
  60. package/dist/8618.js +1 -0
  61. package/dist/890.js +1 -0
  62. package/dist/9055.js +1 -0
  63. package/dist/9055.js.map +1 -0
  64. package/dist/9214.js +1 -0
  65. package/dist/9538.js +1 -0
  66. package/dist/{935.js → 961.js} +2 -2
  67. package/dist/{935.js.map → 961.js.map} +1 -1
  68. package/dist/986.js +1 -0
  69. package/dist/9879.js +1 -0
  70. package/dist/9895.js +1 -0
  71. package/dist/9900.js +1 -0
  72. package/dist/9913.js +1 -0
  73. package/dist/main.js +1 -1
  74. package/dist/main.js.LICENSE.txt +31 -1
  75. package/dist/main.js.map +1 -1
  76. package/dist/openmrs-esm-billing-app.js +1 -1
  77. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +844 -165
  78. package/dist/openmrs-esm-billing-app.js.map +1 -1
  79. package/dist/routes.json +1 -1
  80. package/jest.config.js +4 -1
  81. package/package.json +19 -21
  82. package/src/bill-history/bill-history.component.tsx +5 -3
  83. package/src/bill-history/bill-history.scss +24 -9
  84. package/src/bill-history/bill-history.test.tsx +58 -16
  85. package/src/bill-item-actions/bill-item-actions.scss +26 -0
  86. package/src/bill-item-actions/edit-bill-item.component.tsx +221 -0
  87. package/src/bill-item-actions/edit-bill-item.test.tsx +137 -0
  88. package/src/billable-services/bill-waiver/bill-selection.component.tsx +1 -1
  89. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +2 -2
  90. package/src/billable-services/bill-waiver/bill-waiver-form.scss +4 -4
  91. package/src/billable-services/bill-waiver/bill-waiver.component.tsx +4 -4
  92. package/src/billable-services/bill-waiver/patient-bills.component.tsx +1 -1
  93. package/src/billable-services/billable-service.resource.ts +19 -6
  94. package/src/billable-services/billable-services-home.component.tsx +19 -3
  95. package/src/billable-services/billable-services-menu-item/item.component.tsx +17 -0
  96. package/src/billable-services/billable-services-menu-item/item.scss +14 -0
  97. package/src/billable-services/billable-services.component.tsx +48 -9
  98. package/src/billable-services/billable-services.scss +10 -9
  99. package/src/billable-services/billable-services.test.tsx +172 -8
  100. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +276 -0
  101. package/src/billable-services/cash-point/cash-point-configuration.scss +23 -0
  102. package/src/billable-services/create-edit/add-billable-service.component.tsx +126 -47
  103. package/src/billable-services/create-edit/add-billable-service.scss +14 -8
  104. package/src/billable-services/create-edit/add-billable-service.test.tsx +12 -10
  105. package/src/billable-services/dashboard/dashboard.scss +3 -3
  106. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +280 -0
  107. package/src/billable-services/payyment-modes/payment-modes-config.scss +23 -0
  108. package/src/billing-dashboard/billing-dashboard.component.tsx +17 -4
  109. package/src/billing-dashboard/billing-dashboard.scss +3 -3
  110. package/src/billing-form/billing-form.component.tsx +31 -25
  111. package/src/billing-form/billing-form.scss +9 -10
  112. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +38 -14
  113. package/src/billing-header/billing-header.component.tsx +21 -5
  114. package/src/billing-header/billing-header.scss +1 -1
  115. package/src/billing.resource.ts +21 -4
  116. package/src/bills-table/bills-table.component.tsx +46 -36
  117. package/src/bills-table/bills-table.scss +6 -6
  118. package/src/bills-table/bills-table.test.tsx +108 -68
  119. package/src/config-schema.ts +36 -1
  120. package/src/constants.ts +2 -0
  121. package/src/dashboard.meta.ts +2 -1
  122. package/src/helpers/functions.ts +0 -2
  123. package/src/hooks/selectedDateContext.ts +10 -0
  124. package/src/index.ts +22 -27
  125. package/src/invoice/invoice-table.component.tsx +95 -56
  126. package/src/invoice/invoice-table.scss +7 -8
  127. package/src/invoice/invoice-table.test.tsx +151 -0
  128. package/src/invoice/invoice.component.tsx +7 -9
  129. package/src/invoice/invoice.scss +2 -2
  130. package/src/invoice/invoice.test.tsx +199 -169
  131. package/src/invoice/payments/payment-form/payment-form.component.tsx +84 -55
  132. package/src/invoice/payments/payment-form/payment-form.test.tsx +174 -0
  133. package/src/invoice/payments/payment-history/payment-history.component.tsx +9 -7
  134. package/src/invoice/payments/payment-history/payment-history.test.tsx +160 -0
  135. package/src/invoice/payments/payments.component.test.tsx +121 -0
  136. package/src/invoice/payments/payments.component.tsx +57 -48
  137. package/src/invoice/payments/utils.ts +17 -13
  138. package/src/invoice/printable-invoice/print-receipt.component.tsx +23 -8
  139. package/src/invoice/printable-invoice/print-receipt.test.tsx +50 -0
  140. package/src/metrics-cards/card.component.tsx +4 -2
  141. package/src/metrics-cards/metrics-cards.test.tsx +1 -1
  142. package/src/modal/require-payment-modal.component.tsx +2 -2
  143. package/src/modal/require-payment-modal.test.tsx +66 -0
  144. package/src/modal/require-payment.scss +2 -1
  145. package/src/routes.json +40 -8
  146. package/src/types/index.ts +15 -0
  147. package/{i18next-parser.config.js → tools/i18next-parser.config.js} +19 -19
  148. package/tools/update-openmrs-deps.mjs +42 -0
  149. package/translations/am.json +53 -0
  150. package/translations/ar.json +170 -0
  151. package/translations/ar_SY.json +170 -0
  152. package/translations/bn.json +170 -0
  153. package/translations/de.json +170 -0
  154. package/translations/en.json +53 -0
  155. package/translations/es.json +53 -0
  156. package/translations/es_MX.json +170 -0
  157. package/translations/fr.json +53 -0
  158. package/translations/he.json +53 -0
  159. package/translations/hi.json +170 -0
  160. package/translations/hi_IN.json +170 -0
  161. package/translations/id.json +170 -0
  162. package/translations/it.json +170 -0
  163. package/translations/km.json +53 -0
  164. package/translations/ku.json +170 -0
  165. package/translations/ky.json +170 -0
  166. package/translations/lg.json +170 -0
  167. package/translations/ne.json +170 -0
  168. package/translations/pl.json +170 -0
  169. package/translations/pt.json +170 -0
  170. package/translations/pt_BR.json +170 -0
  171. package/translations/qu.json +170 -0
  172. package/translations/ro_RO.json +170 -0
  173. package/translations/ru_RU.json +170 -0
  174. package/translations/si.json +170 -0
  175. package/translations/sw.json +170 -0
  176. package/translations/sw_KE.json +170 -0
  177. package/translations/tr.json +170 -0
  178. package/translations/tr_TR.json +170 -0
  179. package/translations/uk.json +170 -0
  180. package/translations/uz.json +170 -0
  181. package/translations/uz@Latn.json +170 -0
  182. package/translations/uz_UZ.json +170 -0
  183. package/translations/vi.json +170 -0
  184. package/translations/zh.json +170 -0
  185. package/translations/zh_CN.json +170 -0
  186. package/tsconfig.json +10 -8
  187. package/webpack.config.js +1 -1
  188. package/dist/146.js +0 -1
  189. package/dist/146.js.map +0 -1
  190. package/dist/294.js +0 -2
  191. package/dist/294.js.map +0 -1
  192. package/dist/319.js +0 -1
  193. package/dist/384.js +0 -1
  194. package/dist/384.js.map +0 -1
  195. package/dist/421.js +0 -1
  196. package/dist/421.js.map +0 -1
  197. package/dist/533.js +0 -1
  198. package/dist/533.js.map +0 -1
  199. package/dist/574.js +0 -1
  200. package/dist/591.js +0 -2
  201. package/dist/591.js.map +0 -1
  202. package/dist/614.js +0 -2
  203. package/dist/614.js.LICENSE.txt +0 -37
  204. package/dist/614.js.map +0 -1
  205. package/dist/753.js +0 -1
  206. package/dist/753.js.map +0 -1
  207. package/dist/757.js +0 -1
  208. package/dist/770.js +0 -1
  209. package/dist/770.js.map +0 -1
  210. package/dist/783.js +0 -1
  211. package/dist/783.js.map +0 -1
  212. package/dist/788.js +0 -1
  213. package/dist/800.js +0 -2
  214. package/dist/800.js.LICENSE.txt +0 -3
  215. package/dist/800.js.map +0 -1
  216. package/dist/807.js +0 -1
  217. package/dist/833.js +0 -1
  218. package/dist/992.js +0 -1
  219. package/dist/992.js.map +0 -1
  220. package/src/root.scss +0 -30
  221. /package/dist/{294.js.LICENSE.txt → 6540.js.LICENSE.txt} +0 -0
  222. /package/dist/{935.js.LICENSE.txt → 961.js.LICENSE.txt} +0 -0
  223. /package/{src → tools}/setup-tests.ts +0 -0
  224. /package/{test-helpers.tsx → tools/test-helpers.tsx} +0 -0
@@ -0,0 +1,280 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import {
3
+ Button,
4
+ DataTable,
5
+ TableContainer,
6
+ Table,
7
+ TableHead,
8
+ TableRow,
9
+ TableHeader,
10
+ TableBody,
11
+ TableCell,
12
+ Modal,
13
+ TextInput,
14
+ OverflowMenu,
15
+ OverflowMenuItem,
16
+ } from '@carbon/react';
17
+ import { Add } from '@carbon/react/icons';
18
+ import { useTranslation } from 'react-i18next';
19
+ import { useForm, Controller } from 'react-hook-form';
20
+ import { z } from 'zod';
21
+ import { zodResolver } from '@hookform/resolvers/zod';
22
+ import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
23
+ import { CardHeader } from '@openmrs/esm-patient-common-lib';
24
+ import styles from './payment-modes-config.scss';
25
+
26
+ // Validation schema
27
+ const paymentModeSchema = z.object({
28
+ name: z.string().min(1, 'Payment Mode Name is required'),
29
+ description: z.string().optional(),
30
+ });
31
+
32
+ type PaymentModeFormValues = z.infer<typeof paymentModeSchema>;
33
+
34
+ const PaymentModesConfig: React.FC = () => {
35
+ const { t } = useTranslation();
36
+ const [paymentModes, setPaymentModes] = useState([]);
37
+ const [isModalOpen, setIsModalOpen] = useState(false);
38
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
39
+ const [selectedPaymentMode, setSelectedPaymentMode] = useState(null);
40
+
41
+ const {
42
+ control,
43
+ handleSubmit,
44
+ reset,
45
+ formState: { errors, isSubmitting },
46
+ } = useForm<PaymentModeFormValues>({
47
+ resolver: zodResolver(paymentModeSchema),
48
+ defaultValues: {
49
+ name: '',
50
+ description: '',
51
+ },
52
+ });
53
+
54
+ const fetchPaymentModes = useCallback(async () => {
55
+ try {
56
+ const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode?v=full`);
57
+ setPaymentModes(response.data.results || []);
58
+ } catch (err) {
59
+ showSnackbar({
60
+ title: t('error', 'Error'),
61
+ subtitle: t('errorFetchingPaymentModes', 'An error occurred while fetching payment modes.'),
62
+ kind: 'error',
63
+ isLowContrast: false,
64
+ });
65
+ }
66
+ }, [t]);
67
+
68
+ useEffect(() => {
69
+ fetchPaymentModes();
70
+ }, [fetchPaymentModes]);
71
+
72
+ const onSubmit = async (data: PaymentModeFormValues) => {
73
+ // Check for duplicate payment mode name
74
+ const isDuplicate = paymentModes.some((mode) => mode.name.toLowerCase() === data.name.toLowerCase());
75
+
76
+ if (isDuplicate) {
77
+ showSnackbar({
78
+ title: t('error', 'Error'),
79
+ subtitle: t(
80
+ 'duplicatePaymentModeError',
81
+ 'A payment mode with the same name already exists. Please create another payment mode',
82
+ ),
83
+ kind: 'error',
84
+ isLowContrast: false,
85
+ });
86
+ return;
87
+ }
88
+
89
+ try {
90
+ const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ },
95
+ body: JSON.stringify({
96
+ name: data.name,
97
+ description: data.description,
98
+ }),
99
+ });
100
+
101
+ if (response.ok) {
102
+ showSnackbar({
103
+ title: t('success', 'Success'),
104
+ subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
105
+ kind: 'success',
106
+ });
107
+
108
+ setIsModalOpen(false);
109
+ reset({ name: '', description: '' });
110
+ fetchPaymentModes();
111
+ } else {
112
+ const errorData = response.data || {};
113
+ showSnackbar({
114
+ title: t('error', 'Error'),
115
+ subtitle:
116
+ errorData.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
117
+ kind: 'error',
118
+ isLowContrast: false,
119
+ });
120
+ }
121
+ } catch (err) {
122
+ showSnackbar({
123
+ title: t('error', 'Error'),
124
+ subtitle: t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
125
+ kind: 'error',
126
+ isLowContrast: false,
127
+ });
128
+ }
129
+ };
130
+
131
+ const handleDelete = async () => {
132
+ if (!selectedPaymentMode) return;
133
+
134
+ try {
135
+ await openmrsFetch(`${restBaseUrl}/billing/paymentMode/${selectedPaymentMode.uuid}`, {
136
+ method: 'DELETE',
137
+ });
138
+
139
+ showSnackbar({
140
+ title: t('success', 'Success'),
141
+ subtitle: t('paymentModeDeleted', 'Payment mode was successfully deleted.'),
142
+ kind: 'success',
143
+ });
144
+
145
+ setIsDeleteModalOpen(false);
146
+ setSelectedPaymentMode(null);
147
+ fetchPaymentModes();
148
+ } catch (err) {
149
+ showSnackbar({
150
+ title: t('error', 'Error'),
151
+ subtitle: t('errorDeletingPaymentMode', 'An error occurred while deleting the payment mode.'),
152
+ kind: 'error',
153
+ isLowContrast: false,
154
+ });
155
+ }
156
+ };
157
+
158
+ const rowData = paymentModes.map((mode) => ({
159
+ id: mode.uuid,
160
+ name: mode.name,
161
+ description: mode.description || '--',
162
+ }));
163
+
164
+ const headerData = [
165
+ { key: 'name', header: t('name', 'Name') },
166
+ { key: 'description', header: t('description', 'Description') },
167
+ { key: 'actions', header: t('actions', 'Actions') },
168
+ ];
169
+
170
+ return (
171
+ <div className={styles.container}>
172
+ <div className={styles.card}>
173
+ <CardHeader title={t('paymentModeHistory', 'Payment Mode History')}>
174
+ <Button renderIcon={Add} onClick={() => setIsModalOpen(true)} kind="ghost">
175
+ {t('addPaymentMode', 'Add New Payment Mode')}
176
+ </Button>
177
+ </CardHeader>
178
+ <div className={styles.historyContainer}>
179
+ <DataTable rows={rowData} headers={headerData} isSortable size="lg">
180
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
181
+ <TableContainer>
182
+ <Table className={styles.table} {...getTableProps()}>
183
+ <TableHead>
184
+ <TableRow>
185
+ {headers.map((header) => (
186
+ <TableHeader key={header.key} {...getHeaderProps({ header })}>
187
+ {header.header}
188
+ </TableHeader>
189
+ ))}
190
+ </TableRow>
191
+ </TableHead>
192
+ <TableBody>
193
+ {rows.map((row) => (
194
+ <TableRow key={row.id} {...getRowProps({ row })}>
195
+ {row.cells.map((cell) =>
196
+ cell.info.header !== 'actions' ? (
197
+ <TableCell key={cell.id}>{cell.value}</TableCell>
198
+ ) : (
199
+ <TableCell key={cell.id}>
200
+ <OverflowMenu>
201
+ <OverflowMenuItem
202
+ itemText={t('delete', 'Delete')}
203
+ onClick={() => {
204
+ const selected = paymentModes.find((p) => p.uuid === row.id);
205
+ setSelectedPaymentMode(selected);
206
+ setIsDeleteModalOpen(true);
207
+ }}
208
+ />
209
+ </OverflowMenu>
210
+ </TableCell>
211
+ ),
212
+ )}
213
+ </TableRow>
214
+ ))}
215
+ </TableBody>
216
+ </Table>
217
+ </TableContainer>
218
+ )}
219
+ </DataTable>
220
+ </div>
221
+ </div>
222
+
223
+ {/* Modal for Adding New Payment Mode */}
224
+ <Modal
225
+ open={isModalOpen}
226
+ modalHeading={t('addPaymentMode', 'Add Payment Mode')}
227
+ onRequestClose={() => setIsModalOpen(false)}
228
+ onRequestSubmit={handleSubmit(onSubmit)}
229
+ primaryButtonText={t('save', 'Save')}
230
+ secondaryButtonText={t('cancel', 'Cancel')}
231
+ isPrimaryButtonDisabled={isSubmitting}>
232
+ <form>
233
+ <Controller
234
+ name="name"
235
+ control={control}
236
+ render={({ field }) => (
237
+ <TextInput
238
+ id="payment-mode-name"
239
+ labelText={t('paymentModeName', 'Payment Mode Name')}
240
+ placeholder={t('paymentModeNamePlaceholder', 'e.g., Cash, Credit Card')}
241
+ invalid={!!errors.name}
242
+ invalidText={errors.name?.message}
243
+ {...field}
244
+ />
245
+ )}
246
+ />
247
+ <Controller
248
+ name="description"
249
+ control={control}
250
+ render={({ field }) => (
251
+ <TextInput
252
+ id="payment-mode-description"
253
+ labelText={t('description', 'Description')}
254
+ placeholder={t('descriptionPlaceholder', 'e.g., Used for all cash transactions')}
255
+ invalid={!!errors.description}
256
+ invalidText={errors.description?.message}
257
+ {...field}
258
+ />
259
+ )}
260
+ />
261
+ </form>
262
+ </Modal>
263
+
264
+ {/* Modal for Deleting Payment Mode */}
265
+ <Modal
266
+ open={isDeleteModalOpen}
267
+ modalHeading={t('deletePaymentMode', 'Delete Payment Mode')}
268
+ onRequestClose={() => setIsDeleteModalOpen(false)}
269
+ onRequestSubmit={handleDelete}
270
+ primaryButtonText={t('delete', 'Delete')}
271
+ secondaryButtonText={t('cancel', 'Cancel')}
272
+ primaryButtonDanger
273
+ danger>
274
+ <p>{t('confirmDeleteMessage', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}</p>
275
+ </Modal>
276
+ </div>
277
+ );
278
+ };
279
+
280
+ export default PaymentModesConfig;
@@ -0,0 +1,23 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .container {
6
+ padding: layout.$spacing-05;
7
+ }
8
+
9
+ .card {
10
+ width: 100%;
11
+ max-width: 1200px;
12
+ margin: 0 auto;
13
+ padding: layout.$spacing-05;
14
+ }
15
+
16
+ .historyContainer {
17
+ margin-top: layout.$spacing-05;
18
+ }
19
+
20
+ .table {
21
+ width: 100%;
22
+ table-layout: auto;
23
+ }
@@ -1,20 +1,33 @@
1
- import React from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
+ import dayjs from 'dayjs';
2
3
  import { useTranslation } from 'react-i18next';
4
+ import { useParams } from 'react-router-dom';
5
+ import { omrsDateFormat } from '../constants';
3
6
  import BillingHeader from '../billing-header/billing-header.component';
4
- import MetricsCards from '../metrics-cards/metrics-cards.component';
5
7
  import BillsTable from '../bills-table/bills-table.component';
8
+ import MetricsCards from '../metrics-cards/metrics-cards.component';
9
+ import SelectedDateContext from '../hooks/selectedDateContext';
6
10
  import styles from './billing-dashboard.scss';
7
11
 
8
12
  export function BillingDashboard() {
9
13
  const { t } = useTranslation();
14
+ const [selectedDate, setSelectedDate] = useState<string>(dayjs().startOf('day').format(omrsDateFormat));
15
+
16
+ const params = useParams();
17
+
18
+ useEffect(() => {
19
+ if (params.date) {
20
+ setSelectedDate(dayjs(params.date).startOf('day').format(omrsDateFormat));
21
+ }
22
+ }, [params.date]);
10
23
 
11
24
  return (
12
- <main className={styles.container}>
25
+ <SelectedDateContext.Provider value={{ selectedDate, setSelectedDate }}>
13
26
  <BillingHeader title={t('home', 'Home')} />
14
27
  <MetricsCards />
15
28
  <section className={styles.billsTableContainer}>
16
29
  <BillsTable />
17
30
  </section>
18
- </main>
31
+ </SelectedDateContext.Provider>
19
32
  );
20
33
  }
@@ -3,11 +3,11 @@
3
3
  @use '@carbon/type';
4
4
 
5
5
  .container {
6
- height: calc(100vh - 3rem);
6
+ height: calc(100vh - layout.$spacing-09);
7
7
  }
8
8
 
9
9
  .billsTableContainer {
10
- margin: 2rem 1rem;
10
+ margin: layout.$spacing-07 layout.$spacing-05;
11
11
  }
12
12
 
13
13
  .illo {
@@ -22,6 +22,6 @@
22
22
 
23
23
  .tile {
24
24
  border: 1px solid colors.$gray-20;
25
- padding: 1.5rem 0;
25
+ padding: layout.$spacing-06 0;
26
26
  text-align: center;
27
27
  }
@@ -1,31 +1,32 @@
1
1
  import React, { useState, useEffect, useMemo } from 'react';
2
+ import fuzzy from 'fuzzy';
3
+ import isEmpty from 'lodash-es/isEmpty';
2
4
  import {
3
- ButtonSet,
4
5
  Button,
6
+ ButtonSet,
5
7
  Form,
6
8
  InlineLoading,
7
- RadioButtonGroup,
8
9
  RadioButton,
10
+ RadioButtonGroup,
9
11
  Search,
10
12
  Stack,
11
13
  Table,
12
- TableHead,
13
14
  TableBody,
15
+ TableCell,
16
+ TableHead,
14
17
  TableHeader,
15
18
  TableRow,
16
- TableCell,
17
19
  } from '@carbon/react';
18
- import styles from './billing-form.scss';
19
- import { useTranslation } from 'react-i18next';
20
- import { restBaseUrl, showSnackbar, showToast, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
21
- import { useFetchSearchResults, processBillItems } from '../billing.resource';
20
+ import { TrashCan } from '@carbon/react/icons';
22
21
  import { mutate } from 'swr';
23
- import { convertToCurrency } from '../helpers';
22
+ import { useTranslation } from 'react-i18next';
24
23
  import { z } from 'zod';
25
- import { TrashCan } from '@carbon/react/icons';
26
- import fuzzy from 'fuzzy';
27
- import { type BillabeItem } from '../types';
24
+ import { showSnackbar, showToast, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
28
25
  import { apiBasePath } from '../constants';
26
+ import { convertToCurrency } from '../helpers';
27
+ import { type BillabeItem } from '../types';
28
+ import { useFetchSearchResults, processBillItems } from '../billing.resource';
29
+ import styles from './billing-form.scss';
29
30
 
30
31
  type BillingFormProps = {
31
32
  patientUuid: string;
@@ -34,22 +35,24 @@ type BillingFormProps = {
34
35
 
35
36
  const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }) => {
36
37
  const { t } = useTranslation();
37
- const { defaultCurrency } = useConfig();
38
+ const { defaultCurrency, postBilledItems } = useConfig();
38
39
  const isTablet = useLayoutType() === 'tablet';
39
40
 
40
41
  const [grandTotal, setGrandTotal] = useState(0);
41
42
  const [searchOptions, setSearchOptions] = useState([]);
42
43
  const [billItems, setBillItems] = useState([]);
43
- const [searchVal, setSearchVal] = useState('');
44
44
  const [category, setCategory] = useState('');
45
45
  const [saveDisabled, setSaveDisabled] = useState<boolean>(false);
46
46
  const [isSubmitting, setIsSubmitting] = useState(false);
47
47
  const [addedItems, setAddedItems] = useState([]);
48
48
  const [searchTerm, setSearchTerm] = useState('');
49
49
  const debouncedSearchTerm = useDebounce(searchTerm);
50
+ const [disableSearch, setDisableSearch] = useState<boolean>(true);
50
51
 
51
52
  const toggleSearch = (choiceSelected) => {
52
- (document.getElementById('searchField') as HTMLInputElement).disabled = false;
53
+ if (!isEmpty(choiceSelected)) {
54
+ setDisableSearch(false);
55
+ }
53
56
  setCategory(choiceSelected === 'Stock Item' ? 'Stock Item' : 'Service');
54
57
  };
55
58
 
@@ -82,7 +85,6 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
82
85
 
83
86
  const anyInvalidQuantity = updatedItems.some((item) => item.Qnty <= 0);
84
87
 
85
- setBillItems(updatedItems);
86
88
  setSaveDisabled(!isValid || anyInvalidQuantity);
87
89
 
88
90
  const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0);
@@ -120,7 +122,6 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
120
122
  }
121
123
 
122
124
  setBillItems(updatedItems);
123
- setSearchOptions([]);
124
125
  calculateTotalAfterAddBillItem(updatedItems);
125
126
  (document.getElementById('searchField') as HTMLInputElement).value = '';
126
127
  };
@@ -169,7 +170,7 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
169
170
  .sort((r1, r2) => r1.score - r2.score)
170
171
  .map((result) => result.original)
171
172
  : searchOptions;
172
- }, [debouncedSearchTerm, data, billItems]);
173
+ }, [debouncedSearchTerm, isLoading, error, data, billItems, searchOptions]);
173
174
 
174
175
  useEffect(() => {
175
176
  setSearchOptions(filterItems);
@@ -178,8 +179,8 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
178
179
  const postBillItems = () => {
179
180
  setIsSubmitting(true);
180
181
  const bill = {
181
- cashPoint: '54065383-b4d4-42d2-af4d-d250a1fd2590',
182
- cashier: 'f9badd80-ab76-11e2-9e96-0800200c9a66',
182
+ cashPoint: postBilledItems.cashPoint,
183
+ cashier: postBilledItems.cashier,
183
184
  lineItems: [],
184
185
  payments: [],
185
186
  patient: patientUuid,
@@ -187,11 +188,11 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
187
188
  };
188
189
 
189
190
  billItems.forEach((item) => {
190
- let lineItem: any = {
191
+ const lineItem: any = {
191
192
  quantity: parseInt(item.Qnty),
192
193
  price: item.Price,
193
194
  priceName: 'Default',
194
- priceUuid: '7b9171ac-d3c1-49b4-beff-c9902aee5245',
195
+ priceUuid: postBilledItems.priceUuid,
195
196
  lineItemOrder: 0,
196
197
  paymentStatus: 'PENDING',
197
198
  };
@@ -226,6 +227,10 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
226
227
  );
227
228
  };
228
229
 
230
+ const handleClearSearchTerm = () => {
231
+ setSearchOptions([]);
232
+ };
233
+
229
234
  return (
230
235
  <Form className={styles.form}>
231
236
  <div className={styles.grid}>
@@ -244,12 +249,13 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
244
249
  <Search
245
250
  size="lg"
246
251
  id="searchField"
247
- disabled
252
+ disabled={disableSearch}
248
253
  closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
249
254
  className={styles.mt2}
250
255
  placeholder={t('searchItems', 'Search items and services')}
251
256
  labelText={t('searchItems', 'Search items and services')}
252
257
  onKeyUp={handleSearchTermChange}
258
+ onClear={handleClearSearchTerm}
253
259
  />
254
260
  </Stack>
255
261
  <Stack>
@@ -261,7 +267,7 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
261
267
  id={row.uuid}
262
268
  onClick={(e) => addItemToBill(e, row.uuid, row.Item, row.category, row.Price)}
263
269
  style={{ background: 'inherit', color: 'black' }}>
264
- {row.Item} Qnty.{row.Qnty} Ksh.{row.Price}
270
+ {row.Item} Qnty.{row.Qnty} {defaultCurrency}.{row.Price}
265
271
  </Button>
266
272
  </li>
267
273
  ))}
@@ -330,7 +336,7 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
330
336
  className={styles.button}
331
337
  kind="primary"
332
338
  onClick={postBillItems}
333
- disabled={isSubmitting && saveDisabled}
339
+ disabled={isSubmitting || saveDisabled}
334
340
  type="submit">
335
341
  {isSubmitting ? (
336
342
  <InlineLoading description={t('saving', 'Saving') + '...'} />
@@ -1,9 +1,8 @@
1
1
  @use '@carbon/layout';
2
- @use '@carbon/styles/scss/spacing';
3
- @import '~@openmrs/esm-styleguide/src/vars';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
3
 
5
4
  .tablet {
6
- padding: 1.5rem spacing.$spacing-05;
5
+ padding: layout.$spacing-06 layout.$spacing-05;
7
6
  background-color: $ui-02;
8
7
  }
9
8
 
@@ -12,14 +11,14 @@
12
11
  }
13
12
 
14
13
  .button {
15
- height: 4rem;
14
+ height: layout.$spacing-10;
16
15
  display: flex;
17
16
  align-content: flex-start;
18
17
  align-items: baseline;
19
18
  min-width: 50%;
20
19
 
21
20
  :global(.cds--inline-loading) {
22
- min-height: 1rem !important;
21
+ min-height: layout.$spacing-05 !important;
23
22
  }
24
23
 
25
24
  :global(.cds--inline-loading__text) {
@@ -28,7 +27,7 @@
28
27
  }
29
28
 
30
29
  .mt2 {
31
- margin-top: spacing.$spacing-07;
30
+ margin-top: layout.$spacing-07;
32
31
  }
33
32
 
34
33
  .searchContent {
@@ -73,16 +72,16 @@
73
72
  }
74
73
 
75
74
  .grid {
76
- margin: spacing.$spacing-05;
75
+ margin: layout.$spacing-05;
77
76
  }
78
77
 
79
78
  .row {
80
- margin: spacing.$spacing-03 0rem 0rem;
79
+ margin: layout.$spacing-03 0rem 0rem;
81
80
  display: flex;
82
81
  flex-flow: row wrap;
83
- gap: spacing.$spacing-05;
82
+ gap: layout.$spacing-05;
84
83
  }
85
84
 
86
85
  .spacer {
87
- margin-top: spacing.$spacing-05;
86
+ margin-top: layout.$spacing-05;
88
87
  }
@@ -1,12 +1,12 @@
1
- import React from 'react';
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
2
  import { z } from 'zod';
3
3
  import { zodResolver } from '@hookform/resolvers/zod';
4
4
  import { Controller, useForm } from 'react-hook-form';
5
5
  import { useTranslation } from 'react-i18next';
6
- import { TextInput, InlineLoading, ComboBox, RadioButtonGroup, RadioButton } from '@carbon/react';
6
+ import { ComboBox, InlineLoading, RadioButton, RadioButtonGroup, TextInput } from '@carbon/react';
7
+ import { useConfig } from '@openmrs/esm-framework';
7
8
  import { usePaymentMethods } from '../billing-form.resource';
8
9
  import styles from './visit-attributes-form.scss';
9
- import { useConfig } from '@openmrs/esm-framework';
10
10
 
11
11
  type VisitAttributesFormProps = {
12
12
  setAttributes: (state) => void;
@@ -31,12 +31,13 @@ const visitAttributesFormSchema = z.object({
31
31
 
32
32
  const VisitAttributesForm: React.FC<VisitAttributesFormProps> = ({ setAttributes, setPaymentMethod }) => {
33
33
  const { t } = useTranslation();
34
+ const { patientCatergory, catergoryConcepts, nonPayingPatientCategories } = useConfig();
34
35
  const { control, getValues, watch } = useForm<VisitAttributesFormValue>({
35
36
  mode: 'all',
36
37
  defaultValues: {},
37
38
  resolver: zodResolver(visitAttributesFormSchema),
38
39
  });
39
- const { patientCatergory, catergoryConcepts } = useConfig();
40
+
40
41
  const [paymentDetails, paymentMethods, insuranceSchema, policyNumber, patientCategory] = watch([
41
42
  'paymentDetails',
42
43
  'paymentMethods',
@@ -46,13 +47,17 @@ const VisitAttributesForm: React.FC<VisitAttributesFormProps> = ({ setAttributes
46
47
  ]);
47
48
 
48
49
  const { paymentModes, isLoading: isLoadingPaymentModes } = usePaymentMethods();
49
- React.useEffect(() => {
50
- setAttributes(createVisitAttributesPayload());
51
- }, [paymentDetails, paymentMethods, insuranceSchema, policyNumber, patientCategory]);
50
+ const patientCategoryOptions = useMemo(() => {
51
+ return Object.entries(nonPayingPatientCategories ?? {}).map(([key, uuid]) => ({
52
+ text: key,
53
+ uuid,
54
+ }));
55
+ }, [nonPayingPatientCategories]);
52
56
 
53
- const createVisitAttributesPayload = () => {
57
+ const createVisitAttributesPayload = useCallback(() => {
54
58
  const { paymentDetails, paymentMethods, insuranceScheme, policyNumber, patientCategory } = getValues();
55
- setPaymentMethod(paymentMethods);
59
+ setPaymentMethod?.(paymentMethods);
60
+
56
61
  const formPayload = [
57
62
  { uuid: patientCatergory.paymentDetails, value: paymentDetails },
58
63
  { uuid: patientCatergory.paymentMethods, value: paymentMethods },
@@ -60,6 +65,7 @@ const VisitAttributesForm: React.FC<VisitAttributesFormProps> = ({ setAttributes
60
65
  { uuid: patientCatergory.policyNumber, value: policyNumber },
61
66
  { uuid: patientCatergory.patientCategory, value: patientCategory },
62
67
  ];
68
+
63
69
  const visitAttributesPayload = formPayload.filter(
64
70
  (item) => item.value !== undefined && item.value !== null && item.value !== '',
65
71
  );
@@ -67,7 +73,27 @@ const VisitAttributesForm: React.FC<VisitAttributesFormProps> = ({ setAttributes
67
73
  attributeType: value.uuid,
68
74
  value: value.value,
69
75
  }));
70
- };
76
+ }, [
77
+ getValues,
78
+ patientCatergory.insuranceScheme,
79
+ patientCatergory.patientCategory,
80
+ patientCatergory.paymentDetails,
81
+ patientCatergory.paymentMethods,
82
+ patientCatergory.policyNumber,
83
+ setPaymentMethod,
84
+ ]);
85
+
86
+ useEffect(() => {
87
+ setAttributes(createVisitAttributesPayload());
88
+ }, [
89
+ paymentDetails,
90
+ paymentMethods,
91
+ insuranceSchema,
92
+ policyNumber,
93
+ patientCategory,
94
+ setAttributes,
95
+ createVisitAttributesPayload,
96
+ ]);
71
97
 
72
98
  if (isLoadingPaymentModes) {
73
99
  return (
@@ -156,12 +182,10 @@ const VisitAttributesForm: React.FC<VisitAttributesFormProps> = ({ setAttributes
156
182
  className={styles.sectionField}
157
183
  onChange={({ selectedItem }) => field.onChange(selectedItem?.uuid)}
158
184
  id="patientCategory"
159
- items={[
160
- { text: 'Child under 5', uuid: catergoryConcepts.childUnder5 },
161
- { text: 'Student', uuid: catergoryConcepts.student },
162
- ]}
185
+ items={patientCategoryOptions}
163
186
  itemToString={(item) => (item ? item.text : '')}
164
187
  titleText={t('patientCategory', 'Patient category')}
188
+ placeholder={t('selectPatientCategory', 'Select patient category')}
165
189
  />
166
190
  )}
167
191
  />