@digihmis/esm-administration-app 1.0.0

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 (206) hide show
  1. package/README.md +4 -0
  2. package/dist/101.js +1 -0
  3. package/dist/101.js.map +1 -0
  4. package/dist/117.js +1 -0
  5. package/dist/117.js.map +1 -0
  6. package/dist/132.js +1 -0
  7. package/dist/132.js.map +1 -0
  8. package/dist/15.js +1 -0
  9. package/dist/15.js.map +1 -0
  10. package/dist/152.js +1 -0
  11. package/dist/152.js.map +1 -0
  12. package/dist/153.js +1 -0
  13. package/dist/153.js.map +1 -0
  14. package/dist/190.js +1 -0
  15. package/dist/190.js.map +1 -0
  16. package/dist/209.js +1 -0
  17. package/dist/209.js.map +1 -0
  18. package/dist/266.js +1 -0
  19. package/dist/266.js.map +1 -0
  20. package/dist/317.js +1 -0
  21. package/dist/317.js.map +1 -0
  22. package/dist/349.js +1 -0
  23. package/dist/349.js.map +1 -0
  24. package/dist/371.js +1 -0
  25. package/dist/371.js.map +1 -0
  26. package/dist/378.js +1 -0
  27. package/dist/378.js.map +1 -0
  28. package/dist/394.js +1 -0
  29. package/dist/394.js.map +1 -0
  30. package/dist/442.js +1 -0
  31. package/dist/442.js.map +1 -0
  32. package/dist/45.js +1 -0
  33. package/dist/45.js.map +1 -0
  34. package/dist/454.js +1 -0
  35. package/dist/454.js.map +1 -0
  36. package/dist/466.js +1 -0
  37. package/dist/466.js.map +1 -0
  38. package/dist/482.js +1 -0
  39. package/dist/482.js.map +1 -0
  40. package/dist/501.js +1 -0
  41. package/dist/501.js.map +1 -0
  42. package/dist/508.js +1 -0
  43. package/dist/508.js.map +1 -0
  44. package/dist/578.js +1 -0
  45. package/dist/578.js.map +1 -0
  46. package/dist/598.js +1 -0
  47. package/dist/598.js.map +1 -0
  48. package/dist/606.js +1 -0
  49. package/dist/606.js.map +1 -0
  50. package/dist/61.js +1 -0
  51. package/dist/61.js.map +1 -0
  52. package/dist/640.js +1 -0
  53. package/dist/640.js.map +1 -0
  54. package/dist/685.js +1 -0
  55. package/dist/685.js.map +1 -0
  56. package/dist/689.js +1 -0
  57. package/dist/689.js.map +1 -0
  58. package/dist/709.js +1 -0
  59. package/dist/709.js.map +1 -0
  60. package/dist/712.js +1 -0
  61. package/dist/712.js.map +1 -0
  62. package/dist/720.js +1 -0
  63. package/dist/720.js.map +1 -0
  64. package/dist/737.js +12 -0
  65. package/dist/737.js.map +1 -0
  66. package/dist/742.js +1 -0
  67. package/dist/742.js.map +1 -0
  68. package/dist/747.js +1 -0
  69. package/dist/747.js.map +1 -0
  70. package/dist/771.js +1 -0
  71. package/dist/771.js.map +1 -0
  72. package/dist/806.js +1 -0
  73. package/dist/806.js.map +1 -0
  74. package/dist/912.js +1 -0
  75. package/dist/912.js.map +1 -0
  76. package/dist/913.js +1 -0
  77. package/dist/913.js.map +1 -0
  78. package/dist/916.js +15 -0
  79. package/dist/916.js.map +1 -0
  80. package/dist/940.js +1 -0
  81. package/dist/940.js.map +1 -0
  82. package/dist/digihmis-esm-administration-app.js +6 -0
  83. package/dist/digihmis-esm-administration-app.js.buildmanifest.json +1100 -0
  84. package/dist/digihmis-esm-administration-app.js.map +1 -0
  85. package/dist/main.js +17 -0
  86. package/dist/main.js.map +1 -0
  87. package/dist/routes.json +1 -0
  88. package/increment-version.js +17 -0
  89. package/jest.config.js +26 -0
  90. package/package.json +51 -0
  91. package/release-version.js +16 -0
  92. package/rspack.config.js +1 -0
  93. package/src/bed-management/bed-management.component.tsx +19 -0
  94. package/src/bed-management/bed-management.scss +0 -0
  95. package/src/billing-management/billing-charge-items/billing-charge-items-modal/billing-charge-items.modal.tsx +79 -0
  96. package/src/billing-management/billing-charge-items/billing-charge-items-modal/billing-charge-items.resource.ts +11 -0
  97. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-item.workspace.tsx +212 -0
  98. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-items.resource.ts +51 -0
  99. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-service.workspace.tsx +296 -0
  100. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/concept-search.component.tsx +67 -0
  101. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/price.component.tsx +81 -0
  102. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/stock-search.component.tsx +107 -0
  103. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/global/billing-charge-commodity.scss +61 -0
  104. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/global/billing-charge-items.scss +59 -0
  105. package/src/billing-management/billing-charge-items/billing-charge-items-workspace/schemas/form-schemas.ts +33 -0
  106. package/src/billing-management/billing-charge-items/billing-charge-items.component.tsx +222 -0
  107. package/src/billing-management/billing-charge-items/billing-charge-items.resource.ts +20 -0
  108. package/src/billing-management/billing-charge-items/billing-charge-items.scss +84 -0
  109. package/src/billing-management/billing-charge-items/type/index.ts +88 -0
  110. package/src/billing-management/billing-charge-items/utils/index.ts +96 -0
  111. package/src/billing-management/billing-management.component.tsx +21 -0
  112. package/src/billing-management/billing-management.scss +0 -0
  113. package/src/billing-management/billing-payment-modes/billing-payment-modes-modal/billing-delete-payment-modes.modal.scss +28 -0
  114. package/src/billing-management/billing-payment-modes/billing-payment-modes-modal/billing-delete-payment-modes.modal.tsx +67 -0
  115. package/src/billing-management/billing-payment-modes/billing-payment-modes-modal/delete-payment-mode.test.tsx +56 -0
  116. package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes-form-schema.ts +52 -0
  117. package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.resource.ts +35 -0
  118. package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.workspace.scss +33 -0
  119. package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.workspace.test.tsx +239 -0
  120. package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/billing-payment-modes.workspace.tsx +218 -0
  121. package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/payment-attributes/payment-mode-attributes.component.tsx +156 -0
  122. package/src/billing-management/billing-payment-modes/billing-payment-modes-workspace/payment-attributes/payment-mode-attributes.scss +10 -0
  123. package/src/billing-management/billing-payment-modes/billing-payment-modes.component.tsx +243 -0
  124. package/src/billing-management/billing-payment-modes/billing-payment-modes.resource.ts +23 -0
  125. package/src/billing-management/billing-payment-modes/billing-payment-modes.scss +140 -0
  126. package/src/billing-management/billing-payment-modes/type/index.ts +42 -0
  127. package/src/billing-management/billing-payment-points/billing-payment-point-modal/billing-delete-payment-modes.modal.scss +28 -0
  128. package/src/billing-management/billing-payment-points/billing-payment-point-modal/billing-delete-payment-point.modal.tsx +72 -0
  129. package/src/billing-management/billing-payment-points/billing-payment-point-modal/delete-payment-point.test.tsx +56 -0
  130. package/src/billing-management/billing-payment-points/billing-payment-point.component.tsx +202 -0
  131. package/src/billing-management/billing-payment-points/billing-payment-point.resource.ts +16 -0
  132. package/src/billing-management/billing-payment-points/billing-payment-point.scss +0 -0
  133. package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-point.resource.ts +39 -0
  134. package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-point.workspace.scss +33 -0
  135. package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-points.workspace.tsx +194 -0
  136. package/src/billing-management/billing-payment-points/type/index.ts +35 -0
  137. package/src/billing-management/billing-tabs/billing-tabs.component.scss +15 -0
  138. package/src/billing-management/billing-tabs/billing-tabs.component.tsx +36 -0
  139. package/src/components/custom-icon-wrapper/custom-icon-wrapper.component.tsx +17 -0
  140. package/src/components/custom-page-header/custom-page-header.component.tsx +38 -0
  141. package/src/components/custom-page-header/custom-page-header.scss +76 -0
  142. package/src/config-schema.ts +142 -0
  143. package/src/constant/index.ts +1 -0
  144. package/src/dashboard.meta.ts +47 -0
  145. package/src/declarations.d.ts +6 -0
  146. package/src/form-builder/form-builder.component.tsx +19 -0
  147. package/src/form-builder/form-builder.scss +0 -0
  148. package/src/hooks/useConcept.ts +13 -0
  149. package/src/hooks/useQueues.ts +22 -0
  150. package/src/hooks/useSystemSetting.ts +18 -0
  151. package/src/index.ts +133 -0
  152. package/src/left-panel/left-panel.component.tsx +49 -0
  153. package/src/left-panel/left-panel.scss +13 -0
  154. package/src/location-management/components/auto-suggest/autosuggest.component.tsx +149 -0
  155. package/src/location-management/components/auto-suggest/autosuggest.scss +61 -0
  156. package/src/location-management/components/auto-suggest/location-autosuggest.component.tsx +97 -0
  157. package/src/location-management/components/auto-suggest/location-autosuggest.scss +48 -0
  158. package/src/location-management/components/custom-results-tile/results-tile.component.tsx +43 -0
  159. package/src/location-management/components/custom-results-tile/results-tile.scss +86 -0
  160. package/src/location-management/forms/add-location/add-location.workspace.scss +71 -0
  161. package/src/location-management/forms/add-location/add-location.workspace.tsx +199 -0
  162. package/src/location-management/forms/search-location/search-location.workspace.scss +71 -0
  163. package/src/location-management/forms/search-location/search-location.workspace.tsx +214 -0
  164. package/src/location-management/helpers/index.ts +33 -0
  165. package/src/location-management/hooks/UseFacilityLocations.ts +12 -0
  166. package/src/location-management/hooks/useLocation.ts +18 -0
  167. package/src/location-management/hooks/useLocationTags.ts +15 -0
  168. package/src/location-management/location-management-dashboard.component.tsx +21 -0
  169. package/src/location-management/location-management-dashboard.scss +3 -0
  170. package/src/location-management/tables/locations-table.component.tsx +242 -0
  171. package/src/location-management/tables/locations-table.resource.ts +26 -0
  172. package/src/location-management/tables/locations-table.scss +114 -0
  173. package/src/location-management/types/index.ts +120 -0
  174. package/src/queue-management/queue-management-dashboard.component.tsx +23 -0
  175. package/src/queue-management/queue-management-dashboard.scss +8 -0
  176. package/src/queue-management/queue-management-table/queue-management-table.component.tsx +296 -0
  177. package/src/queue-management/queue-management-table/queue-management-table.scss +78 -0
  178. package/src/queue-management/queue-management-table/queue-management.resource.ts +39 -0
  179. package/src/queue-management/queue-management-workspace/queue-room/queue-room-form.scss +56 -0
  180. package/src/queue-management/queue-management-workspace/queue-room/queue-room-form.test.tsx +82 -0
  181. package/src/queue-management/queue-management-workspace/queue-room/queue-room-form.workspace.tsx +169 -0
  182. package/src/queue-management/queue-management-workspace/queue-room/queue-room.resource.ts +20 -0
  183. package/src/queue-management/queue-management-workspace/queue-service/queue-service-form.scss +61 -0
  184. package/src/queue-management/queue-management-workspace/queue-service/queue-service-form.test.tsx +127 -0
  185. package/src/queue-management/queue-management-workspace/queue-service/queue-service-form.workspace.tsx +178 -0
  186. package/src/queue-management/queue-management-workspace/queue-service/queue-service.resource.ts +55 -0
  187. package/src/queue-management/queue-types/index.ts +33 -0
  188. package/src/routes.json +157 -0
  189. package/src/setup-tests.ts +1 -0
  190. package/src/system-info/system-info.component.tsx +338 -0
  191. package/src/system-info/system-info.resource.ts +40 -0
  192. package/src/system-info/system-info.scss +622 -0
  193. package/src/type/index.ts +6 -0
  194. package/translations/am.json +111 -0
  195. package/translations/ar.json +115 -0
  196. package/translations/en.json +111 -0
  197. package/translations/es.json +112 -0
  198. package/translations/fr.json +112 -0
  199. package/translations/he.json +112 -0
  200. package/translations/km.json +110 -0
  201. package/translations/pt.json +112 -0
  202. package/translations/sw.json +111 -0
  203. package/translations/vi.json +110 -0
  204. package/translations/zh.json +110 -0
  205. package/tsconfig.json +5 -0
  206. package/version.js +27 -0
