@openmrs/esm-billing-app 1.0.2-pre.90 → 1.0.2-pre.901

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 (214) hide show
  1. package/.eslintrc +16 -2
  2. package/README.md +54 -9
  3. package/__mocks__/bills.mock.ts +12 -0
  4. package/__mocks__/react-i18next.js +6 -5
  5. package/dist/1119.js +1 -1
  6. package/dist/1146.js +1 -2
  7. package/dist/1146.js.map +1 -1
  8. package/dist/1197.js +1 -1
  9. package/dist/1537.js +1 -0
  10. package/dist/1537.js.map +1 -0
  11. package/dist/1856.js +1 -0
  12. package/dist/1856.js.map +1 -0
  13. package/dist/2146.js +1 -1
  14. package/dist/2524.js +1 -0
  15. package/dist/2524.js.map +1 -0
  16. package/dist/2690.js +1 -1
  17. package/dist/3099.js +1 -1
  18. package/dist/3584.js +1 -1
  19. package/dist/3717.js +2 -0
  20. package/dist/3717.js.map +1 -0
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4300.js +1 -1
  24. package/dist/4335.js +1 -1
  25. package/dist/4618.js +1 -1
  26. package/dist/4652.js +1 -1
  27. package/dist/4692.js +1 -0
  28. package/dist/4692.js.map +1 -0
  29. package/dist/4724.js +1 -0
  30. package/dist/4724.js.map +1 -0
  31. package/dist/4739.js +1 -1
  32. package/dist/4739.js.map +1 -1
  33. package/dist/4944.js +1 -1
  34. package/dist/5173.js +1 -1
  35. package/dist/5241.js +1 -1
  36. package/dist/5442.js +1 -1
  37. package/dist/5661.js +1 -1
  38. package/dist/6022.js +1 -1
  39. package/dist/6468.js +1 -1
  40. package/dist/6540.js +1 -1
  41. package/dist/6540.js.map +1 -1
  42. package/dist/6679.js +1 -1
  43. package/dist/6840.js +1 -1
  44. package/dist/6859.js +1 -1
  45. package/dist/7097.js +1 -1
  46. package/dist/7159.js +1 -1
  47. package/dist/723.js +1 -1
  48. package/dist/7255.js +1 -1
  49. package/dist/7255.js.map +1 -1
  50. package/dist/7617.js +1 -1
  51. package/dist/795.js +1 -1
  52. package/dist/8163.js +1 -1
  53. package/dist/8349.js +1 -1
  54. package/dist/8618.js +1 -1
  55. package/dist/8708.js +2 -0
  56. package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
  57. package/dist/8708.js.map +1 -0
  58. package/dist/890.js +1 -1
  59. package/dist/9214.js +1 -1
  60. package/dist/9538.js +1 -1
  61. package/dist/9569.js +1 -1
  62. package/dist/961.js +1 -1
  63. package/dist/961.js.map +1 -1
  64. package/dist/986.js +1 -1
  65. package/dist/9879.js +1 -1
  66. package/dist/9895.js +1 -1
  67. package/dist/9900.js +1 -1
  68. package/dist/9913.js +1 -1
  69. package/dist/main.js +1 -1
  70. package/dist/main.js.map +1 -1
  71. package/dist/openmrs-esm-billing-app.js +1 -1
  72. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +282 -296
  73. package/dist/openmrs-esm-billing-app.js.map +1 -1
  74. package/dist/routes.json +1 -1
  75. package/e2e/README.md +19 -18
  76. package/e2e/core/test.ts +1 -1
  77. package/e2e/fixtures/api.ts +1 -1
  78. package/e2e/specs/sample-test.spec.ts +0 -1
  79. package/e2e/support/github/Dockerfile +1 -1
  80. package/package.json +18 -15
  81. package/src/bill-history/bill-history.component.tsx +20 -28
  82. package/src/bill-history/bill-history.scss +4 -94
  83. package/src/bill-history/bill-history.test.tsx +37 -78
  84. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  85. package/src/bill-item-actions/edit-bill-item.modal.tsx +225 -0
  86. package/src/bill-item-actions/edit-bill-item.test.tsx +214 -40
  87. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  88. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +28 -32
  89. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  90. package/src/billable-services/bill-waiver/utils.ts +13 -3
  91. package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
  92. package/src/billable-services/billable-service-form/billable-service-form.test.tsx +893 -0
  93. package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +504 -0
  94. package/src/billable-services/billable-service.resource.ts +42 -26
  95. package/src/billable-services/billable-services-home.component.tsx +13 -42
  96. package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
  97. package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
  98. package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
  99. package/src/billable-services/billable-services.component.tsx +156 -152
  100. package/src/billable-services/billable-services.scss +29 -0
  101. package/src/billable-services/billable-services.test.tsx +6 -49
  102. package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
  103. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  104. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  105. package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
  106. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  107. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +74 -0
  108. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  109. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  110. package/src/billable-services-admin-card-link.component.test.tsx +2 -2
  111. package/src/billable-services-admin-card-link.component.tsx +1 -1
  112. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  113. package/src/billing-form/billing-checkin-form.component.tsx +21 -17
  114. package/src/billing-form/billing-checkin-form.test.tsx +99 -26
  115. package/src/billing-form/billing-form.component.tsx +222 -292
  116. package/src/billing-form/billing-form.scss +143 -0
  117. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
  118. package/src/billing.resource.ts +69 -74
  119. package/src/bills-table/bills-table.component.tsx +3 -3
  120. package/src/bills-table/bills-table.test.tsx +98 -54
  121. package/src/config-schema.ts +52 -24
  122. package/src/dashboard.meta.ts +4 -2
  123. package/src/helpers/functions.ts +5 -4
  124. package/src/index.ts +71 -9
  125. package/src/invoice/invoice-table.component.tsx +36 -70
  126. package/src/invoice/invoice-table.scss +8 -5
  127. package/src/invoice/invoice-table.test.tsx +273 -62
  128. package/src/invoice/invoice.component.tsx +39 -32
  129. package/src/invoice/invoice.scss +11 -4
  130. package/src/invoice/invoice.test.tsx +324 -120
  131. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  132. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  133. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  134. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  135. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  136. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  137. package/src/invoice/payments/payments.component.tsx +55 -67
  138. package/src/invoice/payments/payments.scss +4 -3
  139. package/src/invoice/payments/payments.test.tsx +282 -0
  140. package/src/invoice/payments/utils.ts +15 -27
  141. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  142. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  143. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  144. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  145. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  146. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  147. package/src/invoice/printable-invoice/printable-invoice.component.tsx +20 -34
  148. package/src/left-panel-link.test.tsx +1 -4
  149. package/src/metrics-cards/metrics-cards.component.tsx +16 -6
  150. package/src/metrics-cards/metrics-cards.scss +4 -0
  151. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  152. package/src/modal/require-payment-modal.test.tsx +27 -22
  153. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  154. package/src/routes.json +44 -20
  155. package/src/types/index.ts +81 -23
  156. package/translations/am.json +132 -75
  157. package/translations/ar.json +133 -76
  158. package/translations/ar_SY.json +133 -76
  159. package/translations/bn.json +135 -78
  160. package/translations/de.json +133 -76
  161. package/translations/en.json +133 -78
  162. package/translations/en_US.json +133 -76
  163. package/translations/es.json +132 -75
  164. package/translations/es_MX.json +133 -76
  165. package/translations/fr.json +138 -81
  166. package/translations/he.json +132 -75
  167. package/translations/hi.json +133 -76
  168. package/translations/hi_IN.json +133 -76
  169. package/translations/id.json +133 -76
  170. package/translations/it.json +159 -102
  171. package/translations/ka.json +133 -76
  172. package/translations/km.json +132 -75
  173. package/translations/ku.json +133 -76
  174. package/translations/ky.json +133 -76
  175. package/translations/lg.json +133 -76
  176. package/translations/ne.json +133 -76
  177. package/translations/pl.json +133 -76
  178. package/translations/pt.json +133 -76
  179. package/translations/pt_BR.json +133 -76
  180. package/translations/qu.json +133 -76
  181. package/translations/ro_RO.json +222 -165
  182. package/translations/ru_RU.json +133 -76
  183. package/translations/si.json +133 -76
  184. package/translations/sw.json +133 -76
  185. package/translations/sw_KE.json +133 -76
  186. package/translations/tr.json +133 -76
  187. package/translations/tr_TR.json +133 -76
  188. package/translations/uk.json +133 -76
  189. package/translations/uz.json +133 -76
  190. package/translations/uz@Latn.json +133 -76
  191. package/translations/uz_UZ.json +133 -76
  192. package/translations/vi.json +133 -76
  193. package/translations/zh.json +133 -76
  194. package/translations/zh_CN.json +164 -107
  195. package/dist/1146.js.LICENSE.txt +0 -21
  196. package/dist/2352.js +0 -1
  197. package/dist/2352.js.map +0 -1
  198. package/dist/246.js +0 -1
  199. package/dist/246.js.map +0 -1
  200. package/dist/4689.js +0 -2
  201. package/dist/4689.js.map +0 -1
  202. package/dist/6557.js +0 -2
  203. package/dist/6557.js.map +0 -1
  204. package/dist/8638.js +0 -1
  205. package/dist/8638.js.map +0 -1
  206. package/dist/9968.js +0 -1
  207. package/dist/9968.js.map +0 -1
  208. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  209. package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
  210. package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
  211. package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
  212. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  213. package/src/invoice/payments/payments.component.test.tsx +0 -121
  214. /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
