@openmrs/esm-billing-app 1.0.2-pre.711 → 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.
Files changed (55) hide show
  1. package/dist/4300.js +1 -1
  2. package/dist/4724.js +1 -1
  3. package/dist/4724.js.map +1 -1
  4. package/dist/4739.js +1 -1
  5. package/dist/4739.js.map +1 -1
  6. package/dist/7452.js +1 -1
  7. package/dist/7452.js.map +1 -1
  8. package/dist/8930.js +2 -0
  9. package/dist/8930.js.map +1 -0
  10. package/dist/942.js +1 -0
  11. package/dist/942.js.map +1 -0
  12. package/dist/main.js +1 -1
  13. package/dist/main.js.map +1 -1
  14. package/dist/openmrs-esm-billing-app.js +1 -1
  15. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +46 -70
  16. package/dist/openmrs-esm-billing-app.js.map +1 -1
  17. package/dist/routes.json +1 -1
  18. package/package.json +2 -2
  19. package/src/bill-item-actions/bill-item-actions.scss +0 -4
  20. package/src/bill-item-actions/{edit-bill-item.component.tsx → edit-bill-item.modal.tsx} +58 -60
  21. package/src/bill-item-actions/edit-bill-item.test.tsx +5 -5
  22. package/src/billable-services/bill-waiver/bill-selection.component.tsx +2 -2
  23. package/src/billable-services/billable-services-home.component.tsx +1 -1
  24. package/src/billable-services/billable-services.component.tsx +110 -128
  25. package/src/billable-services/billable-services.scss +3 -0
  26. package/src/billable-services/billable-services.test.tsx +1 -3
  27. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  28. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +16 -191
  29. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  30. package/src/billable-services/create-edit/add-billable-service.component.tsx +23 -26
  31. package/src/billable-services/create-edit/add-billable-service.scss +2 -5
  32. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +50 -0
  33. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  34. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +72 -0
  35. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  36. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -1
  37. package/src/billing-form/billing-checkin-form.component.tsx +2 -2
  38. package/src/billing-form/billing-form.component.tsx +2 -2
  39. package/src/helpers/functions.ts +5 -4
  40. package/src/index.ts +16 -6
  41. package/src/invoice/invoice-table.component.tsx +9 -2
  42. package/src/invoice/invoice.component.tsx +5 -1
  43. package/src/invoice/printable-invoice/print-receipt.component.tsx +2 -1
  44. package/src/modal/require-payment-modal.test.tsx +1 -1
  45. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +17 -18
  46. package/src/routes.json +22 -2
  47. package/translations/en.json +12 -10
  48. package/dist/2352.js +0 -1
  49. package/dist/2352.js.map +0 -1
  50. package/dist/8638.js +0 -1
  51. package/dist/8638.js.map +0 -1
  52. package/dist/929.js +0 -2
  53. package/dist/929.js.map +0 -1
  54. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  55. /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 ChangeStatus from './edit-bill-item.component';
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(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
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(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
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(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
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(<ChangeStatus bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
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>{t('actions', 'Actions')}</StructuredListCell>
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 './payyment-modes/payment-modes-config.component';
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 { useLayoutType, isDesktop, useConfig, usePagination, ErrorState, navigate } from '@openmrs/esm-framework';
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: t('actions', 'Actions'),
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((service) => {
135
- setEditingService(service);
136
- setShowOverlay(true);
137
- }, []);
138
-
139
- const closeModal = useCallback(() => {
140
- setShowOverlay(false);
141
- setEditingService(null);
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
- <EmptyState
152
- displayText={t('billableService', 'Billable Service')}
153
- headerTitle={t('billableService', 'Billable Service')}
154
- launchForm={launchBillableServiceForm}
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
- {billableServices?.length > 0 ? (
161
- <div className={styles.serviceContainer}>
162
- <FilterableTableHeader
163
- handleSearch={handleSearch}
164
- isValidating={isValidating}
165
- layout={layout}
166
- responsiveSize={responsiveSize}
167
- t={t}
168
- />
169
- <DataTable
170
- isSortable
171
- rows={rowData}
172
- headers={headerData}
173
- size={responsiveSize}
174
- useZebraStyles={rowData?.length > 1 ? true : false}>
175
- {({ rows, headers, getRowProps, getTableProps }) => (
176
- <TableContainer>
177
- <Table {...getTableProps()} aria-label="service list">
178
- <TableHead>
179
- <TableRow>
180
- {headers.map((header) => (
181
- <TableHeader key={header.key}>{header.header}</TableHeader>
182
- ))}
183
- </TableRow>
184
- </TableHead>
185
- <TableBody>
186
- {rows.map((row) => (
187
- <TableRow
188
- key={row.id}
189
- {...getRowProps({
190
- row,
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
- </TableBody>
198
- </Table>
199
- </TableContainer>
200
- )}
201
- </DataTable>
202
- {searchResults?.length === 0 && (
203
- <div className={styles.filterEmptyState}>
204
- <Layer level={0}>
205
- <Tile className={styles.filterEmptyStateTile}>
206
- <p className={styles.filterEmptyStateContent}>
207
- {t('noMatchingServicesToDisplay', 'No matching services to display')}
208
- </p>
209
- <p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
210
- </Tile>
211
- </Layer>
212
- </div>
213
- )}
214
- {paginated && (
215
- <Pagination
216
- forwardText="Next page"
217
- backwardText="Previous page"
218
- page={currentPage}
219
- pageSize={pageSize}
220
- pageSizes={pageSizes}
221
- totalItems={searchResults?.length}
222
- className={styles.pagination}
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
- {showOverlay && (
243
- <Modal
244
- open={showOverlay}
245
- modalHeading={t('billableService', 'Billable Service')}
246
- primaryButtonText={null}
247
- secondaryButtonText={t('cancel', 'Cancel')}
248
- onRequestClose={closeModal}
249
- onSecondarySubmit={closeModal}
250
- size="lg"
251
- passiveModal={true}>
252
- <AddBillableService editingService={editingService} onClose={closeModal} onServiceUpdated={mutate} />
253
- </Modal>
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
 
@@ -217,3 +217,6 @@
217
217
  grid-template-columns: 16rem 1fr;
218
218
  }
219
219
 
220
+ .menuItem {
221
+ max-width: none;
222
+ }
@@ -22,9 +22,7 @@ describe('BillableService', () => {
22
22
 
23
23
  render(<BillableServices />);
24
24
 
25
- // Check that empty state is rendered without looking for specific test ID
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;