@kenyaemr/esm-etims-app 5.4.5-pre.1109
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.
- package/.turbo/turbo-build.log +6 -0
- package/README.md +3 -0
- package/dist/1067.js +1 -0
- package/dist/1067.js.map +1 -0
- package/dist/1072.js +1 -0
- package/dist/1072.js.map +1 -0
- package/dist/1148.js +1 -0
- package/dist/1148.js.map +1 -0
- package/dist/1278.js +1 -0
- package/dist/1278.js.map +1 -0
- package/dist/1442.js +1 -0
- package/dist/1442.js.map +1 -0
- package/dist/15.js +1 -0
- package/dist/15.js.map +1 -0
- package/dist/1620.js +1 -0
- package/dist/1620.js.map +1 -0
- package/dist/1638.js +1 -0
- package/dist/1638.js.map +1 -0
- package/dist/1789.js +1 -0
- package/dist/1789.js.map +1 -0
- package/dist/1831.js +1 -0
- package/dist/1831.js.map +1 -0
- package/dist/1844.js +1 -0
- package/dist/1844.js.map +1 -0
- package/dist/1946.js +1 -0
- package/dist/1946.js.map +1 -0
- package/dist/2007.js +1 -0
- package/dist/2007.js.map +1 -0
- package/dist/2115.js +1 -0
- package/dist/2115.js.map +1 -0
- package/dist/2131.js +1 -0
- package/dist/2131.js.map +1 -0
- package/dist/2152.js +1 -0
- package/dist/2152.js.map +1 -0
- package/dist/2202.js +1 -0
- package/dist/2202.js.map +1 -0
- package/dist/2394.js +1 -0
- package/dist/2394.js.map +1 -0
- package/dist/2534.js +1 -0
- package/dist/2534.js.map +1 -0
- package/dist/2537.js +1 -0
- package/dist/2537.js.map +1 -0
- package/dist/2556.js +1 -0
- package/dist/2556.js.map +1 -0
- package/dist/2621.js +1 -0
- package/dist/2621.js.map +1 -0
- package/dist/2648.js +1 -0
- package/dist/2648.js.map +1 -0
- package/dist/2795.js +1 -0
- package/dist/2795.js.map +1 -0
- package/dist/2842.js +1 -0
- package/dist/2842.js.map +1 -0
- package/dist/2916.js +1 -0
- package/dist/2916.js.map +1 -0
- package/dist/2924.js +1 -0
- package/dist/2924.js.map +1 -0
- package/dist/2929.js +1 -0
- package/dist/2929.js.map +1 -0
- package/dist/2947.js +1 -0
- package/dist/2947.js.map +1 -0
- package/dist/2971.js +1 -0
- package/dist/2971.js.map +1 -0
- package/dist/3284.js +1 -0
- package/dist/3284.js.map +1 -0
- package/dist/3285.js +1 -0
- package/dist/3285.js.map +1 -0
- package/dist/3335.js +1 -0
- package/dist/3335.js.map +1 -0
- package/dist/335.js +1 -0
- package/dist/335.js.map +1 -0
- package/dist/3378.js +1 -0
- package/dist/3378.js.map +1 -0
- package/dist/338.js +1 -0
- package/dist/338.js.map +1 -0
- package/dist/3504.js +1 -0
- package/dist/3504.js.map +1 -0
- package/dist/3544.js +1 -0
- package/dist/3544.js.map +1 -0
- package/dist/356.js +1 -0
- package/dist/356.js.map +1 -0
- package/dist/3569.js +1 -0
- package/dist/3569.js.map +1 -0
- package/dist/3723.js +1 -0
- package/dist/3723.js.map +1 -0
- package/dist/3745.js +1 -0
- package/dist/3745.js.map +1 -0
- package/dist/3813.js +1 -0
- package/dist/3813.js.map +1 -0
- package/dist/393.js +6 -0
- package/dist/393.js.map +1 -0
- package/dist/3949.js +1 -0
- package/dist/3949.js.map +1 -0
- package/dist/3953.js +1 -0
- package/dist/3953.js.map +1 -0
- package/dist/3989.js +1 -0
- package/dist/3989.js.map +1 -0
- package/dist/4178.js +1 -0
- package/dist/4178.js.map +1 -0
- package/dist/4303.js +1 -0
- package/dist/4303.js.map +1 -0
- package/dist/4308.js +1 -0
- package/dist/4308.js.map +1 -0
- package/dist/4487.js +11 -0
- package/dist/4487.js.map +1 -0
- package/dist/4588.js +1 -0
- package/dist/4588.js.map +1 -0
- package/dist/4637.js +1 -0
- package/dist/4637.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/4817.js +1 -0
- package/dist/4817.js.map +1 -0
- package/dist/4818.js +1 -0
- package/dist/4818.js.map +1 -0
- package/dist/500.js +1 -0
- package/dist/500.js.map +1 -0
- package/dist/5086.js +1 -0
- package/dist/5086.js.map +1 -0
- package/dist/5117.js +1 -0
- package/dist/5117.js.map +1 -0
- package/dist/5159.js +1 -0
- package/dist/5159.js.map +1 -0
- package/dist/5285.js +1 -0
- package/dist/5285.js.map +1 -0
- package/dist/5361.js +1 -0
- package/dist/5361.js.map +1 -0
- package/dist/5366.js +1 -0
- package/dist/5366.js.map +1 -0
- package/dist/5484.js +1 -0
- package/dist/5484.js.map +1 -0
- package/dist/5598.js +1 -0
- package/dist/5598.js.map +1 -0
- package/dist/5676.js +1 -0
- package/dist/5676.js.map +1 -0
- package/dist/5694.js +1 -0
- package/dist/5694.js.map +1 -0
- package/dist/5731.js +5 -0
- package/dist/5731.js.map +1 -0
- package/dist/5743.js +1 -0
- package/dist/5743.js.map +1 -0
- package/dist/5825.js +1 -0
- package/dist/5825.js.map +1 -0
- package/dist/5932.js +1 -0
- package/dist/5932.js.map +1 -0
- package/dist/5941.js +1 -0
- package/dist/5941.js.map +1 -0
- package/dist/5967.js +1 -0
- package/dist/5967.js.map +1 -0
- package/dist/6104.js +1 -0
- package/dist/6104.js.map +1 -0
- package/dist/624.js +1 -0
- package/dist/624.js.map +1 -0
- package/dist/6259.js +1 -0
- package/dist/6259.js.map +1 -0
- package/dist/6273.js +1 -0
- package/dist/6273.js.map +1 -0
- package/dist/6337.js +1 -0
- package/dist/6337.js.map +1 -0
- package/dist/6427.js +1 -0
- package/dist/6427.js.map +1 -0
- package/dist/647.js +1 -0
- package/dist/647.js.map +1 -0
- package/dist/6478.js +7 -0
- package/dist/6478.js.map +1 -0
- package/dist/6508.js +1 -0
- package/dist/6508.js.map +1 -0
- package/dist/6567.js +1 -0
- package/dist/6567.js.map +1 -0
- package/dist/6634.js +1 -0
- package/dist/6634.js.map +1 -0
- package/dist/6868.js +1 -0
- package/dist/6868.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/7022.js +1 -0
- package/dist/7022.js.map +1 -0
- package/dist/7101.js +1 -0
- package/dist/7101.js.map +1 -0
- package/dist/7209.js +1 -0
- package/dist/7209.js.map +1 -0
- package/dist/7293.js +1 -0
- package/dist/7293.js.map +1 -0
- package/dist/7360.js +1 -0
- package/dist/7360.js.map +1 -0
- package/dist/7453.js +1 -0
- package/dist/7453.js.map +1 -0
- package/dist/7478.js +1 -0
- package/dist/7478.js.map +1 -0
- package/dist/7782.js +1 -0
- package/dist/7782.js.map +1 -0
- package/dist/7808.js +1 -0
- package/dist/7808.js.map +1 -0
- package/dist/7876.js +1 -0
- package/dist/7876.js.map +1 -0
- package/dist/8028.js +1 -0
- package/dist/8028.js.map +1 -0
- package/dist/8196.js +1 -0
- package/dist/8196.js.map +1 -0
- package/dist/820.js +1 -0
- package/dist/820.js.map +1 -0
- package/dist/8240.js +1 -0
- package/dist/8240.js.map +1 -0
- package/dist/8458.js +1 -0
- package/dist/8458.js.map +1 -0
- package/dist/8472.js +1 -0
- package/dist/8472.js.map +1 -0
- package/dist/8602.js +1 -0
- package/dist/8602.js.map +1 -0
- package/dist/8607.js +1 -0
- package/dist/8607.js.map +1 -0
- package/dist/8640.js +1 -0
- package/dist/8640.js.map +1 -0
- package/dist/8762.js +1 -0
- package/dist/8762.js.map +1 -0
- package/dist/9060.js +1 -0
- package/dist/9060.js.map +1 -0
- package/dist/9061.js +1 -0
- package/dist/9061.js.map +1 -0
- package/dist/9087.js +1 -0
- package/dist/9087.js.map +1 -0
- package/dist/9168.js +1 -0
- package/dist/9168.js.map +1 -0
- package/dist/9213.js +1 -0
- package/dist/9213.js.map +1 -0
- package/dist/9286.js +1 -0
- package/dist/9286.js.map +1 -0
- package/dist/9425.js +1 -0
- package/dist/9520.js +1 -0
- package/dist/9520.js.map +1 -0
- package/dist/9547.js +1 -0
- package/dist/9547.js.map +1 -0
- package/dist/9612.js +1 -0
- package/dist/9612.js.map +1 -0
- package/dist/9632.js +1 -0
- package/dist/9632.js.map +1 -0
- package/dist/9771.js +1 -0
- package/dist/9771.js.map +1 -0
- package/dist/9774.js +1 -0
- package/dist/9774.js.map +1 -0
- package/dist/9806.js +1 -0
- package/dist/9806.js.map +1 -0
- package/dist/9885.js +1 -0
- package/dist/9885.js.map +1 -0
- package/dist/992.js +1 -0
- package/dist/992.js.map +1 -0
- package/dist/kenyaemr-esm-etims-app.js +3 -0
- package/dist/kenyaemr-esm-etims-app.js.buildmanifest.json +3354 -0
- package/dist/kenyaemr-esm-etims-app.js.map +1 -0
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/package.json +48 -0
- package/rspack.config.js +1 -0
- package/src/components/header/header.component.tsx +22 -0
- package/src/components/header/header.scss +6 -0
- package/src/components/side-menu/left-panel.component.tsx +22 -0
- package/src/components/side-menu/left-panel.scss +34 -0
- package/src/components/user-menu-link/etims-nav-link.scss +29 -0
- package/src/components/user-menu-link/user-menu-link.component.tsx +25 -0
- package/src/config-schema.ts +1 -0
- package/src/credit-note/etims-credit-note-dashboard.component.tsx +23 -0
- package/src/credit-note/etims-credit-note-dashboard.scss +8 -0
- package/src/credit-note/etims-credit-note-table.component.tsx +284 -0
- package/src/credit-note/etims-credit-note-table.scss +16 -0
- package/src/credit-note/etims-credit-note.resource.ts +96 -0
- package/src/credit-note/etims-credit-note.types.ts +56 -0
- package/src/credit-note/etims-credit-note.workspace.scss +76 -0
- package/src/credit-note/etims-credit-note.workspace.tsx +267 -0
- package/src/declarations.d.ts +2 -0
- package/src/global-properties/etims-global-properties-dashboard.component.tsx +141 -0
- package/src/global-properties/etims-global-properties-dashboard.scss +62 -0
- package/src/global-properties/etims-global-properties-form.workspace.scss +74 -0
- package/src/global-properties/etims-global-properties-form.workspace.tsx +244 -0
- package/src/global-properties/etims-global-properties.resource.ts +60 -0
- package/src/index.ts +100 -0
- package/src/item-mapping/etims-item-mapping-dashboard.component.tsx +29 -0
- package/src/item-mapping/etims-item-mapping-dashboard.scss +42 -0
- package/src/item-mapping/etims-item-mapping-form.fields.tsx +136 -0
- package/src/item-mapping/etims-item-mapping-form.schema.ts +35 -0
- package/src/item-mapping/etims-item-mapping-form.workspace.scss +54 -0
- package/src/item-mapping/etims-item-mapping-form.workspace.tsx +555 -0
- package/src/item-mapping/etims-item-mapping-table.component.tsx +398 -0
- package/src/item-mapping/etims-item-mapping-table.scss +20 -0
- package/src/item-mapping/etims-item-mapping.resource.ts +318 -0
- package/src/item-mapping/etims-item-mapping.types.ts +92 -0
- package/src/item-mapping/register-item-mapping.modal.tsx +113 -0
- package/src/item-mapping/void-item-mapping.modal.tsx +80 -0
- package/src/left-panel-link.component.tsx +39 -0
- package/src/left-panel-link.scss +12 -0
- package/src/reference-data/etims-catalog-table.component.tsx +178 -0
- package/src/reference-data/etims-catalog-table.scss +16 -0
- package/src/reference-data/etims-code-table.component.tsx +87 -0
- package/src/reference-data/etims-item-class-table.component.tsx +86 -0
- package/src/reference-data/etims-reference-data-dashboard.component.tsx +38 -0
- package/src/reference-data/etims-reference-data-dashboard.scss +11 -0
- package/src/reference-data/etims-reference-data.resource.ts +40 -0
- package/src/reference-data/etims-reference-data.types.ts +38 -0
- package/src/reference-data/etims-sync-meta.modal.tsx +62 -0
- package/src/root.component.tsx +38 -0
- package/src/root.scss +11 -0
- package/src/routes.json +117 -0
- package/src/status/etims-status-dashboard.component.tsx +212 -0
- package/src/status/etims-status-dashboard.scss +116 -0
- package/src/status/etims-status.resource.ts +20 -0
- package/src/status/etims-status.types.ts +30 -0
- package/src/transaction/confirm-transaction-action.modal.tsx +113 -0
- package/src/transaction/etims-transaction-dashboard.component.tsx +23 -0
- package/src/transaction/etims-transaction-dashboard.scss +8 -0
- package/src/transaction/etims-transaction-details.workspace.scss +76 -0
- package/src/transaction/etims-transaction-details.workspace.tsx +201 -0
- package/src/transaction/etims-transaction-table.component.tsx +450 -0
- package/src/transaction/etims-transaction-table.scss +20 -0
- package/src/transaction/etims-transaction.resource.ts +180 -0
- package/src/transaction/etims-transaction.types.ts +78 -0
- package/src/transaction/override-reconciliation.modal.scss +7 -0
- package/src/transaction/override-reconciliation.modal.tsx +203 -0
- package/translations/am.json +129 -0
- package/translations/en.json +129 -0
- package/translations/fr.json +130 -0
- package/translations/sw.json +129 -0
- package/tsconfig.json +5 -0
package/src/routes.json
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
3
|
+
"backendDependencies": {
|
|
4
|
+
"kenyaemr.cashier": "^4.2.8-SNAPSHOT"
|
|
5
|
+
},
|
|
6
|
+
"pages": [
|
|
7
|
+
{
|
|
8
|
+
"component": "root",
|
|
9
|
+
"route": "etims",
|
|
10
|
+
"online": true,
|
|
11
|
+
"offline": false
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"extensions": [
|
|
15
|
+
{
|
|
16
|
+
"component": "etimsLeftPanelLink",
|
|
17
|
+
"name": "etims-left-panel-link",
|
|
18
|
+
"slot": "etims-left-panel-slot"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"component": "etimsReferenceDataLeftPanelLink",
|
|
22
|
+
"name": "etims-reference-data-left-panel-link",
|
|
23
|
+
"slot": "etims-left-panel-slot"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"component": "etimsItemMappingLeftPanelLink",
|
|
27
|
+
"name": "etims-item-mapping-left-panel-link",
|
|
28
|
+
"slot": "etims-left-panel-slot"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"component": "etimsTransactionLeftPanelLink",
|
|
32
|
+
"name": "etims-transaction-left-panel-link",
|
|
33
|
+
"slot": "etims-left-panel-slot"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"component": "etimsCreditNoteLeftPanelLink",
|
|
37
|
+
"name": "etims-credit-note-left-panel-link",
|
|
38
|
+
"slot": "etims-left-panel-slot"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"component": "etimsGlobalPropertiesLeftPanelLink",
|
|
42
|
+
"name": "etims-global-properties-left-panel-link",
|
|
43
|
+
"slot": "etims-left-panel-slot"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "etims-nav-link",
|
|
47
|
+
"slot": "navbar-items-slot",
|
|
48
|
+
"component": "etimsNavLink",
|
|
49
|
+
"order": 0,
|
|
50
|
+
"meta": {
|
|
51
|
+
"name": "etims",
|
|
52
|
+
"title": "eTims",
|
|
53
|
+
"search": ["etims", "eTims"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"modals": [
|
|
58
|
+
{
|
|
59
|
+
"name": "etims-void-item-mapping-modal",
|
|
60
|
+
"component": "etimsVoidItemMappingModal"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "etims-register-item-mapping-modal",
|
|
64
|
+
"component": "etimsRegisterItemMappingModal"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "etims-confirm-transaction-action-modal",
|
|
68
|
+
"component": "etimsConfirmTransactionActionModal"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "etims-override-reconciliation-modal",
|
|
72
|
+
"component": "etimsOverrideReconciliationModal"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name":"etims-kra-meta-data-sync-modal",
|
|
76
|
+
"component":"etimsSyncMetaDataModal"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"workspaces2": [
|
|
80
|
+
{
|
|
81
|
+
"name": "etims-global-properties-form",
|
|
82
|
+
"component": "etimsGlobalPropertiesForm",
|
|
83
|
+
"window": "etims-workspace-window"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "etims-item-mapping-form",
|
|
87
|
+
"component": "etimsItemMappingForm",
|
|
88
|
+
"window": "etims-workspace-window"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "etims-transaction-details",
|
|
92
|
+
"component": "etimsTransactionDetails",
|
|
93
|
+
"window": "etims-workspace-window"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "etims-credit-note-form",
|
|
97
|
+
"component": "etimsCreditNoteForm",
|
|
98
|
+
"window": "etims-workspace-window"
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"workspaceWindows2": [
|
|
102
|
+
{
|
|
103
|
+
"name": "etims-workspace-window",
|
|
104
|
+
"canMaximize": true,
|
|
105
|
+
"group": "etims-workspace-group",
|
|
106
|
+
"width": "wider"
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"workspaceGroups2": [
|
|
110
|
+
{
|
|
111
|
+
"name": "etims-workspace-group",
|
|
112
|
+
"scopePattern": "/etims",
|
|
113
|
+
"closeable": true,
|
|
114
|
+
"overlay": true
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Button, InlineLoading, Layer, SkeletonPlaceholder, Tag, Tile } from '@carbon/react';
|
|
3
|
+
import { Renew } from '@carbon/react/icons';
|
|
4
|
+
import { ErrorState, formatDatetime, parseDate, UserHasAccess } from '@openmrs/esm-framework';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import Header from '../components/header/header.component';
|
|
7
|
+
import { useEtimsStatus } from './etims-status.resource';
|
|
8
|
+
import type { EtimsStatus, EtimsSyncState } from './etims-status.types';
|
|
9
|
+
import styles from './etims-status-dashboard.scss';
|
|
10
|
+
|
|
11
|
+
type StatusTagType = 'green' | 'red' | 'gray';
|
|
12
|
+
|
|
13
|
+
type StatusCard = {
|
|
14
|
+
key: string;
|
|
15
|
+
label: string;
|
|
16
|
+
value: string;
|
|
17
|
+
tagType: StatusTagType;
|
|
18
|
+
meta?: string | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const formatNullableDatetime = (value?: string | null) => {
|
|
22
|
+
if (!value) {
|
|
23
|
+
return '--';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
return formatDatetime(parseDate(value), { mode: 'standard', noToday: true });
|
|
28
|
+
} catch {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const displayValue = (value?: string | number | null) => {
|
|
34
|
+
if (value === null || value === undefined || value === '') {
|
|
35
|
+
return '--';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return String(value);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const StatusMetricCard: React.FC<StatusCard> = ({ label, value, tagType, meta }) => (
|
|
42
|
+
<Layer>
|
|
43
|
+
<Tile className={styles.tile}>
|
|
44
|
+
<div className={styles.tileHeader}>
|
|
45
|
+
<span className={styles.tileLabel}>{label}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<Tag type={tagType} size="md">
|
|
48
|
+
{value}
|
|
49
|
+
</Tag>
|
|
50
|
+
{meta ? <p className={styles.tileMeta}>{meta}</p> : null}
|
|
51
|
+
</Tile>
|
|
52
|
+
</Layer>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const DetailRow: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
|
|
56
|
+
<div className={styles.detailRow}>
|
|
57
|
+
<span className={styles.detailLabel}>{label}</span>
|
|
58
|
+
<span className={styles.detailValue}>{value}</span>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const SyncStatusPanel: React.FC<{ title: string; sync: EtimsSyncState | null }> = ({ title, sync }) => {
|
|
63
|
+
const { t } = useTranslation();
|
|
64
|
+
const hasFailure = Boolean(sync?.lastFailureAt || sync?.lastFailureMessage);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<section className={styles.panel}>
|
|
68
|
+
<div className={styles.tileHeader}>
|
|
69
|
+
<h3 className={styles.panelTitle}>{title}</h3>
|
|
70
|
+
<Tag type={hasFailure ? 'red' : 'green'} size="sm">
|
|
71
|
+
{hasFailure ? t('syncHasFailures', 'Needs attention') : t('syncHealthy', 'Healthy')}
|
|
72
|
+
</Tag>
|
|
73
|
+
</div>
|
|
74
|
+
<div className={styles.detailList}>
|
|
75
|
+
<DetailRow label={t('syncCategory', 'Category')} value={displayValue(sync?.syncCategory)} />
|
|
76
|
+
<DetailRow
|
|
77
|
+
label={t('lastRequestDatetime', 'Last request')}
|
|
78
|
+
value={formatNullableDatetime(sync?.lastRequestDatetime)}
|
|
79
|
+
/>
|
|
80
|
+
<DetailRow label={t('lastSuccessAt', 'Last success')} value={formatNullableDatetime(sync?.lastSuccessAt)} />
|
|
81
|
+
<DetailRow label={t('lastFailureAt', 'Last failure')} value={formatNullableDatetime(sync?.lastFailureAt)} />
|
|
82
|
+
<DetailRow label={t('lastRecordCount', 'Last record count')} value={displayValue(sync?.lastRecordCount)} />
|
|
83
|
+
<DetailRow
|
|
84
|
+
label={t('lastFailureMessage', 'Last failure message')}
|
|
85
|
+
value={displayValue(sync?.lastFailureMessage)}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
</section>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const buildStatusCards = (status: EtimsStatus, t: (key: string, fallback: string) => string): StatusCard[] => [
|
|
93
|
+
{
|
|
94
|
+
key: 'enabled',
|
|
95
|
+
label: t('etimsEnabled', 'eTIMS enabled'),
|
|
96
|
+
value: status.enabled ? t('yes', 'Yes') : t('no', 'No'),
|
|
97
|
+
tagType: status.enabled ? 'green' : 'gray',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: 'gateway',
|
|
101
|
+
label: t('gatewayHealth', 'Gateway health'),
|
|
102
|
+
value: status.gatewayHealthy ? t('healthy', 'Healthy') : t('unhealthy', 'Unhealthy'),
|
|
103
|
+
tagType: status.gatewayHealthy ? 'green' : 'red',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: 'licence',
|
|
107
|
+
label: t('licenceStatus', 'Licence'),
|
|
108
|
+
value: status.licence?.valid ? t('valid', 'Valid') : t('invalid', 'Invalid'),
|
|
109
|
+
tagType: status.licence?.valid ? 'green' : 'red',
|
|
110
|
+
meta: status.licence?.resultMessage,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: 'apiKey',
|
|
114
|
+
label: t('apiKeyConfigured', 'API key configured'),
|
|
115
|
+
value: status.apiKeyConfigured ? t('yes', 'Yes') : t('no', 'No'),
|
|
116
|
+
tagType: status.apiKeyConfigured ? 'green' : 'red',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const EtimsStatusContent: React.FC = () => {
|
|
121
|
+
const { t } = useTranslation();
|
|
122
|
+
const { status, error, isLoading, isValidating, mutate } = useEtimsStatus();
|
|
123
|
+
|
|
124
|
+
const cards = useMemo(() => (status ? buildStatusCards(status, t) : []), [status, t]);
|
|
125
|
+
|
|
126
|
+
if (isLoading) {
|
|
127
|
+
return (
|
|
128
|
+
<div className={styles.dashboard}>
|
|
129
|
+
<InlineLoading status="active" description={t('loadingEtimsStatus', 'Loading eTIMS status...')} />
|
|
130
|
+
<div className={styles.cardsRow}>
|
|
131
|
+
{Array.from({ length: 4 }).map((_, index) => (
|
|
132
|
+
<SkeletonPlaceholder key={index} style={{ width: '100%', height: '7.5rem' }} />
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (error || !status) {
|
|
140
|
+
return (
|
|
141
|
+
<div className={styles.dashboard}>
|
|
142
|
+
<ErrorState error={error} headerTitle={t('etimsStatus', 'eTIMS status')} />
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className={styles.dashboard}>
|
|
149
|
+
<div className={styles.sectionHeader}>
|
|
150
|
+
<h2 className={styles.sectionTitle}>{t('connectionOverview', 'Connection overview')}</h2>
|
|
151
|
+
<Button
|
|
152
|
+
kind="ghost"
|
|
153
|
+
size="sm"
|
|
154
|
+
renderIcon={Renew}
|
|
155
|
+
onClick={() => mutate()}
|
|
156
|
+
disabled={isValidating}
|
|
157
|
+
iconDescription={t('refreshStatus', 'Refresh status')}>
|
|
158
|
+
{isValidating ? t('refreshing', 'Refreshing...') : t('refreshStatus', 'Refresh status')}
|
|
159
|
+
</Button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div className={styles.cardsRow}>
|
|
163
|
+
{cards.map((card) => (
|
|
164
|
+
<StatusMetricCard {...card} />
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<section className={styles.panel}>
|
|
169
|
+
<h3 className={styles.panelTitle}>{t('configuration', 'Configuration')}</h3>
|
|
170
|
+
<div className={styles.detailList}>
|
|
171
|
+
<DetailRow label={t('environment', 'Environment')} value={displayValue(status.environment)} />
|
|
172
|
+
<DetailRow label={t('baseUrl', 'Base URL')} value={displayValue(status.baseUrl)} />
|
|
173
|
+
<DetailRow label={t('businessPin', 'Business PIN')} value={displayValue(status.businessPin)} />
|
|
174
|
+
<DetailRow label={t('branchId', 'Branch ID')} value={displayValue(status.branchId)} />
|
|
175
|
+
<DetailRow label={t('contractVersion', 'Contract version')} value={displayValue(status.contractVersion)} />
|
|
176
|
+
<DetailRow
|
|
177
|
+
label={t('sandboxValidatedContractVersion', 'Sandbox validated contract version')}
|
|
178
|
+
value={displayValue(status.sandboxValidatedContractVersion)}
|
|
179
|
+
/>
|
|
180
|
+
<DetailRow
|
|
181
|
+
label={t('licenceResultCode', 'Licence result code')}
|
|
182
|
+
value={displayValue(status.licence?.resultCode)}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</section>
|
|
186
|
+
|
|
187
|
+
<div className={styles.sectionHeader}>
|
|
188
|
+
<h2 className={styles.sectionTitle}>{t('referenceDataSync', 'Reference data sync')}</h2>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div className={styles.panelsRow}>
|
|
192
|
+
<SyncStatusPanel title={t('codeListSync', 'Code list sync')} sync={status.codeListSync} />
|
|
193
|
+
<SyncStatusPanel title={t('classListSync', 'Class list sync')} sync={status.classListSync} />
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const EtimsStatusDashboard: React.FC = () => {
|
|
200
|
+
const { t } = useTranslation();
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<>
|
|
204
|
+
<Header title={t('etimsStatus', 'eTIMS status')} />
|
|
205
|
+
<UserHasAccess privilege="Manage eTIMS Configuration">
|
|
206
|
+
<EtimsStatusContent />
|
|
207
|
+
</UserHasAccess>
|
|
208
|
+
</>
|
|
209
|
+
);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export default EtimsStatusDashboard;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.dashboard {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: layout.$spacing-05;
|
|
9
|
+
padding: layout.$spacing-05;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sectionHeader {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
gap: layout.$spacing-03;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.sectionTitle {
|
|
20
|
+
@include type.type-style('heading-compact-02');
|
|
21
|
+
color: colors.$gray-100;
|
|
22
|
+
margin: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.cardsRow {
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
28
|
+
gap: layout.$spacing-05;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.tile {
|
|
32
|
+
border: 1px solid colors.$gray-20;
|
|
33
|
+
min-height: calc(#{layout.$spacing-12} + #{layout.$spacing-06});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.tileHeader {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
gap: layout.$spacing-03;
|
|
41
|
+
margin-bottom: layout.$spacing-03;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.tileLabel {
|
|
45
|
+
@include type.type-style('heading-compact-01');
|
|
46
|
+
color: colors.$gray-70;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.tileMeta {
|
|
50
|
+
@include type.type-style('helper-text-01');
|
|
51
|
+
color: colors.$gray-70;
|
|
52
|
+
margin-top: layout.$spacing-03;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.panelsRow {
|
|
56
|
+
display: grid;
|
|
57
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
58
|
+
gap: layout.$spacing-05;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.panel {
|
|
62
|
+
border: 1px solid colors.$gray-20;
|
|
63
|
+
background: colors.$white;
|
|
64
|
+
padding: layout.$spacing-05;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.panelTitle {
|
|
68
|
+
@include type.type-style('heading-compact-01');
|
|
69
|
+
color: colors.$gray-100;
|
|
70
|
+
margin: 0 0 layout.$spacing-04;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.detailList {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
gap: layout.$spacing-03;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.detailRow {
|
|
80
|
+
display: grid;
|
|
81
|
+
grid-template-columns: minmax(#{layout.$spacing-13}, 40%) 1fr;
|
|
82
|
+
gap: layout.$spacing-03;
|
|
83
|
+
align-items: start;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.detailLabel {
|
|
87
|
+
@include type.type-style('label-01');
|
|
88
|
+
color: colors.$gray-70;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.detailValue {
|
|
92
|
+
@include type.type-style('body-compact-01');
|
|
93
|
+
color: colors.$gray-100;
|
|
94
|
+
word-break: break-word;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:global(.omrs-breakpoint-lt-desktop) {
|
|
98
|
+
.cardsRow {
|
|
99
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.panelsRow {
|
|
103
|
+
grid-template-columns: 1fr;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:global(.omrs-breakpoint-lt-tablet) {
|
|
108
|
+
.cardsRow {
|
|
109
|
+
grid-template-columns: 1fr;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.detailRow {
|
|
113
|
+
grid-template-columns: 1fr;
|
|
114
|
+
gap: layout.$spacing-01;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import type { EtimsStatus } from './etims-status.types';
|
|
4
|
+
|
|
5
|
+
export const etimsStatusUrl = `${restBaseUrl}/cashier/etimsStatus`;
|
|
6
|
+
|
|
7
|
+
export const useEtimsStatus = () => {
|
|
8
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR<FetchResponse<EtimsStatus>>(
|
|
9
|
+
etimsStatusUrl,
|
|
10
|
+
openmrsFetch,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
status: data?.data ?? null,
|
|
15
|
+
error,
|
|
16
|
+
isLoading,
|
|
17
|
+
isValidating,
|
|
18
|
+
mutate,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface EtimsLicenceStatus {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
resultCode: string | null;
|
|
4
|
+
resultMessage: string | null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface EtimsSyncState {
|
|
8
|
+
uuid: string | null;
|
|
9
|
+
syncCategory: string | null;
|
|
10
|
+
lastRequestDatetime: string | null;
|
|
11
|
+
lastSuccessAt: string | null;
|
|
12
|
+
lastFailureAt: string | null;
|
|
13
|
+
lastRecordCount: number | null;
|
|
14
|
+
lastFailureMessage: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface EtimsStatus {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
environment: string | null;
|
|
20
|
+
baseUrl: string | null;
|
|
21
|
+
businessPin: string | null;
|
|
22
|
+
branchId: string | null;
|
|
23
|
+
apiKeyConfigured: boolean;
|
|
24
|
+
contractVersion: string | null;
|
|
25
|
+
sandboxValidatedContractVersion: string | null;
|
|
26
|
+
gatewayHealthy: boolean;
|
|
27
|
+
licence: EtimsLicenceStatus | null;
|
|
28
|
+
codeListSync: EtimsSyncState | null;
|
|
29
|
+
classListSync: EtimsSyncState | null;
|
|
30
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, InlineLoading } from '@carbon/react';
|
|
3
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import {
|
|
6
|
+
reactivateEtimsTransaction,
|
|
7
|
+
reconcileEtimsTransaction,
|
|
8
|
+
retryEtimsTransaction,
|
|
9
|
+
} from './etims-transaction.resource';
|
|
10
|
+
|
|
11
|
+
type ConfirmTransactionAction = 'retry' | 'reconcile' | 'reactivate';
|
|
12
|
+
|
|
13
|
+
type ConfirmTransactionActionModalProps = {
|
|
14
|
+
close: () => void;
|
|
15
|
+
action: ConfirmTransactionAction;
|
|
16
|
+
uuid: string;
|
|
17
|
+
displayName: string;
|
|
18
|
+
onComplete: () => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ConfirmTransactionActionModal: React.FC<ConfirmTransactionActionModalProps> = ({
|
|
22
|
+
close,
|
|
23
|
+
action,
|
|
24
|
+
uuid,
|
|
25
|
+
displayName,
|
|
26
|
+
onComplete,
|
|
27
|
+
}) => {
|
|
28
|
+
const { t } = useTranslation();
|
|
29
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
30
|
+
|
|
31
|
+
const copy = {
|
|
32
|
+
retry: {
|
|
33
|
+
heading: t('retrySubmission', 'Retry submission'),
|
|
34
|
+
message: t(
|
|
35
|
+
'retrySubmissionConfirmation',
|
|
36
|
+
'Run one submission attempt for "{{displayName}}" using the frozen payload. Inconclusive gateway results stay in reconciliation required.',
|
|
37
|
+
{ displayName },
|
|
38
|
+
),
|
|
39
|
+
successTitle: t('retryComplete', 'Retry complete'),
|
|
40
|
+
errorTitle: t('retryError', 'Unable to retry transaction'),
|
|
41
|
+
run: () => retryEtimsTransaction(uuid),
|
|
42
|
+
},
|
|
43
|
+
reactivate: {
|
|
44
|
+
heading: t('reactivateSubmission', 'Retry submission'),
|
|
45
|
+
message: t(
|
|
46
|
+
'reactivateSubmissionConfirmation',
|
|
47
|
+
'Rebuild "{{displayName}}" from the bill\'s current configuration and item mappings, then attempt submission. Fix the blocking mapping or configuration first; if gaps remain it stays in action required. The bill amounts must be unchanged.',
|
|
48
|
+
{ displayName },
|
|
49
|
+
),
|
|
50
|
+
successTitle: t('retryComplete', 'Retry complete'),
|
|
51
|
+
errorTitle: t('reactivateError', 'Unable to reactivate transaction'),
|
|
52
|
+
run: () => reactivateEtimsTransaction(uuid),
|
|
53
|
+
},
|
|
54
|
+
reconcile: {
|
|
55
|
+
heading: t('reconcileWithKra', 'Reconcile with KRA'),
|
|
56
|
+
message: t(
|
|
57
|
+
'reconcileWithKraConfirmation',
|
|
58
|
+
'Look up "{{displayName}}" on the eTIMS gateway and update the local transaction from that result. This does not resubmit the invoice.',
|
|
59
|
+
{ displayName },
|
|
60
|
+
),
|
|
61
|
+
successTitle: t('reconciliationComplete', 'Reconciliation complete'),
|
|
62
|
+
errorTitle: t('reconciliationError', 'Unable to reconcile transaction'),
|
|
63
|
+
run: () => reconcileEtimsTransaction(uuid),
|
|
64
|
+
},
|
|
65
|
+
}[action];
|
|
66
|
+
|
|
67
|
+
const heading = copy.heading;
|
|
68
|
+
const message = copy.message;
|
|
69
|
+
|
|
70
|
+
const handleSubmit = async () => {
|
|
71
|
+
setIsSubmitting(true);
|
|
72
|
+
try {
|
|
73
|
+
const result = await copy.run();
|
|
74
|
+
showSnackbar({
|
|
75
|
+
kind: 'success',
|
|
76
|
+
title: copy.successTitle,
|
|
77
|
+
subtitle:
|
|
78
|
+
result.resultMessage ?? t('transactionStatusUpdated', 'Status: {{status}}', { status: result.status }),
|
|
79
|
+
});
|
|
80
|
+
onComplete();
|
|
81
|
+
close();
|
|
82
|
+
} catch (error: any) {
|
|
83
|
+
showSnackbar({
|
|
84
|
+
kind: 'error',
|
|
85
|
+
title: copy.errorTitle,
|
|
86
|
+
subtitle: error?.message,
|
|
87
|
+
});
|
|
88
|
+
} finally {
|
|
89
|
+
setIsSubmitting(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<div className="cds--modal-header">
|
|
96
|
+
<h3 className="cds--modal-header__heading">{heading}</h3>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="cds--modal-content">
|
|
99
|
+
<p>{message}</p>
|
|
100
|
+
</div>
|
|
101
|
+
<div className="cds--modal-footer">
|
|
102
|
+
<Button kind="secondary" onClick={close} disabled={isSubmitting}>
|
|
103
|
+
{t('cancel', 'Cancel')}
|
|
104
|
+
</Button>
|
|
105
|
+
<Button kind="primary" onClick={handleSubmit} disabled={isSubmitting}>
|
|
106
|
+
{isSubmitting ? <InlineLoading description={t('working', 'Working...')} /> : heading}
|
|
107
|
+
</Button>
|
|
108
|
+
</div>
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default ConfirmTransactionActionModal;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { UserHasAccess } from '@openmrs/esm-framework';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import Header from '../components/header/header.component';
|
|
5
|
+
import EtimsTransactionTable from './etims-transaction-table.component';
|
|
6
|
+
import styles from './etims-transaction-dashboard.scss';
|
|
7
|
+
|
|
8
|
+
const EtimsTransactionDashboard: React.FC = () => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div>
|
|
13
|
+
<Header title={t('transactions', 'Transactions')} />
|
|
14
|
+
<UserHasAccess privilege="View eTIMS Transactions">
|
|
15
|
+
<div className={styles.dashboard}>
|
|
16
|
+
<EtimsTransactionTable />
|
|
17
|
+
</div>
|
|
18
|
+
</UserHasAccess>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default EtimsTransactionDashboard;
|