@@ -1,65 +1,36 @@
1
1
  import React from 'react';
2
+ import classNames from 'classnames';
2
3
  import { BrowserRouter, Routes, Route } from 'react-router-dom';
3
- import { SideNav, SideNavItems, SideNavLink, SideNavMenu, SideNavMenuItem } from '@carbon/react';
4
- import { Wallet, Money, Settings } from '@carbon/react/icons';
5
4
  import { useTranslation } from 'react-i18next';
6
- import { UserHasAccess, navigate } from '@openmrs/esm-framework';
7
- import AddBillableService from './create-edit/add-billable-service.component';
5
+ import { useLeftNav, WorkspaceContainer, useLayoutType, isDesktop } from '@openmrs/esm-framework';
8
6
  import BillWaiver from './bill-waiver/bill-waiver.component';
9
7
  import BillableServicesDashboard from './dashboard/dashboard.component';
10
8
  import BillingHeader from '../billing-header/billing-header.component';
11
9
  import CashPointConfiguration from './cash-point/cash-point-configuration.component';
12
- import PaymentModesConfig from './payyment-modes/payment-modes-config.component';
10
+ import PaymentModesConfig from './payment-modes/payment-modes-config.component';
13
11
  import styles from './billable-services.scss';
14
12
 
15
13
  const BillableServiceHome: React.FC = () => {
16
14
  const { t } = useTranslation();
15
+ const layout = useLayoutType();
17
16
  const basePath = `${window.spaBase}/billable-services`;
18
17
 
19
- const handleNavigation = (path: string) => {
20
- navigate({ to: `${basePath}/${path}` });
21
- };
22
-
23
- const handleCloseAddService = () => {
24
- navigate({ to: `${basePath}` });
25
- };
18
+ useLeftNav({ name: 'billable-services-left-panel-slot', basePath });
26
19
 
27
20
  return (
28
- <BrowserRouter basename={`${window.spaBase}/billable-services`}>
29
- <main className={styles.mainSection}>
30
- <section>
31
- <SideNav>
32
- <SideNavItems>
33
- <SideNavLink onClick={() => handleNavigation('')} renderIcon={Wallet} isActive>
34
- {t('billableServices', 'Billable Services')}
35
- </SideNavLink>
36
- <UserHasAccess privilege="coreapps.systemAdministration">
37
- <SideNavLink onClick={() => handleNavigation('waive-bill')} renderIcon={Money}>
38
- {t('billWaiver', 'Bill waiver')}
39
- </SideNavLink>
40
- <SideNavMenu title={t('billingSettings', 'Billing Settings')} renderIcon={Settings}>
41
- <SideNavMenuItem onClick={() => handleNavigation('cash-point-config')}>
42
- {t('cashPointConfig', 'Cash Point Config')}
43
- </SideNavMenuItem>
44
- <SideNavMenuItem onClick={() => handleNavigation('payment-modes-config')}>
45
- {t('paymentModesConfig', 'Payment Modes Config')}
46
- </SideNavMenuItem>
47
- </SideNavMenu>
48
- </UserHasAccess>
49
- </SideNavItems>
50
- </SideNav>
51
- </section>
52
- <section>
53
- <BillingHeader title={t('billServicesManagement', 'Bill services management')} />
21
+ <BrowserRouter basename={basePath}>
22
+ <div className={styles.pageWrapper}>
23
+ <main className={classNames(styles.pageContent, { [styles.hasLeftNav]: isDesktop(layout) })}>
24
+ <BillingHeader title={t('billableServicesManagement', 'Billable services management')} />
54
25
  <Routes>
55
26
  <Route path="/" element={<BillableServicesDashboard />} />
56
- <Route path="/add-service" element={<AddBillableService onClose={handleCloseAddService} />} />
57
- <Route path="/waive-bill" element={<BillWaiver />} />
58
27
  <Route path="/cash-point-config" element={<CashPointConfiguration />} />
59
28
  <Route path="/payment-modes-config" element={<PaymentModesConfig />} />
29
+ <Route path="/waive-bill" element={<BillWaiver />} />
60
30
  </Routes>
61
- </section>
62
- </main>
31
+ </main>
32
+ </div>
33
+ <WorkspaceContainer contextKey="billable-services" />
63
34
  </BrowserRouter>
64
35
  );
65
36
  };
