@openmrs/esm-billing-app 1.0.1-pre.95 → 1.0.2-pre.56

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 +19 -7
  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 +24 -8
  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
@@ -1,5 +1,4 @@
1
- /* eslint-disable curly */
2
- import React, { useCallback, useRef, useState } from 'react';
1
+ import React, { useCallback, useRef, useState, useEffect } from 'react';
3
2
  import {
4
3
  Button,
5
4
  ComboBox,
@@ -12,14 +11,15 @@ import {
12
11
  TextInput,
13
12
  Tile,
14
13
  } from '@carbon/react';
15
- import { navigate, showSnackbar, useDebounce, useLayoutType, useSession } from '@openmrs/esm-framework';
16
14
  import { Add, TrashCan, WarningFilled } from '@carbon/react/icons';
17
15
  import { Controller, useFieldArray, useForm } from 'react-hook-form';
18
16
  import { useTranslation } from 'react-i18next';
19
17
  import { z } from 'zod';
20
18
  import { zodResolver } from '@hookform/resolvers/zod';
19
+ import { navigate, showSnackbar, useDebounce, useLayoutType } from '@openmrs/esm-framework';
21
20
  import {
22
21
  createBillableSerice,
22
+ updateBillableService,
23
23
  useConceptsSearch,
24
24
  usePaymentModes,
25
25
  useServiceTypes,
@@ -44,25 +44,35 @@ const servicePriceSchema = z.object({
44
44
  ]),
45
45
  });
46
46
 
47
- const paymentFormSchema = z.object({ payment: z.array(servicePriceSchema) });
47
+ const paymentFormSchema = z.object({
48
+ payment: z.array(servicePriceSchema).min(1, 'At least one payment option is required'),
49
+ });
48
50
 
49
51
  const DEFAULT_PAYMENT_OPTION = { paymentMode: '', price: 0 };
50
52
 
51
- const AddBillableService: React.FC = () => {
53
+ const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }> = ({ editingService, onClose }) => {
52
54
  const { t } = useTranslation();
53
55
 
54
56
  const { paymentModes, isLoading: isLoadingPaymentModes } = usePaymentModes();
55
57
  const { serviceTypes, isLoading: isLoadingServicesTypes } = useServiceTypes();
56
- const [billableServicePayload, setBillableServicePayload] = useState<any>({});
58
+ const [billableServicePayload, setBillableServicePayload] = useState(editingService || {});
57
59
 
58
60
  const {
59
61
  control,
60
62
  handleSubmit,
61
- formState: { errors },
63
+ formState: { errors, isValid },
64
+ setValue,
62
65
  } = useForm<any>({
63
66
  mode: 'all',
64
- defaultValues: {},
67
+ defaultValues: {
68
+ name: editingService?.name,
69
+ serviceShortName: editingService?.shortName,
70
+ serviceType: editingService?.serviceType,
71
+ conceptsSearch: editingService?.concept,
72
+ payment: editingService?.servicePrices || [DEFAULT_PAYMENT_OPTION],
73
+ },
65
74
  resolver: zodResolver(paymentFormSchema),
75
+ shouldUnregister: !editingService,
66
76
  });
67
77
  const { fields, remove, append } = useFieldArray({ name: 'payment', control: control });
68
78
 
@@ -86,50 +96,94 @@ const AddBillableService: React.FC = () => {
86
96
  to: window.getOpenmrsSpaBase() + 'billable-services',
87
97
  });
88
98
 
