@openmrs/esm-billing-app 1.0.2-pre.88 → 1.0.2-pre.880

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 (208) 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/1856.js +1 -0
  10. package/dist/1856.js.map +1 -0
  11. package/dist/2146.js +1 -1
  12. package/dist/2372.js +1 -0
  13. package/dist/2372.js.map +1 -0
  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/4724.js +1 -0
  28. package/dist/4724.js.map +1 -0
  29. package/dist/4739.js +1 -1
  30. package/dist/4739.js.map +1 -1
  31. package/dist/4944.js +1 -1
  32. package/dist/5173.js +1 -1
  33. package/dist/5241.js +1 -1
  34. package/dist/5442.js +1 -1
  35. package/dist/5661.js +1 -1
  36. package/dist/6022.js +1 -1
  37. package/dist/6468.js +1 -1
  38. package/dist/6540.js +1 -1
  39. package/dist/6540.js.map +1 -1
  40. package/dist/6679.js +1 -1
  41. package/dist/6840.js +1 -1
  42. package/dist/6859.js +1 -1
  43. package/dist/7097.js +1 -1
  44. package/dist/7159.js +1 -1
  45. package/dist/723.js +1 -1
  46. package/dist/7255.js +1 -1
  47. package/dist/7255.js.map +1 -1
  48. package/dist/7617.js +1 -1
  49. package/dist/795.js +1 -1
  50. package/dist/8163.js +1 -1
  51. package/dist/8349.js +1 -1
  52. package/dist/8618.js +1 -1
  53. package/dist/8708.js +2 -0
  54. package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
  55. package/dist/8708.js.map +1 -0
  56. package/dist/890.js +1 -1
  57. package/dist/9214.js +1 -1
  58. package/dist/9538.js +1 -1
  59. package/dist/9569.js +1 -1
  60. package/dist/961.js +1 -1
  61. package/dist/961.js.map +1 -1
  62. package/dist/986.js +1 -1
  63. package/dist/9879.js +1 -1
  64. package/dist/9895.js +1 -1
  65. package/dist/9900.js +1 -1
  66. package/dist/9913.js +1 -1
  67. package/dist/main.js +1 -1
  68. package/dist/main.js.map +1 -1
  69. package/dist/openmrs-esm-billing-app.js +1 -1
  70. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +263 -301
  71. package/dist/openmrs-esm-billing-app.js.map +1 -1
  72. package/dist/routes.json +1 -1
  73. package/e2e/README.md +19 -18
  74. package/e2e/core/test.ts +1 -1
  75. package/e2e/fixtures/api.ts +1 -1
  76. package/e2e/specs/sample-test.spec.ts +0 -1
  77. package/e2e/support/github/Dockerfile +1 -1
  78. package/package.json +18 -15
  79. package/src/bill-history/bill-history.component.tsx +20 -28
  80. package/src/bill-history/bill-history.scss +4 -94
  81. package/src/bill-history/bill-history.test.tsx +37 -78
  82. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  83. package/src/bill-item-actions/edit-bill-item.modal.tsx +225 -0
  84. package/src/bill-item-actions/edit-bill-item.test.tsx +214 -40
  85. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  86. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +28 -32
  87. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  88. package/src/billable-services/bill-waiver/utils.ts +13 -3
  89. package/src/billable-services/billable-service.resource.ts +42 -26
  90. package/src/billable-services/billable-services-home.component.tsx +12 -35
  91. package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
  92. package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
  93. package/src/billable-services/billable-services.component.tsx +149 -148
  94. package/src/billable-services/billable-services.scss +29 -0
  95. package/src/billable-services/billable-services.test.tsx +6 -49
  96. package/src/billable-services/cash-point/add-cash-point.modal.tsx +168 -0
  97. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  98. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  99. package/src/billable-services/create-edit/add-billable-service.component.tsx +388 -301
  100. package/src/billable-services/create-edit/add-billable-service.scss +7 -68
  101. package/src/billable-services/create-edit/add-billable-service.test.tsx +720 -77
  102. package/src/billable-services/create-edit/edit-billable-service.modal.tsx +51 -0
  103. package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
  104. package/src/billable-services/payment-modes/add-payment-mode.modal.tsx +121 -0
  105. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +74 -0
  106. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +125 -0
  107. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  108. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  109. package/src/billing-form/billing-checkin-form.component.tsx +21 -17
  110. package/src/billing-form/billing-checkin-form.test.tsx +99 -26
  111. package/src/billing-form/billing-form.component.tsx +222 -292
  112. package/src/billing-form/billing-form.scss +143 -0
  113. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
  114. package/src/billing.resource.ts +69 -74
  115. package/src/bills-table/bills-table.component.tsx +3 -3
  116. package/src/bills-table/bills-table.test.tsx +98 -54
  117. package/src/config-schema.ts +52 -24
  118. package/src/dashboard.meta.ts +4 -2
  119. package/src/helpers/functions.ts +5 -4
  120. package/src/index.ts +67 -9
  121. package/src/invoice/invoice-table.component.tsx +36 -70
  122. package/src/invoice/invoice-table.scss +8 -5
  123. package/src/invoice/invoice-table.test.tsx +273 -62
  124. package/src/invoice/invoice.component.tsx +39 -32
  125. package/src/invoice/invoice.scss +11 -4
  126. package/src/invoice/invoice.test.tsx +324 -120
  127. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  128. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  129. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  130. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  131. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  132. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  133. package/src/invoice/payments/payments.component.tsx +55 -67
  134. package/src/invoice/payments/payments.scss +4 -3
  135. package/src/invoice/payments/payments.test.tsx +282 -0
  136. package/src/invoice/payments/utils.ts +15 -27
  137. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  138. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  139. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  140. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  141. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  142. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  143. package/src/invoice/printable-invoice/printable-invoice.component.tsx +20 -34
  144. package/src/left-panel-link.test.tsx +1 -4
  145. package/src/metrics-cards/metrics-cards.component.tsx +16 -6
  146. package/src/metrics-cards/metrics-cards.scss +4 -0
  147. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  148. package/src/modal/require-payment-modal.test.tsx +27 -22
  149. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  150. package/src/routes.json +39 -8
  151. package/src/types/index.ts +81 -23
  152. package/translations/am.json +127 -75
  153. package/translations/ar.json +128 -76
  154. package/translations/ar_SY.json +128 -76
  155. package/translations/bn.json +130 -78
  156. package/translations/de.json +128 -76
  157. package/translations/en.json +128 -76
  158. package/translations/en_US.json +128 -76
  159. package/translations/es.json +127 -75
  160. package/translations/es_MX.json +128 -76
  161. package/translations/fr.json +133 -81
  162. package/translations/he.json +127 -75
  163. package/translations/hi.json +128 -76
  164. package/translations/hi_IN.json +128 -76
  165. package/translations/id.json +128 -76
  166. package/translations/it.json +154 -102
  167. package/translations/ka.json +128 -76
  168. package/translations/km.json +127 -75
  169. package/translations/ku.json +128 -76
  170. package/translations/ky.json +128 -76
  171. package/translations/lg.json +128 -76
  172. package/translations/ne.json +128 -76
  173. package/translations/pl.json +128 -76
  174. package/translations/pt.json +128 -76
  175. package/translations/pt_BR.json +128 -76
  176. package/translations/qu.json +128 -76
  177. package/translations/ro_RO.json +217 -165
  178. package/translations/ru_RU.json +128 -76
  179. package/translations/si.json +128 -76
  180. package/translations/sw.json +128 -76
  181. package/translations/sw_KE.json +128 -76
  182. package/translations/tr.json +128 -76
  183. package/translations/tr_TR.json +128 -76
  184. package/translations/uk.json +128 -76
  185. package/translations/uz.json +128 -76
  186. package/translations/uz@Latn.json +128 -76
  187. package/translations/uz_UZ.json +128 -76
  188. package/translations/vi.json +128 -76
  189. package/translations/zh.json +128 -76
  190. package/translations/zh_CN.json +159 -107
  191. package/dist/1146.js.LICENSE.txt +0 -21
  192. package/dist/2352.js +0 -1
  193. package/dist/2352.js.map +0 -1
  194. package/dist/246.js +0 -1
  195. package/dist/246.js.map +0 -1
  196. package/dist/4689.js +0 -2
  197. package/dist/4689.js.map +0 -1
  198. package/dist/6557.js +0 -2
  199. package/dist/6557.js.map +0 -1
  200. package/dist/8638.js +0 -1
  201. package/dist/8638.js.map +0 -1
  202. package/dist/9968.js +0 -1
  203. package/dist/9968.js.map +0 -1
  204. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  205. package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
  206. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  207. package/src/invoice/payments/payments.component.test.tsx +0 -121
  208. /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