@@ -0,0 +1,48 @@
1
+ import React, { useMemo } from 'react';
2
+ import { BrowserRouter, useLocation } from 'react-router-dom';
3
+ import { SideNavLink } from '@carbon/react';
4
+ import { navigate, UserHasAccess } from '@openmrs/esm-framework';
5
+
6
+ export interface BillableServicesLinkConfig {
7
+ name: string;
8
+ title: string;
9
+ path: string;
10
+ icon?: React.ComponentType;
11
+ privilege?: string;
12
+ }
13
+
14
+ function BillableServicesLinkExtension({ config }: { config: BillableServicesLinkConfig }) {
15
+ const { title, path, icon: Icon, privilege } = config;
16
+ const location = useLocation();
17
+ const spaBasePath = `${window.spaBase}/billable-services`;
18
+
19
+ const isActive = useMemo(() => {
20
+ const currentPath = location.pathname.replace(spaBasePath, '');
21
+ if (path === '' || path === '/') {
22
+ return currentPath === '' || currentPath === '/';
23
+ }
24
+ return currentPath.startsWith(`/${path}`);
25
+ }, [location.pathname, path, spaBasePath]);
26
+
27
+ const handleNavigation = () => {
28
+ navigate({ to: `${spaBasePath}/${path}` });
29
+ };
30
+
31
+ const link = (
32
+ <SideNavLink onClick={handleNavigation} renderIcon={Icon} isActive={isActive}>
33
+ {title}
34
+ </SideNavLink>
35
+ );
36
+
37
+ if (privilege) {
38
+ return <UserHasAccess privilege={privilege}>{link}</UserHasAccess>;
39
+ }
40
+
41
+ return link;
42
+ }
43
+
44
+ export const createBillableServicesLeftPanelLink = (config: BillableServicesLinkConfig) => () => (
45
+ <BrowserRouter>
46
+ <BillableServicesLinkExtension config={config} />
47
+ </BrowserRouter>
48
+ );
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { BrowserRouter } from 'react-router-dom';
3
+ import { SideNavMenu, SideNavMenuItem } from '@carbon/react';
4
+ import { navigate, UserHasAccess } from '@openmrs/esm-framework';
5
+
6
+ export interface BillableServicesMenuConfig {
7
+ title: string;
8
+ icon?: React.ComponentType;
9
+ privilege?: string;
10
+ items: Array<{
11
+ name: string;
12
+ title: string;
13
+ path: string;
14
+ }>;
15
+ }
16
+
17
+ function BillableServicesMenuExtension({ config }: { config: BillableServicesMenuConfig }) {
18
+ const { title, icon: Icon, items, privilege } = config;
19
+ const spaBasePath = `${window.spaBase}/billable-services`;
20
+
21
+ const handleNavigation = (path: string) => {
22
+ navigate({ to: `${spaBasePath}/${path}` });
23
+ };
24
+
25
+ const menu = (
26
+ <SideNavMenu defaultExpanded title={title} renderIcon={Icon}>
27
+ {items.map((item) => (
28
+ <SideNavMenuItem key={item.name} onClick={() => handleNavigation(item.path)}>
29
+ {item.title}
30
+ </SideNavMenuItem>
31
+ ))}
32
+ </SideNavMenu>
33
+ );
34
+
35
+ if (privilege) {
36
+ return <UserHasAccess privilege={privilege}>{menu}</UserHasAccess>;
37
+ }
38
+
39
+ return menu;
40
+ }
41
+
42
+ export const createBillableServicesLeftPanelMenu = (config: BillableServicesMenuConfig) => () => (
43
+ <BrowserRouter>
44
+ <BillableServicesMenuExtension config={config} />
45
+ </BrowserRouter>
46
+ );
@@ -1,16 +1,17 @@
1
- import { ClickableTile } from '@carbon/react';
2
1
  import React from 'react';