89
- if (isLoadingPaymentModes && isLoadingServicesTypes) {
90
- return (
91
- <InlineLoading
92
- status="active"
93
- iconDescription={t('loadingDescription', 'Loading')}
94
- description={t('loading', 'Loading data...')}
95
- />
96
- );
97
- }
99
+ useEffect(() => {
100
+ if (editingService && !isLoadingPaymentModes) {
101
+ setBillableServicePayload(editingService);
102
+ setValue('serviceName', editingService.name || '');
103
+ setValue('shortName', editingService.shortName || '');
104
+ setValue('serviceType', editingService.serviceType || '');
105
+ setValue(
106
+ 'payment',
107
+ editingService.servicePrices.map((payment) => ({
108
+ paymentMode: payment.paymentMode?.uuid || '',
109
+ price: payment.price,
110
+ })),
111
+ );
112
+ setValue('conceptsSearch', editingService.concept);
113
+
114
+ if (editingService.concept) {
115
+ setSelectedConcept(editingService.concept);
116
+ }
117
+ }
118
+ }, [editingService, isLoadingPaymentModes, paymentModes, serviceTypes, setValue]);
119
+
120
+ const MAX_NAME_LENGTH = 19;
98
121
 
99
122
  const onSubmit = (data) => {
100
- const payload: any = {};
123
+ const payload = {
124
+ name: billableServicePayload.name.substring(0, MAX_NAME_LENGTH),
125
+ shortName: billableServicePayload.shortName.substring(0, MAX_NAME_LENGTH),
126
+ serviceType: billableServicePayload.serviceType.uuid,
127
+ servicePrices: data.payment.map((payment) => {
128
+ const mode = paymentModes.find((m) => m.uuid === payment.paymentMode);
129
+ return {
130
+ paymentMode: payment.paymentMode,
131
+ name: mode?.name || 'Unknown',
132
+ price: parseFloat(payment.price),
133
+ };
134
+ }),
135
+ serviceStatus: 'ENABLED',
136
+ concept: selectedConcept?.uuid,
137
+ };
101
138
 
102
- let servicePrices = [];
103
- data.payment.forEach((element) => {
104
- element.name = paymentModes.filter((p) => p.uuid === element.paymentMode)[0].name;
105
- servicePrices.push(element);
106
- });
107
- payload.name = billableServicePayload.serviceName;
108
- payload.shortName = billableServicePayload.shortName;
109
- payload.serviceType = billableServicePayload.serviceType.uuid;
110
- payload.servicePrices = servicePrices;
111
- payload.serviceStatus = 'ENABLED';
112
- payload.concept = selectedConcept?.concept?.uuid;
139
+ const saveAction = editingService
140
+ ? updateBillableService(editingService.uuid, payload)
141
+ : createBillableSerice(payload);
113
142
 
114
- createBillableSerice(payload).then(
143
+ saveAction.then(
115
144
  (resp) => {
116
145
  showSnackbar({
117
146
  title: t('billableService', 'Billable service'),
118
- subtitle: 'Billable service created successfully',
147
+ subtitle: editingService
148
+ ? t('updatedSuccessfully', 'Billable service updated successfully')
149
+ : t('createdSuccessfully', 'Billable service created successfully'),
119
150
  kind: 'success',
120
151
  timeoutInMs: 3000,
121
152
  });
153
+ onClose();
122
154
  handleNavigateToServiceDashboard();
123
155
  },
124
156
  (error) => {
125
- showSnackbar({ title: 'Bill payment error', kind: 'error', subtitle: error?.message });
157
+ showSnackbar({ title: t('billPaymentError', 'Bill payment error'), kind: 'error', subtitle: error?.message });
126
158
  },
127
159
  );
128
160
  };
129
161
 
162
+ const getPaymentErrorMessage = () => {
163
+ const paymentError = errors.payment;
164
+ if (paymentError && typeof paymentError.message === 'string') {
165
+ return paymentError.message;
166
+ }
167
+ return null;
168
+ };
169
+
170
+ if (isLoadingPaymentModes && isLoadingServicesTypes) {
171
+ return (
172
+ <InlineLoading
173
+ status="active"
174
+ iconDescription={t('loadingDescription', 'Loading')}
175
+ description={t('loading', 'Loading data...')}
176
+ />
177
+ );
178
+ }
179
+
130
180
  return (
131
- <Form className={styles.form}>
132
- <h4>{t('addBillableServices', 'Add Billable Services')}</h4>
181
+ <Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
182
+ <h4>
183
+ {editingService
184
+ ? t('editBillableServices', 'Edit Billable Services')
185
+ : t('addBillableServices', 'Add Billable Services')}
186
+ </h4>
133
187
  <section className={styles.section}>
134
188
  <Layer>
135
189
  <TextInput
@@ -137,14 +191,24 @@ const AddBillableService: React.FC = () => {
137
191
  type="text"
138
192
  labelText={t('serviceName', 'Service Name')}
139
193
  size="md"
140
- onChange={(e) =>
194
+ value={billableServicePayload.name || ''}
195
+ onChange={(e) => {
196
+ const newName = e.target.value.substring(0, MAX_NAME_LENGTH);
141
197
  setBillableServicePayload({
142
198
  ...billableServicePayload,
143
- serviceName: e.target.value,
144
- })
145
- }
199
+ name: newName,
200
+ });
201
+ }}
146
202
  placeholder="Enter service name"
203
+ maxLength={MAX_NAME_LENGTH}
147
204
  />
205
+ {billableServicePayload.name?.length >= MAX_NAME_LENGTH && (
206
+ <span className={styles.errorMessage}>
207
+ {t('serviceNameExceedsLimit', 'Service Name exceeds the character limit of {{MAX_NAME_LENGTH}}.', {
208
+ MAX_NAME_LENGTH,
209
+ })}
210
+ </span>
211
+ )}
148
212
  </Layer>
149
213
  </section>
150
214
  <section className={styles.section}>
@@ -154,14 +218,24 @@ const AddBillableService: React.FC = () => {
154
218
  type="text"
155
219
  labelText={t('serviceShortName', 'Short Name')}
156
220
  size="md"
157
- onChange={(e) =>
221
+ value={billableServicePayload.shortName || ''}
222
+ onChange={(e) => {
223
+ const newShortName = e.target.value.substring(0, MAX_NAME_LENGTH);
158
224
  setBillableServicePayload({
159
225
  ...billableServicePayload,
160
- shortName: e.target.value,
161
- })
162
- }
226
+ shortName: newShortName,
227
+ });
228
+ }}
163
229
  placeholder="Enter service short name"