@@ -1,56 +1,32 @@
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';
5
+ import { useLeftNav, navigate, WorkspaceContainer, useLayoutType, isDesktop } from '@openmrs/esm-framework';
7
6
  import AddBillableService from './create-edit/add-billable-service.component';
8
7
  import BillWaiver from './bill-waiver/bill-waiver.component';
9
8
  import BillableServicesDashboard from './dashboard/dashboard.component';
10
9
  import BillingHeader from '../billing-header/billing-header.component';
11
10
  import CashPointConfiguration from './cash-point/cash-point-configuration.component';
12
- import PaymentModesConfig from './payyment-modes/payment-modes-config.component';
11
+ import PaymentModesConfig from './payment-modes/payment-modes-config.component';
13
12
  import styles from './billable-services.scss';
14
13
 
15
14
  const BillableServiceHome: React.FC = () => {
16
15
  const { t } = useTranslation();
16
+ const layout = useLayoutType();
17
17
  const basePath = `${window.spaBase}/billable-services`;
18
18
 
19
- const handleNavigation = (path: string) => {
20
- navigate({ to: `${basePath}/${path}` });
21
- };
19
+ useLeftNav({ name: 'billable-services-left-panel-slot', basePath });
22
20
 
23
21
  const handleCloseAddService = () => {
24
22
  navigate({ to: `${basePath}` });
25
23
  };
26
24
 
27
25
  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')} />
26
+ <BrowserRouter basename={basePath}>
27
+ <div className={styles.pageWrapper}>
28
+ <main className={classNames(styles.pageContent, { [styles.hasLeftNav]: isDesktop(layout) })}>
29
+ <BillingHeader title={t('billableServicesManagement', 'Billable services management')} />
54
30
  <Routes>
55
31
  <Route path="/" element={<BillableServicesDashboard />} />
56
32
  <Route path="/add-service" element={<AddBillableService onClose={handleCloseAddService} />} />
@@ -58,8 +34,9 @@ const BillableServiceHome: React.FC = () => {
58
34
  <Route path="/cash-point-config" element={<CashPointConfiguration />} />
59
35
  <Route path="/payment-modes-config" element={<PaymentModesConfig />} />
60
36
  </Routes>
61
- </section>
62
- </main>
37
+ </main>
38
+ </div>
39
+ <WorkspaceContainer contextKey="billable-services" />
63
40
  </BrowserRouter>
64
41
  );
65
42
  };