3
- import styles from './item.scss';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { ClickableTile } from '@carbon/react';
4
4
  import { Receipt } from '@carbon/react/icons';
5
+ import styles from './item.scss';
5
6
 
6
7
  const Item = () => {
7
- // items
8
+ const { t } = useTranslation();
8
9
  const openmrsSpaBase = window['getOpenmrsSpaBase']();
9
10
 
10
11
  return (
11
12
  <ClickableTile className={styles.customTile} id="menu-item" href={`${openmrsSpaBase}billable-services`}>
12
13
  <div className="customTileTitle">{<Receipt size={24} />}</div>
13
- <div>Billable Services</div>
14
+ <div>{t('billableServices', 'Billable services')}</div>
14
15
  </ClickableTile>
15
16
  );
16
17
  };
@@ -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,65 +20,76 @@ 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
+ ErrorState,
25
+ getCoreTranslation,
26
+ isDesktop,
27
+ launchWorkspace,
28
+ useConfig,
29
+ useLayoutType,
30
+ usePagination,
31
+ type LayoutType,
32
+ } from '@openmrs/esm-framework';
25
33
  import { EmptyState } from '@openmrs/esm-patient-common-lib';
26
- import { type BillableService } from '../types/index';
34
+ import { type BillableService } from '../types';
35
+ import { type BillingConfig } from '../config-schema';
27
36
  import { useBillableServices } from './billable-service.resource';