230
+ maxLength={MAX_NAME_LENGTH}
164
231
  />
232
+ {billableServicePayload.shortName?.length >= MAX_NAME_LENGTH && (
233
+ <span className={styles.errorMessage}>
234
+ {t('shortNameExceedsLimit', 'Short Name exceeds the character limit of {{MAX_NAME_LENGTH}}.', {
235
+ MAX_NAME_LENGTH,
236
+ })}
237
+ </span>
238
+ )}
165
239
  </Layer>
166
240
  </section>
167
241
  <section>
@@ -179,6 +253,7 @@ const AddBillableService: React.FC = () => {
179
253
  placeholder={t('searchConcepts', 'Search associated concept')}
180
254
  className={errors?.search && styles.serviceError}
181
255
  onChange={(e) => {
256
+ setSearchTerm(e.target.value);
182
257
  onChange(e);
183
258
  handleSearchTermChange(e);
184
259
  }}
@@ -200,6 +275,7 @@ const AddBillableService: React.FC = () => {
200
275
  </ResponsiveWrapper>
201
276
  )}
202
277
  />
278
+
203
279
  {(() => {
204
280
  if (!debouncedSearchTerm || selectedConcept) return null;
205
281
  if (isSearching)
@@ -237,7 +313,8 @@ const AddBillableService: React.FC = () => {
237
313
  id="serviceType"
238
314
  items={serviceTypes ?? []}
239
315
  titleText={t('serviceType', 'Service Type')}
240
- itemToString={(item) => item?.display}
316
+ itemToString={(item) => item?.display || ''}
317
+ selectedItem={billableServicePayload.serviceType || null}
241
318
  onChange={({ selectedItem }) => {
242
319
  setBillableServicePayload({
243
320
  ...billableServicePayload,
@@ -261,11 +338,12 @@ const AddBillableService: React.FC = () => {
261
338
  render={({ field }) => (
262
339
  <Layer>
263
340
  <Dropdown
264
- onChange={({ selectedItem }) => field.onChange(selectedItem?.uuid)}
341
+ onChange={({ selectedItem }) => field.onChange(selectedItem.uuid)}
265
342
  titleText={t('paymentMode', 'Payment Mode')}
266
343
  label={t('selectPaymentMethod', 'Select payment method')}
267
344
  items={paymentModes ?? []}
268
345
  itemToString={(item) => (item ? item.name : '')}
346
+ selectedItem={paymentModes.find((mode) => mode.uuid === field.value)}
269
347
  invalid={!!errors?.payment?.[index]?.paymentMode}
270
348
  invalidText={errors?.payment?.[index]?.paymentMode?.message}
271
349
  />
@@ -288,7 +366,7 @@ const AddBillableService: React.FC = () => {
288
366
  )}
289
367
  />
290
368
  <div className={styles.removeButtonContainer}>
291
- <TrashCan onClick={handleRemovePaymentMode} className={styles.removeButton} size={20} />
369
+ <TrashCan onClick={() => handleRemovePaymentMode(index)} className={styles.removeButton} size={20} />
292
370
  </div>
293
371
  </div>
294
372
  ))}
@@ -300,14 +378,15 @@ const AddBillableService: React.FC = () => {
300
378
  iconDescription="Add">
301
379
  {t('addPaymentOptions', 'Add payment option')}
302
380
  </Button>
381
+ {getPaymentErrorMessage() && <div className={styles.errorMessage}>{getPaymentErrorMessage()}</div>}
303
382
  </div>
304
383
  </section>
305
384
 
306
385
  <section>
307
- <Button kind="secondary" onClick={handleNavigateToServiceDashboard}>
386
+ <Button kind="secondary" onClick={onClose}>
308
387
  {t('cancel', 'Cancel')}
309
388
  </Button>
310
- <Button type="submit" onClick={handleSubmit(onSubmit)}>
389
+ <Button type="submit" disabled={!isValid || Object.keys(errors).length > 0}>
311
390
  {t('save', 'Save')}
312
391
  </Button>
313
392
  </section>
@@ -1,7 +1,7 @@
1
1
  @use '@carbon/colors';
2
2
  @use '@carbon/layout';
3
3
  @use '@carbon/type';
4
- @import '~@openmrs/esm-styleguide/src/vars';
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
5
 
6
6
  .form {
7
7
  display: flex;
@@ -25,7 +25,7 @@
25
25
  }
26
26
 
27
27
  .container {
28
- margin: 1rem;
28
+ margin: layout.$spacing-05;
29
29
  }
30
30
 
31
31
  .paymentContainer {
@@ -43,7 +43,7 @@
43
43
  display: grid;
44
44
  grid-template-columns: repeat(4, minmax(auto, 1fr));
45
45
  align-items: flex-start;
46
- column-gap: 1rem;
46
+ column-gap: layout.$spacing-05;
47
47
  margin: 0.625rem 0;
48
48
  width: 100%;
49
49
  }
@@ -76,7 +76,7 @@
76
76
  }
77
77
 
78
78
  .service {
79
- padding: 1rem 0.75rem;
79
+ padding: layout.$spacing-05 layout.$spacing-04;
80
80
  }
81
81
 
82
82
  .conceptsList {
@@ -93,17 +93,17 @@
93
93
  .emptyResults {
94
94
  @include type.type-style('body-compact-01');
95
95
  color: $text-02;
96
- min-height: 1rem;
96
+ min-height: layout.$spacing-05;
97
97
  border: 1px solid $ui-03;
98
98
  }
99
99
 
100
100
  .conceptLabel {
101
101
  @include type.type-style('label-02');
102
- margin: 1rem;
102
+ margin: layout.$spacing-05;
103
103
  }
104
104
 
105
105
  .errorContainer {
106
- margin: 1rem;
106
+ margin: layout.$spacing-05;
107
107
  }
108
108
 
109
109
  .serviceError {
@@ -126,6 +126,12 @@
126
126
 
127
127
  .spinner {
128
128
  &:global(.cds--inline-loading) {
129
- min-height: 1rem;
129
+ min-height: layout.$spacing-05;
130
130
  }
131
131
  }
132
+
133
+ .errorMessage {
134
+ color: red;
135
+ font-size: 0.875rem;
136
+ }
137
+
@@ -1,21 +1,20 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
4
- import AddBillableService from './add-billable-service.component';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { type FetchResponse, navigate } from '@openmrs/esm-framework';
5
5
  import {
6
6
  useBillableServices,
7
7
  usePaymentModes,
8
8
  useServiceTypes,
9
9
  createBillableSerice,
10
10
  } from '../billable-service.resource';
11
- import { FetchResponse, navigate, showSnackbar } from '@openmrs/esm-framework';
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
16
  const mockCreateBillableSerice = createBillableSerice as jest.MockedFunction<typeof createBillableSerice>;
17
17
  const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
18
- const mockShowSnackbar = showSnackbar as jest.MockedFunction<typeof showSnackbar>;
19
18
 
20
19
  jest.mock('../billable-service.resource', () => ({
21
20
  useBillableServices: jest.fn(),
@@ -57,6 +56,7 @@ xdescribe('AddBillableService', () => {
57
56
 
58
57
  test('should render billable services form and generate correct payload', async () => {
59
58
  const user = userEvent.setup();
59
+ const mockOnClose = jest.fn();
60
60
  mockUseBillableServices.mockReturnValue({
61
61
  billableServices: [],
62
62
  isLoading: false,
@@ -66,10 +66,11 @@ xdescribe('AddBillableService', () => {
66
66
  });
67
67
  mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoading: false });
68
68
  mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoading: false });
69
- render(<AddBillableService />);
70
69
 
71
- const formTtile = screen.getByRole('heading', { name: /Add Billable Services/i });
72
- expect(formTtile).toBeInTheDocument();
70
+ render(<AddBillableService onClose={mockOnClose} />);
71
+
72
+ const formTitle = screen.getByRole('heading', { name: /Add Billable Services/i });
73
+ expect(formTitle).toBeInTheDocument();
73
74
 
74
75
  const serviceNameTextInp = screen.getByRole('textbox', { name: /Service Name/i });
75
76
  expect(serviceNameTextInp).toBeInTheDocument();
@@ -131,6 +132,7 @@ xdescribe('AddBillableService', () => {
131
132
 
132
133
  test("should navigate back to billable services dashboard when 'Cancel' button is clicked", async () => {
133
134
  const user = userEvent.setup();
135
+ const mockOnClose = jest.fn();
134
136
  mockUseBillableServices.mockReturnValue({
135
137
  billableServices: [],
136
138
  isLoading: false,
@@ -140,13 +142,13 @@ xdescribe('AddBillableService', () => {
140
142
  });
141
143
  mockUsePaymentModes.mockReturnValue({ paymentModes: mockPaymentModes, error: null, isLoading: false });
142
144
  mockUseServiceTypes.mockReturnValue({ serviceTypes: mockServiceTypes, error: false, isLoading: false });
143
- render(<AddBillableService />);
145
+
146
+ render(<AddBillableService onClose={mockOnClose} />);
144
147
 
145
148
  const cancelBtn = screen.getByRole('button', { name: /Cancel/i });
146
149
  expect(cancelBtn).toBeInTheDocument();
147
150
  await user.click(cancelBtn);
148
151
 
149
- expect(mockNavigate).toHaveBeenCalledTimes(1);
150
- expect(mockNavigate).toHaveBeenCalledWith({ to: '/openmrs/spa/billable-services' });
152
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
151
153
  });
152
154
  });
@@ -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
  .servicesTableContainer {
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
  }