@openmrs/esm-billing-app 1.0.2-pre.669 → 1.0.2-pre.673

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.
@@ -330,7 +330,7 @@
330
330
  "auxiliaryFiles": [
331
331
  "2352.js.map"
332
332
  ],
333
- "hash": "422ac1657e297fb7",
333
+ "hash": "808a51d03d0ca1dd",
334
334
  "childrenByOrder": {}
335
335
  },
336
336
  {
@@ -571,9 +571,9 @@
571
571
  "initial": false,
572
572
  "entry": false,
573
573
  "recorded": false,
574
- "size": 7316,
574
+ "size": 7097,
575
575
  "sizes": {
576
- "javascript": 7316
576
+ "javascript": 7097
577
577
  },
578
578
  "names": [],
579
579
  "idHints": [],
@@ -585,7 +585,7 @@
585
585
  "4300.js"
586
586
  ],
587
587
  "auxiliaryFiles": [],
588
- "hash": "dc04fbea0153fdc8",
588
+ "hash": "88f7c7e8c98a95c1",
589
589
  "childrenByOrder": {}
590
590
  },
591
591
  {
@@ -659,9 +659,9 @@
659
659
  "initial": false,
660
660
  "entry": false,
661
661
  "recorded": false,
662
- "size": 30526,
662
+ "size": 24140,
663
663
  "sizes": {
664
- "javascript": 30526
664
+ "javascript": 24140
665
665
  },
666
666
  "names": [],
667
667
  "idHints": [],
@@ -675,7 +675,7 @@
675
675
  "auxiliaryFiles": [
676
676
  "4739.js.map"
677
677
  ],
678
- "hash": "68eb58321499a73c",
678
+ "hash": "b0822fd2d079ef17",
679
679
  "childrenByOrder": {}
680
680
  },
681
681
  {
@@ -1236,9 +1236,9 @@
1236
1236
  "initial": false,
1237
1237
  "entry": false,
1238
1238
  "recorded": false,
1239
- "size": 1091132,
1239
+ "size": 1090617,
1240
1240
  "sizes": {
1241
- "javascript": 1091090,
1241
+ "javascript": 1090575,
1242
1242
  "consume-shared": 42
1243
1243
  },
1244
1244
  "names": [],
@@ -1252,7 +1252,7 @@
1252
1252
  "auxiliaryFiles": [
1253
1253
  "8638.js.map"
1254
1254
  ],
1255
- "hash": "4d595aa084ef0f34",
1255
+ "hash": "fc1d72a2976f2aa3",
1256
1256
  "childrenByOrder": {}
1257
1257
  },
1258
1258
  {
@@ -1260,10 +1260,10 @@
1260
1260
  "initial": true,
1261
1261
  "entry": true,
1262
1262
  "recorded": false,
1263
- "size": 5111703,
1263
+ "size": 5111188,
1264
1264
  "sizes": {
1265
1265
  "consume-shared": 210,
1266
- "javascript": 5089051,
1266
+ "javascript": 5088536,
1267
1267
  "share-init": 336,
1268
1268
  "runtime": 22106
1269
1269
  },
@@ -1280,7 +1280,7 @@
1280
1280
  "auxiliaryFiles": [
1281
1281
  "main.js.map"
1282
1282
  ],
1283
- "hash": "13be6e7d9188253f",
1283
+ "hash": "27f78dd3200a3e70",
1284
1284
  "childrenByOrder": {}
1285
1285
  },
