@openmrs/esm-billing-app 1.0.1-pre.100

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 (179) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +57 -0
  4. package/.husky/pre-commit +7 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +14 -0
  7. package/.turbo.json +18 -0
  8. package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
  9. package/LICENSE +401 -0
  10. package/README.md +7 -0
  11. package/__mocks__/bills.mock.ts +394 -0
  12. package/__mocks__/delivery-summary.mock.ts +89 -0
  13. package/__mocks__/encounter-observation.mock.ts +10651 -0
  14. package/__mocks__/encounter-observations.mock.ts +6189 -0
  15. package/__mocks__/hiv-summary.mock.ts +22 -0
  16. package/__mocks__/patient-summary.mock.ts +32 -0
  17. package/__mocks__/patient.mock.ts +59 -0
  18. package/__mocks__/program-summary.mock.ts +43 -0
  19. package/__mocks__/react-i18next.js +57 -0
  20. package/dist/146.js +1 -0
  21. package/dist/146.js.map +1 -0
  22. package/dist/294.js +2 -0
  23. package/dist/294.js.LICENSE.txt +9 -0
  24. package/dist/294.js.map +1 -0
  25. package/dist/319.js +1 -0
  26. package/dist/384.js +1 -0
  27. package/dist/384.js.map +1 -0
  28. package/dist/421.js +1 -0
  29. package/dist/421.js.map +1 -0
  30. package/dist/533.js +1 -0
  31. package/dist/533.js.map +1 -0
  32. package/dist/574.js +1 -0
  33. package/dist/591.js +2 -0
  34. package/dist/591.js.LICENSE.txt +9 -0
  35. package/dist/591.js.map +1 -0
  36. package/dist/614.js +2 -0
  37. package/dist/614.js.LICENSE.txt +37 -0
  38. package/dist/614.js.map +1 -0
  39. package/dist/753.js +1 -0
  40. package/dist/753.js.map +1 -0
  41. package/dist/757.js +1 -0
  42. package/dist/770.js +1 -0
  43. package/dist/770.js.map +1 -0
  44. package/dist/783.js +1 -0
  45. package/dist/783.js.map +1 -0
  46. package/dist/788.js +1 -0
  47. package/dist/800.js +2 -0
  48. package/dist/800.js.LICENSE.txt +3 -0
  49. package/dist/800.js.map +1 -0
  50. package/dist/807.js +1 -0
  51. package/dist/833.js +1 -0
  52. package/dist/935.js +2 -0
  53. package/dist/935.js.LICENSE.txt +19 -0
  54. package/dist/935.js.map +1 -0
  55. package/dist/992.js +1 -0
  56. package/dist/992.js.map +1 -0
  57. package/dist/main.js +2 -0
  58. package/dist/main.js.LICENSE.txt +47 -0
  59. package/dist/main.js.map +1 -0
  60. package/dist/openmrs-esm-billing-app.js +1 -0
  61. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +609 -0
  62. package/dist/openmrs-esm-billing-app.js.map +1 -0
  63. package/dist/routes.json +1 -0
  64. package/e2e/README.md +115 -0
  65. package/e2e/core/global-setup.ts +32 -0
  66. package/e2e/core/index.ts +1 -0
  67. package/e2e/core/test.ts +20 -0
  68. package/e2e/fixtures/api.ts +27 -0
  69. package/e2e/fixtures/index.ts +1 -0
  70. package/e2e/pages/home-page.ts +9 -0
  71. package/e2e/pages/index.ts +1 -0
  72. package/e2e/specs/sample-test.spec.ts +11 -0
  73. package/e2e/support/github/Dockerfile +34 -0
  74. package/e2e/support/github/docker-compose.yml +24 -0
  75. package/e2e/support/github/run-e2e-docker-env.sh +49 -0
  76. package/example.env +6 -0
  77. package/i18next-parser.config.js +89 -0
  78. package/jest.config.js +34 -0
  79. package/package.json +124 -0
  80. package/playwright.config.ts +32 -0
  81. package/prettier.config.js +8 -0
  82. package/src/bill-history/bill-history.component.tsx +199 -0
  83. package/src/bill-history/bill-history.scss +151 -0
  84. package/src/bill-history/bill-history.test.tsx +122 -0
  85. package/src/billable-services/bill-waiver/bill-selection.component.tsx +76 -0
  86. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +110 -0
  87. package/src/billable-services/bill-waiver/bill-waiver-form.scss +34 -0
  88. package/src/billable-services/bill-waiver/bill-waiver.component.tsx +32 -0
  89. package/src/billable-services/bill-waiver/bill-waiver.scss +10 -0
  90. package/src/billable-services/bill-waiver/patient-bills.component.tsx +137 -0
  91. package/src/billable-services/bill-waiver/utils.ts +41 -0
  92. package/src/billable-services/billable-service.resource.ts +72 -0
  93. package/src/billable-services/billable-services-home.component.tsx +51 -0
  94. package/src/billable-services/billable-services.component.tsx +255 -0
  95. package/src/billable-services/billable-services.scss +218 -0
  96. package/src/billable-services/billable-services.test.tsx +16 -0
  97. package/src/billable-services/create-edit/add-billable-service.component.tsx +322 -0
  98. package/src/billable-services/create-edit/add-billable-service.scss +131 -0
  99. package/src/billable-services/create-edit/add-billable-service.test.tsx +152 -0
  100. package/src/billable-services/dashboard/dashboard.component.tsx +15 -0
  101. package/src/billable-services/dashboard/dashboard.scss +27 -0
  102. package/src/billable-services/dashboard/dashboard.test.tsx +11 -0
  103. package/src/billable-services/dashboard/service-metrics.component.tsx +41 -0
  104. package/src/billable-services-admin-card-link.component.test.tsx +21 -0
  105. package/src/billable-services-admin-card-link.component.tsx +25 -0
  106. package/src/billing-dashboard/billing-dashboard.component.tsx +20 -0
  107. package/src/billing-dashboard/billing-dashboard.scss +27 -0
  108. package/src/billing-dashboard/billing-dashboard.test.tsx +13 -0
  109. package/src/billing-form/billing-checkin-form.component.tsx +127 -0
  110. package/src/billing-form/billing-checkin-form.scss +13 -0
  111. package/src/billing-form/billing-checkin-form.test.tsx +134 -0
  112. package/src/billing-form/billing-form.component.tsx +347 -0
  113. package/src/billing-form/billing-form.resource.ts +32 -0
  114. package/src/billing-form/billing-form.scss +88 -0
  115. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +173 -0
  116. package/src/billing-form/visit-attributes/visit-attributes-form.scss +22 -0
  117. package/src/billing-header/billing-header.component.tsx +43 -0
  118. package/src/billing-header/billing-header.scss +83 -0
  119. package/src/billing-header/billing-illustration.component.tsx +30 -0
  120. package/src/billing.resource.ts +148 -0
  121. package/src/bills-table/bills-table.component.tsx +280 -0
  122. package/src/bills-table/bills-table.scss +181 -0
  123. package/src/bills-table/bills-table.test.tsx +154 -0
  124. package/src/config-schema.ts +50 -0
  125. package/src/constants.ts +3 -0
  126. package/src/dashboard.meta.ts +7 -0
  127. package/src/declarations.d.ts +4 -0
  128. package/src/helpers/functions.ts +66 -0
  129. package/src/helpers/index.ts +1 -0
  130. package/src/index.ts +72 -0
  131. package/src/invoice/invoice-table.component.tsx +189 -0
  132. package/src/invoice/invoice-table.scss +91 -0
  133. package/src/invoice/invoice.component.tsx +144 -0
  134. package/src/invoice/invoice.scss +93 -0
  135. package/src/invoice/invoice.test.tsx +242 -0
  136. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.component.tsx +17 -0
  137. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +29 -0
  138. package/src/invoice/payments/payment-form/payment-form.component.tsx +105 -0
  139. package/src/invoice/payments/payment-form/payment-form.scss +54 -0
  140. package/src/invoice/payments/payment-history/payment-history.component.tsx +69 -0
  141. package/src/invoice/payments/payment.resource.ts +44 -0
  142. package/src/invoice/payments/payments.component.tsx +147 -0
  143. package/src/invoice/payments/payments.scss +46 -0
  144. package/src/invoice/payments/utils.ts +68 -0
  145. package/src/invoice/payments/visit-tags/visit-attribute.component.tsx +21 -0
  146. package/src/invoice/printable-invoice/print-receipt.component.tsx +29 -0
  147. package/src/invoice/printable-invoice/print-receipt.scss +14 -0
  148. package/src/invoice/printable-invoice/printable-footer.component.tsx +19 -0
  149. package/src/invoice/printable-invoice/printable-footer.scss +17 -0
  150. package/src/invoice/printable-invoice/printable-footer.test.tsx +30 -0
  151. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +63 -0
  152. package/src/invoice/printable-invoice/printable-invoice-header.scss +61 -0
  153. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +58 -0
  154. package/src/invoice/printable-invoice/printable-invoice.component.tsx +146 -0
  155. package/src/invoice/printable-invoice/printable-invoice.scss +50 -0
  156. package/src/left-panel-link.component.tsx +41 -0
  157. package/src/left-panel-link.test.tsx +38 -0
  158. package/src/metrics-cards/card.component.tsx +14 -0
  159. package/src/metrics-cards/card.scss +20 -0
  160. package/src/metrics-cards/metrics-cards.component.tsx +42 -0
  161. package/src/metrics-cards/metrics-cards.scss +12 -0
  162. package/src/metrics-cards/metrics-cards.test.tsx +44 -0
  163. package/src/metrics-cards/metrics.resource.ts +45 -0
  164. package/src/modal/require-payment-modal.component.tsx +85 -0
  165. package/src/modal/require-payment.scss +6 -0
  166. package/src/root.component.tsx +19 -0
  167. package/src/root.scss +30 -0
  168. package/src/routes.json +78 -0
  169. package/src/setup-tests.ts +13 -0
  170. package/src/types/index.ts +181 -0
  171. package/test-helpers.tsx +23 -0
  172. package/translations/am.json +117 -0
  173. package/translations/en.json +117 -0
  174. package/translations/es.json +117 -0
  175. package/translations/fr.json +117 -0
  176. package/translations/he.json +117 -0
  177. package/translations/km.json +117 -0
  178. package/tsconfig.json +16 -0
  179. package/webpack.config.js +1 -0
