@openmrs/esm-billing-app 1.0.2-pre.715 → 1.0.2-pre.721
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/4300.js +1 -1
- package/dist/4724.js +1 -1
- package/dist/4724.js.map +1 -1
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/7452.js +1 -1
- package/dist/7452.js.map +1 -1
- package/dist/8930.js +2 -0
- package/dist/8930.js.map +1 -0
- package/dist/942.js +1 -0
- package/dist/942.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 +46 -70
- package/dist/openmrs-esm-billing-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/bill-item-actions/bill-item-actions.scss +0 -4
- package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +58 -60
- package/src/bill-item-actions/edit-bill-item.test.tsx +5 -5
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
- package/src/billable-services/billable-services-home.component.tsx +1 -1
- package/src/billable-services/billable-services.component.tsx +110 -128
- package/src/billable-services/billable-services.scss +3 -0
- package/src/billable-services/billable-services.test.tsx +1 -3
- package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
- package/src/billable-services/cash-point/cash-point-configuration.component.tsx +16 -191
- package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
- package/src/billable-services/create-edit/add-billable-service.component.tsx +23 -26
- package/src/billable-services/create-edit/add-billable-service.scss +2 -5
- package/src/billable-services/create-edit/edit-billable-service.modal.tsx +50 -0
- package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
- package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
- package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
- package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -1
- package/src/billing-form/billing-checkin-form.component.tsx +2 -2
- package/src/billing-form/billing-form.component.tsx +2 -2
- package/src/helpers/functions.ts +5 -4
- package/src/index.ts +16 -6
- package/src/invoice/invoice-table.component.tsx +9 -2
- package/src/invoice/invoice.component.tsx +5 -1
- package/src/invoice/printable-invoice/print-receipt.component.tsx +2 -1
- package/src/modal/require-payment-modal.test.tsx +1 -1
- package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
- package/src/routes.json +22 -2
- package/translations/en.json +12 -10
- package/dist/2352.js +0 -1
- package/dist/2352.js.map +0 -1
- package/dist/8638.js +0 -1
- package/dist/8638.js.map +0 -1
- package/dist/929.js +0 -2
- package/dist/929.js.map +0 -1
- package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
- /package/dist/{929.js.LICENSE.txt → 8930.js.LICENSE.txt} +0 -0
|
@@ -4,7 +4,7 @@ import { render, screen, waitFor } from '@testing-library/react';
|
|
|
4
4
|
import { type FetchResponse, showSnackbar } from '@openmrs/esm-framework';
|
|
5
5
|
import { type MappedBill } from '../types';
|
|
6
6
|
import { updateBillItems } from '../billing.resource';
|
|
7
|
-
import
|
|
7
|
+
import EditBillLineItemModal from './edit-bill-item.modal';
|
|
8
8
|
|
|
9
9
|
const mockUpdateBillItems = jest.mocked(updateBillItems);
|
|
10
10
|
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
@@ -77,7 +77,7 @@ describe('ChangeStatus component', () => {
|
|
|
77
77
|
const closeModalMock = jest.fn();
|
|
78
78
|
|
|
79
79
|
test('renders the form with correct fields and default values', () => {
|
|
80
|
-
render(<
|
|
80
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
81
81
|
|
|
82
82
|
expect(screen.getByText('Edit bill line item?')).toBeInTheDocument();
|
|
83
83
|
expect(screen.getByText('John Doe · Main Cashpoint · 123456')).toBeInTheDocument();
|
|
@@ -88,7 +88,7 @@ describe('ChangeStatus component', () => {
|
|
|
88
88
|
|
|
89
89
|
test('updates total when quantity is changed', async () => {
|
|
90
90
|
const user = userEvent.setup();
|
|
91
|
-
render(<
|
|
91
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
92
92
|
|
|
93
93
|
const quantityInput = screen.getByRole('spinbutton', { name: /Quantity/ });
|
|
94
94
|
await user.type(quantityInput, '3');
|
|
@@ -100,7 +100,7 @@ describe('ChangeStatus component', () => {
|
|
|
100
100
|
const user = userEvent.setup();
|
|
101
101
|
mockUpdateBillItems.mockResolvedValueOnce({} as FetchResponse<any>);
|
|
102
102
|
|
|
103
|
-
render(<
|
|
103
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
104
104
|
|
|
105
105
|
await user.click(screen.getByText(/Save/));
|
|
106
106
|
|
|
@@ -120,7 +120,7 @@ describe('ChangeStatus component', () => {
|
|
|
120
120
|
const user = userEvent.setup();
|
|
121
121
|
mockUpdateBillItems.mockRejectedValueOnce({ message: 'Error occurred' });
|
|
122
122
|
|
|
123
|
-
render(<
|
|
123
|
+
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
124
124
|
|
|
125
125
|
await user.click(screen.getByText(/Save/));
|
|
126
126
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
StructuredListWrapper,
|
|
10
10
|
} from '@carbon/react';
|
|
11
11
|
import { useTranslation } from 'react-i18next';
|
|
12
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
12
|
+
import { getCoreTranslation, useConfig } from '@openmrs/esm-framework';
|
|
13
13
|
import { convertToCurrency } from '../../helpers';
|
|
14
14
|
import { type MappedBill, type LineItem } from '../../types';
|
|
15
15
|
import BillWaiverForm from './bill-waiver-form.component';
|
|
@@ -44,7 +44,7 @@ const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (pat
|
|
|
44
44
|
<StructuredListCell head>{t('quantity', 'Quantity')}</StructuredListCell>
|
|
45
45
|
<StructuredListCell head>{t('unitPrice', 'Unit Price')}</StructuredListCell>
|
|
46
46
|
<StructuredListCell head>{t('total', 'Total')}</StructuredListCell>
|
|
47
|
-
<StructuredListCell head>{
|
|
47
|
+
<StructuredListCell head>{getCoreTranslation('actions')}</StructuredListCell>
|
|
48
48
|
</StructuredListRow>
|
|
49
49
|
</StructuredListHead>
|
|
50
50
|
<StructuredListBody>
|
|
@@ -9,7 +9,7 @@ import BillWaiver from './bill-waiver/bill-waiver.component';
|
|
|
9
9
|
import BillableServicesDashboard from './dashboard/dashboard.component';
|
|
10
10
|
import BillingHeader from '../billing-header/billing-header.component';
|
|
11
11
|
import CashPointConfiguration from './cash-point/cash-point-configuration.component';
|
|
12
|
-
import PaymentModesConfig from './
|
|
12
|
+
import PaymentModesConfig from './payment-modes/payment-modes-config.component';
|
|
13
13
|
import styles from './billable-services.scss';
|
|
14
14
|
|
|
15
15
|
const BillableServiceHome: React.FC = () => {
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
DataTable,
|
|
7
7
|
InlineLoading,
|
|
8
8
|
Layer,
|
|
9
|
-
Modal,
|
|
10
9
|
OverflowMenu,
|
|
11
10
|
OverflowMenuItem,
|
|
12
11
|
Pagination,
|
|
@@ -21,11 +20,19 @@ import {
|
|
|
21
20
|
Tile,
|
|
22
21
|
} from '@carbon/react';
|
|
23
22
|
import { ArrowRight } from '@carbon/react/icons';
|
|
24
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
useLayoutType,
|
|
25
|
+
isDesktop,
|
|
26
|
+
useConfig,
|
|
27
|
+
usePagination,
|
|
28
|
+
ErrorState,
|
|
29
|
+
navigate,
|
|
30
|
+
showModal,
|
|
31
|
+
getCoreTranslation,
|
|
32
|
+
} from '@openmrs/esm-framework';
|
|
25
33
|
import { EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
26
34
|
import { type BillableService } from '../types/index';
|
|
27
35
|
import { useBillableServices } from './billable-service.resource';
|
|
28
|
-
import AddBillableService from './create-edit/add-billable-service.component';
|
|
29
36
|
import type { BillingConfig } from '../config-schema';
|
|
30
37
|
import styles from './billable-services.scss';
|
|
31
38
|
|
|
@@ -39,9 +46,6 @@ const BillableServices = () => {
|
|
|
39
46
|
const pageSizes = [10, 20, 30, 40, 50];
|
|
40
47
|
const [pageSize, setPageSize] = useState(configuredPageSize ?? 10);
|
|
41
48
|
|
|
42
|
-
const [showOverlay, setShowOverlay] = useState(false);
|
|
43
|
-
const [editingService, setEditingService] = useState(null);
|
|
44
|
-
|
|
45
49
|
const headerData = [
|
|
46
50
|
{
|
|
47
51
|
header: t('serviceName', 'Service Name'),
|
|
@@ -64,15 +68,13 @@ const BillableServices = () => {
|
|
|
64
68
|
key: 'prices',
|
|
65
69
|
},
|
|
66
70
|
{
|
|
67
|
-
header:
|
|
71
|
+
header: getCoreTranslation('actions'),
|
|
68
72
|
key: 'actions',
|
|
69
73
|
},
|
|
70
74
|
];
|
|
71
75
|
|
|
72
76
|
const launchBillableServiceForm = useCallback(() => {
|
|
73
77
|
navigate({ to: window.getOpenmrsSpaBase() + 'billable-services/add-service' });
|
|
74
|
-
setEditingService(null);
|
|
75
|
-
setShowOverlay(true);
|
|
76
78
|
}, []);
|
|
77
79
|
|
|
78
80
|
const searchResults: BillableService[] = useMemo(() => {
|
|
@@ -104,16 +106,6 @@ const BillableServices = () => {
|
|
|
104
106
|
serviceType: service?.serviceType?.display,
|
|
105
107
|
status: service.serviceStatus,
|
|
106
108
|
prices: '--',
|
|
107
|
-
actions: (
|
|
108
|
-
<TableCell>
|
|
109
|
-
<OverflowMenu size="sm" flipped>
|
|
110
|
-
<OverflowMenuItem
|
|
111
|
-
itemText={t('editBillableService', 'Edit Billable Service')}
|
|
112
|
-
onClick={() => handleEditService(service)}
|
|
113
|
-
/>
|
|
114
|
-
</OverflowMenu>
|
|
115
|
-
</TableCell>
|
|
116
|
-
),
|
|
117
109
|
};
|
|
118
110
|
let cost = '';
|
|
119
111
|
service.servicePrices.forEach((price) => {
|
|
@@ -131,128 +123,118 @@ const BillableServices = () => {
|
|
|
131
123
|
},
|
|
132
124
|
[goTo, setSearchString],
|
|
133
125
|
);
|
|
134
|
-
const handleEditService = useCallback(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
126
|
+
const handleEditService = useCallback(
|
|
127
|
+
(service) => {
|
|
128
|
+
showModal('edit-billable-service-modal', {
|
|
129
|
+
editingService: service,
|
|
130
|
+
onServiceUpdated: mutate,
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
[mutate],
|
|
134
|
+
);
|
|
143
135
|
|
|
144
136
|
if (isLoading) {
|
|
145
|
-
<InlineLoading status="active" iconDescription="Loading" description="Loading data..." />;
|
|
137
|
+
return <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />;
|
|
146
138
|
}
|
|
139
|
+
|
|
147
140
|
if (error) {
|
|
148
|
-
<ErrorState headerTitle={t('billableService', 'Billable Service')} error={error} />;
|
|
141
|
+
return <ErrorState headerTitle={t('billableService', 'Billable Service')} error={error} />;
|
|
149
142
|
}
|
|
143
|
+
|
|
150
144
|
if (billableServices.length === 0) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
return (
|
|
146
|
+
<EmptyState
|
|
147
|
+
displayText={t('billableServices__lower', 'billable services')}
|
|
148
|
+
headerTitle={t('billableService', 'Billable Service')}
|
|
149
|
+
launchForm={launchBillableServiceForm}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
156
152
|
}
|
|
157
153
|
|
|
158
154
|
return (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{(
|
|
176
|
-
<
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
{row.cells.map((cell) => (
|
|
193
|
-
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
194
|
-
))}
|
|
195
|
-
</TableRow>
|
|
155
|
+
<div className={styles.serviceContainer}>
|
|
156
|
+
<FilterableTableHeader
|
|
157
|
+
handleSearch={handleSearch}
|
|
158
|
+
isValidating={isValidating}
|
|
159
|
+
layout={layout}
|
|
160
|
+
responsiveSize={responsiveSize}
|
|
161
|
+
t={t}
|
|
162
|
+
/>
|
|
163
|
+
<DataTable
|
|
164
|
+
isSortable
|
|
165
|
+
rows={rowData}
|
|
166
|
+
headers={headerData}
|
|
167
|
+
size={responsiveSize}
|
|
168
|
+
useZebraStyles={rowData?.length > 1 ? true : false}>
|
|
169
|
+
{({ rows, headers, getRowProps, getTableProps }) => (
|
|
170
|
+
<TableContainer>
|
|
171
|
+
<Table {...getTableProps()} aria-label="service list">
|
|
172
|
+
<TableHead>
|
|
173
|
+
<TableRow>
|
|
174
|
+
{headers.map((header) => (
|
|
175
|
+
<TableHeader key={header.key}>{header.header}</TableHeader>
|
|
176
|
+
))}
|
|
177
|
+
</TableRow>
|
|
178
|
+
</TableHead>
|
|
179
|
+
<TableBody>
|
|
180
|
+
{rows.map((row) => (
|
|
181
|
+
<TableRow
|
|
182
|
+
key={row.id}
|
|
183
|
+
{...getRowProps({
|
|
184
|
+
row,
|
|
185
|
+
})}>
|
|
186
|
+
{row.cells.map((cell) => (
|
|
187
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
196
188
|
))}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
size={responsiveSize}
|
|
224
|
-
onChange={({ pageSize: newPageSize, page: newPage }) => {
|
|
225
|
-
if (newPageSize !== pageSize) {
|
|
226
|
-
setPageSize(newPageSize);
|
|
227
|
-
}
|
|
228
|
-
if (newPage !== currentPage) {
|
|
229
|
-
goTo(newPage);
|
|
230
|
-
}
|
|
231
|
-
}}
|
|
232
|
-
/>
|
|
233
|
-
)}
|
|
189
|
+
<TableCell className="cds--table-column-menu">
|
|
190
|
+
<OverflowMenu size="sm" flipped>
|
|
191
|
+
<OverflowMenuItem
|
|
192
|
+
className={styles.menuItem}
|
|
193
|
+
itemText={t('editBillableService', 'Edit Billable Service')}
|
|
194
|
+
onClick={() => handleEditService(results.find((service) => service.uuid === row.id))}
|
|
195
|
+
/>
|
|
196
|
+
</OverflowMenu>
|
|
197
|
+
</TableCell>
|
|
198
|
+
</TableRow>
|
|
199
|
+
))}
|
|
200
|
+
</TableBody>
|
|
201
|
+
</Table>
|
|
202
|
+
</TableContainer>
|
|
203
|
+
)}
|
|
204
|
+
</DataTable>
|
|
205
|
+
{searchResults?.length === 0 && (
|
|
206
|
+
<div className={styles.filterEmptyState}>
|
|
207
|
+
<Layer level={0}>
|
|
208
|
+
<Tile className={styles.filterEmptyStateTile}>
|
|
209
|
+
<p className={styles.filterEmptyStateContent}>
|
|
210
|
+
{t('noMatchingServicesToDisplay', 'No matching services to display')}
|
|
211
|
+
</p>
|
|
212
|
+
<p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
|
|
213
|
+
</Tile>
|
|
214
|
+
</Layer>
|
|
234
215
|
</div>
|
|
235
|
-
) : (
|
|
236
|
-
<EmptyState
|
|
237
|
-
launchForm={launchBillableServiceForm}
|
|
238
|
-
displayText={t('noServicesToDisplay', 'There are no services to display')}
|
|
239
|
-
headerTitle={t('billableService', 'Billable service')}
|
|
240
|
-
/>
|
|
241
216
|
)}
|
|
242
|
-
{
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
217
|
+
{paginated && (
|
|
218
|
+
<Pagination
|
|
219
|
+
forwardText="Next page"
|
|
220
|
+
backwardText="Previous page"
|
|
221
|
+
page={currentPage}
|
|
222
|
+
pageSize={pageSize}
|
|
223
|
+
pageSizes={pageSizes}
|
|
224
|
+
totalItems={searchResults?.length}
|
|
225
|
+
className={styles.pagination}
|
|
226
|
+
size={responsiveSize}
|
|
227
|
+
onChange={({ pageSize: newPageSize, page: newPage }) => {
|
|
228
|
+
if (newPageSize !== pageSize) {
|
|
229
|
+
setPageSize(newPageSize);
|
|
230
|
+
}
|
|
231
|
+
if (newPage !== currentPage) {
|
|
232
|
+
goTo(newPage);
|
|
233
|
+
}
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
254
236
|
)}
|
|
255
|
-
|
|
237
|
+
</div>
|
|
256
238
|
);
|
|
257
239
|
};
|
|
258
240
|
|
|
@@ -22,9 +22,7 @@ describe('BillableService', () => {
|
|
|
22
22
|
|
|
23
23
|
render(<BillableServices />);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
expect(screen.getByText('Billable service')).toBeInTheDocument();
|
|
27
|
-
// Check that empty state is visible with some indication
|
|
25
|
+
expect(screen.getByRole('button', { name: /record.*billable services/i })).toBeInTheDocument();
|
|
28
26
|
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
|
29
27
|
});
|
|
30
28
|
|
|
@@ -0,0 +1,168 @@
|
|
|
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, 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
|
+
<Controller
|
|
111
|
+
name="name"
|
|
112
|
+
control={control}
|
|
113
|
+
render={({ field }) => (
|
|
114
|
+
<TextInput
|
|
115
|
+
id="cash-point-name"
|
|
116
|
+
labelText={t('cashPointName', 'Cash Point Name')}
|
|
117
|
+
placeholder={t('cashPointNamePlaceholder', 'e.g., Pharmacy Cash Point')}
|
|
118
|
+
invalid={!!errors.name}
|
|
119
|
+
invalidText={errors.name?.message}
|
|
120
|
+
{...field}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
/>
|
|
124
|
+
<Controller
|
|
125
|
+
name="uuid"
|
|
126
|
+
control={control}
|
|
127
|
+
render={({ field }) => (
|
|
128
|
+
<TextInput
|
|
129
|
+
id="cash-point-uuid"
|
|
130
|
+
labelText={t('cashPointUuid', 'Cash Point UUID')}
|
|
131
|
+
placeholder={t('cashPointUuidPlaceholder', 'Enter UUID')}
|
|
132
|
+
invalid={!!errors.uuid}
|
|
133
|
+
invalidText={errors.uuid?.message}
|
|
134
|
+
{...field}
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
<Controller
|
|
139
|
+
name="location"
|
|
140
|
+
control={control}
|
|
141
|
+
render={({ field }) => (
|
|
142
|
+
<Dropdown
|
|
143
|
+
id="cash-point-location"
|
|
144
|
+
label={t('selectLocation', 'Select Location')}
|
|
145
|
+
titleText={t('cashPointLocation', 'Cash Point Location')}
|
|
146
|
+
items={locations}
|
|
147
|
+
selectedItem={locations.find((loc) => loc.id === field.value)}
|
|
148
|
+
onChange={({ selectedItem }) => field.onChange(selectedItem?.id)}
|
|
149
|
+
invalid={!!errors.location}
|
|
150
|
+
invalidText={errors.location?.message}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
/>
|
|
154
|
+
</ModalBody>
|
|
155
|
+
<ModalFooter>
|
|
156
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
157
|
+
{getCoreTranslation('cancel')}
|
|
158
|
+
</Button>
|
|
159
|
+
<Button type="submit" disabled={isSubmitting}>
|
|
160
|
+
{isSubmitting ? t('saving', 'Saving') + '...' : getCoreTranslation('save')}
|
|
161
|
+
</Button>
|
|
162
|
+
</ModalFooter>
|
|
163
|
+
</Form>
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export default AddCashPointModal;
|