@openmrs/esm-billing-app 1.0.2-pre.74 → 1.0.2-pre.749

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 (196) hide show
  1. package/README.md +55 -9
  2. package/__mocks__/bills.mock.ts +12 -0
  3. package/__mocks__/react-i18next.js +6 -5
  4. package/dist/1119.js +1 -1
  5. package/dist/1146.js +1 -2
  6. package/dist/1146.js.map +1 -1
  7. package/dist/1197.js +1 -1
  8. package/dist/1856.js +1 -0
  9. package/dist/1856.js.map +1 -0
  10. package/dist/2146.js +1 -1
  11. package/dist/2177.js +2 -0
  12. package/dist/2177.js.LICENSE.txt +9 -0
  13. package/dist/2177.js.map +1 -0
  14. package/dist/2524.js +1 -0
  15. package/dist/2524.js.map +1 -0
  16. package/dist/2690.js +1 -1
  17. package/dist/3041.js +1 -0
  18. package/dist/3041.js.map +1 -0
  19. package/dist/3099.js +1 -1
  20. package/dist/3584.js +1 -1
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4225.js +1 -0
  24. package/dist/4225.js.map +1 -0
  25. package/dist/4300.js +1 -1
  26. package/dist/4335.js +1 -1
  27. package/dist/4618.js +1 -1
  28. package/dist/4652.js +1 -1
  29. package/dist/4724.js +1 -0
  30. package/dist/4724.js.map +1 -0
  31. package/dist/4739.js +1 -1
  32. package/dist/4739.js.map +1 -1
  33. package/dist/4944.js +1 -1
  34. package/dist/5173.js +1 -1
  35. package/dist/5241.js +1 -1
  36. package/dist/5422.js +1 -0
  37. package/dist/5422.js.map +1 -0
  38. package/dist/5442.js +1 -1
  39. package/dist/5661.js +1 -1
  40. package/dist/6022.js +1 -1
  41. package/dist/6468.js +1 -1
  42. package/dist/6540.js +1 -1
  43. package/dist/6540.js.map +1 -1
  44. package/dist/6606.js +1 -0
  45. package/dist/6606.js.map +1 -0
  46. package/dist/6679.js +1 -1
  47. package/dist/6840.js +1 -1
  48. package/dist/6859.js +1 -1
  49. package/dist/7097.js +1 -1
  50. package/dist/7159.js +1 -1
  51. package/dist/723.js +1 -1
  52. package/dist/7452.js +2 -0
  53. package/dist/7452.js.map +1 -0
  54. package/dist/7617.js +1 -1
  55. package/dist/795.js +1 -0
  56. package/dist/8163.js +1 -1
  57. package/dist/8349.js +1 -1
  58. package/dist/8618.js +1 -1
  59. package/dist/890.js +1 -1
  60. package/dist/8930.js +2 -0
  61. package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
  62. package/dist/8930.js.map +1 -0
  63. package/dist/9214.js +1 -1
  64. package/dist/942.js +1 -0
  65. package/dist/942.js.map +1 -0
  66. package/dist/9538.js +1 -1
  67. package/dist/9569.js +1 -0
  68. package/dist/961.js +1 -1
  69. package/dist/961.js.map +1 -1
  70. package/dist/986.js +1 -1
  71. package/dist/9879.js +1 -1
  72. package/dist/9895.js +1 -1
  73. package/dist/9900.js +1 -1
  74. package/dist/9913.js +1 -1
  75. package/dist/main.js +1 -1
  76. package/dist/main.js.map +1 -1
  77. package/dist/openmrs-esm-billing-app.js +1 -1
  78. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +381 -231
  79. package/dist/openmrs-esm-billing-app.js.map +1 -1
  80. package/dist/routes.json +1 -1
  81. package/e2e/README.md +19 -18
  82. package/e2e/specs/sample-test.spec.ts +0 -1
  83. package/package.json +10 -10
  84. package/src/bill-history/bill-history.component.tsx +17 -25
  85. package/src/bill-history/bill-history.scss +4 -94
  86. package/src/bill-history/bill-history.test.tsx +37 -78
  87. package/src/bill-item-actions/bill-item-actions.scss +0 -4
  88. package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +58 -56
  89. package/src/bill-item-actions/edit-bill-item.test.tsx +22 -24
  90. package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
  91. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  92. package/src/billable-services/billable-service.resource.ts +4 -3
  93. package/src/billable-services/billable-services-home.component.tsx +1 -1
  94. package/src/billable-services/billable-services.component.tsx +115 -132
  95. package/src/billable-services/billable-services.scss +3 -0
  96. package/src/billable-services/billable-services.test.tsx +2 -45
  97. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  98. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +17 -192
  99. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  100. package/src/billable-services/create-edit/add-billable-service.component.tsx +28 -24
  101. package/src/billable-services/create-edit/add-billable-service.scss +2 -5
  102. package/src/billable-services/create-edit/add-billable-service.test.tsx +6 -6
  103. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +50 -0
  104. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  105. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  106. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  107. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -1
  108. package/src/billing-form/billing-checkin-form.component.tsx +2 -3
  109. package/src/billing-form/billing-checkin-form.test.tsx +0 -2
  110. package/src/billing-form/billing-form.component.tsx +210 -268
  111. package/src/billing-form/billing-form.scss +143 -0
  112. package/src/billing.resource.ts +16 -19
  113. package/src/bills-table/bills-table.test.tsx +97 -53
  114. package/src/config-schema.ts +52 -18
  115. package/src/dashboard.meta.ts +4 -2
  116. package/src/helpers/functions.ts +5 -4
  117. package/src/index.ts +17 -6
  118. package/src/invoice/invoice-table.component.tsx +24 -54
  119. package/src/invoice/invoice-table.scss +1 -5
  120. package/src/invoice/invoice-table.test.tsx +21 -47
  121. package/src/invoice/invoice.component.tsx +36 -29
  122. package/src/invoice/invoice.scss +7 -4
  123. package/src/invoice/invoice.test.tsx +22 -48
  124. package/src/invoice/payments/payment-form/payment-form.component.tsx +2 -9
  125. package/src/invoice/payments/payment-form/payment-form.test.tsx +14 -46
  126. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  127. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  128. package/src/invoice/payments/payments.component.tsx +16 -27
  129. package/src/invoice/payments/{payments.component.test.tsx → payments.test.tsx} +24 -10
  130. package/src/invoice/payments/utils.ts +4 -22
  131. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  132. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  133. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  134. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  135. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  136. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  137. package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
  138. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  139. package/src/modal/require-payment-modal.test.tsx +25 -20
  140. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  141. package/src/routes.json +22 -2
  142. package/src/types/index.ts +13 -12
  143. package/translations/am.json +33 -16
  144. package/translations/ar.json +33 -16
  145. package/translations/ar_SY.json +33 -16
  146. package/translations/bn.json +38 -21
  147. package/translations/de.json +33 -16
  148. package/translations/en.json +33 -16
  149. package/translations/en_US.json +187 -0
  150. package/translations/es.json +48 -31
  151. package/translations/es_MX.json +33 -16
  152. package/translations/fr.json +47 -30
  153. package/translations/he.json +33 -16
  154. package/translations/hi.json +33 -16
  155. package/translations/hi_IN.json +33 -16
  156. package/translations/id.json +180 -163
  157. package/translations/it.json +70 -53
  158. package/translations/ka.json +187 -0
  159. package/translations/km.json +33 -16
  160. package/translations/ku.json +33 -16
  161. package/translations/ky.json +33 -16
  162. package/translations/lg.json +33 -16
  163. package/translations/ne.json +33 -16
  164. package/translations/pl.json +33 -16
  165. package/translations/pt.json +33 -16
  166. package/translations/pt_BR.json +33 -16
  167. package/translations/qu.json +33 -16
  168. package/translations/ro_RO.json +182 -165
  169. package/translations/ru_RU.json +33 -16
  170. package/translations/si.json +33 -16
  171. package/translations/sw.json +33 -16
  172. package/translations/sw_KE.json +33 -16
  173. package/translations/tr.json +33 -16
  174. package/translations/tr_TR.json +33 -16
  175. package/translations/uk.json +33 -16
  176. package/translations/uz.json +33 -16
  177. package/translations/uz@Latn.json +33 -16
  178. package/translations/uz_UZ.json +33 -16
  179. package/translations/vi.json +33 -16
  180. package/translations/zh.json +33 -16
  181. package/translations/zh_CN.json +91 -74
  182. package/dist/1146.js.LICENSE.txt +0 -21
  183. package/dist/2352.js +0 -1
  184. package/dist/2352.js.map +0 -1
  185. package/dist/246.js +0 -1
  186. package/dist/246.js.map +0 -1
  187. package/dist/6525.js +0 -2
  188. package/dist/6525.js.map +0 -1
  189. package/dist/8556.js +0 -2
  190. package/dist/8556.js.map +0 -1
  191. package/dist/8638.js +0 -1
  192. package/dist/8638.js.map +0 -1
  193. package/dist/9968.js +0 -1
  194. package/dist/9968.js.map +0 -1
  195. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  196. /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
