@palladium-ethiopia/esm-admin-app 5.4.2-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 (149) hide show
  1. package/.turbo/turbo-build.log +15 -0
  2. package/README.md +12 -0
  3. package/dist/117.js +1 -0
  4. package/dist/152.js +1 -0
  5. package/dist/152.js.map +1 -0
  6. package/dist/209.js +1 -0
  7. package/dist/209.js.map +1 -0
  8. package/dist/41.js +1 -0
  9. package/dist/41.js.map +1 -0
  10. package/dist/442.js +1 -0
  11. package/dist/442.js.map +1 -0
  12. package/dist/466.js +1 -0
  13. package/dist/466.js.map +1 -0
  14. package/dist/555.js +1 -0
  15. package/dist/555.js.map +1 -0
  16. package/dist/61.js +1 -0
  17. package/dist/61.js.map +1 -0
  18. package/dist/672.js +15 -0
  19. package/dist/672.js.map +1 -0
  20. package/dist/689.js +1 -0
  21. package/dist/689.js.map +1 -0
  22. package/dist/710.js +1 -0
  23. package/dist/710.js.map +1 -0
  24. package/dist/712.js +1 -0
  25. package/dist/712.js.map +1 -0
  26. package/dist/771.js +1 -0
  27. package/dist/771.js.map +1 -0
  28. package/dist/789.js +1 -0
  29. package/dist/789.js.map +1 -0
  30. package/dist/806.js +1 -0
  31. package/dist/826.js +1 -0
  32. package/dist/826.js.map +1 -0
  33. package/dist/914.js +27 -0
  34. package/dist/914.js.map +1 -0
  35. package/dist/926.js +17 -0
  36. package/dist/926.js.map +1 -0
  37. package/dist/ethiopia-esm-admin-app.js +6 -0
  38. package/dist/ethiopia-esm-admin-app.js.buildmanifest.json +556 -0
  39. package/dist/ethiopia-esm-admin-app.js.map +1 -0
  40. package/dist/main.js +32 -0
  41. package/dist/main.js.map +1 -0
  42. package/dist/routes.json +1 -0
  43. package/jest.config.js +8 -0
  44. package/package.json +52 -0
  45. package/rspack.config.js +1 -0
  46. package/src/components/confirm-modal/confirmation-operation-modal.component.tsx +43 -0
  47. package/src/components/confirm-modal/confirmation-operation.test.tsx +69 -0
  48. package/src/components/dashboard/dashboard.component.tsx +131 -0
  49. package/src/components/dashboard/dashboard.scss +38 -0
  50. package/src/components/dashboard/etl-dashboard.component.tsx +11 -0
  51. package/src/components/empty-state/empty-state-log.components.tsx +20 -0
  52. package/src/components/empty-state/empty-state-log.scss +28 -0
  53. package/src/components/empty-state/empty-state-log.test.tsx +24 -0
  54. package/src/components/facility-setup/card.component.tsx +16 -0
  55. package/src/components/facility-setup/facility-info.component.tsx +142 -0
  56. package/src/components/facility-setup/facility-info.scss +87 -0
  57. package/src/components/facility-setup/facility-setup.component.tsx +21 -0
  58. package/src/components/facility-setup/facility-setup.resource.tsx +7 -0
  59. package/src/components/facility-setup/facility-setup.scss +38 -0
  60. package/src/components/facility-setup/header/header.component.tsx +23 -0
  61. package/src/components/facility-setup/header/header.scss +19 -0
  62. package/src/components/header/header-illustration.component.tsx +13 -0
  63. package/src/components/header/header.component.tsx +28 -0
  64. package/src/components/header/header.scss +19 -0
  65. package/src/components/hook/healthWorkerAdapter.ts +213 -0
  66. package/src/components/hook/useFacilityInfo.tsx +37 -0
  67. package/src/components/hook/useSystemRoleSetting.tsx +33 -0
  68. package/src/components/locations/auto-suggest/autosuggest.component.tsx +149 -0
  69. package/src/components/locations/auto-suggest/autosuggest.scss +61 -0
  70. package/src/components/locations/auto-suggest/location-autosuggest.component.tsx +94 -0
  71. package/src/components/locations/auto-suggest/location-autosuggest.scss +48 -0
  72. package/src/components/locations/common/results-tile.component.tsx +45 -0
  73. package/src/components/locations/common/results-tile.scss +86 -0
  74. package/src/components/locations/forms/add-location/add-location.workspace.scss +34 -0
  75. package/src/components/locations/forms/add-location/add-location.workspace.tsx +200 -0
  76. package/src/components/locations/forms/search-location/search-location.workspace.scss +79 -0
  77. package/src/components/locations/forms/search-location/search-location.workspace.tsx +215 -0
  78. package/src/components/locations/header/header.component.tsx +48 -0
  79. package/src/components/locations/header/header.scss +58 -0
  80. package/src/components/locations/helpers/index.ts +16 -0
  81. package/src/components/locations/home/home-locations.component.tsx +18 -0
  82. package/src/components/locations/home/home-locations.scss +8 -0
  83. package/src/components/locations/hooks/UseFacilityLocations.ts +12 -0
  84. package/src/components/locations/hooks/useLocation.ts +18 -0
  85. package/src/components/locations/hooks/useLocationTags.ts +15 -0
  86. package/src/components/locations/tables/locations-table.component.tsx +243 -0
  87. package/src/components/locations/tables/locations-table.resource.ts +26 -0
  88. package/src/components/locations/tables/locations-table.scss +115 -0
  89. package/src/components/locations/types/index.ts +120 -0
  90. package/src/components/locations/utils/index.ts +5 -0
  91. package/src/components/logs-table/operation-log-resource.ts +41 -0
  92. package/src/components/logs-table/operation-log-table.component.tsx +120 -0
  93. package/src/components/logs-table/operation-log.scss +10 -0
  94. package/src/components/logs-table/operation-log.test.tsx +47 -0
  95. package/src/components/modal/hwr-confirmation.modal.scss +21 -0
  96. package/src/components/modal/hwr-confirmation.modal.tsx +170 -0
  97. package/src/components/modal/hwr-empty.modal.component.tsx +54 -0
  98. package/src/components/modal/hwr-sync.modal.scss +30 -0
  99. package/src/components/modal/hwr-sync.modal.tsx +209 -0
  100. package/src/components/modal/hwr-sync.resource.ts +23 -0
  101. package/src/components/provider-banner/provider-banner.component.tsx +106 -0
  102. package/src/components/provider-banner/provider-banner.module.scss +51 -0
  103. package/src/components/provider-banner/provider-banner.resource.ts +29 -0
  104. package/src/components/side-menu/left-panel.scss +42 -0
  105. package/src/components/side-menu/left-pannel.component.tsx +22 -0
  106. package/src/components/users/header/header.scss +90 -0
  107. package/src/components/users/header/user-management-header.component.tsx +42 -0
  108. package/src/components/users/manage-users/hooks/useProviderAttributeMapping.ts +110 -0
  109. package/src/components/users/manage-users/hooks/useUserFormSteps.ts +119 -0
  110. package/src/components/users/manage-users/hooks/useUserFormSubmission.ts +264 -0
  111. package/src/components/users/manage-users/hooks/useUserManagementForm.ts +122 -0
  112. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-list/user-role-scope-list.component.tsx +177 -0
  113. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-fields.scss +117 -0
  114. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope-fields.component.tsx +290 -0
  115. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +316 -0
  116. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/userRoleScopeFormSchema.tsx +43 -0
  117. package/src/components/users/manage-users/manage-user.component.tsx +19 -0
  118. package/src/components/users/manage-users/manage-user.scss +31 -0
  119. package/src/components/users/manage-users/provider-autosuggest.component.tsx +117 -0
  120. package/src/components/users/manage-users/provider-search.resource.ts +34 -0
  121. package/src/components/users/manage-users/sections/demographic-section.component.tsx +156 -0
  122. package/src/components/users/manage-users/sections/login-section.component.tsx +88 -0
  123. package/src/components/users/manage-users/sections/provider-section.component.tsx +270 -0
  124. package/src/components/users/manage-users/sections/roles-section.component.tsx +88 -0
  125. package/src/components/users/manage-users/user-details/user-detail.scss +75 -0
  126. package/src/components/users/manage-users/user-details/user-details.component.tsx +182 -0
  127. package/src/components/users/manage-users/user-list/user-list.component.tsx +378 -0
  128. package/src/components/users/manage-users/user-list/user-list.resource.ts +30 -0
  129. package/src/components/users/manage-users/user-list/user-list.scss +37 -0
  130. package/src/components/users/manage-users/user-management.constants.ts +20 -0
  131. package/src/components/users/manage-users/user-management.utils.ts +100 -0
  132. package/src/components/users/manage-users/user-management.workspace.scss +172 -0
  133. package/src/components/users/manage-users/user-management.workspace.tsx +334 -0
  134. package/src/components/users/userManagementFormSchema.tsx +179 -0
  135. package/src/config-schema.ts +142 -0
  136. package/src/constants.ts +50 -0
  137. package/src/declarations.d.ts +2 -0
  138. package/src/index.ts +55 -0
  139. package/src/left-pannel-link.component.tsx +40 -0
  140. package/src/root.component.tsx +39 -0
  141. package/src/root.scss +12 -0
  142. package/src/routes.json +100 -0
  143. package/src/setup-tests.ts +1 -0
  144. package/src/types/index.ts +385 -0
  145. package/src/user-management.resources.ts +232 -0
  146. package/src/utils/utils.ts +20 -0
  147. package/translations/am.json +159 -0
  148. package/translations/en.json +159 -0
  149. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemrCharts":"^1.6.7"},"extensions":[{"component":"adminLeftPanelLink","name":"admin-left-panel-link","slot":"admin-left-panel-slot"},{"component":"userManagementLeftPannelLink","name":"user-management-left-panel-link","slot":"admin-left-panel-slot"},{"component":"locationsLeftPanelLink","name":"locations-left-panel-link","slot":"admin-left-panel-slot"},{"component":"facilitySetupLeftPanelLink","name":"facility-setup-left-panel-link","slot":"admin-left-panel-slot"},{"component":"providerBanner","name":"provider-banner","slot":"provider-banner-info-slot","order":1}],"workspaces":[{"name":"manage-user-workspace","component":"manageUserWorkspace","title":"Manage User Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"user-role-scope-workspace","component":"userRoleScopeWorkspace","title":"User Rple Scope Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"add-location-workspace","title":"Add Location","component":"addLocation","type":"workspace"},{"name":"search-location-workspace","title":"Search Location","component":"searchLocationWorkspace","type":"workspace"},{"name":"hwr-sync-workspace","title":"HWR Sync Workspace","component":"hwrSyncWorkspace","type":"other-form"},{"name":"hwr-sync-modal","title":"HWR Sync Modal","component":"hwrSyncModal","type":"modal"}],"modals":[{"component":"operationConfirmationModal","name":"operation-confirmation-modal"},{"component":"hwrConfirmationModal","name":"hwr-confirmation-modal"},{"component":"hwrEmptyModal","name":"hwr-empty-modal"},{"component":"hwrSyncModal","name":"hwr-syncing-modal"}],"pages":[{"component":"root","route":"admin"}],"version":"5.4.2-pre.100"}
