@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,189 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import fuzzy from 'fuzzy';
4
+ import {
5
+ DataTable,
6
+ DataTableSkeleton,
7
+ Layer,
8
+ Table,
9
+ TableBody,
10
+ TableCell,
11
+ TableContainer,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ TableToolbar,
16
+ TableToolbarContent,
17
+ TableToolbarSearch,
18
+ TableSelectRow,
19
+ Tile,
20
+ type DataTableHeader,
21
+ type DataTableRow,
22
+ } from '@carbon/react';
23
+ import { isDesktop, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
24
+ import { type LineItem, type MappedBill } from '../types';
25
+ import styles from './invoice-table.scss';
26
+ import { convertToCurrency } from '../helpers';
27
+
28
+ type InvoiceTableProps = {
29
+ bill: MappedBill;
30
+ isSelectable?: boolean;
31
+ isLoadingBill?: boolean;
32
+ onSelectItem?: (selectedLineItems: LineItem[]) => void;
33
+ };
34
+
35
+ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true, isLoadingBill, onSelectItem }) => {
36
+ const { t } = useTranslation();
37
+ const lineItems = bill?.lineItems ?? [];
38
+ const layout = useLayoutType();
39
+ const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
40
+ const pendingLineItems = lineItems?.filter((item) => item.paymentStatus === 'PENDING') ?? [];
41
+ const [selectedLineItems, setSelectedLineItems] = useState(pendingLineItems ?? []);
42
+ const [searchTerm, setSearchTerm] = useState('');
43
+ const debouncedSearchTerm = useDebounce(searchTerm);
44
+ const { defaultCurrency } = useConfig();
45
+
46
+ const filteredLineItems = useMemo(() => {
47
+ if (!debouncedSearchTerm) {
48
+ return lineItems;
49
+ }
50
+
51
+ return debouncedSearchTerm
52
+ ? fuzzy
53
+ .filter(debouncedSearchTerm, lineItems, {
54
+ extract: (lineItem: LineItem) => `${lineItem.item}`,
55
+ })
56
+ .sort((r1, r2) => r1.score - r2.score)
57
+ .map((result) => result.original)
58
+ : lineItems;
59
+ }, [debouncedSearchTerm, lineItems]);
60
+
61
+ const tableHeaders: Array<typeof DataTableHeader> = [
62
+ { header: 'No', key: 'no' },
63
+ { header: 'Bill item', key: 'billItem' },
64
+ { header: 'Bill code', key: 'billCode' },
65
+ { header: 'Status', key: 'status' },
66
+ { header: 'Quantity', key: 'quantity' },
67
+ { header: 'Price', key: 'price' },
68
+ { header: 'Total', key: 'total' },
69
+ ];
70
+
71
+ const tableRows: Array<typeof DataTableRow> = useMemo(
72
+ () =>
73
+ filteredLineItems?.map((item, index) => {
74
+ return {
75
+ no: `${index + 1}`,
76
+ id: `${item.uuid}`,
77
+ billItem: item.billableService ? item.billableService : item?.item,
78
+ billCode: bill?.receiptNumber,
79
+ status: item?.paymentStatus,
80
+ quantity: item.quantity,
81
+ price: convertToCurrency(item.price, defaultCurrency),
82
+ total: convertToCurrency(item.price * item.quantity, defaultCurrency),
83
+ };
84
+ }) ?? [],
85
+ [bill?.receiptNumber, filteredLineItems],
86
+ );
87
+
88
+ if (isLoadingBill) {
89
+ return (
90
+ <div className={styles.loaderContainer}>
91
+ <DataTableSkeleton
92
+ columnCount={tableHeaders.length}
93
+ showHeader={false}
94
+ showToolbar={false}
95
+ size={responsiveSize}
96
+ zebra
97
+ />
98
+ </div>
99
+ );
100
+ }
101
+
102
+ const handleRowSelection = (row: typeof DataTableRow, checked: boolean) => {
103
+ const matchingRow = filteredLineItems.find((item) => item.uuid === row.id);
104
+ let newSelectedLineItems;
105
+
106
+ if (checked) {
107
+ newSelectedLineItems = [...selectedLineItems, matchingRow];
108
+ } else {
109
+ newSelectedLineItems = selectedLineItems.filter((item) => item.uuid !== row.id);
110
+ }
111
+ setSelectedLineItems(newSelectedLineItems);
112
+ onSelectItem(newSelectedLineItems);
113
+ };
114
+
115
+ return (
116
+ <div className={styles.invoiceContainer}>
117
+ <DataTable headers={tableHeaders} isSortable rows={tableRows} size={responsiveSize} useZebraStyles>
118
+ {({ rows, headers, getRowProps, getSelectionProps, getTableProps, getToolbarProps }) => (
119
+ <TableContainer
120
+ description={
121
+ <span className={styles.tableDescription}>
122
+ <span>{t('itemsToBeBilled', 'Items to be billed')}</span>
123
+ </span>
124
+ }
125
+ title={t('lineItems', 'Line items')}>
126
+ <div className={styles.toolbarWrapper}>
127
+ <TableToolbar {...getToolbarProps()} className={styles.tableToolbar} size={responsiveSize}>
128
+ <TableToolbarContent className={styles.headerContainer}>
129
+ <TableToolbarSearch
130
+ className={styles.searchbox}
131
+ expanded
132
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
133
+ placeholder={t('searchThisTable', 'Search this table')}
134
+ size={responsiveSize}
135
+ />
136
+ </TableToolbarContent>
137
+ </TableToolbar>
138
+ </div>
139
+ <Table {...getTableProps()} aria-label="Invoice line items" className={styles.table}>
140
+ <TableHead>
141
+ <TableRow>
142
+ {rows.length > 1 && isSelectable ? <TableHeader /> : null}
143
+ {headers.map((header) => (
144
+ <TableHeader key={header.key}>{header.header}</TableHeader>
145
+ ))}
146
+ </TableRow>
147
+ </TableHead>
148
+ <TableBody>
149
+ {rows.map((row, index) => (
150
+ <TableRow
151
+ key={row.id}
152
+ {...getRowProps({
153
+ row,
154
+ })}>
155
+ {rows.length > 1 && isSelectable && (
156
+ <TableSelectRow
157
+ aria-label="Select row"
158
+ {...getSelectionProps({ row })}
159
+ onChange={(checked: boolean) => handleRowSelection(row, checked)}
160
+ checked={Boolean(selectedLineItems?.find((item) => item?.uuid === row?.id))}
161
+ />
162
+ )}
163
+ {row.cells.map((cell) => (
164
+ <TableCell key={cell.id}>{cell.value}</TableCell>
165
+ ))}
166
+ </TableRow>
167
+ ))}
168
+ </TableBody>
169
+ </Table>
170
+ </TableContainer>
171
+ )}
172
+ </DataTable>
173
+ {filteredLineItems?.length === 0 && (
174
+ <div className={styles.filterEmptyState}>
175
+ <Layer>
176
+ <Tile className={styles.filterEmptyStateTile}>
177
+ <p className={styles.filterEmptyStateContent}>
178
+ {t('noMatchingItemsToDisplay', 'No matching items to display')}
179
+ </p>
180
+ <p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
181
+ </Tile>
182
+ </Layer>
183
+ </div>
184
+ )}
185
+ </div>
186
+ );
187
+ };
188
+
189
+ export default InvoiceTable;
@@ -0,0 +1,91 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+ @import '~@openmrs/esm-styleguide/src/vars';
5
+
6
+ .filterEmptyState {
7
+ align-items: center;
8
+ background-color: white;
9
+ display: flex;
10
+ justify-content: center;
11
+ padding: layout.$spacing-09 !important;
12
+ text-align: center;
13
+ }
14
+
15
+ .filterEmptyStateTile {
16
+ margin: auto;
17
+ }
18
+
19
+ .filterEmptyStateContent {
20
+ @include type.type-style('heading-compact-02');
21
+ color: $text-02;
22
+ margin-bottom: 0.5rem;
23
+ }
24
+
25
+ .filterEmptyStateHelper {
26
+ @include type.type-style('body-compact-01');
27
+ color: $text-02;
28
+ }
29
+
30
+ .headerContainer {
31
+ background-color: colors.$gray-10;
32
+ }
33
+
34
+ .invoiceContainer {
35
+ margin: layout.$spacing-09 layout.$spacing-05 0;
36
+ border: 1px solid $ui-03;
37
+ }
38
+
39
+ .searchbox {
40
+ input:focus {
41
+ outline: 2px solid colors.$orange-40 !important;
42
+ }
43
+ }
44
+
45
+ .table {
46
+ td {
47
+ border-bottom: none !important;
48
+ }
49
+ }
50
+
51
+ .tableDescription {
52
+ display: flex;
53
+ align-items: flex-start;
54
+ margin-top: layout.$spacing-02;
55
+ column-gap: layout.$spacing-01;
56
+
57
+ ::after {
58
+ content: '';
59
+ display: block;
60
+ width: 2rem;
61
+ padding-top: 0.188rem;
62
+ border-bottom: 0.375rem solid var(--brand-03);
63
+ }
64
+
65
+ & > span {
66
+ @include type.type-style('body-01');
67
+ }
68
+ }
69
+
70
+ .tableToolbar {
71
+ width: 20%;
72
+ min-width: 12.5rem;
73
+ }
74
+
75
+ .toolbarWrapper {
76
+ position: relative;
77
+ display: flex;
78
+ justify-content: flex-end;
79
+ }
80
+
81
+ :global(.omrs-breakpoint-lt-desktop) {
82
+ .toolbarWrapper {
83
+ height: layout.$spacing-09;
84
+ }
85
+ }
86
+
87
+ :global(.omrs-breakpoint-gt-tablet) {
88
+ .toolbarWrapper {
89
+ height: layout.$spacing-07;
90
+ }
91
+ }
@@ -0,0 +1,144 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { Button, InlineLoading } from '@carbon/react';
3
+ import { Printer } from '@carbon/react/icons';
4
+ import { useParams } from 'react-router-dom';
5
+ import { useReactToPrint } from 'react-to-print';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { ExtensionSlot, useConfig, usePatient } from '@openmrs/esm-framework';
8
+ import { ErrorState } from '@openmrs/esm-patient-common-lib';
9
+ import { convertToCurrency } from '../helpers';
10
+ import { type LineItem } from '../types';
11
+ import { useBill } from '../billing.resource';
12
+ import InvoiceTable from './invoice-table.component';
13
+ import Payments from './payments/payments.component';
14
+ import PrintReceipt from './printable-invoice/print-receipt.component';
15
+ import PrintableInvoice from './printable-invoice/printable-invoice.component';
16
+ import styles from './invoice.scss';
17
+
18
+ interface InvoiceDetailsProps {
19
+ label: string;
20
+ value: string | number;
21
+ }
22
+
23
+ const Invoice: React.FC = () => {
24
+ const { t } = useTranslation();
25
+ const { billUuid, patientUuid } = useParams();
26
+ const { patient, isLoading: isLoadingPatient } = usePatient(patientUuid);
27
+ const { bill, isLoading: isLoadingBill, error } = useBill(billUuid);
28
+ const [isPrinting, setIsPrinting] = useState(false);
29
+ const [selectedLineItems, setSelectedLineItems] = useState([]);
30
+ const componentRef = useRef<HTMLDivElement>(null);
31
+ const onBeforeGetContentResolve = useRef<(() => void) | null>(null);
32
+ const handleSelectItem = (lineItems: Array<LineItem>) => {
33
+ setSelectedLineItems(lineItems);
34
+ };
35
+ const { defaultCurrency } = useConfig();
36
+
37
+ const handleAfterPrint = useCallback(() => {
38
+ onBeforeGetContentResolve.current = null;
39
+ setIsPrinting(false);
40
+ }, []);
41
+
42
+ const reactToPrintContent = useCallback(() => componentRef.current, []);
43
+
44
+ const handleOnBeforeGetContent = useCallback(() => {
45
+ return new Promise<void>((resolve) => {
46
+ if (patient && bill) {
47
+ setIsPrinting(true);
48
+ onBeforeGetContentResolve.current = resolve;
49
+ }
50
+ });
51
+ }, [bill, patient]);
52
+
53
+ const handlePrint = useReactToPrint({
54
+ content: reactToPrintContent,
55
+ documentTitle: `Invoice ${bill?.receiptNumber} - ${patient?.name?.[0]?.given?.join(' ')} ${
56
+ patient?.name?.[0].family
57
+ }`,
58
+ onBeforeGetContent: handleOnBeforeGetContent,
59
+ onAfterPrint: handleAfterPrint,
60
+ removeAfterPrint: true,
61
+ });
62
+
63
+ useEffect(() => {
64
+ if (isPrinting && onBeforeGetContentResolve.current) {
65
+ onBeforeGetContentResolve.current();
66
+ }
67
+ }, [isPrinting]);
68
+
69
+ useEffect(() => {
70
+ const unPaidLineItems = bill?.lineItems?.filter((item) => item.paymentStatus === 'PENDING') ?? [];
71
+ setSelectedLineItems(unPaidLineItems);
72
+ }, [bill?.lineItems]);
73
+
74
+ const invoiceDetails = {
75
+ 'Total Amount': convertToCurrency(bill?.totalAmount, defaultCurrency),
76
+ 'Amount Tendered': convertToCurrency(bill?.tenderedAmount, defaultCurrency),
77
+ 'Invoice Number': bill?.receiptNumber,
78
+ 'Date And Time': bill?.dateCreated,
79
+ 'Invoice Status': bill?.status,
80
+ };
81
+
82
+ if (isLoadingPatient && isLoadingBill) {
83
+ return (
84
+ <div className={styles.invoiceContainer}>
85
+ <InlineLoading
86
+ className={styles.loader}
87
+ status="active"
88
+ iconDescription="Loading"
89
+ description="Loading patient header..."
90
+ />
91
+ </div>
92
+ );
93
+ }
94
+
95
+ if (error) {
96
+ return (
97
+ <div className={styles.errorContainer}>
98
+ <ErrorState headerTitle={t('invoiceError', 'Invoice error')} error={error} />
99
+ </div>
100
+ );
101
+ }
102
+
103
+ return (
104
+ <div className={styles.invoiceContainer}>
105
+ {patient && patientUuid && <ExtensionSlot name="patient-header-slot" state={{ patient, patientUuid }} />}
106
+ <div className={styles.detailsContainer}>
107
+ <section className={styles.details}>
108
+ {Object.entries(invoiceDetails).map(([key, val]) => (
109
+ <InvoiceDetails key={key} label={key} value={val} />
110
+ ))}
111
+ </section>
112
+ <div>
113
+ <Button
114
+ disabled={isPrinting}
115
+ onClick={handlePrint}
116
+ renderIcon={(props) => <Printer size={24} {...props} />}
117
+ iconDescription="Print bill"
118
+ size="md">
119
+ {t('printBill', 'Print bill')}
120
+ </Button>
121
+ {bill?.status === 'PAID' ? <PrintReceipt billId={bill?.id} /> : null}
122
+ </div>
123
+ </div>
124
+
125
+ <InvoiceTable bill={bill} isLoadingBill={isLoadingBill} onSelectItem={handleSelectItem} />
126
+ <Payments bill={bill} selectedLineItems={selectedLineItems} />
127
+
128
+ <div className={styles.printContainer} ref={componentRef}>
129
+ {isPrinting && <PrintableInvoice bill={bill} patient={patient} isLoading={isLoadingPatient} />}
130
+ </div>
131
+ </div>
132
+ );
133
+ };
134
+
135
+ function InvoiceDetails({ label, value }: InvoiceDetailsProps) {
136
+ return (
137
+ <div>
138
+ <h1 className={styles.label}>{label}</h1>
139
+ <span className={styles.value}>{value}</span>
140
+ </div>
141
+ );
142
+ }
143
+
144
+ export default Invoice;
@@ -0,0 +1,93 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .invoiceContainer {
6
+ background-color: colors.$gray-10;
7
+ height: calc(100vh - 3rem);
8
+ }
9
+
10
+ .errorContainer {
11
+ margin: layout.$spacing-05;
12
+ }
13
+
14
+ .loader {
15
+ display: flex;
16
+ min-height: layout.$spacing-09;
17
+ justify-content: center;
18
+ }
19
+
20
+ .detailsContainer {
21
+ display: flex;
22
+ }
23
+
24
+ .details {
25
+ display: flex;
26
+ flex: 3;
27
+ flex-flow: row wrap;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ margin: layout.$spacing-05;
31
+ row-gap: 1.5rem;
32
+ }
33
+
34
+ .label {
35
+ @include type.type-style('body-compact-02');
36
+ color: colors.$gray-70;
37
+ margin: layout.$spacing-01;
38
+ }
39
+
40
+ .value {
41
+ @include type.type-style('heading-03');
42
+ display: inline-block;
43
+ margin-top: layout.$spacing-04;
44
+ }
45
+
46
+ .backButton {
47
+ margin: layout.$spacing-04;
48
+
49
+ button {
50
+ display: flex;
51
+ padding-left: 0 !important;
52
+
53
+ svg {
54
+ order: 1;
55
+ margin-right: layout.$spacing-03;
56
+ margin-left: 0 !important;
57
+ }
58
+
59
+ span {
60
+ order: 2;
61
+ }
62
+ }
63
+ }
64
+
65
+ .invoicePaymentsContainer {
66
+ display: flex;
67
+ flex-direction: column;
68
+ margin: layout.$spacing-05;
69
+ }
70
+
71
+ .paymentSection {
72
+ display: flex;
73
+ flex-direction: row;
74
+ }
75
+
76
+ .billDetail {
77
+ font-weight: bold;
78
+ color: colors.$cool-gray-90;
79
+ }
80
+
81
+ @media screen {
82
+ .printContainer {
83
+ background-color: colors.$white;
84
+ display: none;
85
+ }
86
+ }
87
+
88
+ @media print {
89
+ html,
90
+ body {
91
+ background-color: colors.$white !important;
92
+ }
93
+ }