1286
1286
  {
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.669"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.673"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-billing-app",
3
- "version": "1.0.2-pre.669",
3
+ "version": "1.0.2-pre.673",
4
4
  "description": "O3 frontend module for handling billing concerns in healthcare settings",
5
5
  "browser": "dist/openmrs-esm-billing-app.js",
6
6
  "main": "src/index.ts",
@@ -1,31 +1,28 @@
1
- import React, { useState, useEffect, useMemo } from 'react';
2
- import fuzzy from 'fuzzy';
3
- import isEmpty from 'lodash-es/isEmpty';
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { mutate } from 'swr';
4
4
  import {
5
5
  Button,
6
6
  ButtonSet,
7
+ ComboBox,
7
8
  Form,
9
+ NumberInput,
8
10
  InlineLoading,
9
- RadioButton,
10
- RadioButtonGroup,
11
- Search,
12
- Stack,
11
+ InlineNotification,
13
12
  Table,
14
- TableBody,
15
- TableCell,
16
13
  TableHead,
17
- TableHeader,
14
+ TableBody,
18
15
  TableRow,
16
+ TableHeader,
17
+ TableCell,
19
18
  } from '@carbon/react';
20
19
  import { TrashCan } from '@carbon/react/icons';
21
- import { mutate } from 'swr';
22
- import { useTranslation } from 'react-i18next';
23
- import { z } from 'zod';
24
- import { showSnackbar, showToast, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
20
+ import { useConfig, useLayoutType, showSnackbar } from '@openmrs/esm-framework';
21
+ import { processBillItems, useBillableServices } from '../billing.resource';
22
+ import { calculateTotalAmount, convertToCurrency } from '../helpers/functions';
23
+ import type { BillingConfig } from '../config-schema';
24
+ import type { BillableItem, LineItem } from '../types';
25
25
  import { apiBasePath } from '../constants';
26
- import { convertToCurrency } from '../helpers';
27
- import { type BillabeItem } from '../types';
28
- import { useFetchSearchResults, processBillItems } from '../billing.resource';
29
26
  import styles from './billing-form.scss';
30
27
 
31
28
  type BillingFormProps = {
@@ -34,150 +31,47 @@ type BillingFormProps = {
34
31
  };
35
32
 
36
33
  const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }) => {
37
- const { t } = useTranslation();
38
- const { defaultCurrency, postBilledItems } = useConfig();
39
34
  const isTablet = useLayoutType() === 'tablet';
40
-
41
- const [grandTotal, setGrandTotal] = useState(0);
42
- const [searchOptions, setSearchOptions] = useState([]);
43
- const [billItems, setBillItems] = useState([]);
44
- const [category, setCategory] = useState('');
45
- const [saveDisabled, setSaveDisabled] = useState<boolean>(false);
35
+ const { t } = useTranslation();
36
+ const { defaultCurrency, postBilledItems } = useConfig<BillingConfig>();
46
37
  const [isSubmitting, setIsSubmitting] = useState(false);
47
- const [addedItems, setAddedItems] = useState([]);
48
- const [searchTerm, setSearchTerm] = useState('');
49
- const debouncedSearchTerm = useDebounce(searchTerm);
50
- const [disableSearch, setDisableSearch] = useState<boolean>(true);
51
-
52
- const toggleSearch = (choiceSelected) => {
53
- if (!isEmpty(choiceSelected)) {
54
- setDisableSearch(false);
55
- }
56
- setCategory(choiceSelected === 'Stock Item' ? 'Stock Item' : 'Service');
57
- };
58
-
59
- const billItemSchema = z.object({
60
- Qnty: z.number().min(1, t('quantityGreaterThanZero', 'Quantity must be at least one for all items.')), // zod logic
61
- });
62
-
63
- const calculateTotal = (event, itemName) => {
64
- const quantity = parseInt(event.target.value);
65
- let isValid = true;
66
-
67
- try {
68
- billItemSchema.parse({ Qnty: quantity });
69
- } catch (error) {
70
- isValid = false;
71
- const parsedErrorMessage = JSON.parse(error.message);
72
- showToast({
73
- title: t('saveBill', 'Save Bill'),
74
- kind: 'error',
75
- description: parsedErrorMessage[0].message,
76
- });
38
+ const [selectedItems, setSelectedItems] = useState<LineItem[]>([]);
39
+ const { data, error, isLoading } = useBillableServices();
40
+
41
+ const selectBillableItem = (item: BillableItem) => {
42
+ if (!item) return;
43
+ const existingItem = selectedItems.find((selectedItem) => selectedItem.uuid === item.uuid);
44
+
45
+ if (existingItem) {
46
+ const updatedItem = { ...existingItem, quantity: existingItem.quantity + 1 };
47
+ setSelectedItems(
48
+ [...selectedItems].map((selectedItem) => (selectedItem.uuid === item.uuid ? updatedItem : selectedItem)),
49
+ );
50
+ return;
77
51
  }
78
52
 
79
- const updatedItems = billItems.map((item) => {
80
- if (item.Item.toLowerCase().includes(itemName.toLowerCase())) {
81
- return { ...item, Qnty: quantity, Total: quantity > 0 ? item.Price * quantity : 0 };
82
- }
83
- return item;
84
- });
85
-
86
- const anyInvalidQuantity = updatedItems.some((item) => item.Qnty <= 0);
87
-
88
- setSaveDisabled(!isValid || anyInvalidQuantity);
89
-
90
- setBillItems(updatedItems);
91
-
92
- const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0);
93
- setGrandTotal(updatedGrandTotal);
94
- };
95
-
96
- const calculateTotalAfterAddBillItem = (items) => {
97
- const sum = items.reduce((acc, item) => acc + item.Price * item.Qnty, 0);
98
- setGrandTotal(sum);
53
+ const mappedItem: LineItem = {
54
+ uuid: item.uuid,
55
+ display: item.name,
56
+ quantity: 1,
57
+ price: item.servicePrices?.length > 0 ? parseFloat(item.servicePrices?.[0]?.price) : 0,
58
+ billableService: item.uuid,
59
+ paymentStatus: 'PENDING',
60
+ lineItemOrder: 0,
61
+ };
62
+ setSelectedItems([...selectedItems, mappedItem]);
99
63
  };
100
64
 
101
- const addItemToBill = (event, itemid, itemname, itemcategory, itemPrice) => {
102
- const existingItemIndex = billItems.findIndex((item) => item.uuid === itemid);
103
-
104
- let updatedItems = [];
105
- if (existingItemIndex >= 0) {
106
- updatedItems = billItems.map((item, index) => {
107
- if (index === existingItemIndex) {
108
- const updatedQuantity = item.Qnty + 1;
109
- return { ...item, Qnty: updatedQuantity, Total: updatedQuantity * item.Price };
110
- }
111
- return item;
112
- });
113
- } else {
114
- const newItem = {
115
- uuid: itemid,
116
- Item: itemname,
117
- Qnty: 1,
118
- Price: itemPrice,
119
- Total: itemPrice,
120
- category: itemcategory,
121
- };
122
- updatedItems = [...billItems, newItem];
123
- setAddedItems([...addedItems, newItem]);
124
- }
125
-
126
- setBillItems(updatedItems);
127
- calculateTotalAfterAddBillItem(updatedItems);
128
- (document.getElementById('searchField') as HTMLInputElement).value = '';
65
+ const updateQuantity = (uuid: string, quantity: number) => {
66
+ const updatedItems = [...selectedItems].map((item) => (item.uuid === uuid ? { ...item, quantity } : item));
67
+ setSelectedItems(updatedItems);
129
68
  };
130
69
 
131
- const removeItemFromBill = (uuid) => {
132
- const updatedItems = billItems.filter((item) => item.uuid !== uuid);
133
- setBillItems(updatedItems);
134
-
135
- // Update the list of added items
136
- setAddedItems(addedItems.filter((item) => item.uuid !== uuid));
137
-
138
- const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0);
139
- setGrandTotal(updatedGrandTotal);
70
+ const removeSelectedBillableItem = (uuid: string) => {
71
+ const updatedItems = [...selectedItems].filter((item) => item.uuid !== uuid);
72
+ setSelectedItems(updatedItems);
140
73
  };
141
74
 
142
- const { data, error, isLoading, isValidating } = useFetchSearchResults(debouncedSearchTerm, category);
143
-
144
- const handleSearchTermChange = (e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value);
145
-
146
- const filterItems = useMemo(() => {
147
- if (!debouncedSearchTerm || isLoading || error) {
148
- return [];
149
- }
150
-
151
- const res = data as { results: BillabeItem[] };
152
- const existingItemUuids = new Set(billItems.map((item) => item.uuid));
153
-
154
- const preprocessedData = res?.results
155
- ?.map((item) => {
156
- return {
157
- uuid: item.uuid || '',
158
- Item: item.commonName ? item.commonName : item.name,
159
- Qnty: 1,
160
- Price: item.commonName ? 10 : item.servicePrices[0]?.price,
161
- Total: item.commonName ? 10 : item.servicePrices[0]?.price,
162
- category: item.commonName ? 'StockItem' : 'Service',
163
- };
164
- })
165
- .filter((item) => !existingItemUuids.has(item.uuid));
166
-
167
- return debouncedSearchTerm
168
- ? fuzzy
169
- .filter(debouncedSearchTerm, preprocessedData, {
170
- extract: (o) => `${o.Item}`,
171
- })
172
- .sort((r1, r2) => r1.score - r2.score)
173
- .map((result) => result.original)
174
- : searchOptions;
175
- }, [debouncedSearchTerm, isLoading, error, data, billItems, searchOptions]);
176
-
177
- useEffect(() => {
178
- setSearchOptions(filterItems);
179
- }, [filterItems]);
180
-
181
75
  const postBillItems = () => {
182
76
  setIsSubmitting(true);
183
77
  const bill = {
@@ -189,23 +83,16 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
189
83
  status: 'PENDING',
190
84
  };
191
85
 
192
- billItems.forEach((item) => {
193
- const lineItem: any = {
194
- quantity: parseInt(item.Qnty),
195
- price: item.Price,
196
- priceName: 'Default',
197
- priceUuid: postBilledItems.priceUuid,
86
+ selectedItems.forEach((item) => {
87
+ const lineItem: LineItem = {
88
+ quantity: item.quantity,
89
+ price: item.price,
198
90
  lineItemOrder: 0,
199
91
  paymentStatus: 'PENDING',
92
+ billableService: item.uuid,
200
93
  };
201
94
 
202
- if (item.category === 'StockItem') {
203
- lineItem.item = item.uuid;
204
- } else {
205
- lineItem.billableService = item.uuid;
206
- }
207
-
208
- bill?.lineItems.push(lineItem);
95
+ bill.lineItems.push(lineItem);
209
96
  });
210
97
 
211
98
  const url = `${apiBasePath}bill`;
@@ -233,57 +120,28 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
233
120
  );
234
121
  };
235
122
 
236
- const handleClearSearchTerm = () => {
237
- setSearchOptions([]);
238
- };
239
-
240
123
  return (
241
124
  <Form className={styles.form}>
242
125
  <div className={styles.grid}>
243
- <Stack>
244
- <RadioButtonGroup
245
- legendText={t('selectCategory', 'Select category')}
246
- name="radio-button-group"
247
- defaultSelected="radio-1"
248
- className={styles.mt2}
249
- onChange={toggleSearch}>
250
- <RadioButton labelText={t('stockItem', 'Stock Item')} value="Stock Item" id="stockItem" />
251
- <RadioButton labelText={t('service', 'Service')} value="Service" id="service" />
252
- </RadioButtonGroup>
253
- </Stack>
254
- <Stack>
255
- <Search
256
- size="lg"
257
- id="searchField"
258
- disabled={disableSearch}
259
- closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
260
- className={styles.mt2}
261
- placeholder={t('searchItems', 'Search items and services')}
262
- labelText={t('searchItems', 'Search items and services')}
263
- onKeyUp={handleSearchTermChange}
264
- onClear={handleClearSearchTerm}
126
+ {isLoading ? (
127
+ <InlineLoading description={t('loading', 'Loading') + '...'} />
128
+ ) : error ? (
129
+ <InlineNotification
130
+ kind="error"
131
+ lowContrast
132
+ title={t('billErrorService', 'Bill service error')}
133
+ subtitle={t('errorLoadingBillServices', 'Error loading bill services')}
265
134
  />
266
- </Stack>
267
- <Stack>
268
- <ul className={styles.searchContent}>
269
- {searchOptions?.length > 0 &&
270
- searchOptions?.map((row) => (
271
- <li key={row.uuid} className={styles.searchItem}>
272
- <Button
273
- id={row.uuid}
274
- onClick={(e) => addItemToBill(e, row.uuid, row.Item, row.category, row.Price)}
275
- style={{ background: 'inherit', color: 'black' }}>
276
- {row.Item} Qnty.{row.Qnty} {defaultCurrency}.{row.Price}
277
- </Button>
278
- </li>
279
- ))}
280
-
281
- {searchOptions?.length === 0 && !isLoading && !!debouncedSearchTerm && (
282
- <p>{t('noResultsFound', 'No results found')}</p>
283
- )}
284
- </ul>
285
- </Stack>
286
- <Stack>
135
+ ) : (
136
+ <ComboBox
137
+ id="searchItems"
138
+ onChange={({ selectedItem: item }: { selectedItem: BillableItem }) => selectBillableItem(item)}
139
+ itemToString={(item: BillableItem) => item?.name || ''}
140
+ items={data ?? []}
141
+ titleText={t('searchItems', 'Search items and services')}
142
+ />
143
+ )}
144
+ {selectedItems && selectedItems.length > 0 && (
287
145
  <Table aria-label="sample table" className={styles.mt2}>
288
146
  <TableHead>
289
147
  <TableRow>
@@ -295,63 +153,53 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
295
153
  </TableRow>
296
154
  </TableHead>
297
155
  <TableBody>
298
- {billItems && Array.isArray(billItems) ? (
299
- billItems.map((row) => (
300
- <TableRow>
301
- <TableCell>{row.Item}</TableCell>
302
- <TableCell>
303
- <input
304
- type="number"
305
- className={`form-control ${row.Qnty <= 0 ? styles.invalidInput : ''}`}
306
- id={row.Item}
307
- min={0}
308
- max={100}
309
- value={row.Qnty}
310
- onChange={(e) => {
311
- calculateTotal(e, row.Item);
312
- row.Qnty = e.target.value;
313
- }}
314
- />
315
- </TableCell>
316
- <TableCell id={row.Item + 'Price'}>{row.Price}</TableCell>
317
- <TableCell id={row.Item + 'Total'} className="totalValue">
318
- {row.Total}
319
- </TableCell>
320
- <TableCell>
321
- <TrashCan onClick={() => removeItemFromBill(row.uuid)} className={styles.removeButton} />
322
- </TableCell>
323
- </TableRow>
324
- ))
325
- ) : (
326
- <p>{t('loading', 'Loading...')}</p>
327
- )}
156
+ {selectedItems.map((row) => (
157
+ <TableRow>
158
+ <TableCell>{row.display}</TableCell>
159
+ <TableCell>
160
+ <NumberInput
161
+ id={row.uuid}
162
+ min={1}
163
+ value={row.quantity}
164
+ onChange={(_, { value }) => {
165
+ const number = parseFloat(String(value));
166
+ updateQuantity(row.uuid, isNaN(number) ? 1 : number);
167
+ }}
168
+ />
169
+ </TableCell>
170
+ <TableCell id={row.uuid + 'Price'}>{row.price}</TableCell>
171
+ <TableCell id={row.uuid + 'Total'} className="totalValue">
172
+ {row.price * row.quantity}
173
+ </TableCell>
174
+ <TableCell>
175
+ <TrashCan className={styles.removeButton} onClick={() => removeSelectedBillableItem(row.uuid)} />
176
+ </TableCell>
177
+ </TableRow>
178
+ ))}
328
179
  <TableRow>
329
180
  <TableCell colSpan={3}></TableCell>
330
181
  <TableCell style={{ fontWeight: 'bold' }}>{t('grandTotal', 'Grand total')}:</TableCell>
331
- <TableCell id="GrandTotalSum">{convertToCurrency(grandTotal, defaultCurrency)}</TableCell>
182
+ <TableCell id="GrandTotalSum">
183
+ {convertToCurrency(calculateTotalAmount(selectedItems), defaultCurrency)}
184
+ </TableCell>
332
185
  </TableRow>
333
186
  </TableBody>
334
187
  </Table>
335
- </Stack>
336
-
337
- <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
338
- <Button className={styles.button} kind="secondary" disabled={isSubmitting} onClick={closeWorkspace}>
339
- {t('discard', 'Discard')}
340
- </Button>
341
- <Button
342
- className={styles.button}
343
- kind="primary"
344
- onClick={postBillItems}
345
- disabled={isSubmitting || saveDisabled}
346
- type="submit">
347
- {isSubmitting ? (
348
- <InlineLoading description={t('saving', 'Saving') + '...'} />
349
- ) : (
350
- t('saveAndClose', 'Save and close')
351
- )}
352
- </Button>
353
- </ButtonSet>
188
+ )}
354
189
  </div>
190
+
191
+ <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
192
+ <Button className={styles.button} kind="secondary" disabled={isSubmitting || selectedItems.length === 0} onClick={closeWorkspace}>
193
+ {t('discard', 'Discard')}
194
+ </Button>
195
+ <Button className={styles.button} kind="primary" onClick={postBillItems} disabled={isSubmitting} type="submit">
196
+ {isSubmitting ? (
197
+ <InlineLoading description={t('saving', 'Saving') + '...'} />
198
+ ) : (
199
+ t('saveAndClose', 'Save and close')
200
+ )}
201
+ </Button>
202
+ </ButtonSet>
355
203
  </Form>
356
204
  );
357
205
  };
@@ -73,6 +73,9 @@
73
73
 
74
74
  .grid {
75
75
  margin: layout.$spacing-05;
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: layout.$spacing-05;
76
79
  }
77
80
 
78
81
  .row {
@@ -9,11 +9,11 @@ import {
9
9
  openmrsFetch,
10
10
  useSession,
11
11
  useVisit,
12
- restBaseUrl,
13
12
  type SessionLocation,
13
+ useOpenmrsFetchAll,
14
14
  } from '@openmrs/esm-framework';
15
15
  import { apiBasePath, omrsDateFormat } from './constants';
16
- import type { MappedBill, PatientInvoice } from './types';
16
+ import type { MappedBill, PatientInvoice, BillableItem } from './types';
17
17
  import SelectedDateContext from './hooks/selectedDateContext';
18
18
 
19
19
  export const useBills = (patientUuid: string = '', billStatus: string = '') => {
@@ -134,16 +134,9 @@ export const usePatientPaymentInfo = (patientUuid: string) => {
134
134
  return paymentInformation;
135
135
  };
136
136
 
137
- export function useFetchSearchResults(searchVal, category) {
138
- let url = ``;
139
- if (category == 'Stock Item') {
140
- url = `${restBaseUrl}/stockmanagement/stockitem?v=default&limit=10&q=${searchVal}`;
141
- } else {
142
- url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
143
- }
144
- const { data, error, isLoading, isValidating } = useSWR(searchVal ? url : null, openmrsFetch, {});
145
-
146
- return { data: data?.data, error, isLoading: isLoading, isValidating };
137
+ export function useBillableServices() {
138
+ const url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
139
+ return useOpenmrsFetchAll<BillableItem>(url);
147
140
  }
148
141
 
149
142
  export const processBillItems = (payload) => {
@@ -52,19 +52,19 @@ interface Provider {
52
52
  }
53
53
 
54
54
  export interface LineItem {
55
- uuid: string;
56
- display: string;
57
- voided: boolean;
58
- voidReason: string | null;
59
- item: string;
60
- billableService: string;
55
+ uuid?: string;
56
+ item?: string;
57
+ paymentStatus: string;
58
+ billableService?: string;
61
59
  quantity: number;
62
60
  price: number;
63
- priceName: string;
64
- priceUuid: string;
65
- lineItemOrder: number;
66
- resourceVersion: string;
67
- paymentStatus: string;
61
+ priceName?: string;
62
+ priceUuid?: string;
63
+ lineItemOrder?: number;
64
+ resourceVersion?: string;
65
+ display?: string;
66
+ voided?: boolean;
67
+ voidReason?: string | null;
68
68
  }
69
69
 
70
70
  interface PatientLink {
@@ -168,7 +168,7 @@ export type ServiceConcept = {
168
168
  display: string;
169
169
  };
170
170
 
171
- export type BillabeItem = {
171
+ export type BillableItem = {
172
172
  uuid: string;
173
173
  id?: number;
174
174
  name?: string;
@@ -177,6 +177,7 @@ export type BillabeItem = {
177
177
  };
178
178
 
179
179
  export type ServicePrice = {
180
+ name: string;
180
181
  price: string;
181
182
  uuid: string;
182
183
  };