package/jest.config.js ADDED
@@ -0,0 +1,8 @@
1
+ const rootConfig = require('../../jest.config.js');
2
+
3
+ const packageConfig = {
4
+ ...rootConfig,
5
+ collectCoverage: false,
6
+ };
7
+
8
+ module.exports = packageConfig;
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@palladium-ethiopia/esm-admin-app",
3
+ "version": "5.4.2-pre.100",
4
+ "description": "Admin app for EthiopiaEMR",
5
+ "keywords": [
6
+ "openmrs"
7
+ ],
8
+ "homepage": "https://github.com/palladium-group/ethiopiaemr-esm-3.x#readme",
9
+ "bugs": {
10
+ "url": "https://github.com/palladium-group/ethiopiaemr-esm-3.x/issues"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/palladium-group/ethiopiaemr-esm-3.x#readme"
15
+ },
16
+ "license": "MPL-2.0",
17
+ "main": "src/index.ts",
18
+ "source": true,
19
+ "browser": "dist/ethiopia-esm-admin-app.js",
20
+ "scripts": {
21
+ "analyze": "rspack --mode=production --env analyze=true",
22
+ "build": "rspack --mode=production",
23
+ "coverage": "yarn test --coverage",
24
+ "debug": "npm run serve",
25
+ "extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js",
26
+ "lint": "eslint src --ext ts,tsx",
27
+ "serve": "rspack serve --mode=development",
28
+ "start": "openmrs develop",
29
+ "test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests",
30
+ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js",
31
+ "typescript": "tsc"
32
+ },
33
+ "browserslist": [
34
+ "extends browserslist-config-openmrs"
35
+ ],
36
+ "dependencies": {
37
+ "lodash-es": "^4.17.15",
38
+ "react-to-print": "2.14.13"
39
+ },
40
+ "peerDependencies": {
41
+ "@carbon/react": "^1.83.0",
42
+ "@openmrs/esm-framework": "8.x",
43
+ "react": "^18.1.0",
44
+ "react-i18next": "11.x",
45
+ "react-router-dom": "6.x",
46
+ "swr": "2.x"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "stableVersion": "1.0.0"
52
+ }
@@ -0,0 +1 @@
1
+ module.exports = require('openmrs/default-rspack-config');
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button } from '@carbon/react';
4
+
5
+ interface OperationConfirmationModalProps {
6
+ close: () => void;
7
+ confirm: () => void;
8
+ operationName: string;
9
+ operationType: string;
10
+ }
11
+
12
+ const OperationConfirmation: React.FC<OperationConfirmationModalProps> = ({
13
+ close,
14
+ confirm,
15
+ operationName,
16
+ operationType,
17
+ }) => {
18
+ const { t } = useTranslation();
19
+ const message = t('operationsConfirmationMessages', 'Do you want to {{operationTypeOrName}}?', {
20
+ operationTypeOrName: operationType || operationName,
21
+ });
22
+
23
+ return (
24
+ <>
25
+ <div className="cds--modal-header">
26
+ <h3 className="cds--modal-header__heading">{t('confirmation', 'Confirmation')}</h3>
27
+ </div>
28
+ <div className="cds--modal-content">
29
+ <p>{message}</p>
30
+ </div>
31
+ <div className="cds--modal-footer">
32
+ <Button kind="secondary" onClick={close}>
33
+ {t('noRespond', 'No')}
34
+ </Button>
35
+ <Button kind="primary" onClick={confirm}>
36
+ {t('yesRespond', 'Yes')}
37
+ </Button>
38
+ </div>
39
+ </>
40
+ );
41
+ };
42
+
43
+ export default OperationConfirmation;
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { useTranslation } from 'react-i18next';
5
+ import OperationConfirmation from './confirmation-operation-modal.component';
6
+
7
+ jest.mock('react-i18next', () => ({
8
+ useTranslation: jest.fn(),
9
+ }));
10
+
11
+ describe('OperationConfirmation', () => {
12
+ beforeEach(() => {
13
+ (useTranslation as jest.Mock).mockReturnValue({
14
+ t: (key, defaultValue, options) =>
15
+ options?.operationTypeOrName ? `Do you want to ${options.operationTypeOrName}?` : defaultValue || key,
16
+ });
17
+ });
18
+
19
+ it('renders the component with provided props', () => {
20
+ const closeMock = jest.fn();
21
+ const confirmMock = jest.fn();
22
+ const operationName = 'refresh';
23
+ const operationType = 'refreshed';
24
+
25
+ render(
26
+ <OperationConfirmation
27
+ close={closeMock}
28
+ confirm={confirmMock}
29
+ operationName={operationName}
30
+ operationType={operationType}
31
+ />,
32
+ );
33
+
34
+ expect(screen.getByRole('heading', { name: 'Confirmation' })).toBeInTheDocument();
35
+ expect(screen.getByText('Do you want to refreshed?')).toBeInTheDocument();
36
+ expect(screen.getByText('No')).toBeInTheDocument();
37
+ expect(screen.getByText('Yes')).toBeInTheDocument();
38
+ });
39
+
40
+ it('calls close when the No button is clicked', () => {
41
+ const closeMock = jest.fn();
42
+ const confirmMock = jest.fn();
43
+
44
+ render(
45
+ <OperationConfirmation close={closeMock} confirm={confirmMock} operationName="delete" operationType="deleted" />,
46
+ );
47
+
48
+ const noButton = screen.getByText('No');
49
+ fireEvent.click(noButton);
50
+
51
+ expect(closeMock).toHaveBeenCalledTimes(1);
52
+ expect(confirmMock).not.toHaveBeenCalled();
53
+ });
54
+
55
+ it('calls confirm when the Yes button is clicked', () => {
56
+ const closeMock = jest.fn();
57
+ const confirmMock = jest.fn();
58
+
59
+ render(
60
+ <OperationConfirmation close={closeMock} confirm={confirmMock} operationName="delete" operationType="deleted" />,
61
+ );
62
+
63
+ const yesButton = screen.getByText('Yes');
64
+ fireEvent.click(yesButton);
65
+
66
+ expect(confirmMock).toHaveBeenCalledTimes(1);
67
+ expect(closeMock).not.toHaveBeenCalled();
68
+ });
69
+ });
@@ -0,0 +1,131 @@
1
+ import React, { useState } from 'react';
2
+ import Header from '../header/header.component';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Layer, ComboButton, MenuItem, InlineLoading } from '@carbon/react';
5
+ import styles from './dashboard.scss';
6
+ import {
7
+ recreateTables,
8
+ refreshTables,
9
+ recreateDatatools,
10
+ refreshDwapi,
11
+ recreateFacilityWideTables,
12
+ } from '../logs-table/operation-log-resource';
13
+ import LogTable from '../logs-table/operation-log-table.component';
14
+ import { showModal, showSnackbar } from '@openmrs/esm-framework';
15
+
16
+ const Dashboard: React.FC = () => {
17
+ const { t } = useTranslation();
18
+ const [isLoading, setIsLoading] = useState(false);
19
+ const [currentOperation, setCurrentOperation] = useState<string>('');
20
+ const [logData, setLogData] = useState<Array<any>>([]);
21
+ const [isRefreshing, setIsRefreshing] = useState(false);
22
+
23
+ const openConfirmationModal = (
24
+ operation: () => Promise<any>,
25
+ operationName: string,
26
+ clearDataOnRecreate: boolean = false,
27
+ ) => {
28
+ const dispose = showModal('operation-confirmation-modal', {
29
+ confirm: async () => {
30
+ dispose();
31
+ setIsLoading(true);
32
+ setCurrentOperation(operationName);
33
+ if (clearDataOnRecreate) {
34
+ setLogData([]);
35
+ }
36
+
37
+ try {
38
+ setIsRefreshing(operationName === 'refreshTables');
39
+
40
+ const data = await operation();
41
+ const isRecreate = operationName.toLowerCase().includes('recreate');
42
+ const operationType = isRecreate ? t('recreated', 'recreated') : t('refreshed', 'refreshed');
43
+
44
+ showSnackbar({
45
+ title: t('operationSuccess', '{{operationName}} successfully {{operationType}}', {
46
+ operationName,
47
+ operationType,
48
+ }),
49
+ subtitle: t('operationSuccessSubtitle', 'The operation completed successfully.'),
50
+ kind: 'success',
51
+ isLowContrast: true,
52
+ });
53
+
54
+ if (operationName === 'refreshTables') {
55
+ setLogData((prevData) => [...prevData, ...data]);
56
+ } else {
57
+ setLogData(data);
58
+ }
59
+ } catch (error) {
60
+ showSnackbar({
61
+ title: t('operationError', '{{operationName}} failed', { operationName }),
62
+ subtitle: t('operationErrorSubtitle', 'An error occurred during the operation.'),
63
+ kind: 'error',
64
+ isLowContrast: true,
65
+ });
66
+ } finally {
67
+ setIsLoading(false);
68
+ setIsRefreshing(false);
69
+ setCurrentOperation('');
70
+ }
71
+ },
72
+ close: () => {
73
+ dispose();
74
+ },
75
+ operationName,
76
+ operationType: operationName,
77
+ });
78
+ };
79
+
80
+ return (
81
+ <div className="omrs-main-content">
82
+ <Header title={t('home', 'Home')} />
83
+ <Layer className={styles.btnLayer}>
84
+ {isLoading ? (
85
+ <InlineLoading
86
+ description={t('etlsOperationsLoading', 'Please wait {{currentOperation}} is in progress...', {
87
+ currentOperation,
88
+ })}
89
+ className={styles.loading}
90
+ />
91
+ ) : (
92
+ <ComboButton tooltipAlignment="left" label={t('etlOperation', 'ETL operations')} size="md">
93
+ <MenuItem
94
+ label={t('refreshTables', 'Refresh tables')}
95
+ onClick={() => openConfirmationModal(refreshTables, t('refreshTables', 'Refresh tables'), false)}
96
+ />
97
+ <MenuItem
98
+ label={t('refreshDwapi', 'Refresh DWAPI tables')}
99
+ onClick={() => openConfirmationModal(refreshDwapi, t('refreshDwapi', 'Refresh DWAPI tables'), false)}
100
+ />
101
+ <MenuItem
102
+ label={t('recreateTables', 'Recreate tables')}
103
+ onClick={() => openConfirmationModal(recreateTables, t('recreateTables', 'Recreate tables'), true)}
104
+ />
105
+ <MenuItem
106
+ label={t('recreateDatatools', 'Recreate datatools')}
107
+ onClick={() =>
108
+ openConfirmationModal(recreateDatatools, t('recreateDatatools', 'Recreate datatools'), true)
109
+ }
110
+ />{' '}
111
+ <MenuItem
112
+ label={t('recreateFacilityWideTables', 'Recreate facility wide tables')}
113
+ onClick={() =>
114
+ openConfirmationModal(
115
+ recreateFacilityWideTables,
116
+ t('recreateFacilityWideTables', 'Recreate facility wide tables'),
117
+ true,
118
+ )
119
+ }
120
+ />
121
+ </ComboButton>
122
+ )}
123
+ </Layer>
124
+ <Layer className={styles.tableLayer}>
125
+ <LogTable logData={logData} isLoading={isRefreshing} />
126
+ </Layer>
127
+ </div>
128
+ );
129
+ };
130
+
131
+ export default Dashboard;
@@ -0,0 +1,38 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+
5
+ .omrs-main-content {
6
+ background-color: white;
7
+ }
8
+
9
+ .btnLayer {
10
+ display: flex;
11
+ padding-top: layout.$spacing-05;
12
+ padding-right: layout.$spacing-05;
13
+ padding-bottom: layout.$spacing-05;
14
+ margin-top: layout.$spacing-05;
15
+ flex-direction: row;
16
+ justify-content: flex-end;
17
+ background-color: white;
18
+ width: 100%;
19
+ }
20
+
21
+ .tableLayer {
22
+ padding-left: layout.$spacing-05;
23
+ padding-right: layout.$spacing-05;
24
+ background: white;
25
+ padding-top: layout.$spacing-01;
26
+ }
27
+
28
+ .loading {
29
+ display: flex;
30
+ padding-top: layout.$spacing-05;
31
+ padding-right: layout.$spacing-05;
32
+ padding-bottom: layout.$spacing-05;
33
+ margin-top: layout.$spacing-05;
34
+ flex-direction: row;
35
+ justify-content: flex-end;
36
+ background-color: white;
37
+ width: 100%;
38
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import Dashboard from './dashboard.component';
3
+
4
+ const EtlAdminDashboard: React.FC = () => {
5
+ return (
6
+ <section>
7
+ <Dashboard />
8
+ </section>
9
+ );
10
+ };
11
+ export default EtlAdminDashboard;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import styles from './empty-state-log.scss';
4
+ import { DataEnrichment } from '@carbon/react/icons';
5
+ import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
6
+
7
+ interface EmptyStateProps {
8
+ subTitle: string;
9
+ }
10
+
11
+ const EmptyState: React.FC<EmptyStateProps> = ({ subTitle }) => {
12
+ return (
13
+ <div className={styles.emptyStateContainer}>
14
+ <EmptyDataIllustration />
15
+ <p className={styles.subTitle}>{subTitle}</p>
16
+ </div>
17
+ );
18
+ };
19
+
20
+ export default EmptyState;
@@ -0,0 +1,28 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .emptyStateContainer {
6
+ display: flex;
7
+ flex-direction: column;
8
+ align-items: center;
9
+ justify-content: center;
10
+ min-height: 300px;
11
+ background-color: colors.$gray-10;
12
+ row-gap: layout.$spacing-02;
13
+
14
+ & form {
15
+ border: none;
16
+ }
17
+ .subTitle {
18
+ @include type.type-style('body-compact-01');
19
+ color: colors.$cool-gray-70;
20
+ margin-top: layout.$spacing-04;
21
+ }
22
+ }
23
+
24
+ svg.iconOverrides {
25
+ width: layout.$spacing-11;
26
+ height: layout.$spacing-11;
27
+ fill: var(--brand-03);
28
+ }
@@ -0,0 +1,24 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import '@testing-library/jest-dom';
3
+ import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
4
+ import EmptyState from './empty-state-log.components';
5
+ import React from 'react';
6
+
7
+ // Mock the EmptyDataIllustration component
8
+ jest.mock('@openmrs/esm-patient-common-lib', () => ({
9
+ EmptyDataIllustration: jest.fn(() => <div>Mocked EmptyDataIllustration</div>),
10
+ }));
11
+
12
+ describe('EmptyState', () => {
13
+ it('renders the EmptyState component with the given subtitle', () => {
14
+ const testSubtitle = 'No data available';
15
+
16
+ render(<EmptyState subTitle={testSubtitle} />);
17
+
18
+ const subtitleElement = screen.getByText(testSubtitle);
19
+ expect(subtitleElement).toBeInTheDocument();
20
+
21
+ const illustrationElement = screen.getByText('Mocked EmptyDataIllustration');
22
+ expect(illustrationElement).toBeInTheDocument();
23
+ });
24
+ });
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+
3
+ type CardProps = {
4
+ label: string;
5
+ value?: string;
6
+ };
7
+
8
+ const Card: React.FC<CardProps> = ({ label, value }) => {
9
+ return (
10
+ <p>
11
+ <strong>{label}:</strong> {value ?? '--'}
12
+ </p>
13
+ );
14
+ };
15
+
16
+ export default Card;
@@ -0,0 +1,142 @@
1
+ import { Button, Column, Grid, InlineLoading, Layer, Tile } from '@carbon/react';
2
+ import { formatDate, parseDate, showSnackbar } from '@openmrs/esm-framework';
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { useLocalFacilityInfo, useShaFacilityInfo } from '../hook/useFacilityInfo';
6
+ import styles from './facility-info.scss';
7
+ import Card from './card.component';
8
+ import dayjs from 'dayjs';
9
+ import { syncPackagesAndInterventions } from './facility-setup.resource';
10
+
11
+ const FacilityInfo: React.FC = () => {
12
+ const { t } = useTranslation();
13
+ const [shouldSynchronize, setshouldSynchronize] = useState<boolean>(false);
14
+ const {
15
+ shaFacility,
16
+ isLoading: isShaFacilityLoading,
17
+ error: shaFacilityError,
18
+ mutate: mutateShafacility,
19
+ } = useShaFacilityInfo(shouldSynchronize);
20
+ const {
21
+ localFacility,
22
+ isLoading: isLocalFacilityLoading,
23
+ mutate: mutateLocalFacility,
24
+ error: localFacilityError,
25
+ } = useLocalFacilityInfo();
26
+ const mutateFacility = useCallback(async () => {
27
+ const defaultFacility = await mutateLocalFacility();
28
+ const shaFacility = await mutateShafacility();
29
+ return {
30
+ shaFacility,
31
+ defaultFacility,
32
+ };
33
+ }, [mutateLocalFacility, mutateShafacility]);
34
+ const shaStatus = useMemo(
35
+ () =>
36
+ shaFacility?.operationalStatus
37
+ ? `${shaFacility.operationalStatus.at(0).toUpperCase()}${shaFacility.operationalStatus
38
+ .slice(1)
39
+ ?.toLocaleLowerCase()}`
40
+ : undefined,
41
+ [shaFacility],
42
+ );
43
+ const shaExpiry = useMemo(
44
+ () =>
45
+ shaFacility?.shaFacilityExpiryDate && dayjs(shaFacility.shaFacilityExpiryDate).isValid()
46
+ ? formatDate(parseDate(shaFacility.shaFacilityExpiryDate))
47
+ : undefined,
48
+ [shaFacility],
49
+ );
50
+
51
+ const synchronizeFacilityData = useCallback(async () => {
52
+ try {
53
+ setshouldSynchronize(true);
54
+ const { shaFacility } = await mutateFacility();
55
+ showSnackbar({
56
+ title: t('syncingHieSuccess', 'Synchronization Complete'),
57
+ kind: 'success',
58
+ isLowContrast: true,
59
+ });
60
+ if (shaFacility.data?.source != 'HIE') {
61
+ showSnackbar({
62
+ kind: 'warning',
63
+ title: 'HIE Sync Failed. Pulling local info.',
64
+ isLowContrast: true,
65
+ });
66
+ }
67
+ // sync packages and intervensions
68
+ await syncPackagesAndInterventions();
69
+ } catch (error) {
70
+ const errorMessage =
71
+ error?.responseBody?.error?.message ??
72
+ t('hieSynchronizationError', 'An error occurred while synchronizing with HIE');
73
+ showSnackbar({
74
+ title: t('syncingHieError', 'Syncing with HIE Failed'),
75
+ subtitle: errorMessage,
76
+ kind: 'error',
77
+ isLowContrast: true,
78
+ });
79
+ }
80
+ }, [mutateFacility, t]);
81
+
82
+ return (
83
+ <div className={styles.facilityInfoContainer}>
84
+ <div>
85
+ <Layer className={styles.btnLayer}>
86
+ {isShaFacilityLoading || isLocalFacilityLoading ? (
87
+ <InlineLoading
88
+ description={t('synchronizingFacilityData', 'Please wait, Synchronizing Info.')}
89
+ className={styles.loading}
90
+ />
91
+ ) : (
92
+ <Button kind="primary" onClick={synchronizeFacilityData}>
93
+ {t('synchronizeWithHie', 'Synchronize with HIE')}
94
+ </Button>
95
+ )}
96
+ </Layer>
97
+ </div>
98
+
99
+ <Grid narrow>
100
+ {/* General Info Column */}
101
+ <Column sm={4} md={4} lg={8}>
102
+ <Tile className={styles.card}>
103
+ <h3 className={styles.cardTitle}>{t('generalInformation', 'General Information')}</h3>
104
+ <hr className={styles.cardDivider} />
105
+ <div className={styles.cardContent}>
106
+ <Card label={t('facilityName', 'Facility Name')} value={localFacility?.display} />
107
+ <Card label={t('facilityCode', 'Facility KMHFR Code')} value={shaFacility?.mflCode} />
108
+ <Card label={t('kephLevel', 'Keph Level')} value={shaFacility?.kephLevel} />
109
+ <br />
110
+ <br />
111
+ </div>
112
+ </Tile>
113
+ </Column>
114
+
115
+ {/* SHA Info Column */}
116
+ <Column sm={4} md={4} lg={8}>
117
+ <Layer>
118
+ <Tile className={styles.card}>
119
+ <h3 className={styles.cardTitle}>{t('facilityInformation', 'Facility Information')}</h3>
120
+ <hr className={styles.cardDivider} />
121
+ <div className={styles.cardContent}>
122
+ <Card
123
+ label={t('facilityRegistryCode', 'Facility Registry Code')}
124
+ value={shaFacility?.facilityRegistryCode}
125
+ />
126
+ <Card
127
+ label={t('shalicenceNumber', 'SHA License Number')}
128
+ value={shaFacility?.shaFacilityLicenseNumber}
129
+ />
130
+ <Card label={t('shaStatus', 'SHA Status')} value={shaStatus} />
131
+ <Card label={t('shaContracted', 'SHA Contracted')} value={shaFacility?.approved} />
132
+ <Card label={t('shaContractExpiry', 'SHA Contract Expiry Date')} value={shaExpiry} />
133
+ </div>
134
+ </Tile>
135
+ </Layer>
136
+ </Column>
137
+ </Grid>
138
+ </div>
139
+ );
140
+ };
141
+
142
+ export default FacilityInfo;