@@ -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 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
+ );
@@ -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,75 @@ 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
+ navigate,
28
+ showModal,
29
+ useConfig,
30
+ useLayoutType,
31
+ usePagination,
32
+ type LayoutType,
33
+ } from '@openmrs/esm-framework';
25
34
  import { EmptyState } from '@openmrs/esm-patient-common-lib';
26
35
  import { type BillableService } from '../types/index';
27
36
  import { useBillableServices } from './billable-service.resource';
28
- import AddBillableService from './create-edit/add-billable-service.component';
37
+ import type { BillingConfig } from '../config-schema';
29
38
  import styles from './billable-services.scss';
30
39
 
40
+ interface FilterableTableHeaderProps {
41
+ layout: LayoutType;
42
+ handleSearch: (e: React.ChangeEvent<HTMLInputElement>) => void;
43
+ isValidating: boolean;
44
+ responsiveSize: 'sm' | 'md' | 'lg';
45
+ t: (key: string, fallback: string) => string;
46
+ }
47
+
31
48
  const BillableServices = () => {
32
49
  const { t } = useTranslation();
33
50
  const { billableServices, isLoading, isValidating, error, mutate } = useBillableServices();
34
51
  const layout = useLayoutType();
35
- const config = useConfig();
52
+ const { pageSize: configuredPageSize } = useConfig<BillingConfig>();
36
53
  const [searchString, setSearchString] = useState('');
37
54
  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);
55
+ const pageSizes = [10, 20, 30, 40, 50];
56
+ const [pageSize, setPageSize] = useState(configuredPageSize ?? 10);
43
57
 
