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

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 (214) 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/1537.js +1 -0
  10. package/dist/1537.js.map +1 -0
  11. package/dist/1856.js +1 -0
  12. package/dist/1856.js.map +1 -0
  13. package/dist/2146.js +1 -1
  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/3099.js +1 -1
  18. package/dist/3584.js +1 -1
  19. package/dist/3717.js +2 -0
  20. package/dist/3717.js.map +1 -0
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4300.js +1 -1
  24. package/dist/4335.js +1 -1
  25. package/dist/4618.js +1 -1
  26. package/dist/4652.js +1 -1
  27. package/dist/4724.js +1 -0
  28. package/dist/4724.js.map +1 -0
  29. package/dist/4739.js +1 -1
  30. package/dist/4739.js.map +1 -1
  31. package/dist/4944.js +1 -1
  32. package/dist/5173.js +1 -1
  33. package/dist/5241.js +1 -1
  34. package/dist/5442.js +1 -1
  35. package/dist/5661.js +1 -1
  36. package/dist/6022.js +1 -1
  37. package/dist/6468.js +1 -1
  38. package/dist/6540.js +1 -1
  39. package/dist/6540.js.map +1 -1
  40. package/dist/6679.js +1 -1
  41. package/dist/6840.js +1 -1
  42. package/dist/6859.js +1 -1
  43. package/dist/7097.js +1 -1
  44. package/dist/7159.js +1 -1
  45. package/dist/723.js +1 -1
  46. package/dist/7255.js +1 -1
  47. package/dist/7255.js.map +1 -1
  48. package/dist/7617.js +1 -1
  49. package/dist/795.js +1 -1
  50. package/dist/8163.js +1 -1
  51. package/dist/8349.js +1 -1
  52. package/dist/8572.js +1 -0
  53. package/dist/8572.js.map +1 -0
  54. package/dist/8618.js +1 -1
  55. package/dist/8708.js +2 -0
  56. package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
  57. package/dist/8708.js.map +1 -0
  58. package/dist/890.js +1 -1
  59. package/dist/9214.js +1 -1
  60. package/dist/9538.js +1 -1
  61. package/dist/9569.js +1 -1
  62. package/dist/961.js +1 -1
  63. package/dist/961.js.map +1 -1
  64. package/dist/986.js +1 -1
  65. package/dist/9879.js +1 -1
  66. package/dist/9895.js +1 -1
  67. package/dist/9900.js +1 -1
  68. package/dist/9913.js +1 -1
  69. package/dist/main.js +1 -1
  70. package/dist/main.js.map +1 -1
  71. package/dist/openmrs-esm-billing-app.js +1 -1
  72. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +271 -285
  73. package/dist/openmrs-esm-billing-app.js.map +1 -1
  74. package/dist/routes.json +1 -1
  75. package/e2e/README.md +19 -18
  76. package/e2e/core/test.ts +1 -1
  77. package/e2e/fixtures/api.ts +1 -1
  78. package/e2e/specs/sample-test.spec.ts +0 -1
  79. package/e2e/support/github/Dockerfile +1 -1
  80. package/package.json +18 -15
  81. package/src/bill-history/bill-history.component.tsx +20 -28
  82. package/src/bill-history/bill-history.scss +4 -94
  83. package/src/bill-history/bill-history.test.tsx +37 -78
  84. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  85. package/src/bill-item-actions/edit-bill-item.modal.tsx +226 -0
  86. package/src/bill-item-actions/edit-bill-item.test.tsx +233 -40
  87. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  88. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +34 -37
  89. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  90. package/src/billable-services/bill-waiver/utils.ts +13 -3
  91. package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
  92. package/src/billable-services/billable-service-form/billable-service-form.test.tsx +1048 -0
  93. package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +515 -0
  94. package/src/billable-services/billable-service.resource.ts +71 -27
  95. package/src/billable-services/billable-services-home.component.tsx +13 -42
  96. package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
  97. package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
  98. package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
  99. package/src/billable-services/billable-services.component.tsx +156 -152
  100. package/src/billable-services/billable-services.scss +29 -0
  101. package/src/billable-services/billable-services.test.tsx +6 -49
  102. package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
  103. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  104. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  105. package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
  106. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +77 -0
  107. package/src/billable-services/payment-modes/payment-mode-form.modal.tsx +131 -0
  108. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +139 -0
  109. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  110. package/src/billable-services-admin-card-link.component.test.tsx +2 -2
  111. package/src/billable-services-admin-card-link.component.tsx +1 -1
  112. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  113. package/src/billing-form/billing-checkin-form.component.tsx +21 -17
  114. package/src/billing-form/billing-checkin-form.test.tsx +99 -26
  115. package/src/billing-form/billing-form.component.tsx +226 -289
  116. package/src/billing-form/billing-form.scss +143 -0
  117. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
  118. package/src/billing.resource.ts +69 -74
  119. package/src/bills-table/bills-table.component.tsx +3 -3
  120. package/src/bills-table/bills-table.test.tsx +98 -54
  121. package/src/config-schema.ts +52 -24
  122. package/src/dashboard.meta.ts +4 -2
  123. package/src/helpers/functions.ts +5 -4
  124. package/src/index.ts +71 -9
  125. package/src/invoice/invoice-table.component.tsx +36 -70
  126. package/src/invoice/invoice-table.scss +8 -5
  127. package/src/invoice/invoice-table.test.tsx +273 -62
  128. package/src/invoice/invoice.component.tsx +39 -33
  129. package/src/invoice/invoice.scss +11 -4
  130. package/src/invoice/invoice.test.tsx +324 -120
  131. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  132. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  133. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  134. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  135. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  136. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  137. package/src/invoice/payments/payments.component.tsx +55 -67
  138. package/src/invoice/payments/payments.scss +4 -3
  139. package/src/invoice/payments/payments.test.tsx +282 -0
  140. package/src/invoice/payments/utils.ts +15 -27
  141. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -3
  142. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  143. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  144. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  145. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +20 -11
  146. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +95 -16
  147. package/src/invoice/printable-invoice/printable-invoice.component.tsx +21 -35
  148. package/src/left-panel-link.test.tsx +1 -4
  149. package/src/metrics-cards/metrics-cards.component.tsx +16 -6
  150. package/src/metrics-cards/metrics-cards.scss +4 -0
  151. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  152. package/src/modal/require-payment-modal.test.tsx +27 -22
  153. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  154. package/src/routes.json +44 -20
  155. package/src/types/index.ts +87 -24
  156. package/translations/am.json +135 -78
  157. package/translations/ar.json +136 -79
  158. package/translations/ar_SY.json +136 -79
  159. package/translations/bn.json +138 -81
  160. package/translations/de.json +136 -79
  161. package/translations/en.json +136 -79
  162. package/translations/en_US.json +136 -79
  163. package/translations/es.json +135 -78
  164. package/translations/es_MX.json +136 -79
  165. package/translations/fr.json +141 -84
  166. package/translations/he.json +135 -78
  167. package/translations/hi.json +136 -79
  168. package/translations/hi_IN.json +136 -79
  169. package/translations/id.json +136 -79
  170. package/translations/it.json +162 -105
  171. package/translations/ka.json +136 -79
  172. package/translations/km.json +135 -78
  173. package/translations/ku.json +136 -79
  174. package/translations/ky.json +136 -79
  175. package/translations/lg.json +136 -79
  176. package/translations/ne.json +136 -79
  177. package/translations/pl.json +136 -79
  178. package/translations/pt.json +136 -79
  179. package/translations/pt_BR.json +136 -79
  180. package/translations/qu.json +136 -79
  181. package/translations/ro_RO.json +222 -165
  182. package/translations/ru_RU.json +136 -79
  183. package/translations/si.json +136 -79
  184. package/translations/sw.json +136 -79
  185. package/translations/sw_KE.json +136 -79
  186. package/translations/tr.json +136 -79
  187. package/translations/tr_TR.json +136 -79
  188. package/translations/uk.json +136 -79
  189. package/translations/uz.json +136 -79
  190. package/translations/uz@Latn.json +136 -79
  191. package/translations/uz_UZ.json +136 -79
  192. package/translations/vi.json +136 -79
  193. package/translations/zh.json +136 -79
  194. package/translations/zh_CN.json +166 -109
  195. package/dist/1146.js.LICENSE.txt +0 -21
  196. package/dist/2352.js +0 -1
  197. package/dist/2352.js.map +0 -1
  198. package/dist/246.js +0 -1
  199. package/dist/246.js.map +0 -1
  200. package/dist/4689.js +0 -2
  201. package/dist/4689.js.map +0 -1
  202. package/dist/6557.js +0 -2
  203. package/dist/6557.js.map +0 -1
  204. package/dist/8638.js +0 -1
  205. package/dist/8638.js.map +0 -1
  206. package/dist/9968.js +0 -1
  207. package/dist/9968.js.map +0 -1
  208. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  209. package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
  210. package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
  211. package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
  212. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  213. package/src/invoice/payments/payments.component.test.tsx +0 -121
  214. /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