@@ -0,0 +1,157 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "backendDependencies": {
4
+ "fhir2": ">=1.2",
5
+ "webservices.rest": "^2.24.0"
6
+ },
7
+ "extensions": [
8
+ {
9
+ "name": "system-info-management-dashboard-link",
10
+ "slot": "administration-dashboard-slot",
11
+ "component": "systemInfoLeftPanelLink",
12
+ "meta": {
13
+ "name": "system-info-management",
14
+ "slot": "system-info-management-homepage-dashboard-slot",
15
+ "title": "System Info"
16
+ }
17
+ },
18
+ {
19
+ "name": "queue-management-dashboard-link",
20
+ "slot": "administration-dashboard-slot",
21
+ "component": "queueLeftPanelLink",
22
+ "meta": {
23
+ "name": "queue-management",
24
+ "slot": "queue-management-homepage-dashboard-slot",
25
+ "title": "Queue Management"
26
+ }
27
+ },
28
+ {
29
+ "name": "location-management-dashboard-link",
30
+ "slot": "administration-dashboard-slot",
31
+ "component": "locationLeftPanelLink",
32
+ "meta": {
33
+ "name": "location-management",
34
+ "slot": "location-management-homepage-dashboard-slot",
35
+ "title": "Location Management"
36
+ }
37
+ },
38
+ {
39
+ "name": "bed-management-dashboard-link",
40
+ "slot": "administration-dashboard-slot",
41
+ "component": "bedLeftPanelLink",
42
+ "meta": {
43
+ "name": "bed-management",
44
+ "slot": "bed-management-homepage-dashboard-slot",
45
+ "title": "Bed Management"
46
+ }
47
+ },
48
+ {
49
+ "name": "form-builder-dashboard-link",
50
+ "slot": "administration-dashboard-slot",
51
+ "component": "formBuilderLeftPanelLink",
52
+ "meta": {
53
+ "name": "form-builder",
54
+ "slot": "form-builder-homepage-dashboard-slot",
55
+ "title": "Form Builder"
56
+ }
57
+ },
58
+ {
59
+ "name": "billing-management-dashboard-link",
60
+ "slot": "administration-dashboard-slot",
61
+ "component": "billingLeftPanelLink",
62
+ "meta": {
63
+ "name": "billing-management",
64
+ "slot": "billing-management-homepage-dashboard-slot",
65
+ "title": "Billing Management"
66
+ }
67
+ },
68
+ {
69
+ "name": "queue-management-dashboard-content",
70
+ "slot": "queue-management-homepage-dashboard-slot",
71
+ "component": "queueManagementDashboard"
72
+ },
73
+ {
74
+ "name": "location-management-dashboard-content",
75
+ "slot": "location-management-homepage-dashboard-slot",
76
+ "component": "locationManagementDashboard"
77
+ },
78
+ {
79
+ "name": "bed-management-dashboard-content",
80
+ "slot": "bed-management-homepage-dashboard-slot",
81
+ "component": "bedManagementDashboard"
82
+ },
83
+ {
84
+ "name": "form-builder-dashboard-content",
85
+ "slot": "form-builder-homepage-dashboard-slot",
86
+ "component": "formBuilderDashboard"
87
+ },
88
+ {
89
+ "name": "billing-management-dashboard-content",
90
+ "slot": "billing-management-homepage-dashboard-slot",
91
+ "component": "billingManagementDashboard"
92
+ },
93
+ {
94
+ "name": "system-info-management-dashboard-content",
95
+ "slot": "system-info-management-homepage-dashboard-slot",
96
+ "component": "systemInfoDashboard"
97
+ }
98
+ ],
99
+ "workspaces": [
100
+ {
101
+ "name": "service-queues-service-form",
102
+ "title": "addNewQueueService",
103
+ "component": "addNewQueueServiceWorkspace",
104
+ "type": "service-queues"
105
+ },
106
+ {
107
+ "name": "service-queues-room-workspace",
108
+ "title": "addNewQueueServiceRoom",
109
+ "component": "addNewQueueServiceRoomWorkspace",
110
+ "type": "service-queues"
111
+ },{
112
+ "name": "add-location-workspace",
113
+ "title": "addNewLocationWorkspace",
114
+ "component": "addNewLocationWorkspace",
115
+ "type": "location-management"
116
+ },{
117
+ "name": "search-location-workspace",
118
+ "title": "searchLocationWorkspace",
119
+ "component": "searchLocationWorkspace",
120
+ "type": "location-management"
121
+ },{
122
+ "name": "payment-mode-workspace",
123
+ "title": "paymentModeWorkspace",
124
+ "component": "paymentModeWorkspace",
125
+ "type": "billing-management"
126
+ },{
127
+ "name": "payment-point-workspace",
128
+ "title": "paymentPointWorkspace",
129
+ "component": "paymentPointWorkspace",
130
+ "type": "billing-management"
131
+ },{
132
+ "name": "billing-charge-service-workspace",
133
+ "title": "addServiceItem",
134
+ "component": "billableServiceForm",
135
+ "type": "billing-management"
136
+ },{
137
+ "name": "billing-charge-item-workspace",
138
+ "title": "addCommodityItem",
139
+ "component": "commodityForm",
140
+ "type": "billing-management"
141
+ }
142
+ ],
143
+ "modals": [
144
+ {
145
+ "name": "delete-payment-mode-modal",
146
+ "component": "deletePaymentModeModal"
147
+ },
148
+ {
149
+ "name": "delete-payment-point-modal",
150
+ "component": "deletePaymentPointModal"
151
+ },
152
+ {
153
+ "name": "delete-billable-service-modal",
154
+ "component": "deleteBillableService"
155
+ }
156
+ ]
157
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/extend-expect';
@@ -0,0 +1,338 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { formatDatetime } from '@openmrs/esm-framework';
3
+ import { useTranslation } from 'react-i18next';
4
+ import styles from './system-info.scss';
5
+ import { useFrontendModules, useModules } from './system-info.resource';
6
+ import {
7
+ ChevronDown,
8
+ ChevronUp,
9
+ Package,
10
+ Search,
11
+ CheckmarkFilled,
12
+ WarningAlt,
13
+ ArrowUp,
14
+ Renew,
15
+ } from '@carbon/react/icons';
16
+ import { IconButton, InlineLoading, SkeletonText } from '@carbon/react';
17
+ const packageInfo = require('../../release-version.js');
18
+
19
+ const compareVersions = (v1: string, v2: string): number => {
20
+ const parts1 = v1.replace(/^v/, '').split('.').map(Number);
21
+ const parts2 = v2.replace(/^v/, '').split('.').map(Number);
22
+
23
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
24
+ const part1 = parts1[i] || 0;
25
+ const part2 = parts2[i] || 0;
26
+
27
+ if (part1 > part2) {
28
+ return 1;
29
+ }
30
+ if (part1 < part2) {
31
+ return -1;
32
+ }
33
+ }
34
+ return 0;
35
+ };
36
+
37
+ const SystemInfoDashboard: React.FC = () => {
38
+ const { t } = useTranslation();
39
+ const { modules, isLoading } = useModules();
40
+ const kenyaEMR = modules.find(({ uuid }) => uuid === 'kenyaemr');
41
+ const { VERSION } = packageInfo;
42
+ const installedModules = useFrontendModules();
43
+
44
+ const [showModules, setShowModules] = useState(true);
45
+ const [searchTerm, setSearchTerm] = useState('');
46
+ const [moduleVersions, setModuleVersions] = useState<Record<string, any>>({});
47
+ const [isCheckingVersions, setIsCheckingVersions] = useState(false);
48
+ const [hasCheckedOnMount, setHasCheckedOnMount] = useState(false);
49
+
50
+ const checkModuleVersions = useCallback(async () => {
51
+ if (installedModules.length === 0) {
52
+ return;
53
+ }
54
+
55
+ setIsCheckingVersions(true);
56
+ const versions: Record<string, any> = {};
57
+
58
+ for (const module of installedModules) {
59
+ try {
60
+ const packageName = module.fullName || module.name;
61
+
62
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
63
+ signal: AbortSignal.timeout(5000),
64
+ });
65
+
66
+ if (response.ok) {
67
+ const data = await response.json();
68
+ versions[module.name] = {
69
+ latest: data.version,
70
+ packageName: packageName,
71
+ error: null,
72
+ };
73
+ } else {
74
+ versions[module.name] = {
75
+ latest: null,
76
+ error: 'Not found in registry',
77
+ };
78
+ }
79
+ } catch (err) {
80
+ versions[module.name] = {
81
+ latest: null,
82
+ error: 'Unable to check',
83
+ };
84
+ }
85
+ await new Promise((resolve) => setTimeout(resolve, 50));
86
+ }
87
+
88
+ setModuleVersions(versions);
89
+ setIsCheckingVersions(false);
90
+ }, [installedModules]);
91
+
92
+ useEffect(() => {
93
+ if (installedModules.length > 0 && !hasCheckedOnMount) {
94
+ setHasCheckedOnMount(true);
95
+ checkModuleVersions();
96
+ }
97
+ }, [installedModules.length, hasCheckedOnMount, checkModuleVersions]);
98
+
99
+ const filteredModules = installedModules.filter((module) =>
100
+ module.name.toLowerCase().includes(searchTerm.toLowerCase()),
101
+ );
102
+
103
+ const getModuleStatus = (moduleName: string, currentVersion: string) => {
104
+ const versionInfo = moduleVersions[moduleName];
105
+
106
+ if (!versionInfo || !versionInfo.latest) {
107
+ return null;
108
+ }
109
+
110
+ const comparison = compareVersions(currentVersion, versionInfo.latest);
111
+
112
+ return { comparison, latestVersion: versionInfo.latest };
113
+ };
114
+
115
+ const updateStats = installedModules.reduce(
116
+ (acc, module) => {
117
+ const status = getModuleStatus(module.name, module.version);
118
+ if (status) {
119
+ if (status.comparison < 0) {
120
+ acc.outdated++;
121
+ } else if (status.comparison === 0) {
122
+ acc.upToDate++;
123
+ } else {
124
+ acc.ahead++;
125
+ }
126
+ }
127
+ return acc;
128
+ },
129
+ { outdated: 0, upToDate: 0, ahead: 0 },
130
+ );
131
+
132
+ const handleToggleModules = () => {
133
+ setShowModules(!showModules);
134
+ };
135
+
136
+ const handleKeyDownToggle = (e: React.KeyboardEvent) => {
137
+ if (e.key === 'Enter' || e.key === ' ') {
138
+ e.preventDefault();
139
+ handleToggleModules();
140
+ }
141
+ };
142
+
143
+ return (
144
+ <div className={styles.main}>
145
+ <div className={styles.aboutPage}>
146
+ <div className={styles.container}>
147
+ <div className={styles.title}>
148
+ <h3>{t('SystemInformation', 'System Information')}</h3>
149
+ </div>
150
+ <div className={styles.aboutBody}>
151
+ <p>DigiHMIS Version</p>
152
+ <p>{`v${kenyaEMR?.version}`}</p>
153
+ <p>SPA Version</p>
154
+ <p>{`v${VERSION.version}`}</p>
155
+ <p>{t('BuildDateTime', 'Build date time')}</p>
156
+ <p>{formatDatetime(new Date(VERSION.buildDate), { mode: 'standard' })}</p>
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ <div className={styles.aboutPage}>
162
+ <div className={styles.container}>
163
+ <div
164
+ className={styles.cardHeader}
165
+ onClick={handleToggleModules}
166
+ onKeyDown={handleKeyDownToggle}
167
+ role="button"
168
+ tabIndex={0}
169
+ aria-expanded={showModules}
170
+ aria-controls="module-list-content">
171
+ <div className={styles.cardHeaderContent}>
172
+ <Package size={20} />
173
+ <h3>Installed Frontend Modules</h3>
174
+ <span className={styles.badge}>{installedModules.length}</span>
175
+ {isCheckingVersions && <span className={styles.checkingStatus}>Checking for updates...</span>}
176
+ {!isCheckingVersions && updateStats.outdated > 0 && (
177
+ <span className={styles.updatesAvailable}>
178
+ {updateStats.outdated} update{updateStats.outdated !== 1 ? 's' : ''} available
179
+ </span>
180
+ )}
181
+ </div>
182
+ <div className={styles.cardHeaderActions}>
183
+ <IconButton
184
+ size="sm"
185
+ kind="ghost"
186
+ align="bottom"
187
+ label={t('checkForUpdates', 'Check for updates')}
188
+ disabled={isCheckingVersions}
189
+ onClick={(e) => {
190
+ e.stopPropagation();
191
+ checkModuleVersions();
192
+ }}>
193
+ {isCheckingVersions ? <InlineLoading /> : <Renew />}
194
+ </IconButton>
195
+ {showModules ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
196
+ </div>
197
+ </div>
198
+
199
+ {showModules && (
200
+ <div className={styles.cardBody} id="module-list-content">
201
+ <div className={styles.searchContainer}>
202
+ <Search size={16} className={styles.searchIcon} />
203
+ <input
204
+ type="text"
205
+ placeholder={t('searchModules', 'Search modules...')}
206
+ value={searchTerm}
207
+ onChange={(e) => setSearchTerm(e.target.value)}
208
+ className={styles.searchInput}
209
+ />
210
+ {searchTerm && (
211
+ <button
212
+ onClick={() => setSearchTerm('')}
213
+ className={styles.clearButton}
214
+ aria-label={t('clearSearch', 'Clear search')}>
215
+ ×
216
+ </button>
217
+ )}
218
+ </div>
219
+
220
+ <div className={styles.moduleList}>
221
+ {isCheckingVersions && Object.keys(moduleVersions).length === 0 ? (
222
+ Array.from({ length: Math.min(5, installedModules.length) }).map((_, index) => (
223
+ <div key={index} className={styles.moduleItem}>
224
+ <div className={styles.moduleInfo}>
225
+ <Package size={16} className={styles.moduleIcon} />
226
+ <SkeletonText width="60%" />
227
+ </div>
228
+ <div className={styles.statusIndicator}>
229
+ <SkeletonText width="50px" />
230
+ </div>
231
+ </div>
232
+ ))
233
+ ) : filteredModules.length > 0 ? (
234
+ filteredModules.map((module, index) => {
235
+ const status = getModuleStatus(module.name, module.version);
236
+ const isLoadingThisModule = isCheckingVersions && !moduleVersions[module.name];
237
+
238
+ return (
239
+ <div key={index} className={styles.moduleItem}>
240
+ <div className={styles.moduleInfo}>
241
+ <Package size={16} className={styles.moduleIcon} />
242
+ <span className={styles.moduleName}>{module.name}</span>
243
+ </div>
244
+ <div className={styles.statusIndicator}>
245
+ <span className={styles.moduleVersion}>v{module.version}</span>
246
+ {isLoadingThisModule ? (
247
+ <InlineLoading description="" />
248
+ ) : (
249
+ status && (
250
+ <>
251
+ {status.comparison === 0 && (
252
+ <CheckmarkFilled
253
+ size={16}
254
+ className={styles.statusIconUpToDate}
255
+ title={t('upToDate', 'Up to date')}
256
+ />
257
+ )}
258
+ {status.comparison < 0 && (
259
+ <div className={styles.statusIndicator}>
260
+ <ArrowUp
261
+ size={16}
262
+ className={styles.statusIconUpdate}
263
+ title={t('updateAvailable', 'Update available: v{{version}}', {
264
+ version: status.latestVersion,
265
+ })}
266
+ />
267
+ <span className={styles.latestVersion}>v{status.latestVersion}</span>
268
+ </div>
269
+ )}
270
+ {status.comparison > 0 && (
271
+ <WarningAlt
272
+ size={16}
273
+ className={styles.statusIconAhead}
274
+ title={t('aheadOfPublishedVersion', 'Ahead of published version')}
275
+ />
276
+ )}
277
+ </>
278
+ )
279
+ )}
280
+ </div>
281
+ </div>
282
+ );
283
+ })
284
+ ) : (
285
+ <div className={styles.emptyState}>
286
+ <Package size={32} />
287
+ <p>{t('noModulesFound', "No modules found matching '{{searchTerm}}'", { searchTerm })}</p>
288
+ </div>
289
+ )}
290
+ </div>
291
+
292
+ {searchTerm && filteredModules.length > 0 && (
293
+ <div className={styles.moduleSummary}>
294
+ {t('showingModules', 'Showing {{count}} of {{total}} modules', {
295
+ count: filteredModules.length,
296
+ total: installedModules.length,
297
+ })}
298
+ </div>
299
+ )}
300
+
301
+ {!searchTerm && !isCheckingVersions && Object.keys(moduleVersions).length > 0 && (
302
+ <div className={styles.moduleSummary}>
303
+ {updateStats.upToDate > 0 && `${updateStats.upToDate} up to date`}
304
+ {updateStats.upToDate > 0 && updateStats.outdated > 0 && ' • '}
305
+ {updateStats.outdated > 0 &&
306
+ `${updateStats.outdated} update${updateStats.outdated !== 1 ? 's' : ''} available`}
307
+ {(updateStats.upToDate > 0 || updateStats.outdated > 0) && updateStats.ahead > 0 && ' • '}
308
+ {updateStats.ahead > 0 && `${updateStats.ahead} ahead`}
309
+ </div>
310
+ )}
311
+ </div>
312
+ )}
313
+ </div>
314
+ </div>
315
+
316
+ <div className={styles.aboutPage}>
317
+ <div className={styles.container}>
318
+ <div className={styles.aboutBodyContact}>
319
+ <p>{t('AboutDigiHMIS', 'Made with ❤️ by the DigiHMIS Team')}</p>
320
+ <br />
321
+ <p>
322
+ {t('officialWebsite', 'Official Website:')} <a href="https://www.digihmis.org">www.digihmis.org</a>
323
+ </p>
324
+ <br />
325
+ <p>
326
+ {t('supportContact', 'For support, please contact us at')}{' '}
327
+ <a href="mailto:support@digihmis.org">support@digihmis.org</a>
328
+ </p>
329
+ <br />
330
+ <p>{t('telephoneSupport', 'Telephone Support:')} +254 707 000 000</p>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </div>
335
+ );
336
+ };
337
+
338
+ export default SystemInfoDashboard;
@@ -0,0 +1,40 @@
1
+ import { OpenmrsResource, openmrsFetch } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+ import useSWRImmutable from 'swr/immutable';
4
+ import { FrontendModule } from '../type';
5
+
6
+ interface Modules extends OpenmrsResource {
7
+ version: string;
8
+ name: string;
9
+ }
10
+
11
+ export function useModules() {
12
+ const { data, isLoading } = useSWRImmutable<{ data: { results: Array<Modules> } }>(
13
+ 'ws/rest/v1/module?v=custom:(uuid,name,version)',
14
+ openmrsFetch,
15
+ );
16
+
17
+ return { modules: data?.data?.results ?? [], isLoading };
18
+ }
19
+
20
+ export function useFrontendModules() {
21
+ return useMemo<Array<FrontendModule>>(() => {
22
+ return (window.installedModules ?? [])
23
+ .filter((module) => Boolean(module) && Boolean(module[1]))
24
+ .map((module) => {
25
+ const fullName = module[0];
26
+ const version = module[1].version;
27
+
28
+ const firstSlash = fullName.indexOf('/');
29
+ const orgName = firstSlash > 0 ? fullName.substring(0, firstSlash) : null;
30
+ const moduleName = firstSlash > 0 ? fullName.substring(firstSlash + 1) : fullName;
31
+
32
+ return {
33
+ version,
34
+ name: moduleName,
35
+ fullName,
36
+ org: orgName,
37
+ };
38
+ });
39
+ }, []);
40
+ }