@openmrs/esm-billing-app 1.0.2-pre.863 → 1.0.2-pre.868
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.
- package/dist/3717.js +1 -1
- package/dist/4060.js +1 -0
- package/dist/4060.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/4724.js +1 -1
- package/dist/4739.js +1 -1
- package/dist/8708.js +2 -0
- package/dist/8708.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +61 -61
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/billable-services/billable-services-home.component.tsx +11 -34
- package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
- package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
- package/src/billable-services/billable-services.scss +26 -0
- package/src/billable-services/create-edit/add-billable-service.component.tsx +88 -67
- package/src/billable-services/create-edit/add-billable-service.test.tsx +558 -1
- package/src/index.ts +50 -0
- package/src/routes.json +18 -0
- package/translations/en.json +1 -1
- package/dist/4344.js +0 -1
- package/dist/4344.js.map +0 -1
- package/dist/6295.js +0 -2
- package/dist/6295.js.map +0 -1
- /package/dist/{6295.js.LICENSE.txt → 8708.js.LICENSE.txt} +0 -0
|
@@ -20,7 +20,7 @@ import { Add, TrashCan } from '@carbon/react/icons';
|
|
|
20
20
|
import { z } from 'zod';
|
|
21
21
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
22
22
|
import { getCoreTranslation, navigate, ResponsiveWrapper, showSnackbar, useDebounce } from '@openmrs/esm-framework';
|
|
23
|
-
import type { BillableService,
|
|
23
|
+
import type { BillableService, ServicePrice } from '../../types';
|
|
24
24
|
import {
|
|
25
25
|
createBillableService,
|
|
26
26
|
updateBillableService,
|
|
@@ -30,34 +30,74 @@ import {
|
|
|
30
30
|
} from '../billable-service.resource';
|
|
31
31
|
import styles from './add-billable-service.scss';
|
|
32
32
|
|
|
33
|
-
interface
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
interface PaymentModeForm {
|
|
39
|
-
paymentMode: string;
|
|
40
|
-
price: string | number | undefined;
|
|
33
|
+
interface AddBillableServiceProps {
|
|
34
|
+
serviceToEdit?: BillableService;
|
|
35
|
+
onClose: () => void;
|
|
36
|
+
onServiceUpdated?: () => void;
|
|
37
|
+
isModal?: boolean;
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
interface BillableServiceFormData {
|
|
44
41
|
name: string;
|
|
45
|
-
|
|
42
|
+
payment: PaymentModeForm[];
|
|
46
43
|
serviceType: ServiceType | null;
|
|
47
44
|
concept?: { uuid: string; display: string } | null;
|
|
48
|
-
|
|
45
|
+
shortName?: string;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
interface
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
onServiceUpdated?: () => void;
|
|
55
|
-
isModal?: boolean;
|
|
48
|
+
interface PaymentModeForm {
|
|
49
|
+
paymentMode: string;
|
|
50
|
+
price: string | number | undefined;
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
|
|
53
|
+
interface ServiceType {
|
|
54
|
+
uuid: string;
|
|
55
|
+
display: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const DEFAULT_PAYMENT_OPTION: PaymentModeForm = { paymentMode: '', price: '' };
|
|
59
59
|
const MAX_NAME_LENGTH = 255;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Transforms a BillableService into form data structure
|
|
63
|
+
* Centralizes the mapping logic to avoid duplication between defaultValues and reset()
|
|
64
|
+
* Exported for testing
|
|
65
|
+
*/
|
|
66
|
+
export const transformServiceToFormData = (service?: BillableService): BillableServiceFormData => {
|
|
67
|
+
if (!service) {
|
|
68
|
+
return {
|
|
69
|
+
name: '',
|
|
70
|
+
shortName: '',
|
|
71
|
+
serviceType: null,
|
|
72
|
+
concept: null,
|
|
73
|
+
payment: [DEFAULT_PAYMENT_OPTION],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
name: service.name || '',
|
|
79
|
+
shortName: service.shortName || '',
|
|
80
|
+
serviceType: service.serviceType || null,
|
|
81
|
+
concept: service.concept ? { uuid: service.concept.uuid, display: service.concept.display } : null,
|
|
82
|
+
payment: service.servicePrices?.map((servicePrice: ServicePrice) => ({
|
|
83
|
+
paymentMode: servicePrice.paymentMode?.uuid || '',
|
|
84
|
+
price: servicePrice.price ?? '',
|
|
85
|
+
})) || [DEFAULT_PAYMENT_OPTION],
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Normalizes price value from form (string | number | undefined) to number
|
|
91
|
+
* Handles Carbon NumberInput which can return either type
|
|
92
|
+
* Exported for testing
|
|
93
|
+
*/
|
|
94
|
+
export const normalizePrice = (price: string | number | undefined): number => {
|
|
95
|
+
if (typeof price === 'number') {
|
|
96
|
+
return price;
|
|
97
|
+
}
|
|
98
|
+
return parseFloat(String(price));
|
|
99
|
+
};
|
|
100
|
+
|
|
61
101
|
const createBillableServiceSchema = (t: TFunction) => {
|
|
62
102
|
const servicePriceSchema = z.object({
|
|
63
103
|
paymentMode: z
|
|
@@ -143,18 +183,7 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
143
183
|
reset,
|
|
144
184
|
} = useForm<BillableServiceFormData>({
|
|
145
185
|
mode: 'all',
|
|
146
|
-
defaultValues:
|
|
147
|
-
name: serviceToEdit?.name || '',
|
|
148
|
-
shortName: serviceToEdit?.shortName || '',
|
|
149
|
-
serviceType: serviceToEdit?.serviceType || null,
|
|
150
|
-
concept: serviceToEdit?.concept
|
|
151
|
-
? { uuid: serviceToEdit.concept.uuid, display: serviceToEdit.concept.display }
|
|
152
|
-
: null,
|
|
153
|
-
payment: serviceToEdit?.servicePrices?.map((servicePrice: ServicePrice) => ({
|
|
154
|
-
paymentMode: servicePrice.paymentMode?.uuid || '',
|
|
155
|
-
price: servicePrice.price || '',
|
|
156
|
-
})) || [DEFAULT_PAYMENT_OPTION],
|
|
157
|
-
},
|
|
186
|
+
defaultValues: transformServiceToFormData(serviceToEdit),
|
|
158
187
|
resolver: zodResolver(billableServiceSchema),
|
|
159
188
|
});
|
|
160
189
|
const { fields, remove, append } = useFieldArray({ name: 'payment', control });
|
|
@@ -174,22 +203,13 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
174
203
|
to: window.getOpenmrsSpaBase() + 'billable-services',
|
|
175
204
|
});
|
|
176
205
|
|
|
206
|
+
// Re-initialize form when editing and dependencies load
|
|
207
|
+
// Needed because serviceTypes/paymentModes may not be available during initial render
|
|
177
208
|
useEffect(() => {
|
|
178
209
|
if (serviceToEdit && !isLoadingPaymentModes && !isLoadingServiceTypes) {
|
|
179
|
-
reset(
|
|
180
|
-
name: serviceToEdit.name || '',
|
|
181
|
-
shortName: serviceToEdit.shortName || '',
|
|
182
|
-
serviceType: serviceToEdit.serviceType || null,
|
|
183
|
-
concept: serviceToEdit.concept
|
|
184
|
-
? { uuid: serviceToEdit.concept.uuid, display: serviceToEdit.concept.display }
|
|
185
|
-
: null,
|
|
186
|
-
payment: serviceToEdit.servicePrices.map((payment: ServicePrice) => ({
|
|
187
|
-
paymentMode: payment.paymentMode?.uuid || '',
|
|
188
|
-
price: payment.price || '',
|
|
189
|
-
})),
|
|
190
|
-
});
|
|
210
|
+
reset(transformServiceToFormData(serviceToEdit));
|
|
191
211
|
}
|
|
192
|
-
}, [serviceToEdit, isLoadingPaymentModes,
|
|
212
|
+
}, [serviceToEdit, isLoadingPaymentModes, isLoadingServiceTypes, reset]);
|
|
193
213
|
|
|
194
214
|
const onSubmit = async (data: BillableServiceFormData) => {
|
|
195
215
|
const payload = {
|
|
@@ -201,7 +221,7 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
201
221
|
return {
|
|
202
222
|
paymentMode: payment.paymentMode,
|
|
203
223
|
name: mode?.name || 'Unknown',
|
|
204
|
-
price:
|
|
224
|
+
price: normalizePrice(payment.price),
|
|
205
225
|
};
|
|
206
226
|
}),
|
|
207
227
|
serviceStatus: 'ENABLED',
|
|
@@ -277,13 +297,14 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
277
297
|
<Layer>
|
|
278
298
|
<TextInput
|
|
279
299
|
{...field}
|
|
300
|
+
enableCounter
|
|
280
301
|
id="serviceName"
|
|
281
|
-
type="text"
|
|
282
|
-
labelText={t('serviceName', 'Service name')}
|
|
283
|
-
placeholder={t('enterServiceName', 'Enter service name')}
|
|
284
|
-
maxLength={MAX_NAME_LENGTH}
|
|
285
302
|
invalid={!!errors.name}
|
|
286
303
|
invalidText={errors.name?.message}
|
|
304
|
+
labelText={t('serviceName', 'Service name')}
|
|
305
|
+
maxCount={MAX_NAME_LENGTH}
|
|
306
|
+
placeholder={t('enterServiceName', 'Enter service name')}
|
|
307
|
+
type="text"
|
|
287
308
|
/>
|
|
288
309
|
</Layer>
|
|
289
310
|
)}
|
|
@@ -298,14 +319,15 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
298
319
|
<Layer>
|
|
299
320
|
<TextInput
|
|
300
321
|
{...field}
|
|
301
|
-
|
|
322
|
+
enableCounter
|
|
302
323
|
id="serviceShortName"
|
|
303
|
-
type="text"
|
|
304
|
-
labelText={t('shortName', 'Short name')}
|
|
305
|
-
placeholder={t('enterServiceShortName', 'Enter service short name')}
|
|
306
|
-
maxLength={MAX_NAME_LENGTH}
|
|
307
324
|
invalid={!!errors.shortName}
|
|
308
325
|
invalidText={errors.shortName?.message}
|
|
326
|
+
labelText={t('shortName', 'Short name')}
|
|
327
|
+
maxCount={MAX_NAME_LENGTH}
|
|
328
|
+
placeholder={t('enterServiceShortName', 'Enter service short name')}
|
|
329
|
+
type="text"
|
|
330
|
+
value={field.value || ''}
|
|
309
331
|
/>
|
|
310
332
|
</Layer>
|
|
311
333
|
)}
|
|
@@ -315,15 +337,15 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
315
337
|
<FormLabel className={styles.conceptLabel}>{t('associatedConcept', 'Associated concept')}</FormLabel>
|
|
316
338
|
<ResponsiveWrapper>
|
|
317
339
|
<Search
|
|
318
|
-
ref={searchInputRef}
|
|
319
340
|
id="conceptsSearch"
|
|
320
341
|
labelText={t('associatedConcept', 'Associated concept')}
|
|
321
|
-
placeholder={t('searchConcepts', 'Search associated concept')}
|
|
322
342
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
|
|
323
343
|
onClear={() => {
|
|
324
344
|
setSearchTerm('');
|
|
325
345
|
setValue('concept', null);
|
|
326
346
|
}}
|
|
347
|
+
placeholder={t('searchConcepts', 'Search associated concept')}
|
|
348
|
+
ref={searchInputRef}
|
|
327
349
|
value={selectedConcept?.display || searchTerm}
|
|
328
350
|
/>
|
|
329
351
|
</ResponsiveWrapper>
|
|
@@ -340,7 +362,6 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
340
362
|
<ul className={styles.conceptsList}>
|
|
341
363
|
{searchResults?.map((searchResult) => (
|
|
342
364
|
<li
|
|
343
|
-
role="menuitem"
|
|
344
365
|
className={styles.service}
|
|
345
366
|
key={searchResult.concept.uuid}
|
|
346
367
|
onClick={() => {
|
|
@@ -349,7 +370,8 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
349
370
|
display: searchResult.display,
|
|
350
371
|
});
|
|
351
372
|
setSearchTerm('');
|
|
352
|
-
}}
|
|
373
|
+
}}
|
|
374
|
+
role="menuitem">
|
|
353
375
|
{searchResult.display}
|
|
354
376
|
</li>
|
|
355
377
|
))}
|
|
@@ -399,14 +421,14 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
399
421
|
<Layer>
|
|
400
422
|
<Dropdown
|
|
401
423
|
id={`paymentMode-${index}`}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
label={t('selectPaymentMode', 'Select payment mode')}
|
|
424
|
+
invalid={!!errors?.payment?.[index]?.paymentMode}
|
|
425
|
+
invalidText={errors?.payment?.[index]?.paymentMode?.message}
|
|
405
426
|
items={paymentModes ?? []}
|
|
406
427
|
itemToString={(item) => (item ? item.name : '')}
|
|
428
|
+
label={t('selectPaymentMode', 'Select payment mode')}
|
|
429
|
+
onChange={({ selectedItem }) => field.onChange(selectedItem.uuid)}
|
|
407
430
|
selectedItem={paymentModes.find((mode) => mode.uuid === field.value)}
|
|
408
|
-
|
|
409
|
-
invalidText={errors?.payment?.[index]?.paymentMode?.message}
|
|
431
|
+
titleText={t('paymentMode', 'Payment mode')}
|
|
410
432
|
/>
|
|
411
433
|
</Layer>
|
|
412
434
|
)}
|
|
@@ -424,12 +446,11 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
424
446
|
label={t('sellingPrice', 'Selling price')}
|
|
425
447
|
min={0}
|
|
426
448
|
onChange={(_, { value }) => {
|
|
427
|
-
|
|
428
|
-
field.onChange(numValue);
|
|
449
|
+
field.onChange(value === '' || value === undefined ? '' : value);
|
|
429
450
|
}}
|
|
430
451
|
placeholder={t('enterSellingPrice', 'Enter selling price')}
|
|
431
452
|
step={0.01}
|
|
432
|
-
value={field.value
|
|
453
|
+
value={field.value === undefined || field.value === null ? '' : field.value}
|
|
433
454
|
/>
|
|
434
455
|
</Layer>
|
|
435
456
|
)}
|
|
@@ -440,12 +461,12 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
440
461
|
</div>
|
|
441
462
|
))}
|
|
442
463
|
<Button
|
|
464
|
+
className={styles.paymentButtons}
|
|
465
|
+
iconDescription={t('add', 'Add')}
|
|
443
466
|
kind="tertiary"
|
|
444
|
-
type="button"
|
|
445
467
|
onClick={handleAppendPaymentMode}
|
|
446
|
-
className={styles.paymentButtons}
|
|
447
468
|
renderIcon={(props) => <Add size={24} {...props} />}
|
|
448
|
-
|
|
469
|
+
type="button">
|
|
449
470
|
{t('addPaymentOption', 'Add payment option')}
|
|
450
471
|
</Button>
|
|
451
472
|
{getPaymentErrorMessage() && <div className={styles.errorMessage}>{getPaymentErrorMessage()}</div>}
|