28
- import AddBillableService from './create-edit/add-billable-service.component';
29
37
  import styles from './billable-services.scss';
30
38
 
39
+ interface FilterableTableHeaderProps {
40
+ layout: LayoutType;
41
+ handleSearch: (e: React.ChangeEvent<HTMLInputElement>) => void;
42
+ isValidating: boolean;
43
+ responsiveSize: 'sm' | 'md' | 'lg';
44
+ t: (key: string, fallback: string) => string;
45
+ }
46
+
31
47
  const BillableServices = () => {
32
48
  const { t } = useTranslation();
33
49
  const { billableServices, isLoading, isValidating, error, mutate } = useBillableServices();
34
50
  const layout = useLayoutType();
35
- const config = useConfig();
51
+ const { pageSize: configuredPageSize } = useConfig<BillingConfig>();
36
52
  const [searchString, setSearchString] = useState('');
37
53
  const responsiveSize = isDesktop(layout) ? 'lg' : 'sm';
38
- const pageSizes = config?.billableServices?.pageSizes ?? [10, 20, 30, 40, 50];
39
- const [pageSize, setPageSize] = useState(config?.billableServices?.pageSize ?? 10);
40
-
41
- const [showOverlay, setShowOverlay] = useState(false);
42
- const [editingService, setEditingService] = useState(null);
54
+ const pageSizes = [10, 20, 30, 40, 50];
55
+ const [pageSize, setPageSize] = useState(configuredPageSize ?? 10);
43
56
 
44
57
  const headerData = [
45
58
  {
46
- header: t('serviceName', 'Service Name'),
59
+ header: t('serviceName', 'Service name'),
47
60
  key: 'serviceName',
48
61
  },
49
62
  {
50
- header: t('shortName', 'Short Name'),
63
+ header: t('shortName', 'Short name'),
51
64
  key: 'shortName',
52
65
  },
53
66
  {
54
- header: t('serviceType', 'Service Type'),
67
+ header: t('serviceType', 'Service type'),
55
68
  key: 'serviceType',
56
69
  },
57
70
  {
58
- header: t('status', 'Service Status'),
71
+ header: t('serviceStatus', 'Service status'),
59
72
  key: 'status',
60
73
  },
61
74
  {
62
75
  header: t('prices', 'Prices'),
63
76
  key: 'prices',
64
77
  },
65
- {
66
- header: t('actions', 'Actions'),
67
- key: 'actions',
68
- },
69
78
  ];
70
79
 
71
80
  const launchBillableServiceForm = useCallback(() => {
72
- navigate({ to: window.getOpenmrsSpaBase() + 'billable-services/add-service' });
73
- setEditingService(null);
74
- setShowOverlay(true);
75
- }, []);
81
+ launchWorkspace('billable-service-form', {
82
+ workspaceTitle: t('addBillableService', 'Add billable service'),
83
+ });
84
+ }, [t]);
76
85
 
