@openmrs/esm-billing-app 1.0.2-pre.78 → 1.0.2-pre.786

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 (203) hide show
  1. package/.eslintrc +16 -2
  2. package/README.md +54 -9
  3. package/__mocks__/bills.mock.ts +12 -0
  4. package/__mocks__/react-i18next.js +6 -5
  5. package/dist/1119.js +1 -1
  6. package/dist/1146.js +1 -2
  7. package/dist/1146.js.map +1 -1
  8. package/dist/1197.js +1 -1
  9. package/dist/1856.js +1 -0
  10. package/dist/1856.js.map +1 -0
  11. package/dist/2146.js +1 -1
  12. package/dist/2177.js +2 -0
  13. package/dist/2177.js.LICENSE.txt +9 -0
  14. package/dist/2177.js.map +1 -0
  15. package/dist/2524.js +1 -0
  16. package/dist/2524.js.map +1 -0
  17. package/dist/2690.js +1 -1
  18. package/dist/3041.js +1 -0
  19. package/dist/3041.js.map +1 -0
  20. package/dist/3099.js +1 -1
  21. package/dist/3584.js +1 -1
  22. package/dist/4055.js +1 -1
  23. package/dist/4132.js +1 -1
  24. package/dist/4225.js +1 -0
  25. package/dist/4225.js.map +1 -0
  26. package/dist/4300.js +1 -1
  27. package/dist/4335.js +1 -1
  28. package/dist/4618.js +1 -1
  29. package/dist/4652.js +1 -1
  30. package/dist/4724.js +1 -0
  31. package/dist/4724.js.map +1 -0
  32. package/dist/4739.js +1 -1
  33. package/dist/4739.js.map +1 -1
  34. package/dist/4944.js +1 -1
  35. package/dist/5173.js +1 -1
  36. package/dist/5241.js +1 -1
  37. package/dist/5422.js +1 -0
  38. package/dist/5422.js.map +1 -0
  39. package/dist/5442.js +1 -1
  40. package/dist/5661.js +1 -1
  41. package/dist/6022.js +1 -1
  42. package/dist/6468.js +1 -1
  43. package/dist/6540.js +1 -1
  44. package/dist/6540.js.map +1 -1
  45. package/dist/6606.js +1 -0
  46. package/dist/6606.js.map +1 -0
  47. package/dist/6679.js +1 -1
  48. package/dist/6840.js +1 -1
  49. package/dist/6859.js +1 -1
  50. package/dist/7097.js +1 -1
  51. package/dist/7159.js +1 -1
  52. package/dist/723.js +1 -1
  53. package/dist/7452.js +2 -0
  54. package/dist/7452.js.map +1 -0
  55. package/dist/7617.js +1 -1
  56. package/dist/795.js +1 -1
  57. package/dist/8163.js +1 -1
  58. package/dist/8349.js +1 -1
  59. package/dist/8618.js +1 -1
  60. package/dist/890.js +1 -1
  61. package/dist/8930.js +2 -0
  62. package/dist/{6525.js.LICENSE.txt → 8930.js.LICENSE.txt} +16 -4
  63. package/dist/8930.js.map +1 -0
  64. package/dist/9214.js +1 -1
  65. package/dist/942.js +1 -0
  66. package/dist/942.js.map +1 -0
  67. package/dist/9538.js +1 -1
  68. package/dist/9569.js +1 -1
  69. package/dist/961.js +1 -1
  70. package/dist/961.js.map +1 -1
  71. package/dist/986.js +1 -1
  72. package/dist/9879.js +1 -1
  73. package/dist/9895.js +1 -1
  74. package/dist/9900.js +1 -1
  75. package/dist/9913.js +1 -1
  76. package/dist/main.js +1 -1
  77. package/dist/main.js.map +1 -1
  78. package/dist/openmrs-esm-billing-app.js +1 -1
  79. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +368 -262
  80. package/dist/openmrs-esm-billing-app.js.map +1 -1
  81. package/dist/routes.json +1 -1
  82. package/e2e/README.md +19 -18
  83. package/e2e/core/test.ts +1 -1
  84. package/e2e/fixtures/api.ts +1 -1
  85. package/e2e/specs/sample-test.spec.ts +0 -1
  86. package/e2e/support/github/Dockerfile +1 -1
  87. package/package.json +13 -10
  88. package/src/bill-history/bill-history.component.tsx +17 -25
  89. package/src/bill-history/bill-history.scss +4 -94
  90. package/src/bill-history/bill-history.test.tsx +37 -78
  91. package/src/bill-item-actions/bill-item-actions.scss +0 -4
  92. package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +100 -78
  93. package/src/bill-item-actions/edit-bill-item.test.tsx +116 -31
  94. package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
  95. package/src/billable-services/bill-waiver/patient-bills.component.tsx +3 -3
  96. package/src/billable-services/billable-service.resource.ts +17 -9
  97. package/src/billable-services/billable-services-home.component.tsx +1 -1
  98. package/src/billable-services/billable-services.component.tsx +142 -145
  99. package/src/billable-services/billable-services.scss +3 -0
  100. package/src/billable-services/billable-services.test.tsx +2 -45
  101. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  102. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +18 -192
  103. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  104. package/src/billable-services/create-edit/add-billable-service.component.tsx +345 -298
  105. package/src/billable-services/create-edit/add-billable-service.scss +5 -6
  106. package/src/billable-services/create-edit/add-billable-service.test.tsx +37 -36
  107. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
  108. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  109. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  110. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  111. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  112. package/src/billing-form/billing-checkin-form.component.tsx +2 -3
  113. package/src/billing-form/billing-checkin-form.test.tsx +97 -24
  114. package/src/billing-form/billing-form.component.tsx +216 -269
  115. package/src/billing-form/billing-form.scss +143 -0
  116. package/src/billing.resource.ts +16 -19
  117. package/src/bills-table/bills-table.test.tsx +98 -54
  118. package/src/config-schema.ts +52 -24
  119. package/src/dashboard.meta.ts +4 -2
  120. package/src/helpers/functions.ts +5 -4
  121. package/src/index.ts +17 -6
  122. package/src/invoice/invoice-table.component.tsx +35 -69
  123. package/src/invoice/invoice-table.scss +1 -5
  124. package/src/invoice/invoice-table.test.tsx +273 -62
  125. package/src/invoice/invoice.component.tsx +36 -29
  126. package/src/invoice/invoice.scss +7 -4
  127. package/src/invoice/invoice.test.tsx +324 -120
  128. package/src/invoice/payments/payment-form/payment-form.component.tsx +31 -29
  129. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  130. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  131. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  132. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  133. package/src/invoice/payments/payments.component.tsx +53 -65
  134. package/src/invoice/payments/payments.test.tsx +282 -0
  135. package/src/invoice/payments/utils.ts +5 -23
  136. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  137. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  138. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  139. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  140. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  141. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  142. package/src/invoice/printable-invoice/printable-invoice.component.tsx +19 -33
  143. package/src/left-panel-link.test.tsx +1 -4
  144. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  145. package/src/modal/require-payment-modal.test.tsx +27 -22
  146. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  147. package/src/routes.json +22 -2
  148. package/src/types/index.ts +26 -17
  149. package/translations/am.json +70 -21
  150. package/translations/ar.json +70 -21
  151. package/translations/ar_SY.json +70 -21
  152. package/translations/bn.json +75 -26
  153. package/translations/de.json +70 -21
  154. package/translations/en.json +70 -21
  155. package/translations/en_US.json +70 -21
  156. package/translations/es.json +70 -21
  157. package/translations/es_MX.json +70 -21
  158. package/translations/fr.json +83 -34
  159. package/translations/he.json +70 -21
  160. package/translations/hi.json +70 -21
  161. package/translations/hi_IN.json +70 -21
  162. package/translations/id.json +70 -21
  163. package/translations/it.json +105 -56
  164. package/translations/ka.json +70 -21
  165. package/translations/km.json +70 -21
  166. package/translations/ku.json +70 -21
  167. package/translations/ky.json +70 -21
  168. package/translations/lg.json +70 -21
  169. package/translations/ne.json +70 -21
  170. package/translations/pl.json +70 -21
  171. package/translations/pt.json +70 -21
  172. package/translations/pt_BR.json +70 -21
  173. package/translations/qu.json +70 -21
  174. package/translations/ro_RO.json +214 -165
  175. package/translations/ru_RU.json +70 -21
  176. package/translations/si.json +70 -21
  177. package/translations/sw.json +70 -21
  178. package/translations/sw_KE.json +70 -21
  179. package/translations/tr.json +70 -21
  180. package/translations/tr_TR.json +70 -21
  181. package/translations/uk.json +70 -21
  182. package/translations/uz.json +70 -21
  183. package/translations/uz@Latn.json +70 -21
  184. package/translations/uz_UZ.json +70 -21
  185. package/translations/vi.json +70 -21
  186. package/translations/zh.json +70 -21
  187. package/translations/zh_CN.json +125 -76
  188. package/dist/1146.js.LICENSE.txt +0 -21
  189. package/dist/2352.js +0 -1
  190. package/dist/2352.js.map +0 -1
  191. package/dist/246.js +0 -1
  192. package/dist/246.js.map +0 -1
  193. package/dist/6525.js +0 -2
  194. package/dist/6525.js.map +0 -1
  195. package/dist/8556.js +0 -2
  196. package/dist/8556.js.map +0 -1
  197. package/dist/8638.js +0 -1
  198. package/dist/8638.js.map +0 -1
  199. package/dist/9968.js +0 -1
  200. package/dist/9968.js.map +0 -1
  201. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  202. package/src/invoice/payments/payments.component.test.tsx +0 -121
  203. /package/dist/{8556.js.LICENSE.txt → 7452.js.LICENSE.txt} +0 -0
