@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.
- package/.eslintrc +16 -2
- package/README.md +54 -9
- package/__mocks__/bills.mock.ts +12 -0
- package/__mocks__/react-i18next.js +6 -5
- package/dist/1119.js +1 -1
- package/dist/1146.js +1 -2
- package/dist/1146.js.map +1 -1
- package/dist/1197.js +1 -1
- package/dist/1537.js +1 -0
- package/dist/1537.js.map +1 -0
- package/dist/1856.js +1 -0
- package/dist/1856.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2524.js +1 -0
- package/dist/2524.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/3717.js +2 -0
- package/dist/3717.js.map +1 -0
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4724.js +1 -0
- package/dist/4724.js.map +1 -0
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/6022.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6540.js +1 -1
- package/dist/6540.js.map +1 -1
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7255.js +1 -1
- package/dist/7255.js.map +1 -1
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8572.js +1 -0
- package/dist/8572.js.map +1 -0
- package/dist/8618.js +1 -1
- package/dist/8708.js +2 -0
- package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
- package/dist/8708.js.map +1 -0
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/961.js +1 -1
- package/dist/961.js.map +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- 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 +271 -285
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/README.md +19 -18
- package/e2e/core/test.ts +1 -1
- package/e2e/fixtures/api.ts +1 -1
- package/e2e/specs/sample-test.spec.ts +0 -1
- package/e2e/support/github/Dockerfile +1 -1
- package/package.json +18 -15
- package/src/bill-history/bill-history.component.tsx +20 -28
- package/src/bill-history/bill-history.scss +4 -94
- package/src/bill-history/bill-history.test.tsx +37 -78
- package/src/bill-item-actions/bill-item-actions.scss +21 -5
- package/src/bill-item-actions/edit-bill-item.modal.tsx +226 -0
- package/src/bill-item-actions/edit-bill-item.test.tsx +233 -40
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +34 -37
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
- package/src/billable-services/bill-waiver/utils.ts +13 -3
- package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
- package/src/billable-services/billable-service-form/billable-service-form.test.tsx +1048 -0
- package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +515 -0
- package/src/billable-services/billable-service.resource.ts +71 -27
- package/src/billable-services/billable-services-home.component.tsx +13 -42
- 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-menu-item/item.component.tsx +5 -4
- package/src/billable-services/billable-services.component.tsx +156 -152
- package/src/billable-services/billable-services.scss +29 -0
- package/src/billable-services/billable-services.test.tsx +6 -49
- package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
- package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +77 -0
- package/src/billable-services/payment-modes/payment-mode-form.modal.tsx +131 -0
- package/src/billable-services/payment-modes/payment-modes-config.component.tsx +139 -0
- package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
- package/src/billable-services-admin-card-link.component.test.tsx +2 -2
- package/src/billable-services-admin-card-link.component.tsx +1 -1
- package/src/billing-dashboard/billing-dashboard.scss +1 -1
- package/src/billing-form/billing-checkin-form.component.tsx +21 -17
- package/src/billing-form/billing-checkin-form.test.tsx +99 -26
- package/src/billing-form/billing-form.component.tsx +226 -289
- package/src/billing-form/billing-form.scss +143 -0
- package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
- package/src/billing.resource.ts +69 -74
- package/src/bills-table/bills-table.component.tsx +3 -3
- package/src/bills-table/bills-table.test.tsx +98 -54
- package/src/config-schema.ts +52 -24
- package/src/dashboard.meta.ts +4 -2
- package/src/helpers/functions.ts +5 -4
- package/src/index.ts +71 -9
- package/src/invoice/invoice-table.component.tsx +36 -70
- package/src/invoice/invoice-table.scss +8 -5
- package/src/invoice/invoice-table.test.tsx +273 -62
- package/src/invoice/invoice.component.tsx +39 -33
- package/src/invoice/invoice.scss +11 -4
- package/src/invoice/invoice.test.tsx +324 -120
- package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
- package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
- package/src/invoice/payments/payment-form/payment-form.scss +5 -6
- package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
- package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
- package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
- package/src/invoice/payments/payments.component.tsx +55 -67
- package/src/invoice/payments/payments.scss +4 -3
- package/src/invoice/payments/payments.test.tsx +282 -0
- package/src/invoice/payments/utils.ts +15 -27
- package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -3
- package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
- package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
- package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +20 -11
- package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +95 -16
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +21 -35
- package/src/left-panel-link.test.tsx +1 -4
- package/src/metrics-cards/metrics-cards.component.tsx +16 -6
- package/src/metrics-cards/metrics-cards.scss +4 -0
- package/src/metrics-cards/metrics-cards.test.tsx +18 -5
- package/src/modal/require-payment-modal.test.tsx +27 -22
- package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
- package/src/routes.json +44 -20
- package/src/types/index.ts +87 -24
- package/translations/am.json +135 -78
- package/translations/ar.json +136 -79
- package/translations/ar_SY.json +136 -79
- package/translations/bn.json +138 -81
- package/translations/de.json +136 -79
- package/translations/en.json +136 -79
- package/translations/en_US.json +136 -79
- package/translations/es.json +135 -78
- package/translations/es_MX.json +136 -79
- package/translations/fr.json +141 -84
- package/translations/he.json +135 -78
- package/translations/hi.json +136 -79
- package/translations/hi_IN.json +136 -79
- package/translations/id.json +136 -79
- package/translations/it.json +162 -105
- package/translations/ka.json +136 -79
- package/translations/km.json +135 -78
- package/translations/ku.json +136 -79
- package/translations/ky.json +136 -79
- package/translations/lg.json +136 -79
- package/translations/ne.json +136 -79
- package/translations/pl.json +136 -79
- package/translations/pt.json +136 -79
- package/translations/pt_BR.json +136 -79
- package/translations/qu.json +136 -79
- package/translations/ro_RO.json +222 -165
- package/translations/ru_RU.json +136 -79
- package/translations/si.json +136 -79
- package/translations/sw.json +136 -79
- package/translations/sw_KE.json +136 -79
- package/translations/tr.json +136 -79
- package/translations/tr_TR.json +136 -79
- package/translations/uk.json +136 -79
- package/translations/uz.json +136 -79
- package/translations/uz@Latn.json +136 -79
- package/translations/uz_UZ.json +136 -79
- package/translations/vi.json +136 -79
- package/translations/zh.json +136 -79
- package/translations/zh_CN.json +166 -109
- package/dist/1146.js.LICENSE.txt +0 -21
- package/dist/2352.js +0 -1
- package/dist/2352.js.map +0 -1
- package/dist/246.js +0 -1
- package/dist/246.js.map +0 -1
- package/dist/4689.js +0 -2
- package/dist/4689.js.map +0 -1
- package/dist/6557.js +0 -2
- package/dist/6557.js.map +0 -1
- package/dist/8638.js +0 -1
- package/dist/8638.js.map +0 -1
- package/dist/9968.js +0 -1
- package/dist/9968.js.map +0 -1
- package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
- package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
- package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
- package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
- package/src/invoice/payments/payments.component.test.tsx +0 -121
- /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
|
@@ -4,55 +4,13 @@ import userEvent from '@testing-library/user-event';
|
|
|
4
4
|
import BillableServices from './billable-services.component';
|
|
5
5
|
import { useBillableServices } from './billable-service.resource';
|
|
6
6
|
|
|
7
|
-
// Mock the resource
|
|
8
7
|
jest.mock('./billable-service.resource', () => ({
|
|
9
8
|
useBillableServices: jest.fn(),
|
|
10
9
|
}));
|
|
11
10
|
|
|
12
|
-
// Mock the empty state component
|
|
13
|
-
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
14
|
-
EmptyState: jest.fn(({ displayText, headerTitle }) => (
|
|
15
|
-
<div data-testid="empty-state">
|
|
16
|
-
<h1>{headerTitle}</h1>
|
|
17
|
-
<p>{displayText}</p>
|
|
18
|
-
</div>
|
|
19
|
-
)),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
// Mock navigation
|
|
23
|
-
jest.mock('@openmrs/esm-framework', () => ({
|
|
24
|
-
useLayoutType: jest.fn(() => 'desktop'),
|
|
25
|
-
isDesktop: jest.fn(() => true),
|
|
26
|
-
useConfig: jest.fn(() => ({
|
|
27
|
-
billableServices: {
|
|
28
|
-
pageSizes: [10, 20, 30, 40, 50],
|
|
29
|
-
pageSize: 10,
|
|
30
|
-
},
|
|
31
|
-
})),
|
|
32
|
-
usePagination: jest.fn().mockImplementation((data) => ({
|
|
33
|
-
currentPage: 1,
|
|
34
|
-
goTo: jest.fn(),
|
|
35
|
-
results: data,
|
|
36
|
-
paginated: true,
|
|
37
|
-
})),
|
|
38
|
-
navigate: jest.fn(),
|
|
39
|
-
ErrorState: jest.fn(({ error }) => <div>Error: {error?.message || error}</div>),
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
// Mock i18next
|
|
43
|
-
jest.mock('react-i18next', () => ({
|
|
44
|
-
useTranslation: () => ({
|
|
45
|
-
t: (key: string, fallback: string) => fallback || key,
|
|
46
|
-
}),
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
11
|
describe('BillableService', () => {
|
|
50
12
|
const mockedUseBillableServices = useBillableServices as jest.Mock;
|
|
51
13
|
|
|
52
|
-
beforeEach(() => {
|
|
53
|
-
jest.clearAllMocks();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
14
|
it('renders an empty state when there are no billable services', () => {
|
|
57
15
|
mockedUseBillableServices.mockReturnValue({
|
|
58
16
|
billableServices: [],
|
|
@@ -64,9 +22,8 @@ describe('BillableService', () => {
|
|
|
64
22
|
|
|
65
23
|
render(<BillableServices />);
|
|
66
24
|
|
|
67
|
-
expect(screen.
|
|
68
|
-
expect(screen.
|
|
69
|
-
expect(screen.getByText('There are no services to display')).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByRole('button', { name: /record.*billable services/i })).toBeInTheDocument();
|
|
26
|
+
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
|
70
27
|
});
|
|
71
28
|
|
|
72
29
|
it('renders billable services table correctly', () => {
|
|
@@ -100,10 +57,10 @@ describe('BillableService', () => {
|
|
|
100
57
|
render(<BillableServices />);
|
|
101
58
|
|
|
102
59
|
// Check table headers
|
|
103
|
-
expect(screen.getByText('Service
|
|
104
|
-
expect(screen.getByText('Short
|
|
105
|
-
expect(screen.getByText('Service
|
|
106
|
-
expect(screen.getByText('Service
|
|
60
|
+
expect(screen.getByText('Service name')).toBeInTheDocument();
|
|
61
|
+
expect(screen.getByText('Short name')).toBeInTheDocument();
|
|
62
|
+
expect(screen.getByText('Service type')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByText('Service status')).toBeInTheDocument();
|
|
107
64
|
|
|
108
65
|
// Check service data
|
|
109
66
|
expect(screen.getByText('Service 1')).toBeInTheDocument();
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } 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, Dropdown, Form, ModalBody, ModalFooter, ModalHeader, Stack, TextInput } from '@carbon/react';
|
|
7
|
+
import { showSnackbar, openmrsFetch, restBaseUrl, getCoreTranslation } from '@openmrs/esm-framework';
|
|
8
|
+
|
|
9
|
+
type CashPointFormValues = {
|
|
10
|
+
name: string;
|
|
11
|
+
uuid: string;
|
|
12
|
+
location: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface AddCashPointModalProps {
|
|
16
|
+
closeModal: () => void;
|
|
17
|
+
onCashPointAdded: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const AddCashPointModal: React.FC<AddCashPointModalProps> = ({ closeModal, onCashPointAdded }) => {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const [locations, setLocations] = useState([]);
|
|
23
|
+
|
|
24
|
+
const cashPointSchema = z.object({
|
|
25
|
+
name: z.string().min(1, t('cashPointNameRequired', 'Cash Point Name is required')),
|
|
26
|
+
uuid: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1, t('uuidRequired', 'UUID is required'))
|
|
29
|
+
.regex(
|
|
30
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
31
|
+
t('invalidUuidFormat', 'Invalid UUID format'),
|
|
32
|
+
),
|
|
33
|
+
location: z.string().min(1, t('locationRequired', 'Location is required')),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
control,
|
|
38
|
+
handleSubmit,
|
|
39
|
+
reset,
|
|
40
|
+
formState: { errors, isSubmitting },
|
|
41
|
+
} = useForm<CashPointFormValues>({
|
|
42
|
+
resolver: zodResolver(cashPointSchema),
|
|
43
|
+
defaultValues: {
|
|
44
|
+
name: '',
|
|
45
|
+
uuid: '',
|
|
46
|
+
location: '',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const fetchLocations = useCallback(async () => {
|
|
51
|
+
try {
|
|
52
|
+
const response = await openmrsFetch(`${restBaseUrl}/location?v=default`);
|
|
53
|
+
const allLocations = response.data.results.map((loc: any) => ({
|
|
54
|
+
id: loc.uuid,
|
|
55
|
+
label: loc.display,
|
|
56
|
+
}));
|
|
57
|
+
setLocations(allLocations);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
showSnackbar({
|
|
60
|
+
title: getCoreTranslation('error'),
|
|
61
|
+
subtitle: t('errorFetchingLocations', 'An error occurred while fetching locations.'),
|
|
62
|
+
kind: 'error',
|
|
63
|
+
isLowContrast: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}, [t]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
fetchLocations();
|
|
70
|
+
}, [fetchLocations]);
|
|
71
|
+
|
|
72
|
+
const onSubmit = async (data: CashPointFormValues) => {
|
|
73
|
+
try {
|
|
74
|
+
await openmrsFetch(`${restBaseUrl}/billing/cashPoint`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
},
|
|
79
|
+
body: {
|
|
80
|
+
name: data.name,
|
|
81
|
+
uuid: data.uuid,
|
|
82
|
+
location: { uuid: data.location },
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
showSnackbar({
|
|
87
|
+
title: t('success', 'Success'),
|
|
88
|
+
subtitle: t('cashPointSaved', 'Cash point was successfully saved.'),
|
|
89
|
+
kind: 'success',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
closeModal();
|
|
93
|
+
reset({ name: '', uuid: '', location: '' });
|
|
94
|
+
onCashPointAdded();
|
|
95
|
+
} catch (err) {
|
|
96
|
+
showSnackbar({
|
|
97
|
+
title: getCoreTranslation('error'),
|
|
98
|
+
subtitle: err?.message || t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
|
|
99
|
+
kind: 'error',
|
|
100
|
+
isLowContrast: false,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<>
|
|
107
|
+
<ModalHeader closeModal={closeModal} title={t('addCashPoint', 'Add Cash Point')} />
|
|
108
|
+
<Form onSubmit={handleSubmit(onSubmit)}>
|
|
109
|
+
<ModalBody>
|
|
110
|
+
<Stack gap={5}>
|
|
111
|
+
<Controller
|
|
112
|
+
name="name"
|
|
113
|
+
control={control}
|
|
114
|
+
render={({ field }) => (
|
|
115
|
+
<TextInput
|
|
116
|
+
id="cash-point-name"
|
|
117
|
+
labelText={t('cashPointName', 'Cash Point Name')}
|
|
118
|
+
placeholder={t('cashPointNamePlaceholder', 'For example, Pharmacy Cash Point')}
|
|
119
|
+
invalid={!!errors.name}
|
|
120
|
+
invalidText={errors.name?.message}
|
|
121
|
+
{...field}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
/>
|
|
125
|
+
<Controller
|
|
126
|
+
name="uuid"
|
|
127
|
+
control={control}
|
|
128
|
+
render={({ field }) => (
|
|
129
|
+
<TextInput
|
|
130
|
+
id="cash-point-uuid"
|
|
131
|
+
labelText={t('cashPointUuid', 'Cash Point UUID')}
|
|
132
|
+
placeholder={t('cashPointUuidPlaceholder', 'Enter UUID')}
|
|
133
|
+
invalid={!!errors.uuid}
|
|
134
|
+
invalidText={errors.uuid?.message}
|
|
135
|
+
{...field}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
/>
|
|
139
|
+
<Controller
|
|
140
|
+
name="location"
|
|
141
|
+
control={control}
|
|
142
|
+
render={({ field }) => (
|
|
143
|
+
<Dropdown
|
|
144
|
+
id="cash-point-location"
|
|
145
|
+
label={t('selectLocation', 'Select Location')}
|
|
146
|
+
titleText={t('cashPointLocation', 'Cash Point Location')}
|
|
147
|
+
items={locations}
|
|
148
|
+
selectedItem={locations.find((loc) => loc.id === field.value)}
|
|
149
|
+
onChange={({ selectedItem }) => field.onChange(selectedItem?.id)}
|
|
150
|
+
invalid={!!errors.location}
|
|
151
|
+
invalidText={errors.location?.message}
|
|
152
|
+
/>
|
|
153
|
+
)}
|
|
154
|
+
/>
|
|
155
|
+
</Stack>
|
|
156
|
+
</ModalBody>
|
|
157
|
+
<ModalFooter>
|
|
158
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
159
|
+
{getCoreTranslation('cancel')}
|
|
160
|
+
</Button>
|
|
161
|
+
<Button type="submit" disabled={isSubmitting}>
|
|
162
|
+
{isSubmitting ? t('saving', 'Saving') + '...' : getCoreTranslation('save')}
|
|
163
|
+
</Button>
|
|
164
|
+
</ModalFooter>
|
|
165
|
+
</Form>
|
|
166
|
+
</>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default AddCashPointModal;
|
|
@@ -2,59 +2,23 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
Button,
|
|
4
4
|
DataTable,
|
|
5
|
-
TableContainer,
|
|
6
5
|
Table,
|
|
7
|
-
TableHead,
|
|
8
|
-
TableRow,
|
|
9
|
-
TableHeader,
|
|
10
6
|
TableBody,
|
|
11
7
|
TableCell,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Dropdown,
|
|
8
|
+
TableContainer,
|
|
9
|
+
TableHead,
|
|
10
|
+
TableHeader,
|
|
11
|
+
TableRow,
|
|
17
12
|
} from '@carbon/react';
|
|
18
13
|
import { Add } from '@carbon/react/icons';
|
|
19
14
|
import { useTranslation } from 'react-i18next';
|
|
20
|
-
import {
|
|
21
|
-
import { z } from 'zod';
|
|
22
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
23
|
-
import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
15
|
+
import { showSnackbar, openmrsFetch, restBaseUrl, showModal, getCoreTranslation } from '@openmrs/esm-framework';
|
|
24
16
|
import { CardHeader } from '@openmrs/esm-patient-common-lib';
|
|
25
17
|
import styles from './cash-point-configuration.scss';
|
|
26
18
|
|
|
27
|
-
// Validation schema
|
|
28
|
-
const cashPointSchema = z.object({
|
|
29
|
-
name: z.string().min(1, 'Cash Point Name is required'),
|
|
30
|
-
uuid: z
|
|
31
|
-
.string()
|
|
32
|
-
.min(1, 'UUID is required')
|
|
33
|
-
.regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, 'Invalid UUID format'),
|
|
34
|
-
location: z.string().min(1, 'Location is required'),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
type CashPointFormValues = z.infer<typeof cashPointSchema>;
|
|
38
|
-
|
|
39
19
|
const CashPointConfiguration: React.FC = () => {
|
|
40
20
|
const { t } = useTranslation();
|
|
41
21
|
const [cashPoints, setCashPoints] = useState([]);
|
|
42
|
-
const [locations, setLocations] = useState([]);
|
|
43
|
-
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
44
|
-
|
|
45
|
-
const {
|
|
46
|
-
control,
|
|
47
|
-
handleSubmit,
|
|
48
|
-
reset,
|
|
49
|
-
formState: { errors, isSubmitting },
|
|
50
|
-
} = useForm<CashPointFormValues>({
|
|
51
|
-
resolver: zodResolver(cashPointSchema),
|
|
52
|
-
defaultValues: {
|
|
53
|
-
name: '',
|
|
54
|
-
uuid: '',
|
|
55
|
-
location: '',
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
22
|
|
|
59
23
|
const fetchCashPoints = useCallback(async () => {
|
|
60
24
|
try {
|
|
@@ -62,7 +26,7 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
62
26
|
setCashPoints(response.data.results || []);
|
|
63
27
|
} catch (err) {
|
|
64
28
|
showSnackbar({
|
|
65
|
-
title:
|
|
29
|
+
title: getCoreTranslation('error'),
|
|
66
30
|
subtitle: t('errorFetchingCashPoints', 'An error occurred while fetching cash points.'),
|
|
67
31
|
kind: 'error',
|
|
68
32
|
isLowContrast: false,
|
|
@@ -70,87 +34,15 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
70
34
|
}
|
|
71
35
|
}, [t]);
|
|
72
36
|
|
|
73
|
-
const fetchLocations = useCallback(async () => {
|
|
74
|
-
try {
|
|
75
|
-
const response = await openmrsFetch(`${restBaseUrl}/location?v=default`);
|
|
76
|
-
const allLocations = response.data.results.map((loc) => ({
|
|
77
|
-
id: loc.uuid,
|
|
78
|
-
label: loc.display,
|
|
79
|
-
}));
|
|
80
|
-
setLocations(allLocations);
|
|
81
|
-
} catch (err) {
|
|
82
|
-
showSnackbar({
|
|
83
|
-
title: t('error', 'Error'),
|
|
84
|
-
subtitle: t('errorFetchingLocations', 'An error occurred while fetching locations.'),
|
|
85
|
-
kind: 'error',
|
|
86
|
-
isLowContrast: false,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}, [t]);
|
|
90
|
-
|
|
91
37
|
useEffect(() => {
|
|
92
38
|
fetchCashPoints();
|
|
93
|
-
|
|
94
|
-
}, [fetchCashPoints, fetchLocations]);
|
|
95
|
-
|
|
96
|
-
const onSubmit = async (data: CashPointFormValues) => {
|
|
97
|
-
const isDuplicate = cashPoints.some(
|
|
98
|
-
(point) => point.name.toLowerCase() === data.name.toLowerCase() || point.uuid === data.uuid,
|
|
99
|
-
);
|
|
39
|
+
}, [fetchCashPoints]);
|
|
100
40
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'A cash point with the same name or UUID already exists. Please use a unique name and UUID.',
|
|
107
|
-
),
|
|
108
|
-
kind: 'error',
|
|
109
|
-
isLowContrast: false,
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const response = await openmrsFetch(`${restBaseUrl}/billing/cashPoint`, {
|
|
116
|
-
method: 'POST',
|
|
117
|
-
headers: {
|
|
118
|
-
'Content-Type': 'application/json',
|
|
119
|
-
},
|
|
120
|
-
body: {
|
|
121
|
-
name: data.name,
|
|
122
|
-
uuid: data.uuid,
|
|
123
|
-
location: { uuid: data.location },
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (response.ok) {
|
|
128
|
-
showSnackbar({
|
|
129
|
-
title: t('success', 'Success'),
|
|
130
|
-
subtitle: t('cashPointSaved', 'Cash point was successfully saved.'),
|
|
131
|
-
kind: 'success',
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
setIsModalOpen(false);
|
|
135
|
-
reset({ name: '', uuid: '', location: '' });
|
|
136
|
-
fetchCashPoints();
|
|
137
|
-
} else {
|
|
138
|
-
const errorData = response.data || {};
|
|
139
|
-
showSnackbar({
|
|
140
|
-
title: t('error', 'Error'),
|
|
141
|
-
subtitle: errorData.message || t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
|
|
142
|
-
kind: 'error',
|
|
143
|
-
isLowContrast: false,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
} catch (err) {
|
|
147
|
-
showSnackbar({
|
|
148
|
-
title: t('error', 'Error'),
|
|
149
|
-
subtitle: t('errorSavingCashPoint', 'An error occurred while saving the cash point.'),
|
|
150
|
-
kind: 'error',
|
|
151
|
-
isLowContrast: false,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
41
|
+
const handleAddCashPoint = () => {
|
|
42
|
+
const dispose = showModal('add-cash-point-modal', {
|
|
43
|
+
onCashPointAdded: fetchCashPoints,
|
|
44
|
+
closeModal: () => dispose(),
|
|
45
|
+
});
|
|
154
46
|
};
|
|
155
47
|
|
|
156
48
|
const rowData = cashPoints.map((point) => ({
|
|
@@ -164,18 +56,17 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
164
56
|
{ key: 'name', header: t('name', 'Name') },
|
|
165
57
|
{ key: 'uuid', header: t('uuid', 'UUID') },
|
|
166
58
|
{ key: 'location', header: t('location', 'Location') },
|
|
167
|
-
{ key: 'actions', header: t('actions', 'Actions') },
|
|
168
59
|
];
|
|
169
60
|
|
|
170
61
|
return (
|
|
171
62
|
<div className={styles.container}>
|
|
172
63
|
<div className={styles.card}>
|
|
173
|
-
<CardHeader title={t('cashPointHistory', 'Cash
|
|
174
|
-
<Button renderIcon={Add} onClick={
|
|
175
|
-
{t('
|
|
64
|
+
<CardHeader title={t('cashPointHistory', 'Cash point history')}>
|
|
65
|
+
<Button renderIcon={Add} onClick={handleAddCashPoint} kind="ghost">
|
|
66
|
+
{t('addNewCashPoint', 'Add new cash point')}
|
|
176
67
|
</Button>
|
|
177
68
|
</CardHeader>
|
|
178
|
-
<div
|
|
69
|
+
<div>
|
|
179
70
|
<DataTable rows={rowData} headers={headerData} isSortable size="lg">
|
|
180
71
|
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
181
72
|
<TableContainer>
|
|
@@ -192,17 +83,9 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
192
83
|
<TableBody>
|
|
193
84
|
{rows.map((row) => (
|
|
194
85
|
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
195
|
-
{row.cells.map((cell) =>
|
|
196
|
-
cell.
|
|
197
|
-
|
|
198
|
-
) : (
|
|
199
|
-
<TableCell key={cell.id}>
|
|
200
|
-
<OverflowMenu>
|
|
201
|
-
<OverflowMenuItem itemText={t('delete', 'Delete')} disabled />
|
|
202
|
-
</OverflowMenu>
|
|
203
|
-
</TableCell>
|
|
204
|
-
),
|
|
205
|
-
)}
|
|
86
|
+
{row.cells.map((cell) => (
|
|
87
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
88
|
+
))}
|
|
206
89
|
</TableRow>
|
|
207
90
|
))}
|
|
208
91
|
</TableBody>
|
|
@@ -212,63 +95,6 @@ const CashPointConfiguration: React.FC = () => {
|
|
|
212
95
|
</DataTable>
|
|
213
96
|
</div>
|
|
214
97
|
</div>
|
|
215
|
-
|
|
216
|
-
{/* Modal for Adding New Cash Point */}
|
|
217
|
-
<Modal
|
|
218
|
-
open={isModalOpen}
|
|
219
|
-
modalHeading={t('addCashPoint', 'Add Cash Point')}
|
|
220
|
-
onRequestClose={() => setIsModalOpen(false)}
|
|
221
|
-
onRequestSubmit={handleSubmit(onSubmit)}
|
|
222
|
-
primaryButtonText={t('save', 'Save')}
|
|
223
|
-
secondaryButtonText={t('cancel', 'Cancel')}
|
|
224
|
-
isPrimaryButtonDisabled={isSubmitting}>
|
|
225
|
-
<form>
|
|
226
|
-
<Controller
|
|
227
|
-
name="name"
|
|
228
|
-
control={control}
|
|
229
|
-
render={({ field }) => (
|
|
230
|
-
<TextInput
|
|
231
|
-
id="cash-point-name"
|
|
232
|
-
labelText={t('cashPointName', 'Cash Point Name')}
|
|
233
|
-
placeholder={t('cashPointNamePlaceholder', 'e.g., Pharmacy Cash Point')}
|
|
234
|
-
invalid={!!errors.name}
|
|
235
|
-
invalidText={errors.name?.message}
|
|
236
|
-
{...field}
|
|
237
|
-
/>
|
|
238
|
-
)}
|
|
239
|
-
/>
|
|
240
|
-
<Controller
|
|
241
|
-
name="uuid"
|
|
242
|
-
control={control}
|
|
243
|
-
render={({ field }) => (
|
|
244
|
-
<TextInput
|
|
245
|
-
id="cash-point-uuid"
|
|
246
|
-
labelText={t('cashPointUuid', 'Cash Point UUID')}
|
|
247
|
-
placeholder={t('cashPointUuidPlaceholder', 'Enter UUID')}
|
|
248
|
-
invalid={!!errors.uuid}
|
|
249
|
-
invalidText={errors.uuid?.message}
|
|
250
|
-
{...field}
|
|
251
|
-
/>
|
|
252
|
-
)}
|
|
253
|
-
/>
|
|
254
|
-
<Controller
|
|
255
|
-
name="location"
|
|
256
|
-
control={control}
|
|
257
|
-
render={({ field }) => (
|
|
258
|
-
<Dropdown
|
|
259
|
-
id="cash-point-location"
|
|
260
|
-
label={t('location', 'Select Location')}
|
|
261
|
-
titleText={t('cashPointLocation', 'Cash Point Location')}
|
|
262
|
-
items={locations}
|
|
263
|
-
selectedItem={locations.find((loc) => loc.id === field.value)}
|
|
264
|
-
onChange={({ selectedItem }) => field.onChange(selectedItem?.id)}
|
|
265
|
-
invalid={!!errors.location}
|
|
266
|
-
invalidText={errors.location?.message}
|
|
267
|
-
/>
|
|
268
|
-
)}
|
|
269
|
-
/>
|
|
270
|
-
</form>
|
|
271
|
-
</Modal>
|
|
272
98
|
</div>
|
|
273
99
|
);
|
|
274
100
|
};
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import BillableServices from '../billable-services.component';
|
|
3
3
|
import styles from './dashboard.scss';
|
|
4
|
-
import { ExtensionSlot } from '@openmrs/esm-framework';
|
|
5
4
|
|
|
6
5
|
export default function BillableServicesDashboard() {
|
|
7
6
|
return (
|
|
8
7
|
<main className={styles.container}>
|
|
9
|
-
<ExtensionSlot name="billing-home-tiles-slot" />
|
|
10
8
|
<main className={styles.servicesTableContainer}>
|
|
11
9
|
<BillableServices />
|
|
12
10
|
</main>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useSWRConfig } from 'swr';
|
|
4
|
+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
5
|
+
import { showSnackbar, openmrsFetch, restBaseUrl, getCoreTranslation } from '@openmrs/esm-framework';
|
|
6
|
+
import { apiBasePath } from '../../constants';
|
|
7
|
+
|
|
8
|
+
interface DeletePaymentModeModalProps {
|
|
9
|
+
closeModal: () => void;
|
|
10
|
+
paymentModeUuid: string;
|
|
11
|
+
paymentModeName: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DeletePaymentModeModal: React.FC<DeletePaymentModeModalProps> = ({
|
|
15
|
+
closeModal,
|
|
16
|
+
paymentModeUuid,
|
|
17
|
+
paymentModeName,
|
|
18
|
+
}) => {
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const { mutate } = useSWRConfig();
|
|
21
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
22
|
+
|
|
23
|
+
const handleDelete = async () => {
|
|
24
|
+
setIsDeleting(true);
|
|
25
|
+
const url = `${apiBasePath}paymentMode`;
|
|
26
|
+
try {
|
|
27
|
+
await openmrsFetch(`${restBaseUrl}/billing/paymentMode/${paymentModeUuid}`, {
|
|
28
|
+
method: 'DELETE',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
32
|
+
|
|
33
|
+
showSnackbar({
|
|
34
|
+
title: t('success', 'Success'),
|
|
35
|
+
subtitle: t('paymentModeDeleted', 'Payment mode was successfully deleted.'),
|
|
36
|
+
kind: 'success',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
closeModal();
|
|
40
|
+
} catch (err) {
|
|
41
|
+
showSnackbar({
|
|
42
|
+
title: getCoreTranslation('error'),
|
|
43
|
+
subtitle: err?.message || t('errorDeletingPaymentMode', 'An error occurred while deleting the payment mode.'),
|
|
44
|
+
kind: 'error',
|
|
45
|
+
isLowContrast: false,
|
|
46
|
+
});
|
|
47
|
+
} finally {
|
|
48
|
+
setIsDeleting(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
<ModalHeader closeModal={closeModal} title={t('deletePaymentMode', 'Delete Payment Mode')} />
|
|
55
|
+
<ModalBody>
|
|
56
|
+
<p>{t('confirmDeleteMessage', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}</p>
|
|
57
|
+
{paymentModeName && (
|
|
58
|
+
<p>
|
|
59
|
+
<strong>
|
|
60
|
+
{t('paymentModeNameToDelete', 'Payment Mode Name: {{paymentModeName}}', { paymentModeName })}
|
|
61
|
+
</strong>
|
|
62
|
+
</p>
|
|
63
|
+
)}
|
|
64
|
+
</ModalBody>
|
|
65
|
+
<ModalFooter>
|
|
66
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
67
|
+
{getCoreTranslation('cancel')}
|
|
68
|
+
</Button>
|
|
69
|
+
<Button kind="danger" onClick={handleDelete} disabled={isDeleting}>
|
|
70
|
+
{isDeleting ? t('deleting', 'Deleting') + '...' : getCoreTranslation('delete')}
|
|
71
|
+
</Button>
|
|
72
|
+
</ModalFooter>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default DeletePaymentModeModal;
|