77
86
  const searchResults: BillableService[] = useMemo(() => {
78
87
  const flatBillableServices = Array.isArray(billableServices) ? billableServices.flat() : billableServices;
79
88
 
80
89
  if (flatBillableServices !== undefined && flatBillableServices.length > 0) {
81
- if (searchString && searchString.trim() !== '') {
82
- const search = searchString.toLowerCase();
90
+ const trimmedSearch = searchString.trim();
91
+ if (trimmedSearch) {
92
+ const search = trimmedSearch.toLowerCase();
83
93
  return flatBillableServices.filter((service: BillableService) =>
84
94
  Object.entries(service).some(([header, value]) => {
85
95
  return header === 'uuid' ? false : `${value}`.toLowerCase().includes(search);
@@ -94,31 +104,16 @@ const BillableServices = () => {
94
104
  const rowData = [];
95
105
 
96
106
  if (results) {
97
- results.forEach((service, index) => {
107
+ results.forEach((service) => {
98
108
  const s = {
99
- id: `${index}`,
109
+ id: service.uuid,
100
110
  uuid: service.uuid,
101
111
  serviceName: service.name,
102
112
  shortName: service.shortName,
103
113
  serviceType: service?.serviceType?.display,
104
114
  status: service.serviceStatus,
105
- prices: '--',
106
- actions: (
107
- <TableCell>
108
- <OverflowMenu size="sm" flipped>
109
- <OverflowMenuItem
110
- itemText={t('editBillableService', 'Edit Billable Service')}
111
- onClick={() => handleEditService(service)}
112
- />
113
- </OverflowMenu>
114
- </TableCell>
115
- ),
115
+ prices: service.servicePrices.map((price) => `${price.name} (${price.price})`).join(', ') || '--',
116
116
  };
117
- let cost = '';
118
- service.servicePrices.forEach((price) => {
119
- cost += `${price.name} (${price.price}) `;
120
- });
121
- s.prices = cost;
122
117
  rowData.push(s);
123
118
  });
124
119
  }
@@ -130,132 +125,138 @@ const BillableServices = () => {
130
125
  },
131
126
  [goTo, setSearchString],
132
127
  );
133
- const handleEditService = useCallback((service) => {
134
- setEditingService(service);
135
- setShowOverlay(true);
136
- }, []);
137
128
 
138
- const closeModal = useCallback(() => {
139
- setShowOverlay(false);
140
- setEditingService(null);
141
- }, []);
129
+ const handleEditService = useCallback(
130
+ (service: BillableService) => {
131
+ launchWorkspace('billable-service-form', {
132
+ workspaceTitle: t('editBillableService', 'Edit billable service'),
133
+ serviceToEdit: service,
134
+ onWorkspaceClose: mutate,
135
+ });
136
+ },
137
+ [mutate, t],
138
+ );
142
139
 
143
140
  if (isLoading) {
144
- <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />;
141
+ return (
142
+ <InlineLoading
143
+ status="active"
144
+ iconDescription={getCoreTranslation('loading')}
145
+ description={t('loading', 'Loading data') + '...'}
146
+ />
147
+ );
145
148
  }
149
+
146
150
  if (error) {
147
- <ErrorState headerTitle={t('billableService', 'Billable Service')} error={error} />;
151
+ return <ErrorState headerTitle={t('billableService', 'Billable service')} error={error} />;
148
152
  }
153
+
149
154
  if (billableServices.length === 0) {
150
- <EmptyState
151
- displayText={t('billableService', 'Billable Service')}
152
- headerTitle={t('billableService', 'Billable Service')}
153
- launchForm={launchBillableServiceForm}
154
- />;
155
+ return (
156
+ <EmptyState
157
+ displayText={t('billableServices__lower', 'billable services')}
158
+ headerTitle={t('billableService', 'Billable service')}
159
+ launchForm={launchBillableServiceForm}
160
+ />
161
+ );
155
162
  }
156
163
 
157
164
  return (
158
- <>
159
- {billableServices?.length > 0 ? (
160
- <div className={styles.serviceContainer}>
161
- <FilterableTableHeader
162
- handleSearch={handleSearch}
163
- isValidating={isValidating}
164
- layout={layout}
165
- responsiveSize={responsiveSize}
166
- t={t}
167
- />
168
- <DataTable
169
- isSortable
170
- rows={rowData}
171
- headers={headerData}
172
- size={responsiveSize}
173
- useZebraStyles={rowData?.length > 1 ? true : false}>
174
- {({ rows, headers, getRowProps, getTableProps }) => (
175
- <TableContainer>
176
- <Table {...getTableProps()} aria-label="service list">
177
- <TableHead>
178
- <TableRow>
179
- {headers.map((header) => (
180
- <TableHeader key={header.key}>{header.header}</TableHeader>
181
- ))}
182
- </TableRow>
183
- </TableHead>
184
- <TableBody>
185
- {rows.map((row) => (
186
- <TableRow
187
- key={row.id}
188
- {...getRowProps({
189
- row,
190
- })}>
191
- {row.cells.map((cell) => (
192
- <TableCell key={cell.id}>{cell.value}</TableCell>
193
- ))}
194
- </TableRow>
165
+ <div className={styles.serviceContainer}>
166
+ <FilterableTableHeader
167
+ handleSearch={handleSearch}
168
+ isValidating={isValidating}
169
+ layout={layout}
170
+ responsiveSize={responsiveSize}
171
+ t={t}
172
+ />
173
+ <DataTable
174
+ isSortable
175
+ rows={rowData}
176
+ headers={headerData}
177
+ overflowMenuOnHover={isDesktop(layout)}
178
+ size={responsiveSize}
179
+ useZebraStyles={rowData?.length > 1}>
180
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
181
+ <TableContainer>
182
+ <Table {...getTableProps()} aria-label={t('serviceList', 'Service list')}>
183
+ <TableHead>
184
+ <TableRow>
185
+ {headers.map((header) => (
186
+ <TableHeader
187
+ {...getHeaderProps({
188
+ header,
189
+ })}
190
+ key={header.key}>
191
+ {header.header}
192
+ </TableHeader>
193
+ ))}
194
+ <TableHeader aria-label={getCoreTranslation('actions')} />
195
+ </TableRow>
196
+ </TableHead>
197
+ <TableBody>
198
+ {rows.map((row) => (
199
+ <TableRow
200
+ key={row.id}
201
+ {...getRowProps({
202
+ row,
203
+ })}>
204
+ {row.cells.map((cell) => (
205
+ <TableCell key={cell.id}>{cell.value}</TableCell>
195
206
  ))}
196
- </TableBody>
197
- </Table>
198
- </TableContainer>
199
- )}
200
- </DataTable>
201
- {searchResults?.length === 0 && (
202
- <div className={styles.filterEmptyState}>
203
- <Layer level={0}>
204
- <Tile className={styles.filterEmptyStateTile}>
205
- <p className={styles.filterEmptyStateContent}>
206
- {t('noMatchingServicesToDisplay', 'No matching services to display')}
207
- </p>
208
- <p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
209
- </Tile>
210
- </Layer>
211
- </div>
212
- )}
213
- {paginated && (
214
- <Pagination
215
- forwardText="Next page"
216
- backwardText="Previous page"
217
- page={currentPage}
218
- pageSize={pageSize}
219
- pageSizes={pageSizes}
220
- totalItems={searchResults?.length}
221
- className={styles.pagination}
222
- size={responsiveSize}
223
- onChange={({ pageSize: newPageSize, page: newPage }) => {
224
- if (newPageSize !== pageSize) {
225
- setPageSize(newPageSize);
226
- }
227
- if (newPage !== currentPage) {
228
- goTo(newPage);
229
- }
230
- }}
231
- />
232
- )}
207
+ <TableCell className="cds--table-column-menu">
208
+ <OverflowMenu size="lg" flipped>
209
+ <OverflowMenuItem
210
+ className={styles.menuItem}
211
+ itemText={t('editBillableService', 'Edit billable service')}
212
+ onClick={() => handleEditService(results.find((service) => service.uuid === row.id))}
213
+ />
214
+ </OverflowMenu>
215
+ </TableCell>
216
+ </TableRow>
217
+ ))}
218
+ </TableBody>
219
+ </Table>
220
+ </TableContainer>
221
+ )}
222
+ </DataTable>
223
+ {searchResults?.length === 0 && (
224
+ <div className={styles.filterEmptyState}>
225
+ <Layer level={0}>
226
+ <Tile className={styles.filterEmptyStateTile}>
227
+ <p className={styles.filterEmptyStateContent}>
228
+ {t('noMatchingServicesToDisplay', 'No matching services to display')}
229
+ </p>
230
+ <p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
231
+ </Tile>
232
+ </Layer>
233
233
  </div>
234
- ) : (
235
- <EmptyState
236
- launchForm={launchBillableServiceForm}
237
- displayText={t('noServicesToDisplay', 'There are no services to display')}
238
- headerTitle={t('billableService', 'Billable service')}
239
- />
240
234
  )}
241
- {showOverlay && (
242
- <Modal
243
- open={showOverlay}
244
- modalHeading={t('billableService', 'Billable Service')}
245
- primaryButtonText={null}
246
- secondaryButtonText={t('cancel', 'Cancel')}
247
- onRequestClose={closeModal}
248
- onSecondarySubmit={closeModal}
249
- size="lg"
250
- passiveModal={true}>
251
- <AddBillableService editingService={editingService} onClose={closeModal} />
252
- </Modal>
235
+ {paginated && (
236
+ <Pagination
237
+ forwardText={t('nextPage', 'Next page')}
238
+ backwardText={t('previousPage', 'Previous page')}
239
+ page={currentPage}
240
+ pageSize={pageSize}
241
+ pageSizes={pageSizes}
242
+ totalItems={searchResults?.length}
243
+ className={styles.pagination}
244
+ size={responsiveSize}
245
+ onChange={({ pageSize: newPageSize, page: newPage }) => {
246
+ if (newPageSize !== pageSize) {
247
+ setPageSize(newPageSize);
248
+ }
249
+ if (newPage !== currentPage) {
250
+ goTo(newPage);
251
+ }
252
+ }}
253
+ />
253
254
  )}
254
- </>
255
+ </div>
255
256
  );
256
257
  };
257
258
 
258
- function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveSize, t }) {
259
+ function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveSize, t }: FilterableTableHeaderProps) {
259
260
  return (
260
261
  <>
261
262
  <div className={styles.headerContainer}>
@@ -282,7 +283,9 @@ function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveS
282
283
  kind="primary"
283
284
  renderIcon={(props) => <ArrowRight size={16} {...props} />}
284
285
  onClick={() => {
285
- navigate({ to: window.getOpenmrsSpaBase() + 'billable-services/add-service' });
286
+ launchWorkspace('billable-service-form', {
287
+ workspaceTitle: t('addBillableService', 'Add billable service'),
288
+ });
286
289
  }}
287
290
  iconDescription={t('addNewBillableService', 'Add new billable service')}>
288
291
  {t('addNewService', 'Add new service')}
@@ -291,4 +294,5 @@ function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveS
291
294
  </>
292
295
  );
293
296
  }
297
+
294
298
  export default BillableServices;
@@ -1,3 +1,4 @@
1
+ @use '@carbon/colors';
1
2
  @use '@carbon/layout';
2
3
  @use '@carbon/type';
3
4
  @use '@openmrs/esm-styleguide/src/vars' as *;
@@ -217,3 +218,31 @@
217
218
  grid-template-columns: 16rem 1fr;
218
219
  }
219
220
 
221
+ .menuItem {
222
+ max-width: none;
223
+ }
224
+
225
+ .pageWrapper {
226
+ display: flex;
227
+ }
228
+
229
+ .pageContent {
230
+ display: flex;
231
+ flex-grow: 1;
232
+ flex-direction: column;
233
+ margin: 0;
234
+ // Full vertical height minus the primary navigation menu height
235
+ height: calc(100vh - layout.$spacing-09);
236
+ // Ensure the width is fixed and does not expand based on children
237
+ width: calc(100% - 16rem);
238
+ background-color: colors.$gray-10;
239
+
240
+ & a {
241
+ width: 100%;
242
+ }
243
+
244
+ &.hasLeftNav {
245
+ // Make space for the left nav menu
246
+ margin-inline-start: 16rem;
247
+ }
248
+ }