@@ -6,12 +6,8 @@
6
6
  .form {
7
7
  display: flex;
8
8
  flex-direction: column;
9
- justify-content: space-between;
10
9
  height: 100%;
11
- }
12
-
13
- .section {
14
- margin: layout.$spacing-03;
10
+ margin: layout.$spacing-05;
15
11
  }
16
12
 
17
13
  .sectionTitle {
@@ -99,7 +95,7 @@
99
95
 
100
96
  .conceptLabel {
101
97
  @include type.type-style('label-02');
102
- margin: layout.$spacing-05;
98
+ margin-bottom: layout.$spacing-05;
103
99
  }
104
100
 
105
101
  .errorContainer {
@@ -135,3 +131,6 @@
135
131
  font-size: 0.875rem;
136
132
  }
137
133
 
134
+ .serviceNameLabel {
135
+ @include type.type-style('body-compact-02');
136
+ }
@@ -1,26 +1,29 @@
1
1
  import React from 'react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { render, screen } from '@testing-library/react';
4
- import { type FetchResponse, navigate } from '@openmrs/esm-framework';
4
+ import { navigate, type FetchResponse } from '@openmrs/esm-framework';
5
5
  import {
6
+ createBillableService,
6
7
  useBillableServices,
8
+ useConceptsSearch,
7
9
  usePaymentModes,
8
10
  useServiceTypes,
9
- createBillableSerice,
10
11
  } from '../billable-service.resource';
11
12
  import AddBillableService from './add-billable-service.component';
12
13
 
13
- const mockUseBillableServices = useBillableServices as jest.MockedFunction<typeof useBillableServices>;
14
- const mockUsePaymentModes = usePaymentModes as jest.MockedFunction<typeof usePaymentModes>;
15
- const mockUseServiceTypes = useServiceTypes as jest.MockedFunction<typeof useServiceTypes>;
16
- const mockCreateBillableSerice = createBillableSerice as jest.MockedFunction<typeof createBillableSerice>;
17
- const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
14
+ const mockUseBillableServices = jest.mocked(useBillableServices);
15
+ const mockUsePaymentModes = jest.mocked(usePaymentModes);
16
+ const mockUseServiceTypes = jest.mocked(useServiceTypes);
17
+ const mockCreateBillableService = jest.mocked(createBillableService);
18
+ const mockUseConceptsSearch = jest.mocked(useConceptsSearch);
18
19
 
19
20
  jest.mock('../billable-service.resource', () => ({
20
21
  useBillableServices: jest.fn(),
21
22
  usePaymentModes: jest.fn(),
22
23
  useServiceTypes: jest.fn(),
23
- createBillableSerice: jest.fn(),
24
+ createBillableService: jest.fn(),
25
+ updateBillableService: jest.fn(),
26
+ useConceptsSearch: jest.fn(),
24
27
  }));
25
28
 
26
29
  const mockPaymentModes = [
@@ -49,11 +52,7 @@ const mockServiceTypes = [
49
52
  { uuid: 'a487a743-62ce-4f93-a66b-c5154ee8987d', display: 'Adherence counselling service' },
50
53
  ];
51
54
 
52
- xdescribe('AddBillableService', () => {
53
- beforeEach(() => {
54
- jest.resetAllMocks();
55
- });
56
-
55
+ describe('AddBillableService', () => {
57
56
  test('should render billable services form and generate correct payload', async () => {
58
57
  const user = userEvent.setup();
59
58
  const mockOnClose = jest.fn();
@@ -64,8 +63,9 @@ xdescribe('AddBillableService', () => {
64
63
  mutate: jest.fn(),
65
64
  isValidating: false,
66
65
  });
67
- mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoading: false });
68
- mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoading: false });
66
+ mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoadingPaymentModes: false });
67
+ mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoadingServiceTypes: false });
68
+ mockUseConceptsSearch.mockReturnValue({ searchResults: [], isSearching: false, error: null });
69
69
 