@@ -0,0 +1,32 @@
1
+ import React, { useState } from 'react';
2
+ import { ExtensionSlot, UserHasAccess } from '@openmrs/esm-framework';
3
+ import PatientBills from './patient-bills.component';
4
+ import { useBills } from '../../billing.resource';
5
+ import styles from './bill-waiver.scss';
6
+
7
+ type BillWaiverProps = {};
8
+
9
+ const BillWaiver: React.FC<BillWaiverProps> = () => {
10
+ const [patientUuid, setPatientUuid] = useState<string>('');
11
+ const { bills } = useBills(patientUuid);
12
+ const filterBills = bills.filter((bill) => bill?.status !== 'PAID' && patientUuid === bill.patientUuid) ?? [];
13
+ return (
14
+ <UserHasAccess privilege="coreapps.systemAdministration">
15
+ <div className={styles.billWaiverContainer}>
16
+ <ExtensionSlot
17
+ name="patient-search-bar-slot"
18
+ state={{
19
+ selectPatientAction: (patientUuid) => setPatientUuid(patientUuid),
20
+ buttonProps: {
21
+ kind: 'primary',
22
+ },
23
+ }}
24
+ />
25
+
26
+ <PatientBills patientUuid={patientUuid} bills={filterBills} setPatientUuid={setPatientUuid} />
27
+ </div>
28
+ </UserHasAccess>
29
+ );
30
+ };
31
+
32
+ export default BillWaiver;
@@ -0,0 +1,10 @@
1
+ @use '@carbon/layout';
2
+
3
+ .billWaiverContainer {
4
+ margin: layout.$layout-01;
5
+ row-gap: layout.$layout-01;
6
+ }
7
+
8
+ .billListContainer {
9
+ background-color: white;
10
+ }
@@ -0,0 +1,137 @@
1
+ import React from 'react';
2
+ import {
3
+ DataTable,
4
+ Layer,
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableContainer,
9
+ TableExpandedRow,
10
+ TableExpandHeader,
11
+ TableExpandRow,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ Tile,
16
+ } from '@carbon/react';
17
+ import { useTranslation } from 'react-i18next';
18
+ import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
19
+ import { type MappedBill } from '../../types';
20
+ import { convertToCurrency } from '../../helpers';
21
+ import PatientBillsSelections from './bill-selection.component';
22
+ import styles from '../../bills-table/bills-table.scss';
23
+ import { useConfig } from '@openmrs/esm-framework';
24
+
25
+ type PatientBillsProps = {
26
+ patientUuid: string;
27
+ bills: Array<MappedBill>;
28
+ setPatientUuid: (patientUuid: string) => void;
29
+ };
30
+
31
+ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPatientUuid }) => {
32
+ const { t } = useTranslation();
33
+ const { defaultCurrency } = useConfig();
34
+
35
+ if (!patientUuid) {
36
+ return;
37
+ }
38
+
39
+ const tableHeaders = [
40
+ { header: 'Date', key: 'date' },
41
+ { header: 'Billable Service', key: 'billableService' },
42
+ { header: 'Total Amount', key: 'totalAmount' },
43
+ ];
44
+
45
+ const tableRows = bills.map((bill) => ({
46
+ id: `${bill.uuid}`,
47
+ date: bill.dateCreated,
48
+ billableService: bill.billingService,
49
+ totalAmount: convertToCurrency(bill?.totalAmount, defaultCurrency),
50
+ }));
51
+
52
+ if (bills.length === 0 && patientUuid !== '') {
53
+ return (
54
+ <>
55
+ <div style={{ marginTop: '0.625rem' }}>
56
+ <Layer className={styles.emptyStateContainer}>
57
+ <Tile className={styles.tile}>
58
+ <div className={styles.illo}>
59
+ <EmptyDataIllustration />
60
+ </div>
61
+ <p className={styles.content}>{t('noBilltoDisplay', 'There are no bills to display for this patient')}</p>
62
+ </Tile>
63
+ </Layer>
64
+ </div>
65
+ </>
66
+ );
67
+ }
68
+
69
+ return (
70
+ <div style={{ marginTop: '1rem' }}>
71
+ <DataTable
72
+ rows={tableRows}
73
+ headers={tableHeaders}
74
+ size="sm"
75
+ useZebraStyles
76
+ render={({
77
+ rows,
78
+ headers,
79
+ getHeaderProps,
80
+ getExpandHeaderProps,
81
+ getRowProps,
82
+ getExpandedRowProps,
83
+ getTableProps,
84
+ getTableContainerProps,
85
+ }) => (
86
+ <TableContainer
87
+ title={t('patientBills', 'Patient bill')}
88
+ description={t('patientBillsDescription', 'List of patient bills')}
89
+ {...getTableContainerProps()}>
90
+ <Table {...getTableProps()} aria-label="sample table">
91
+ <TableHead>
92
+ <TableRow>
93
+ <TableExpandHeader enableToggle={true} {...getExpandHeaderProps()} />
94
+ {headers.map((header, i) => (
95
+ <TableHeader
96
+ key={i}
97
+ {...getHeaderProps({
98
+ header,
99
+ })}>
100
+ {header.header}
101
+ </TableHeader>
102
+ ))}
103
+ </TableRow>
104
+ </TableHead>
105
+ <TableBody>
106
+ {rows.map((row, index) => (
107
+ <React.Fragment key={row.id}>
108
+ <TableExpandRow
109
+ {...getRowProps({
110
+ row,
111
+ })}>
112
+ {row.cells.map((cell) => (
113
+ <TableCell key={cell.id}>{cell.value}</TableCell>
114
+ ))}
115
+ </TableExpandRow>
116
+ <TableExpandedRow
117
+ colSpan={headers.length + 1}
118
+ className="demo-expanded-td"
119
+ {...getExpandedRowProps({
120
+ row,
121
+ })}>
122
+ <div>
123
+ <PatientBillsSelections bills={bills[index]} setPatientUuid={setPatientUuid} />
124
+ </div>
125
+ </TableExpandedRow>
126
+ </React.Fragment>
127
+ ))}
128
+ </TableBody>
129
+ </Table>
130
+ </TableContainer>
131
+ )}
132
+ />
133
+ </div>
134
+ );
135
+ };
136
+
137
+ export default PatientBills;
@@ -0,0 +1,41 @@
1
+ import { type OpenmrsResource } from '@openmrs/esm-framework';
2
+ import type { LineItem, MappedBill } from '../../types';
3
+
4
+ const WAIVER_UUID = 'eb6173cb-9678-4614-bbe1-0ccf7ed9d1d4';
5
+
6
+ export const createBillWaiverPayload = (
7
+ bill: MappedBill,
8
+ amountWaived: number,
9
+ totalAmount: number,
10
+ lineItems: Array<LineItem>,
11
+ billableLineItems: Array<OpenmrsResource>,
12
+ ) => {
13
+ const { cashier } = bill;
14
+
15
+ const billPayment = {
16
+ amount: parseFloat(totalAmount.toFixed(2)),
17
+ amountTendered: parseFloat(Number(amountWaived).toFixed(2)),
18
+ attributes: [],
19
+ instanceType: WAIVER_UUID,
20
+ };
21
+
22
+ const processedLineItems = lineItems.map((lineItem) => ({
23
+ ...lineItem,
24
+ billableService: findBillableServiceUuid(billableLineItems, lineItem),
25
+ paymentStatus: 'PAID',
26
+ }));
27
+
28
+ const processedPayment = {
29
+ cashPoint: bill.cashPointUuid,
30
+ cashier: cashier.uuid,
31
+ lineItems: processedLineItems,
32
+ payments: [...bill.payments, billPayment],
33
+ patient: bill.patientUuid,
34
+ };
35
+
36
+ return processedPayment;
37
+ };
38
+
39
+ const findBillableServiceUuid = (billableService: Array<OpenmrsResource>, lineItems: LineItem) => {
40
+ return billableService.find((service) => service.name === lineItems.billableService)?.uuid ?? null;
41
+ };
@@ -0,0 +1,72 @@
1
+ import useSWR from 'swr';
2
+ import { type OpenmrsResource, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
3
+ import { type ServiceConcept } from '../types';
4
+ import { apiBasePath } from '../constants';
5
+
6
+ type ResponseObject = {
7
+ results: Array<OpenmrsResource>;
8
+ };
9
+
10
+ export const useBillableServices = () => {
11
+ const url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price))`;
12
+
13
+ const { data, isLoading, isValidating, error, mutate } = useSWR<{ data: ResponseObject }>(url, openmrsFetch);
14
+
15
+ return {
16
+ billableServices: data?.data.results ?? [],
17
+ isLoading,
18
+ isValidating,
19
+ error,
20
+ mutate,
21
+ };
22
+ };
23
+
24
+ export function useServiceTypes() {
25
+ const url = `${restBaseUrl}/concept/21b8cf43-9f9f-4d02-9f4a-d710ece54261?v=custom:(setMembers:(uuid,display))`;
26
+
27
+ const { data, error, isLoading } = useSWR<{ data }>(url, openmrsFetch);
28
+
29
+ return {
30
+ serviceTypes: data?.data.setMembers ?? [],
31
+ error,
32
+ isLoading,
33
+ };
34
+ }
35
+
36
+ export const usePaymentModes = () => {
37
+ const url = `${apiBasePath}paymentMode`;
38
+
39
+ const { data, error, isLoading } = useSWR<{ data: ResponseObject }>(url, openmrsFetch);
40
+
41
+ return {
42
+ paymentModes: data?.data.results ?? [],
43
+ error,
44
+ isLoading,
45
+ };
46
+ };
47
+
48
+ export const createBillableSerice = (payload: any) => {
49
+ const url = `${apiBasePath}api/billable-service`;
50
+ return openmrsFetch(url, {
51
+ method: 'POST',
52
+ body: payload,
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ },
56
+ });
57
+ };
58
+
59
+ export function useConceptsSearch(conceptToLookup: string) {
60
+ const conditionsSearchUrl = `${restBaseUrl}/conceptsearch?q=${conceptToLookup}`;
61
+
62
+ const { data, error, isLoading } = useSWR<{ data: { results: Array<ServiceConcept> } }, Error>(
63
+ conceptToLookup ? conditionsSearchUrl : null,
64
+ openmrsFetch,
65
+ );
66
+
67
+ return {
68
+ searchResults: data?.data?.results ?? [],
69
+ error: error,
70
+ isSearching: isLoading,
71
+ };
72
+ }
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { BrowserRouter, Routes, Route } from 'react-router-dom';
3
+ import { SideNav, SideNavItems, SideNavLink } from '@carbon/react';
4
+ import { Wallet, Money } from '@carbon/react/icons';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { UserHasAccess, navigate } from '@openmrs/esm-framework';
7
+ import AddBillableService from './create-edit/add-billable-service.component';
8
+ import BillWaiver from './bill-waiver/bill-waiver.component';
9
+ import BillableServicesDashboard from './dashboard/dashboard.component';
10
+ import BillingHeader from '../billing-header/billing-header.component';
11
+ import styles from './billable-services.scss';
12
+
13
+ const BillableServiceHome: React.FC = () => {
14
+ const { t } = useTranslation();
15
+ const basePath = `${window.spaBase}/billable-services`;
16
+
17
+ const handleNavigation = (path: string) => {
18
+ navigate({ to: `${basePath}/${path}` });
19
+ };
20
+
21
+ return (
22
+ <BrowserRouter basename={`${window.spaBase}/billable-services`}>
23
+ <main className={styles.mainSection}>
24
+ <section>
25
+ <SideNav>
26
+ <SideNavItems>
27
+ <SideNavLink onClick={() => handleNavigation('')} renderIcon={Wallet} isActive>
28
+ {t('billableServices', 'Billable Services')}
29
+ </SideNavLink>
30
+ <UserHasAccess privilege="coreapps.systemAdministration">
31
+ <SideNavLink onClick={() => handleNavigation('waive-bill')} renderIcon={Money}>
32
+ {t('billWaiver', 'Bill waiver')}
33
+ </SideNavLink>
34
+ </UserHasAccess>
35
+ </SideNavItems>
36
+ </SideNav>
37
+ </section>
38
+ <section>
39
+ <BillingHeader title={t('billServicesManagement', 'Bill services management')} />
40
+ <Routes>
41
+ <Route path="/" element={<BillableServicesDashboard />} />
42
+ <Route path="/add-service" element={<AddBillableService />} />
43
+ <Route path="/waive-bill" element={<BillWaiver />} />
44
+ </Routes>
45
+ </section>
46
+ </main>
47
+ </BrowserRouter>
48
+ );
49
+ };
50
+
51
+ export default BillableServiceHome;
@@ -0,0 +1,255 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import classNames from 'classnames';
4
+ import {
5
+ Button,
6
+ DataTable,
7
+ InlineLoading,
8
+ Layer,
9
+ Pagination,
10
+ Search,
11
+ Table,
12
+ TableBody,
13
+ TableCell,
14
+ TableContainer,
15
+ TableHead,
16
+ TableHeader,
17
+ TableRow,
18
+ Tile,
19
+ } from '@carbon/react';
20
+ import { ArrowRight } from '@carbon/react/icons';
21
+ import { useLayoutType, isDesktop, useConfig, usePagination, ErrorState, navigate } from '@openmrs/esm-framework';
22
+ import { EmptyState } from '@openmrs/esm-patient-common-lib';
23
+ import { useBillableServices } from './billable-service.resource';
24
+ import styles from './billable-services.scss';
25
+
26
+ const BillableServices = () => {
27
+ const { t } = useTranslation();
28
+ const { billableServices, isLoading, isValidating, error, mutate } = useBillableServices();
29
+ const layout = useLayoutType();
30
+ const config = useConfig();
31
+ const [searchString, setSearchString] = useState('');
32
+ const responsiveSize = isDesktop(layout) ? 'lg' : 'sm';
33
+ const pageSizes = config?.billableServices?.pageSizes ?? [10, 20, 30, 40, 50];
34
+ const [pageSize, setPageSize] = useState(config?.billableServices?.pageSize ?? 10);
35
+
36
+ //creating service state
37
+ const [showOverlay, setShowOverlay] = useState(false);
38
+ const [overlayHeader, setOverlayTitle] = useState('');
39
+
40
+ const headerData = [
41
+ {
42
+ header: t('serviceName', 'Service Name'),
43
+ key: 'serviceName',
44
+ },
45
+ {
46
+ header: t('shortName', 'Short Name'),
47
+ key: 'shortName',
48
+ },
49
+ {
50
+ header: t('serviceType', 'Service Type'),
51
+ key: 'serviceType',
52
+ },
53
+ {
54
+ header: t('status', 'Service Status'),
55
+ key: 'status',
56
+ },
57
+ {
58
+ header: t('prices', 'Prices'),
59
+ key: 'prices',
60
+ },
61
+ {
62
+ header: t('actions', 'Actions'),
63
+ key: 'actions',
64
+ },
65
+ ];
66
+
67
+ const launchBillableServiceForm = useCallback(() => {
68
+ navigate({ to: window.getOpenmrsSpaBase() + 'billable-services/add-service' });
69
+ }, []);
70
+
71
+ const searchResults = useMemo(() => {
72
+ if (billableServices !== undefined && billableServices.length > 0) {
73
+ if (searchString && searchString.trim() !== '') {
74
+ const search = searchString.toLowerCase();
75
+ return billableServices?.filter((service) =>
76
+ Object.entries(service).some(([header, value]) => {
77
+ return header === 'uuid' ? false : `${value}`.toLowerCase().includes(search);
78
+ }),
79
+ );
80
+ }
81
+ }
82
+ return billableServices;
83
+ }, [searchString, billableServices]);
84
+
85
+ const { paginated, goTo, results, currentPage } = usePagination(searchResults, pageSize);
86
+
87
+ let rowData = [];
88
+ if (results) {
89
+ results.forEach((service, index) => {
90
+ const s = {
91
+ id: `${index}`,
92
+ uuid: service.uuid,
93
+ serviceName: service.name,
94
+ shortName: service.shortName,
95
+ serviceType: service?.serviceType?.display,
96
+ status: service.serviceStatus,
97
+ prices: '--',
98
+ actions: '--',
99
+ };
100
+ let cost = '';
101
+ service.servicePrices.forEach((price) => {
102
+ cost += `${price.name} (${price.price}) `;
103
+ });
104
+ s.prices = cost;
105
+ rowData.push(s);
106
+ });
107
+ }
108
+
109
+ const handleSearch = useCallback(
110
+ (e) => {
111
+ goTo(1);
112
+ setSearchString(e.target.value);
113
+ },
114
+ [goTo, setSearchString],
115
+ );
116
+
117
+ if (isLoading) {
118
+ <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />;
119
+ }
120
+ if (error) {
121
+ <ErrorState headerTitle={t('billableService', 'Billable Service')} error={error} />;
122
+ }
123
+ if (billableServices.length === 0) {
124
+ <EmptyState
125
+ displayText={t('billableService', 'Billable Service')}
126
+ headerTitle={t('billableService', 'Billable Service')}
127
+ launchForm={launchBillableServiceForm}
128
+ />;
129
+ }
130
+
131
+ return (
132
+ <>
133
+ {billableServices?.length > 0 ? (
134
+ <div className={styles.serviceContainer}>
135
+ <FilterableTableHeader
136
+ handleSearch={handleSearch}
137
+ isValidating={isValidating}
138
+ layout={layout}
139
+ responsiveSize={responsiveSize}
140
+ t={t}
141
+ />
142
+ <DataTable
143
+ isSortable
144
+ rows={rowData}
145
+ headers={headerData}
146
+ size={responsiveSize}
147
+ useZebraStyles={rowData?.length > 1 ? true : false}>
148
+ {({ rows, headers, getRowProps, getTableProps }) => (
149
+ <TableContainer>
150
+ <Table {...getTableProps()} aria-label="service list">
151
+ <TableHead>
152
+ <TableRow>
153
+ {headers.map((header) => (
154
+ <TableHeader key={header.key}>{header.header}</TableHeader>
155
+ ))}
156
+ </TableRow>
157
+ </TableHead>
158
+ <TableBody>
159
+ {rows.map((row) => (
160
+ <TableRow
161
+ key={row.id}
162
+ {...getRowProps({
163
+ row,
164
+ })}>
165
+ {row.cells.map((cell) => (
166
+ <TableCell key={cell.id}>{cell.value}</TableCell>
167
+ ))}
168
+ </TableRow>
169
+ ))}
170
+ </TableBody>
171
+ </Table>
172
+ </TableContainer>
173
+ )}
174
+ </DataTable>
175
+ {searchResults?.length === 0 && (
176
+ <div className={styles.filterEmptyState}>
177
+ <Layer level={0}>
178
+ <Tile className={styles.filterEmptyStateTile}>
179
+ <p className={styles.filterEmptyStateContent}>
180
+ {t('noMatchingServicesToDisplay', 'No matching services to display')}
181
+ </p>
182
+ <p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
183
+ </Tile>
184
+ </Layer>
185
+ </div>
186
+ )}
187
+ {paginated && (
188
+ <Pagination
189
+ forwardText="Next page"
190
+ backwardText="Previous page"
191
+ page={currentPage}
192
+ pageSize={pageSize}
193
+ pageSizes={pageSizes}
194
+ totalItems={searchResults?.length}
195
+ className={styles.pagination}
196
+ size={responsiveSize}
197
+ onChange={({ pageSize: newPageSize, page: newPage }) => {
198
+ if (newPageSize !== pageSize) {
199
+ setPageSize(newPageSize);
200
+ }
201
+ if (newPage !== currentPage) {
202
+ goTo(newPage);
203
+ }
204
+ }}
205
+ />
206
+ )}
207
+ </div>
208
+ ) : (
209
+ <EmptyState
210
+ launchForm={launchBillableServiceForm}
211
+ displayText={t('noServicesToDisplay', 'There are no services to display')}
212
+ headerTitle={t('billableService', 'Billable service')}
213
+ />
214
+ )}
215
+ </>
216
+ );
217
+ };
218
+
219
+ function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveSize, t }) {
220
+ return (
221
+ <>
222
+ <div className={styles.headerContainer}>
223
+ <div
224
+ className={classNames({
225
+ [styles.tabletHeading]: !isDesktop(layout),
226
+ [styles.desktopHeading]: isDesktop(layout),
227
+ })}>
228
+ <h4>{t('servicesList', 'Services list')}</h4>
229
+ </div>
230
+ <div className={styles.backgroundDataFetchingIndicator}>
231
+ <span>{isValidating ? <InlineLoading /> : null}</span>
232
+ </div>
233
+ </div>
234
+ <div className={styles.actionsContainer}>
235
+ <Search
236
+ labelText=""
237
+ placeholder={t('filterTable', 'Filter table')}
238
+ onChange={handleSearch}
239
+ size={responsiveSize}
240
+ />
241
+ <Button
242
+ size={responsiveSize}
243
+ kind="primary"
244
+ renderIcon={(props) => <ArrowRight size={16} {...props} />}
245
+ onClick={() => {
246
+ navigate({ to: window.getOpenmrsSpaBase() + 'billable-services/add-service' });
247
+ }}
248
+ iconDescription={t('addNewBillableService', 'Add new billable service')}>
249
+ {t('addNewService', 'Add new service')}
250
+ </Button>
251
+ </div>
252
+ </>
253
+ );
254
+ }
255
+ export default BillableServices;