@@ -2,59 +2,23 @@ import React, { useState, useEffect, useCallback } from 'react';
2
2
  import {
3
3
  Button,
4
4
  DataTable,
5
- TableContainer,
6
5
  Table,
7
- TableHead,
8
- TableRow,
9
- TableHeader,
10
6
  TableBody,
11
7
  TableCell,
12
- Modal,
13
- TextInput,
14
- OverflowMenu,
15
- OverflowMenuItem,
16
- Dropdown,
8
+ TableContainer,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
17
12
  } from '@carbon/react';
18
13
  import { Add } from '@carbon/react/icons';
19
14
  import { useTranslation } from 'react-i18next';
20
- import { useForm, Controller } from 'react-hook-form';
21
- import { z } from 'zod';
22
- import { zodResolver } from '@hookform/resolvers/zod';
23
- import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
15
+ import { showSnackbar, openmrsFetch, restBaseUrl, showModal, getCoreTranslation } from '@openmrs/esm-framework';
24
16
  import { CardHeader } from '@openmrs/esm-patient-common-lib';
25
17
  import styles from './cash-point-configuration.scss';
26
18
 
27
- // Validation schema
28
- const cashPointSchema = z.object({
29
- name: z.string().min(1, 'Cash Point Name is required'),
30
- uuid: z
31
- .string()
32
- .min(1, 'UUID is required')
33
- .regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, 'Invalid UUID format'),
34
- location: z.string().min(1, 'Location is required'),
35
- });
36
-
37
- type CashPointFormValues = z.infer<typeof cashPointSchema>;
38
-
39
19
  const CashPointConfiguration: React.FC = () => {
40
20
  const { t } = useTranslation();
41
21
  const [cashPoints, setCashPoints] = useState([]);
42
- const [locations, setLocations] = useState([]);
43
- const [isModalOpen, setIsModalOpen] = useState(false);
44
-
45
- const {
46
- control,
47
- handleSubmit,
48
- reset,
49
- formState: { errors, isSubmitting },
50
- } = useForm<CashPointFormValues>({
51
- resolver: zodResolver(cashPointSchema),
52
- defaultValues: {
53
- name: '',
54
- uuid: '',
55
- location: '',
56
- },
57
- });
58
22
 
59
23
  const fetchCashPoints = useCallback(async () => {
60
24
  try {
@@ -62,7 +26,7 @@ const CashPointConfiguration: React.FC = () => {
62
26
  setCashPoints(response.data.results || []);
63
27
  } catch (err) {
64
28
  showSnackbar({
65
- title: t('error', 'Error'),
29
+ title: getCoreTranslation('error'),
66
30
  subtitle: t('errorFetchingCashPoints', 'An error occurred while fetching cash points.'),
67
31
  kind: 'error',
68
32
  isLowContrast: false,
@@ -70,87 +34,14 @@ const CashPointConfiguration: React.FC = () => {
70
34
  }
71
35
  }, [t]);
72
36
 
73
- const fetchLocations = useCallback(async () => {
74
- try {
75
- const response = await openmrsFetch(`${restBaseUrl}/location?v=default`);
76
- const allLocations = response.data.results.map((loc) => ({
77
- id: loc.uuid,
78
- label: loc.display,
79
- }));
80
- setLocations(allLocations);
81
- } catch (err) {
82
- showSnackbar({
83
- title: t('error', 'Error'),
84
- subtitle: t('errorFetchingLocations', 'An error occurred while fetching locations.'),
85
- kind: 'error',
86
- isLowContrast: false,
87
- });
88
- }
89
- }, [t]);
90
-
91
37
  useEffect(() => {
92
38
  fetchCashPoints();
93
- fetchLocations();
94
- }, [fetchCashPoints, fetchLocations]);
95
-
96
- const onSubmit = async (data: CashPointFormValues) => {
97
- const isDuplicate = cashPoints.some(
98
- (point) => point.name.toLowerCase() === data.name.toLowerCase() || point.uuid === data.uuid,
99
- );
39
+ }, [fetchCashPoints]);
100
40
 
101
- if (isDuplicate) {
102
- showSnackbar({
103
- title: t('error', 'Error'),
104
- subtitle: t(
105
- 'duplicateCashPointError',
106
- 'A cash point with the same name or UUID already exists. Please use a unique name and UUID.',
107
- ),
108
- kind: 'error',
109
- isLowContrast: false,
110
- });
111
- return;
112
- }
113
-
114
- try {
115
- const response = await openmrsFetch(`${restBaseUrl}/billing/cashPoint`, {
116
- method: 'POST',
117
- headers: {
118
- 'Content-Type': 'application/json',
119
- },
120
- body: {
121
- name: data.name,
122
- uuid: data.uuid,
123
- location: { uuid: data.location },
124
- },
125
- });
126
-
127
- if (response.ok) {
128
- showSnackbar({
129
- title: t('success', 'Success'),
130
- subtitle: t('cashPointSaved', 'Cash point was successfully saved.'),
131
- kind: 'success',
132
- });
133
-
134
- setIsModalOpen(false);
135
- reset({ name: '', uuid: '', location: '' });
136
- fetchCashPoints();
137
- } else {
138
- const errorData = response.data || {};
139
- showSnackbar({
140
- title: t('error', 'Error'),
141
- subtitle: errorData.message || t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
142
- kind: 'error',
143
- isLowContrast: false,
144
- });
145
- }
146
- } catch (err) {
147
- showSnackbar({
148
- title: t('error', 'Error'),
149
- subtitle: t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
150
- kind: 'error',
151
- isLowContrast: false,
152
- });
153
- }
41
+ const handleAddCashPoint = () => {
42
+ showModal('add-cash-point-modal', {
43
+ onCashPointAdded: fetchCashPoints,
44
+ });
154
45
  };
155
46
 
156
47
  const rowData = cashPoints.map((point) => ({
@@ -164,18 +55,17 @@ const CashPointConfiguration: React.FC = () => {
164
55
  { key: 'name', header: t('name', 'Name') },
165
56
  { key: 'uuid', header: t('uuid', 'UUID') },
166
57
  { key: 'location', header: t('location', 'Location') },
167
- { key: 'actions', header: t('actions', 'Actions') },
168
58
  ];
169
59
 
170
60
  return (
171
61
  <div className={styles.container}>
172
62
  <div className={styles.card}>
173
63
  <CardHeader title={t('cashPointHistory', 'Cash Point History')}>
174
- <Button renderIcon={Add} onClick={() => setIsModalOpen(true)} kind="ghost">
175
- {t('addCashPoint', 'Add New Cash Point')}
64
+ <Button renderIcon={Add} onClick={handleAddCashPoint} kind="ghost">
65
+ {t('addNewCashPoint', 'Add New Cash Point')}
176
66
  </Button>
177
67
  </CardHeader>
178
- <div className={styles.billHistoryContainer}>
68
+ <div>
179
69
  <DataTable rows={rowData} headers={headerData} isSortable size="lg">
180
70
  {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
181
71
  <TableContainer>
@@ -192,17 +82,9 @@ const CashPointConfiguration: React.FC = () => {
192
82
  <TableBody>
193
83
  {rows.map((row) => (
194
84
  <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 itemText={t('delete', 'Delete')} disabled />
202
- </OverflowMenu>
203
- </TableCell>
204
- ),
205
- )}
85
+ {row.cells.map((cell) => (
86
+ <TableCell key={cell.id}>{cell.value}</TableCell>
87
+ ))}
206
88
  </TableRow>
207
89
  ))}
208
90
  </TableBody>
@@ -212,63 +94,6 @@ const CashPointConfiguration: React.FC = () => {
212
94
  </DataTable>
213
95
  </div>
214
96
  </div>
215
-
216
- {/* Modal for Adding New Cash Point */}
217
- <Modal
218
- open={isModalOpen}
219
- modalHeading={t('addCashPoint', 'Add Cash Point')}
220
- onRequestClose={() => setIsModalOpen(false)}
221
- onRequestSubmit={handleSubmit(onSubmit)}
222
- primaryButtonText={t('save', 'Save')}
223
- secondaryButtonText={t('cancel', 'Cancel')}
224
- isPrimaryButtonDisabled={isSubmitting}>
225
- <form>
226
- <Controller
227
- name="name"
228
- control={control}
229
- render={({ field }) => (
230
- <TextInput
231
- id="cash-point-name"
232
- labelText={t('cashPointName', 'Cash Point Name')}
233
- placeholder={t('cashPointNamePlaceholder', 'e.g., Pharmacy Cash Point')}
234
- invalid={!!errors.name}
235
- invalidText={errors.name?.message}
236
- {...field}
237
- />
238
- )}
239
- />
240
- <Controller
241
- name="uuid"
242
- control={control}
243
- render={({ field }) => (
244
- <TextInput
245
- id="cash-point-uuid"
246
- labelText={t('cashPointUuid', 'Cash Point UUID')}
247
- placeholder={t('cashPointUuidPlaceholder', 'Enter UUID')}
248
- invalid={!!errors.uuid}
249
- invalidText={errors.uuid?.message}
250
- {...field}
251
- />
252
- )}
253
- />
254
- <Controller
255
- name="location"
256
- control={control}
257
- render={({ field }) => (
258
- <Dropdown
259
- id="cash-point-location"
260
- label={t('location', 'Select Location')}
261
- titleText={t('cashPointLocation', 'Cash Point Location')}
262
- items={locations}
263
- selectedItem={locations.find((loc) => loc.id === field.value)}
264
- onChange={({ selectedItem }) => field.onChange(selectedItem?.id)}
265
- invalid={!!errors.location}
266
- invalidText={errors.location?.message}
267
- />
268
- )}
269
- />
270
- </form>
271
- </Modal>
272
97
  </div>
273
98
  );
274
99
  };
@@ -13,11 +13,7 @@
13
13
  padding: layout.$spacing-05;
14
14
  }
15
15
 
16
- .billHistoryContainer {
17
- margin-top: layout.$spacing-05;
18
- }
19
-
20
16
  .table {
21
17
  width: 100%;
22
18
  table-layout: auto;
23
- }
19
+ }
@@ -16,9 +16,9 @@ import { Controller, useFieldArray, useForm } from 'react-hook-form';
16
16
  import { useTranslation } from 'react-i18next';
17
17
  import { z } from 'zod';
18
18
  import { zodResolver } from '@hookform/resolvers/zod';
19
- import { navigate, showSnackbar, useDebounce, useLayoutType } from '@openmrs/esm-framework';
19
+ import { getCoreTranslation, navigate, showSnackbar, useDebounce, useLayoutType } from '@openmrs/esm-framework';
20
20
  import {
21
- createBillableSerice,
21
+ createBillableService,
22
22
  updateBillableService,
23
23
  useConceptsSearch,
24
24
  usePaymentModes,
@@ -32,10 +32,6 @@ type PaymentMode = {
32
32
  price: string | number;
33
33
  };
34
34
 
35
- type PaymentModeFormValue = {
36
- payment: Array<PaymentMode>;
37
- };
38
-
39
35
  const servicePriceSchema = z.object({
40
36
  paymentMode: z.string().refine((value) => !!value, 'Payment method is required'),
41
37
  price: z.union([
@@ -50,7 +46,12 @@ const paymentFormSchema = z.object({
50
46
 
51
47
  const DEFAULT_PAYMENT_OPTION = { paymentMode: '', price: 0 };
52
48
 
53
- const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }> = ({ editingService, onClose }) => {
49
+ const AddBillableService: React.FC<{
50
+ editingService?: any;
51
+ onClose: () => void;
52
+ onServiceUpdated?: () => void;
53
+ isModal?: boolean;
54
+ }> = ({ editingService, onClose, onServiceUpdated, isModal = false }) => {
54
55
  const { t } = useTranslation();
55
56
 
56
57
  const { paymentModes, isLoading: isLoadingPaymentModes } = usePaymentModes();
@@ -138,7 +139,7 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
138
139
 
139
140
  const saveAction = editingService
140
141
  ? updateBillableService(editingService.uuid, payload)
141
- : createBillableSerice(payload);
142
+ : createBillableService(payload);
142
143
 
143
144
  saveAction.then(
144
145
  (resp) => {
@@ -150,6 +151,9 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
150
151
  kind: 'success',
151
152
  timeoutInMs: 3000,
152
153
  });
154
+ if (onServiceUpdated) {
155
+ onServiceUpdated();
156
+ }
153
157
  onClose();
154
158
  handleNavigateToServiceDashboard();
155
159
  },
@@ -172,19 +176,19 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
172
176
  <InlineLoading
173
177
  status="active"
174
178
  iconDescription={t('loadingDescription', 'Loading')}
175
- description={t('loading', 'Loading data...')}
179
+ description={t('loadingData', 'Loading data') + '...'}
176
180
  />
177
181
  );
178
182
  }
179
183
 
180
184
  return (
181
- <Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
185
+ <Form id="billable-service-form" className={styles.form} onSubmit={handleSubmit(onSubmit)}>
182
186
  <h4>
183
187
  {editingService
184
188
  ? t('editBillableServices', 'Edit Billable Services')
185
189
  : t('addBillableServices', 'Add Billable Services')}
186
190
  </h4>
187
- <section className={styles.section}>
191
+ <section>
188
192
  <Layer>
189
193
  <TextInput
190
194
  id="serviceName"
@@ -211,7 +215,7 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
211
215
  )}
212
216
  </Layer>
213
217
  </section>
214
- <section className={styles.section}>
218
+ <section>
215
219
  <Layer>
216
220
  <TextInput
217
221
  id="serviceShortName"
@@ -307,7 +311,7 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
307
311
  );
308
312
  })()}
309
313
  </section>
310
- <section className={styles.section}>
314
+ <section>
311
315
  <Layer>
312
316
  <ComboBox
313
317
  id="serviceType"
@@ -327,9 +331,8 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
327
331
  />
328
332
  </Layer>
329
333
  </section>
330
-
331
334
  <section>
332
- <div className={styles.container}>
335
+ <div>
333
336
  {fields.map((field, index) => (
334
337
  <div key={field.id} className={styles.paymentMethodContainer}>
335
338
  <Controller
@@ -381,15 +384,16 @@ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }
381
384
  {getPaymentErrorMessage() && <div className={styles.errorMessage}>{getPaymentErrorMessage()}</div>}
382
385
  </div>
383
386
  </section>
384
-
385
- <section>
386
- <Button kind="secondary" onClick={onClose}>
387
- {t('cancel', 'Cancel')}
388
- </Button>
389
- <Button type="submit" disabled={!isValid || Object.keys(errors).length > 0}>
390
- {t('save', 'Save')}
391
- </Button>
392
- </section>
387
+ {!isModal && (
388
+ <section>
389
+ <Button kind="secondary" onClick={onClose}>
390
+ {getCoreTranslation('cancel')}
391
+ </Button>
392
+ <Button type="submit" disabled={!isValid || Object.keys(errors).length > 0}>
393
+ {getCoreTranslation('save')}
394
+ </Button>
395
+ </section>
396
+ )}
393
397
  </Form>
394
398
  );
395
399
  };
@@ -8,10 +8,7 @@
8
8
  flex-direction: column;
9
9
  justify-content: space-between;
10
10
  height: 100%;
11
- }
12
-
13
- .section {
14
- margin: layout.$spacing-03;
11
+ margin: layout.$spacing-05;
15
12
  }
16
13
 
17
14
  .sectionTitle {
@@ -99,7 +96,7 @@
99
96
 
100
97
  .conceptLabel {
101
98
  @include type.type-style('label-02');
102
- margin: layout.$spacing-05;
99
+ margin-bottom: layout.$spacing-05;
103
100
  }
104
101
 
105
102
  .errorContainer {
@@ -6,21 +6,21 @@ import {
6
6
  useBillableServices,
7
7
  usePaymentModes,
8
8
  useServiceTypes,
9
- createBillableSerice,
9
+ createBillableService,
10
10
  } from '../billable-service.resource';
11
11
  import AddBillableService from './add-billable-service.component';
12
12
 
13
13
  const mockUseBillableServices = useBillableServices as jest.MockedFunction<typeof useBillableServices>;
14
14
  const mockUsePaymentModes = usePaymentModes as jest.MockedFunction<typeof usePaymentModes>;
15
15
  const mockUseServiceTypes = useServiceTypes as jest.MockedFunction<typeof useServiceTypes>;
16
- const mockCreateBillableSerice = createBillableSerice as jest.MockedFunction<typeof createBillableSerice>;
16
+ const mockcreateBillableService = createBillableService as jest.MockedFunction<typeof createBillableService>;
17
17
  const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
18
18
 
19
19
  jest.mock('../billable-service.resource', () => ({
20
20
  useBillableServices: jest.fn(),
21
21
  usePaymentModes: jest.fn(),
22
22
  useServiceTypes: jest.fn(),
23
- createBillableSerice: jest.fn(),
23
+ createBillableService: jest.fn(),
24
24
  }));
25
25
 
26
26
  const mockPaymentModes = [
@@ -107,13 +107,13 @@ xdescribe('AddBillableService', () => {
107
107
  expect(priceTextInp).toBeInTheDocument();
108
108
  await user.type(priceTextInp, '1000');
109
109
 
110
- mockCreateBillableSerice.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
110
+ mockcreateBillableService.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
111
111
  const saveBtn = screen.getByRole('button', { name: /Save/i });
112
112
  expect(saveBtn).toBeInTheDocument();
113
113
  await user.click(saveBtn);
114
114
 
115
- expect(mockCreateBillableSerice).toHaveBeenCalledTimes(1);
116
- expect(mockCreateBillableSerice).toHaveBeenCalledWith({
115
+ expect(mockcreateBillableService).toHaveBeenCalledTimes(1);
116
+ expect(mockcreateBillableService).toHaveBeenCalledWith({
117
117
  name: 'Test Service Name',
118
118
  shortName: 'Test Short Name',
119
119
  serviceType: undefined,
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import AddBillableService from './add-billable-service.component';
5
+ import { getCoreTranslation } from '@openmrs/esm-framework';
6
+
7
+ interface EditBillableServiceModalProps {
8
+ closeModal: () => void;
9
+ editingService?: any;
10
+ onServiceUpdated: () => void;
11
+ }
12
+
13
+ const EditBillableServiceModal: React.FC<EditBillableServiceModalProps> = ({
14
+ closeModal,
15
+ editingService,
16
+ onServiceUpdated,
17
+ }) => {
18
+ const { t } = useTranslation();
19
+
20
+ return (
21
+ <>
22
+ <ModalHeader closeModal={closeModal} title={t('billableService', 'Billable Service')} />
23
+ <ModalBody>
24
+ <AddBillableService
25
+ editingService={editingService}
26
+ onClose={closeModal}
27
+ onServiceUpdated={onServiceUpdated}
28
+ isModal={true}
29
+ />
30
+ </ModalBody>
31
+ <ModalFooter>
32
+ <Button kind="secondary" onClick={closeModal}>
33
+ {getCoreTranslation('cancel')}
34
+ </Button>
35
+ <Button
36
+ onClick={() => {
37
+ // Trigger form submission programmatically
38
+ const form = document.getElementById('billable-service-form') as HTMLFormElement;
39
+ if (form) {
40
+ form.requestSubmit();
41
+ }
42
+ }}>
43
+ {getCoreTranslation('save')}
44
+ </Button>
45
+ </ModalFooter>
46
+ </>
47
+ );
48
+ };
49
+
50
+ export default EditBillableServiceModal;
@@ -0,0 +1,121 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useForm, Controller } from 'react-hook-form';
4
+ import { z } from 'zod';
5
+ import { zodResolver } from '@hookform/resolvers/zod';
6
+ import { Button, Form, ModalBody, ModalFooter, ModalHeader, Stack, TextInput } from '@carbon/react';
7
+ import { showSnackbar, openmrsFetch, restBaseUrl, getCoreTranslation } from '@openmrs/esm-framework';
8
+
9
+ type PaymentModeFormValues = {
10
+ name: string;
11
+ description: string;
12
+ };
13
+
14
+ interface AddPaymentModeModalProps {
15
+ closeModal: () => void;
16
+ onPaymentModeAdded: () => void;
17
+ }
18
+
19
+ const AddPaymentModeModal: React.FC<AddPaymentModeModalProps> = ({ closeModal, onPaymentModeAdded }) => {
20
+ const { t } = useTranslation();
21
+
22
+ const paymentModeSchema = z.object({
23
+ name: z.string().min(1, t('paymentModeNameRequired', 'Payment Mode Name is required')),
24
+ description: z.string().optional(),
25
+ });
26
+
27
+ const {
28
+ control,
29
+ handleSubmit,
30
+ reset,
31
+ formState: { errors, isSubmitting },
32
+ } = useForm<PaymentModeFormValues>({
33
+ resolver: zodResolver(paymentModeSchema),
34
+ defaultValues: {
35
+ name: '',
36
+ description: '',
37
+ },
38
+ });
39
+
40
+ const onSubmit = async (data: PaymentModeFormValues) => {
41
+ try {
42
+ await openmrsFetch(`${restBaseUrl}/billing/paymentMode`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ },
47
+ body: {
48
+ name: data.name,
49
+ description: data.description || '',
50
+ },
51
+ });
52
+
53
+ showSnackbar({
54
+ title: t('success', 'Success'),
55
+ subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
56
+ kind: 'success',
57
+ });
58
+
59
+ closeModal();
60
+ reset({ name: '', description: '' });
61
+ onPaymentModeAdded();
62
+ } catch (err) {
63
+ showSnackbar({
64
+ title: getCoreTranslation('error'),
65
+ subtitle: err?.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
66
+ kind: 'error',
67
+ isLowContrast: false,
68
+ });
69
+ }
70
+ };
71
+
72
+ return (
73
+ <>
74
+ <ModalHeader closeModal={closeModal} title={t('addPaymentMode', 'Add Payment Mode')} />
75
+ <Form onSubmit={handleSubmit(onSubmit)}>
76
+ <ModalBody>
77
+ <Stack gap={5}>
78
+ <Controller
79
+ name="name"
80
+ control={control}
81
+ render={({ field }) => (
82
+ <TextInput
83
+ id="payment-mode-name"
84
+ labelText={t('paymentModeName', 'Payment Mode Name')}
85
+ placeholder={t('paymentModeNamePlaceholder', 'e.g., Cash, Credit Card')}
86
+ invalid={!!errors.name}
87
+ invalidText={errors.name?.message}
88
+ {...field}
89
+ />
90
+ )}
91
+ />
92
+ <Controller
93
+ name="description"
94
+ control={control}
95
+ render={({ field }) => (
96
+ <TextInput
97
+ id="payment-mode-description"
98
+ labelText={t('description', 'Description')}
99
+ placeholder={t('descriptionPlaceholder', 'e.g., Used for all cash transactions')}
100
+ invalid={!!errors.description}
101
+ invalidText={errors.description?.message}
102
+ {...field}
103
+ />
104
+ )}
105
+ />
106
+ </Stack>
107
+ </ModalBody>
108
+ <ModalFooter>
109
+ <Button kind="secondary" onClick={closeModal}>
110
+ {getCoreTranslation('cancel')}
111
+ </Button>
112
+ <Button type="submit" disabled={isSubmitting}>
113
+ {isSubmitting ? t('saving', 'Saving') + '...' : getCoreTranslation('save')}
114
+ </Button>
115
+ </ModalFooter>
116
+ </Form>
117
+ </>
118
+ );
119
+ };
120
+
121
+ export default AddPaymentModeModal;