70
70
  render(<AddBillableService onClose={mockOnClose} />);
71
71
 
@@ -84,50 +84,50 @@ xdescribe('AddBillableService', () => {
84
84
  expect(serviceNameTextInp).toHaveValue('Test Service Name');
85
85
  expect(serviceShortNameTextInp).toHaveValue('Test Short Name');
86
86
 
87
- const serviceTypeComboBox = screen.getByRole('combobox', { name: /Service Type/i });
87
+ const serviceTypeComboBox = screen.getByRole('combobox', { name: /Service type/i });
88
88
  expect(serviceTypeComboBox).toBeInTheDocument();
89
89
  await user.click(serviceTypeComboBox);
90
90
  const serviceTypeOptions = screen.getByRole('option', { name: /Lab service/i });
91
91
  expect(serviceTypeOptions).toBeInTheDocument();
92
92
  await user.click(serviceTypeOptions);
93
93
 
94
- const addPaymentMethodBtn = screen.getByRole('button', { name: /Add payment option/i });
95
- expect(addPaymentMethodBtn).toBeInTheDocument();
96
-
97
- await user.click(addPaymentMethodBtn);
98
-
99
- const paymentMethodComboBox = screen.getByRole('combobox', { name: /Payment Mode/i });
100
- expect(paymentMethodComboBox).toBeInTheDocument();
101
- await user.click(paymentMethodComboBox);
94
+ // Fill in the default payment option (first one)
95
+ const paymentMethodComboBoxes = screen.getAllByRole('combobox', { name: /Payment mode/i });
96
+ expect(paymentMethodComboBoxes).toHaveLength(1); // Should have one default
97
+ await user.click(paymentMethodComboBoxes[0]);
102
98
  const paymentMethodOptions = screen.getByRole('option', { name: /Cash/i });
103
99
  expect(paymentMethodOptions).toBeInTheDocument();
104
100
  await user.click(paymentMethodOptions);
105
101
 
106
- const priceTextInp = screen.getByRole('textbox', { name: /Price/i });
102
+ const priceTextInps = screen.getAllByRole('textbox', { name: /Selling Price/i });
103
+ expect(priceTextInps).toHaveLength(1); // Should have one price input for the default payment method
104
+ const priceTextInp = priceTextInps[0];
107
105
  expect(priceTextInp).toBeInTheDocument();
108
106
  await user.type(priceTextInp, '1000');
109
107
 
110
- mockCreateBillableSerice.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
111
- const saveBtn = screen.getByRole('button', { name: /Save/i });
108
+ mockCreateBillableService.mockReturnValue(Promise.resolve({} as FetchResponse<any>));
109
+ const saveBtn = screen.getAllByRole('button').find((btn) => btn.getAttribute('type') === 'submit');
112
110
  expect(saveBtn).toBeInTheDocument();
111
+
113
112
  await user.click(saveBtn);
114
113
 
115
- expect(mockCreateBillableSerice).toHaveBeenCalledTimes(1);
116
- expect(mockCreateBillableSerice).toHaveBeenCalledWith({
114
+ expect(mockCreateBillableService).toHaveBeenCalledTimes(1);
115
+ expect(mockCreateBillableService).toHaveBeenCalledWith({
117
116
  name: 'Test Service Name',
118
117
  shortName: 'Test Short Name',
119
- serviceType: undefined,
118
+ serviceType: 'c9604249-db0a-4d03-b074-fc6bc2fa13e6',
120
119
  servicePrices: [
121
120
  {
122
121
  paymentMode: '63eff7a4-6f82-43c4-a333-dbcc58fe9f74',
123
- price: '01000',
122
+ price: 1000,
124
123
  name: 'Cash',
125
124
  },
126
125
  ],
127
126
  serviceStatus: 'ENABLED',
127
+ concept: undefined,
128
128
  });
129
- expect(mockNavigate).toHaveBeenCalledTimes(1);
130
- expect(mockNavigate).toHaveBeenCalledWith({ to: '/openmrs/spa/billable-services' });
129
+ expect(navigate).toHaveBeenCalledTimes(1);
130
+ expect(navigate).toHaveBeenCalledWith({ to: '/openmrs/spa/billable-services' });
131
131
  });
132
132
 
133
133
  test("should navigate back to billable services dashboard when 'Cancel' button is clicked", async () => {
@@ -140,12 +140,13 @@ xdescribe('AddBillableService', () => {
140
140
  mutate: jest.fn(),
141
141
  isValidating: false,
142
142
  });
143
- mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoading: false });
144
- mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoading: false });
143
+ mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoadingPaymentModes: false });
144
+ mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoadingServiceTypes: false });
145
+ mockUseConceptsSearch.mockReturnValue({ searchResults: [], isSearching: false, error: null });
145
146
 