44
58
  const headerData = [
45
59
  {
46
- header: t('serviceName', 'Service Name'),
60
+ header: t('serviceName', 'Service name'),
47
61
  key: 'serviceName',
48
62
  },
49
63
  {
50
- header: t('shortName', 'Short Name'),
64
+ header: t('shortName', 'Short name'),
51
65
  key: 'shortName',
52
66
  },
53
67
  {
54
- header: t('serviceType', 'Service Type'),
68
+ header: t('serviceType', 'Service type'),
55
69
  key: 'serviceType',
56
70
  },
57
71
  {
58
- header: t('status', 'Service Status'),
72
+ header: t('serviceStatus', 'Service status'),
59
73
  key: 'status',
60
74
  },
61
75
  {
62
76
  header: t('prices', 'Prices'),
63
77
  key: 'prices',
64
78
  },
65
- {
66
- header: t('actions', 'Actions'),
67
- key: 'actions',
68
- },
69
79
  ];
70
80
 
71
81
  const launchBillableServiceForm = useCallback(() => {
72
82
  navigate({ to: window.getOpenmrsSpaBase() + 'billable-services/add-service' });
73
- setEditingService(null);
74
- setShowOverlay(true);
75
83
  }, []);
76
84
 
77
85
  const searchResults: BillableService[] = useMemo(() => {
78
86
  const flatBillableServices = Array.isArray(billableServices) ? billableServices.flat() : billableServices;
79
87
 
80
88
  if (flatBillableServices !== undefined && flatBillableServices.length > 0) {
81
- if (searchString && searchString.trim() !== '') {
82
- const search = searchString.toLowerCase();
89
+ const trimmedSearch = searchString.trim();
90
+ if (trimmedSearch) {
91
+ const search = trimmedSearch.toLowerCase();
83
92
  return flatBillableServices.filter((service: BillableService) =>
84
93
  Object.entries(service).some(([header, value]) => {
85
94
  return header === 'uuid' ? false : `${value}`.toLowerCase().includes(search);
@@ -94,31 +103,16 @@ const BillableServices = () => {
94
103
  const rowData = [];
95
104
 
96
105
  if (results) {
97
- results.forEach((service, index) => {
106
+ results.forEach((service) => {
98
107
  const s = {
99
- id: `${index}`,
108
+ id: service.uuid,
100
109
  uuid: service.uuid,
101
110
  serviceName: service.name,
102
111
  shortName: service.shortName,
103
112
  serviceType: service?.serviceType?.display,
104
113
  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
- ),
114
+ prices: service.servicePrices.map((price) => `${price.name} (${price.price})`).join(', ') || '--',
116
115
  };
117
- let cost = '';
118
- service.servicePrices.forEach((price) => {
119
- cost += `${price.name} (${price.price}) `;
120
- });
121
- s.prices = cost;
122
116
  rowData.push(s);
123
117
  });
124
118
  }
@@ -130,132 +124,138 @@ const BillableServices = () => {
130
124
  },
131
125
  [goTo, setSearchString],
132
126
  );
133
- const handleEditService = useCallback((service) => {
134
- setEditingService(service);
135
- setShowOverlay(true);
136
- }, []);
137
127
 
138
- const closeModal = useCallback(() => {
139
- setShowOverlay(false);
140
- setEditingService(null);
141
- }, []);
128
+ const handleEditService = useCallback(
129
+ (service: BillableService) => {
130
+ const dispose = showModal('edit-billable-service-modal', {
131
+ serviceToEdit: service,
132
+ onServiceUpdated: mutate,
133
+ closeModal: () => dispose(),
134
+ });
135
+ },
136
+ [mutate],
137
+ );
142
138
 
143
139
  if (isLoading) {
144
- <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />;
140
+ return (
141
+ <InlineLoading
142
+ status="active"
143
+ iconDescription={getCoreTranslation('loading')}
144
+ description={t('loading', 'Loading data') + '...'}
145
+ />
146
+ );
145
147
  }
148
+
146
149
  if (error) {
147
- <ErrorState headerTitle={t('billableService', 'Billable Service')} error={error} />;
150
+ return <ErrorState headerTitle={t('billableService', 'Billable service')} error={error} />;
148
151
  }
152
+
149
153
  if (billableServices.length === 0) {
150
- <EmptyState
151
- displayText={t('billableService', 'Billable Service')}
152
- headerTitle={t('billableService', 'Billable Service')}
153
- launchForm={launchBillableServiceForm}
154
- />;
154
+ return (
155
+ <EmptyState
156
+ displayText={t('billableServices__lower', 'billable services')}
157
+ headerTitle={t('billableService', 'Billable service')}
158
+ launchForm={launchBillableServiceForm}
159
+ />
160
+ );
155
161
  }
156
162
 
157
163
  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>
164
+ <div className={styles.serviceContainer}>
165
+ <FilterableTableHeader
166
+ handleSearch={handleSearch}
167
+ isValidating={isValidating}
168
+ layout={layout}
169
+ responsiveSize={responsiveSize}
170
+ t={t}
171
+ />
172
+ <DataTable
173
+ isSortable
174
+ rows={rowData}
175
+ headers={headerData}
176
+ overflowMenuOnHover={isDesktop(layout)}
177
+ size={responsiveSize}
178
+ useZebraStyles={rowData?.length > 1}>
179
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
180
+ <TableContainer>
181
+ <Table {...getTableProps()} aria-label={t('serviceList', 'Service list')}>
182
+ <TableHead>
183
+ <TableRow>
184
+ {headers.map((header) => (
185
+ <TableHeader
186
+ {...getHeaderProps({
187
+ header,
188
+ })}
189
+ key={header.key}>
190
+ {header.header}
191
+ </TableHeader>
192
+ ))}
193
+ <TableHeader aria-label={getCoreTranslation('actions')} />
194
+ </TableRow>
195
+ </TableHead>
196
+ <TableBody>
197
+ {rows.map((row) => (
198
+ <TableRow
199
+ key={row.id}
200
+ {...getRowProps({
201
+ row,
202
+ })}>
203
+ {row.cells.map((cell) => (
204
+ <TableCell key={cell.id}>{cell.value}</TableCell>
195
205
  ))}
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
- )}
206
+ <TableCell className="cds--table-column-menu">
207
+ <OverflowMenu size="lg" flipped>
208
+ <OverflowMenuItem
209
+ className={styles.menuItem}
210
+ itemText={t('editBillableService', 'Edit billable service')}
211
+ onClick={() => handleEditService(results.find((service) => service.uuid === row.id))}
212
+ />
213
+ </OverflowMenu>
214
+ </TableCell>
215
+ </TableRow>
216
+ ))}
217
+ </TableBody>
218
+ </Table>
219
+ </TableContainer>
220
+ )}
221
+ </DataTable>
222
+ {searchResults?.length === 0 && (
223
+ <div className={styles.filterEmptyState}>
224
+ <Layer level={0}>
225
+ <Tile className={styles.filterEmptyStateTile}>
226
+ <p className={styles.filterEmptyStateContent}>
227
+ {t('noMatchingServicesToDisplay', 'No matching services to display')}
228
+ </p>
229
+ <p className={styles.filterEmptyStateHelper}>{t('checkFilters', 'Check the filters above')}</p>
230
+ </Tile>
231
+ </Layer>
233
232
  </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
233
  )}
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>
234
+ {paginated && (
235
+ <Pagination
236
+ forwardText={t('nextPage', 'Next page')}
237
+ backwardText={t('previousPage', 'Previous page')}
238
+ page={currentPage}
239
+ pageSize={pageSize}
240
+ pageSizes={pageSizes}
241
+ totalItems={searchResults?.length}
242
+ className={styles.pagination}
243
+ size={responsiveSize}
244
+ onChange={({ pageSize: newPageSize, page: newPage }) => {
245
+ if (newPageSize !== pageSize) {
246
+ setPageSize(newPageSize);
247
+ }
248
+ if (newPage !== currentPage) {
249
+ goTo(newPage);
250
+ }
251
+ }}
252
+ />
253
253
  )}
254
- </>
254
+ </div>
255
255
  );
256
256
  };
257
257
 
258
- function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveSize, t }) {
258
+ function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveSize, t }: FilterableTableHeaderProps) {
259
259
  return (
260
260
  <>
261
261
  <div className={styles.headerContainer}>
@@ -291,4 +291,5 @@ function FilterableTableHeader({ layout, handleSearch, isValidating, responsiveS
291
291
  </>
292
292
  );
293
293
  }
294
+
294
295
  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
+ }