@@ -1,221 +0,0 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import {
3
- Button,
4
- Column,
5
- Form,
6
- InlineLoading,
7
- InlineNotification,
8
- ModalBody,
9
- ModalFooter,
10
- ModalHeader,
11
- NumberInput,
12
- TextInput,
13
- } from '@carbon/react';
14
- import { useTranslation } from 'react-i18next';
15
- import { Controller, type FieldErrors, useForm } from 'react-hook-form';
16
- import { mutate } from 'swr';
17
- import { z } from 'zod';
18
- import { zodResolver } from '@hookform/resolvers/zod';
19
- import { showSnackbar } from '@openmrs/esm-framework';
20
- import { apiBasePath } from '../constants';
21
- import { getBillableServiceUuid } from '../invoice/payments/utils';
22
- import { type LineItem, type MappedBill } from '../types';
23
- import { updateBillItems } from '../billing.resource';
24
- import { useBillableServices } from '../billable-services/billable-service.resource';
25
- import styles from './bill-item-actions.scss';
26
-
27
- interface BillLineItemProps {
28
- bill: MappedBill;
29
- item: LineItem;
30
- closeModal: () => void;
31
- }
32
-
33
- const ChangeStatus: React.FC<BillLineItemProps> = ({ bill, item, closeModal }) => {
34
- const { t } = useTranslation();
35
- const { billableServices } = useBillableServices();
36
- const [showErrorNotification, setShowErrorNotification] = useState(false);
37
- const [total, setTotal] = useState(0);
38
-
39
- const schema = useMemo(
40
- () =>
41
- z.object({
42
- quantity: z.string({ required_error: t('quantityRequired', 'Quantity is required') }),
43
- price: z.string({ required_error: t('priceIsRequired', 'Price is required') }),
44
- }),
45
- [t],
46
- );
47
-
48
- type BillLineItemForm = z.infer<typeof schema>;
49
-
50
- const onError = (errors: FieldErrors<LineItem>) => {
51
- if (errors) {
52
- setShowErrorNotification(true);
53
- }
54
- };
55
-
56
- const {
57
- control,
58
- handleSubmit,
59
- formState: { isSubmitting, errors, isDirty },
60
- watch,
61
- } = useForm<BillLineItemForm>({
62
- defaultValues: {
63
- quantity: item.quantity.toString(),
64
- price: item.price.toString(),
65
- },
66
- resolver: zodResolver(schema),
67
- });
68
-
69
- const quantity = watch('quantity');
70
- const price = watch('price');
71
-
72
- useEffect(() => {
73
- const newTotal = parseInt(quantity) * parseInt(price);
74
- setTotal(newTotal);
75
- }, [quantity, price]);
76
-
77
- const onSubmit = (data: BillLineItemForm) => {
78
- const url = `${apiBasePath}bill`;
79
-
80
- const newItem = {
81
- ...item,
82
- quantity: parseInt(data.quantity),
83
- price: parseInt(data?.price),
84
- billableService: getBillableServiceUuid(billableServices, item.billableService),
85
- item: item?.item,
86
- };
87
-
88
- const previousLineitems = bill?.lineItems
89
- .filter((currItem) => currItem.uuid !== item?.uuid)
90
- .map((currItem) => ({
91
- ...currItem,
92
- billableService: getBillableServiceUuid(billableServices, item.billableService),
93
- }));
94
- const updatedLineItems = previousLineitems.concat(newItem);
95
-
96
- const payload = {
97
- cashPoint: bill.cashPointUuid,
98
- cashier: bill.cashier.uuid,
99
- lineItems: updatedLineItems,
100
- patient: bill.patientUuid,
101
- status: bill.status,
102
- uuid: bill.uuid,
103
- };
104
- updateBillItems(payload).then(
105
- (res) => {
106
- mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
107
- showSnackbar({
108
- title: t('billItems', 'Save Bill'),
109
- subtitle: 'Bill processing has been successful',
110
- kind: 'success',
111
- timeoutInMs: 3000,
112
- });
113
- closeModal();
114
- },
115
- (error) => {
116
- showSnackbar({ title: 'Bill processing error', kind: 'error', subtitle: error?.message });
117
- },
118
- );
119
- };
120
-
121
- if (Object.keys(bill)?.length === 0) {
122
- return <ModalHeader closeModal={closeModal} title={t('billLineItemEmpty', 'This bill has no line items')} />;
123
- }
124
-
125
- if (Object.keys(bill)?.length > 0) {
126
- return (
127
- <div>
128
- <Form onSubmit={handleSubmit(onSubmit, onError)}>
129
- <ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item?')} />
130
- <ModalBody>
131
- <div className={styles.modalBody}>
132
- <h5>
133
- {bill?.patientName} &nbsp; · &nbsp;{bill?.cashPointName} &nbsp; · &nbsp;{bill?.receiptNumber}&nbsp;
134
- </h5>
135
- </div>
136
- <section className={styles.section}>
137
- <p className={styles.label}>
138
- {t('item', 'Item')} : {item?.billableService ? item?.billableService : item?.item}
139
- </p>
140
- <p className={styles.label}>
141
- {t('currentPrice', 'Current price')} : {item?.price}
142
- </p>
143
- <p className={styles.label}>
144
- {t('status', 'status')} : {item?.paymentStatus}
145
- </p>
146
- <Controller
147
- name="quantity"
148
- control={control}
149
- render={({ field: { onChange, onBlur, value } }) => (
150
- <NumberInput
151
- label={t('quantity', 'Quantity')}
152
- id="quantityInput"
153
- min={0}
154
- max={100}
155
- value={value}
156
- onChange={onChange}
157
- className={styles.controlField}
158
- invalid={errors.quantity?.message}
159
- invalidText={errors.quantity?.message}
160
- />
161
- )}
162
- />
163
-
164
- <Controller
165
- name="price"
166
- control={control}
167
- render={({ field: { value } }) => (
168
- <TextInput
169
- id="priceInput"
170
- labelText={t('price', 'Unit Price')}
171
- value={value}
172
- readOnly={true}
173
- className={styles.controlField}
174
- helperText="This is the unit Price for this item."
175
- />
176
- )}
177
- />
178
-
179
- <p className={styles.label}>
180
- {t('total', 'Total')} : {total}{' '}
181
- </p>
182
-
183
- {showErrorNotification && (
184
- <Column className={styles.errorContainer}>
185
- <InlineNotification
186
- lowContrast
187
- title={t('error', 'Error')}
188
- subtitle={t('pleaseRequiredFields', 'Please fill all required fields') + '.'}
189
- onClose={() => setShowErrorNotification(false)}
190
- />
191
- </Column>
192
- )}
193
- </section>
194
- </ModalBody>
195
- <ModalFooter>
196
- <Button kind="secondary" onClick={closeModal}>
197
- {t('cancel', 'Cancel')}
198
- </Button>
199
- <Button disabled={isSubmitting} type="submit">
200
- <>
201
- {isSubmitting ? (
202
- <div className={styles.inline}>
203
- <InlineLoading
204
- status="active"
205
- iconDescription={t('submitting', 'Submitting')}
206
- description={t('submitting', 'Submitting...')}
207
- />
208
- </div>
209
- ) : (
210
- t('save', 'Save')
211
- )}
212
- </>
213
- </Button>
214
- </ModalFooter>
215
- </Form>
216
- </div>
217
- );
218
- }
219
- };
220
-
221
- export default ChangeStatus;
@@ -1,401 +0,0 @@
1
- import React, { useCallback, useRef, useState, useEffect } from 'react';
2
- import {
3
- Button,
4
- ComboBox,
5
- Dropdown,
6
- Form,
7
- FormLabel,
8
- InlineLoading,
9
- Layer,
10
- Search,
11
- TextInput,
12
- Tile,
13
- } from '@carbon/react';
14
- import { Add, TrashCan, WarningFilled } from '@carbon/react/icons';
15
- import { Controller, useFieldArray, useForm } from 'react-hook-form';
16
- import { useTranslation } from 'react-i18next';
17
- import { z } from 'zod';
18
- import { zodResolver } from '@hookform/resolvers/zod';
19
- import { navigate, showSnackbar, useDebounce, useLayoutType } from '@openmrs/esm-framework';
20
- import {
21
- createBillableSerice,
22
- updateBillableService,
23
- useConceptsSearch,
24
- usePaymentModes,
25
- useServiceTypes,
26
- } from '../billable-service.resource';
27
- import { type ServiceConcept } from '../../types';
28
- import styles from './add-billable-service.scss';
29
-
30
- type PaymentMode = {
31
- paymentMode: string;
32
- price: string | number;
33
- };
34
-
35
- type PaymentModeFormValue = {
36
- payment: Array<PaymentMode>;
37
- };
38
-
39
- const servicePriceSchema = z.object({
40
- paymentMode: z.string().refine((value) => !!value, 'Payment method is required'),
41
- price: z.union([
42
- z.number().refine((value) => !!value, 'Price is required'),
43
- z.string().refine((value) => !!value, 'Price is required'),
44
- ]),
45
- });
46
-
47
- const paymentFormSchema = z.object({
48
- payment: z.array(servicePriceSchema).min(1, 'At least one payment option is required'),
49
- });
50
-
51
- const DEFAULT_PAYMENT_OPTION = { paymentMode: '', price: 0 };
52
-
53
- const AddBillableService: React.FC<{ editingService?: any; onClose: () => void }> = ({ editingService, onClose }) => {
54
- const { t } = useTranslation();
55
-
56
- const { paymentModes, isLoading: isLoadingPaymentModes } = usePaymentModes();
57
- const { serviceTypes, isLoading: isLoadingServicesTypes } = useServiceTypes();
58
- const [billableServicePayload, setBillableServicePayload] = useState(editingService || {});
59
-
60
- const {
61
- control,
62
- handleSubmit,
63
- formState: { errors, isValid },
64
- setValue,
65
- } = useForm<any>({
66
- mode: 'all',
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
- },
74
- resolver: zodResolver(paymentFormSchema),
75
- shouldUnregister: !editingService,
76
- });
77
- const { fields, remove, append } = useFieldArray({ name: 'payment', control: control });
78
-
79
- const handleAppendPaymentMode = useCallback(() => append(DEFAULT_PAYMENT_OPTION), [append]);
80
- const handleRemovePaymentMode = useCallback((index) => remove(index), [remove]);
81
-
82
- const isTablet = useLayoutType() === 'tablet';
83
- const searchInputRef = useRef(null);
84
- const handleSearchTermChange = (event: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(event.target.value);
85
-
86
- const [selectedConcept, setSelectedConcept] = useState<ServiceConcept>(null);
87
- const [searchTerm, setSearchTerm] = useState('');
88
- const debouncedSearchTerm = useDebounce(searchTerm);
89
- const { searchResults, isSearching } = useConceptsSearch(debouncedSearchTerm);
90
- const handleConceptChange = useCallback((selectedConcept: any) => {
91
- setSelectedConcept(selectedConcept);
92
- }, []);
93
-
94
- const handleNavigateToServiceDashboard = () =>
95
- navigate({
96
- to: window.getOpenmrsSpaBase() + 'billable-services',
97
- });
98
-
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;
121
-
122
- const onSubmit = (data) => {
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
- };
138
-
139
- const saveAction = editingService
140
- ? updateBillableService(editingService.uuid, payload)
141
- : createBillableSerice(payload);
142
-
143
- saveAction.then(
144
- (resp) => {
145
- showSnackbar({
146
- title: t('billableService', 'Billable service'),
147
- subtitle: editingService
148
- ? t('updatedSuccessfully', 'Billable service updated successfully')
149
- : t('createdSuccessfully', 'Billable service created successfully'),
150
- kind: 'success',
151
- timeoutInMs: 3000,
152
- });
153
- onClose();
154
- handleNavigateToServiceDashboard();
155
- },
156
- (error) => {
157
- showSnackbar({ title: t('billPaymentError', 'Bill payment error'), kind: 'error', subtitle: error?.message });
158
- },
159
- );
160
- };
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
-
180
- return (
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>
187
- <section className={styles.section}>
188
- <Layer>
189
- <TextInput
190
- id="serviceName"
191
- type="text"
192
- labelText={t('serviceName', 'Service Name')}
193
- size="md"
194
- value={billableServicePayload.name || ''}
195
- onChange={(e) => {
196
- const newName = e.target.value.substring(0, MAX_NAME_LENGTH);
197
- setBillableServicePayload({
198
- ...billableServicePayload,
199
- name: newName,
200
- });
201
- }}
202
- placeholder="Enter service name"
203
- maxLength={MAX_NAME_LENGTH}
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
- )}
212
- </Layer>
213
- </section>
214
- <section className={styles.section}>
215
- <Layer>
216
- <TextInput
217
- id="serviceShortName"
218
- type="text"
219
- labelText={t('serviceShortName', 'Short Name')}
220
- size="md"
221
- value={billableServicePayload.shortName || ''}
222
- onChange={(e) => {
223
- const newShortName = e.target.value.substring(0, MAX_NAME_LENGTH);
224
- setBillableServicePayload({
225
- ...billableServicePayload,
226
- shortName: newShortName,
227
- });
228
- }}
229
- placeholder="Enter service short name"
230
- maxLength={MAX_NAME_LENGTH}
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
- )}
239
- </Layer>
240
- </section>
241
- <section>
242
- <FormLabel className={styles.conceptLabel}>Associated Concept</FormLabel>
243
- <Controller
244
- name="search"
245
- control={control}
246
- render={({ field: { onChange, value, onBlur } }) => (
247
- <ResponsiveWrapper isTablet={isTablet}>
248
- <Search
249
- ref={searchInputRef}
250
- size="md"
251
- id="conceptsSearch"
252
- labelText={t('enterConcept', 'Associated concept')}
253
- placeholder={t('searchConcepts', 'Search associated concept')}
254
- className={errors?.search && styles.serviceError}
255
- onChange={(e) => {
256
- setSearchTerm(e.target.value);
257
- onChange(e);
258
- handleSearchTermChange(e);
259
- }}
260
- renderIcon={errors?.search && <WarningFilled />}
261
- onBlur={onBlur}
262
- onClear={() => {
263
- setSearchTerm('');
264
- setSelectedConcept(null);
265
- }}
266
- value={(() => {
267
- if (selectedConcept) {
268
- return selectedConcept.display;
269
- }
270
- if (debouncedSearchTerm) {
271
- return value;
272
- }
273
- })()}
274
- />
275
- </ResponsiveWrapper>
276
- )}
277
- />
278
-
279
- {(() => {
280
- if (!debouncedSearchTerm || selectedConcept) return null;
281
- if (isSearching)
282
- return <InlineLoading className={styles.loader} description={t('searching', 'Searching') + '...'} />;
283
- if (searchResults && searchResults.length) {
284
- return (
285
- <ul className={styles.conceptsList}>
286
- {/*TODO: use uuid instead of index as the key*/}
287
- {searchResults?.map((searchResult, index) => (
288
- <li
289
- role="menuitem"
290
- className={styles.service}
291
- key={index}
292
- onClick={() => handleConceptChange(searchResult)}>
293
- {searchResult.display}
294
- </li>
295
- ))}
296
- </ul>
297
- );
298
- }
299
- return (
300
- <Layer>
301
- <Tile className={styles.emptyResults}>
302
- <span>
303
- {t('noResultsFor', 'No results for')} <strong>"{debouncedSearchTerm}"</strong>
304
- </span>
305
- </Tile>
306
- </Layer>
307
- );
308
- })()}
309
- </section>
310
- <section className={styles.section}>
311
- <Layer>
312
- <ComboBox
313
- id="serviceType"
314
- items={serviceTypes ?? []}
315
- titleText={t('serviceType', 'Service Type')}
316
- itemToString={(item) => item?.display || ''}
317
- selectedItem={billableServicePayload.serviceType || null}
318
- onChange={({ selectedItem }) => {
319
- setBillableServicePayload({
320
- ...billableServicePayload,
321
- display: selectedItem?.display,
322
- serviceType: selectedItem,
323
- });
324
- }}
325
- placeholder="Select service type"
326
- required
327
- />
328
- </Layer>
329
- </section>
330
-
331
- <section>
332
- <div className={styles.container}>
333
- {fields.map((field, index) => (
334
- <div key={field.id} className={styles.paymentMethodContainer}>
335
- <Controller
336
- control={control}
337
- name={`payment.${index}.paymentMode`}
338
- render={({ field }) => (
339
- <Layer>
340
- <Dropdown
341
- onChange={({ selectedItem }) => field.onChange(selectedItem.uuid)}
342
- titleText={t('paymentMode', 'Payment Mode')}
343
- label={t('selectPaymentMethod', 'Select payment method')}
344
- items={paymentModes ?? []}
345
- itemToString={(item) => (item ? item.name : '')}
346
- selectedItem={paymentModes.find((mode) => mode.uuid === field.value)}
347
- invalid={!!errors?.payment?.[index]?.paymentMode}
348
- invalidText={errors?.payment?.[index]?.paymentMode?.message}
349
- />
350
- </Layer>
351
- )}
352
- />
353
- <Controller
354
- control={control}
355
- name={`payment.${index}.price`}
356
- render={({ field }) => (
357
- <Layer>
358
- <TextInput
359
- {...field}
360
- invalid={!!errors?.payment?.[index]?.price}
361
- invalidText={errors?.payment?.[index]?.price?.message}
362
- labelText={t('sellingPrice', 'Selling Price')}
363
- placeholder={t('sellingAmount', 'Enter selling price')}
364
- />
365
- </Layer>
366
- )}
367
- />
368
- <div className={styles.removeButtonContainer}>
369
- <TrashCan onClick={() => handleRemovePaymentMode(index)} className={styles.removeButton} size={20} />
370
- </div>
371
- </div>
372
- ))}
373
- <Button
374
- size="md"
375
- onClick={handleAppendPaymentMode}
376
- className={styles.paymentButtons}
377
- renderIcon={(props) => <Add size={24} {...props} />}
378
- iconDescription="Add">
379
- {t('addPaymentOptions', 'Add payment option')}
380
- </Button>
381
- {getPaymentErrorMessage() && <div className={styles.errorMessage}>{getPaymentErrorMessage()}</div>}
382
- </div>
383
- </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>
393
- </Form>
394
- );
395
- };
396
-
397
- function ResponsiveWrapper({ children, isTablet }: { children: React.ReactNode; isTablet: boolean }) {
398
- return isTablet ? <Layer>{children} </Layer> : <>{children}</>;
399
- }
400
-
401
- export default AddBillableService;