146
147
  render(<AddBillableService onClose={mockOnClose} />);
147
148
 
148
- const cancelBtn = screen.getByRole('button', { name: /Cancel/i });
149
+ const cancelBtn = screen.getAllByRole('button').find((btn) => btn.className.includes('cds--btn--secondary'));
149
150
  expect(cancelBtn).toBeInTheDocument();
150
151
  await user.click(cancelBtn);
151
152
 
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import { getCoreTranslation } from '@openmrs/esm-framework';
5
+ import { type BillableService } from '../../types';
6
+ import AddBillableService from './add-billable-service.component';
7
+
8
+ interface EditBillableServiceModalProps {
9
+ closeModal: () => void;
10
+ onServiceUpdated: () => void;
11
+ serviceToEdit?: BillableService;
12
+ }
13
+
14
+ const EditBillableServiceModal: React.FC<EditBillableServiceModalProps> = ({
15
+ closeModal,
16
+ serviceToEdit,
17
+ onServiceUpdated,
18
+ }) => {
19
+ const { t } = useTranslation();
20
+
21
+ return (
22
+ <>
23
+ <ModalHeader closeModal={closeModal} title={t('billableService', 'Billable Service')} />
24
+ <ModalBody>
25
+ <AddBillableService
26
+ serviceToEdit={serviceToEdit}
27
+ isModal
28
+ onClose={closeModal}
29
+ onServiceUpdated={onServiceUpdated}
30
+ />
31
+ </ModalBody>
32
+ <ModalFooter>
33
+ <Button kind="secondary" onClick={closeModal}>
34
+ {getCoreTranslation('cancel')}
35
+ </Button>
36
+ <Button
37
+ onClick={() => {
38
+ // Trigger form submission programmatically
39
+ const form = document.getElementById('billable-service-form') as HTMLFormElement;
40
+ if (form) {
41
+ form.requestSubmit();
42
+ }
43
+ }}>
44
+ {getCoreTranslation('save')}
45
+ </Button>
46
+ </ModalFooter>
47
+ </>
48
+ );
49
+ };
50
+
51
+ 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;
@@ -0,0 +1,72 @@
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import { showSnackbar, openmrsFetch, restBaseUrl, getCoreTranslation } from '@openmrs/esm-framework';
5
+
6
+ interface DeletePaymentModeModalProps {
7
+ closeModal: () => void;
8
+ paymentModeUuid: string;
9
+ paymentModeName: string;
10
+ onPaymentModeDeleted: () => void;
11
+ }
12
+
13
+ const DeletePaymentModeModal: React.FC<DeletePaymentModeModalProps> = ({
14
+ closeModal,
15
+ paymentModeUuid,
16
+ paymentModeName,
17
+ onPaymentModeDeleted,
18
+ }) => {
19
+ const { t } = useTranslation();
20
+ const [isDeleting, setIsDeleting] = useState(false);
21
+
22
+ const handleDelete = async () => {
23
+ setIsDeleting(true);
24
+ try {
25
+ await openmrsFetch(`${restBaseUrl}/billing/paymentMode/${paymentModeUuid}`, {
26
+ method: 'DELETE',
27
+ });
28
+
29
+ showSnackbar({
30
+ title: t('success', 'Success'),
31
+ subtitle: t('paymentModeDeleted', 'Payment mode was successfully deleted.'),
32
+ kind: 'success',
33
+ });
34
+
35
+ closeModal();
36
+ onPaymentModeDeleted();
37
+ } catch (err) {
38
+ showSnackbar({
39
+ title: getCoreTranslation('error'),
40
+ subtitle: err?.message || t('errorDeletingPaymentMode', 'An error occurred while deleting the payment mode.'),
41
+ kind: 'error',
42
+ isLowContrast: false,
43
+ });
44
+ } finally {
45
+ setIsDeleting(false);
46
+ }
47
+ };
48
+
49
+ return (
50
+ <>
51
+ <ModalHeader closeModal={closeModal} title={t('deletePaymentMode', 'Delete Payment Mode')} />
52
+ <ModalBody>
53
+ <p>{t('confirmDeleteMessage', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}</p>
54
+ {paymentModeName && (
55
+ <p>
56
+ <strong>{t('paymentModeName', 'Payment Mode Name: {{paymentModeName}}', { paymentModeName })}</strong>
57
+ </p>
58
+ )}
59
+ </ModalBody>
60
+ <ModalFooter>
61
+ <Button kind="secondary" onClick={closeModal}>
62
+ {getCoreTranslation('cancel')}
63
+ </Button>
64
+ <Button kind="danger" onClick={handleDelete} disabled={isDeleting}>
65
+ {isDeleting ? t('deleting', 'Deleting') + '...' : getCoreTranslation('delete')}
66
+ </Button>
67
+ </ModalFooter>
68
+ </>
69
+ );
70
+ };
71
+
72
+ export default DeletePaymentModeModal;
@@ -0,0 +1,125 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import {
3
+ Button,
4
+ DataTable,
5
+ OverflowMenu,
6
+ OverflowMenuItem,
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableContainer,
11
+ TableHead,
12
+ TableHeader,
13
+ TableRow,
14
+ } from '@carbon/react';
15
+ import { Add } from '@carbon/react/icons';
16
+ import { useTranslation } from 'react-i18next';
17
+ import { showSnackbar, openmrsFetch, restBaseUrl, showModal, getCoreTranslation } from '@openmrs/esm-framework';
18
+ import { CardHeader } from '@openmrs/esm-patient-common-lib';
19
+ import styles from './payment-modes-config.scss';
20
+
21
+ const PaymentModesConfig: React.FC = () => {
22
+ const { t } = useTranslation();
23
+ const [paymentModes, setPaymentModes] = useState([]);
24
+
25
+ const fetchPaymentModes = useCallback(async () => {
26
+ try {
27
+ const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode?v=full`);
28
+ setPaymentModes(response.data.results || []);
29
+ } catch (err) {
30
+ showSnackbar({
31
+ title: getCoreTranslation('error'),
32
+ subtitle: t('errorFetchingPaymentModes', 'An error occurred while fetching payment modes.'),
33
+ kind: 'error',
34
+ isLowContrast: false,
35
+ });
36
+ }
37
+ }, [t]);
38
+
39
+ useEffect(() => {
40
+ fetchPaymentModes();
41
+ }, [fetchPaymentModes]);
42
+
43
+ const handleAddPaymentMode = () => {
44
+ const dispose = showModal('add-payment-mode-modal', {
45
+ onPaymentModeAdded: fetchPaymentModes,
46
+ closeModal: () => dispose(),
47
+ });
48
+ };
49
+
50
+ const handleDeletePaymentMode = (paymentMode) => {
51
+ const dispose = showModal('delete-payment-mode-modal', {
52
+ paymentModeUuid: paymentMode.uuid,
53
+ paymentModeName: paymentMode.name,
54
+ onPaymentModeDeleted: fetchPaymentModes,
55
+ closeModal: () => dispose(),
56
+ });
57
+ };
58
+
59
+ const rowData = paymentModes.map((mode) => ({
60
+ id: mode.uuid,
61
+ name: mode.name,
62
+ description: mode.description || '--',
63
+ }));
64
+
65
+ const headerData = [
66
+ { key: 'name', header: t('name', 'Name') },
67
+ { key: 'description', header: t('description', 'Description') },
68
+ { key: 'actions', header: getCoreTranslation('actions') },
69
+ ];
70
+
71
+ return (
72
+ <div className={styles.container}>
73
+ <div className={styles.card}>
74
+ <CardHeader title={t('paymentModeHistory', 'Payment Mode History')}>
75
+ <Button renderIcon={Add} onClick={handleAddPaymentMode} kind="ghost">
76
+ {t('addNewPaymentMode', 'Add New Payment Mode')}
77
+ </Button>
78
+ </CardHeader>
79
+ <DataTable rows={rowData} headers={headerData} isSortable size="lg">
80
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
81
+ <TableContainer>
82
+ <Table className={styles.table} {...getTableProps()}>
83
+ <TableHead>
84
+ <TableRow>
85
+ {headers.map((header) => (
86
+ <TableHeader key={header.key} {...getHeaderProps({ header })}>
87
+ {header.header}
88
+ </TableHeader>
89
+ ))}
90
+ </TableRow>
91
+ </TableHead>
92
+ <TableBody>
93
+ {rows.map((row) => (
94
+ <TableRow key={row.id} {...getRowProps({ row })}>
95
+ {row.cells.map((cell) =>
96
+ cell.info.header !== 'actions' ? (
97
+ <TableCell key={cell.id}>{cell.value}</TableCell>
98
+ ) : (
99
+ <TableCell key={cell.id}>
100
+ <OverflowMenu>
101
+ <OverflowMenuItem
102
+ className={styles.menuItem}
103
+ itemText={getCoreTranslation('delete')}
104
+ onClick={() => {
105
+ const selected = paymentModes.find((p) => p.uuid === row.id);
106
+ handleDeletePaymentMode(selected);
107
+ }}
108
+ />
109
+ </OverflowMenu>
110
+ </TableCell>
111
+ ),
112
+ )}
113
+ </TableRow>
114
+ ))}
115
+ </TableBody>
116
+ </Table>
117
+ </TableContainer>
118
+ )}
119
+ </DataTable>
120
+ </div>
121
+ </div>
122
+ );
123
+ };
124
+
125
+ export default PaymentModesConfig;
@@ -13,11 +13,12 @@
13
13
  padding: layout.$spacing-05;
14
14
  }
15
15
 
16
- .historyContainer {
17
- margin-top: layout.$spacing-05;
18
- }
19
16
 
20
17
  .table {
21
18
  width: 100%;
22
19
  table-layout: auto;
23
- }
20
+ }
21
+
22
+ .menuItem {
23
+ max-width: none;
24
+ }
@@ -1,12 +1,11 @@
1
1
  import React, { useCallback, useState } from 'react';
2
2
  import { Dropdown, InlineLoading, InlineNotification } from '@carbon/react';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { showSnackbar, useConfig } from '@openmrs/esm-framework';
4
+ import { showSnackbar, getCoreTranslation } from '@openmrs/esm-framework';
5
5
  import { useCashPoint, useBillableItems, createPatientBill } from './billing-form.resource';
6
6
  import VisitAttributesForm from './visit-attributes/visit-attributes-form.component';
7
7
  import styles from './billing-checkin-form.scss';
8
8
 
9
- const DEFAULT_PRICE = 500.00001;
10
9
  const PENDING_PAYMENT_STATUS = 'PENDING';
11
10
 
12
11
  type BillingCheckInFormProps = {
@@ -74,7 +73,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
74
73
  return (
75
74
  <InlineLoading
76
75
  status="active"
77
- iconDescription={t('loading', 'Loading')}
76
+ iconDescription={getCoreTranslation('loading')}
78
77
  description={t('loadingBillingServices', 'Loading billing services...')}
79
78
  />
80
79
  );