@kenyaemr/esm-admin-app 5.4.4-pre.145 → 5.4.4-pre.15
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/package.json +1 -1
- package/src/components/confirm-modal/confirmation-operation.test.tsx +8 -19
- package/src/components/dashboard/dashboard.component.tsx +4 -1
- package/src/components/empty-state/empty-state-log.test.tsx +3 -4
- package/src/components/facility-setup/constant/index.ts +3 -0
- package/src/components/facility-setup/facility-info.component.tsx +176 -106
- package/src/components/facility-setup/facility-info.scss +39 -1
- package/src/components/facility-setup/facility-setup.component.tsx +2 -2
- package/src/components/facility-setup/type/index.ts +61 -0
- package/src/components/facility-setup/useFacilityRegistry.ts +29 -0
- package/src/components/global-property/dashboard/global-property-dashboard.component.tsx +23 -0
- package/src/components/global-property/dashboard/global-property-dashboard.scss +6 -0
- package/src/components/global-property/hooks/useGlobalProperty.ts +64 -0
- package/src/components/global-property/index.ts +14 -0
- package/src/components/global-property/modal/delete-global-property-modal.component.tsx +71 -0
- package/src/components/global-property/modal/delete-global-property-modal.test.tsx +131 -0
- package/src/components/global-property/table/global-property-table.component.tsx +249 -0
- package/src/components/global-property/table/global-property-table.scss +34 -0
- package/src/components/global-property/table/global-property-table.test.tsx +198 -0
- package/src/components/global-property/workspace/global-property-form-schema.ts +32 -0
- package/src/components/global-property/workspace/global-property.workspace.scss +40 -0
- package/src/components/global-property/workspace/global-property.workspace.test.tsx +172 -0
- package/src/components/global-property/workspace/global-property.workspace.tsx +260 -0
- package/src/components/hook/healthWorkerRegistry.ts +78 -0
- package/src/components/hook/useProfessionalRegistryEnums.ts +59 -0
- package/src/components/logs-table/operation-log-table.component.tsx +87 -75
- package/src/components/logs-table/operation-log.test.tsx +134 -28
- package/src/components/modal/hwr-confirmation.modal.scss +80 -4
- package/src/components/modal/hwr-confirmation.modal.tsx +118 -128
- package/src/components/modal/hwr-sync.modal.tsx +194 -106
- package/src/components/users/manage-users/user-details/user-detail.scss +167 -39
- package/src/components/users/manage-users/user-details/user-details.component.tsx +130 -122
- package/src/components/users/manage-users/user-management.workspace.scss +233 -95
- package/src/components/users/manage-users/user-management.workspace.tsx +481 -375
- package/src/components/users/userManagementFormSchema.tsx +17 -8
- package/src/config-schema.ts +48 -68
- package/src/index.ts +9 -1
- package/src/root.component.tsx +2 -0
- package/src/routes.json +20 -1
- package/src/types/index.ts +11 -2
- package/translations/am.json +175 -10
- package/translations/en.json +108 -40
- package/translations/fr.json +171 -101
- package/translations/sw.json +312 -147
- package/.turbo/turbo-build.log +0 -6
- package/dist/1074.js +0 -1
- package/dist/1074.js.map +0 -1
- package/dist/12.js +0 -17
- package/dist/12.js.map +0 -1
- package/dist/1201.js +0 -1
- package/dist/1201.js.map +0 -1
- package/dist/1242.js +0 -1
- package/dist/1242.js.map +0 -1
- package/dist/1311.js +0 -1
- package/dist/1311.js.map +0 -1
- package/dist/1462.js +0 -1
- package/dist/1462.js.map +0 -1
- package/dist/1469.js +0 -1
- package/dist/1469.js.map +0 -1
- package/dist/1506.js +0 -13
- package/dist/1506.js.map +0 -1
- package/dist/1718.js +0 -1
- package/dist/1718.js.map +0 -1
- package/dist/1722.js +0 -1
- package/dist/1722.js.map +0 -1
- package/dist/1772.js +0 -1
- package/dist/1772.js.map +0 -1
- package/dist/1889.js +0 -1
- package/dist/1889.js.map +0 -1
- package/dist/1972.js +0 -1
- package/dist/1972.js.map +0 -1
- package/dist/1990.js +0 -1
- package/dist/1990.js.map +0 -1
- package/dist/2016.js +0 -1
- package/dist/2016.js.map +0 -1
- package/dist/2080.js +0 -1
- package/dist/2080.js.map +0 -1
- package/dist/2096.js +0 -1
- package/dist/2096.js.map +0 -1
- package/dist/2153.js +0 -1
- package/dist/2153.js.map +0 -1
- package/dist/216.js +0 -1
- package/dist/216.js.map +0 -1
- package/dist/2270.js +0 -1
- package/dist/2270.js.map +0 -1
- package/dist/2294.js +0 -1
- package/dist/2294.js.map +0 -1
- package/dist/2345.js +0 -1
- package/dist/2345.js.map +0 -1
- package/dist/2402.js +0 -1
- package/dist/2402.js.map +0 -1
- package/dist/2500.js +0 -1
- package/dist/2500.js.map +0 -1
- package/dist/251.js +0 -1
- package/dist/251.js.map +0 -1
- package/dist/257.js +0 -1
- package/dist/257.js.map +0 -1
- package/dist/2586.js +0 -1
- package/dist/2586.js.map +0 -1
- package/dist/2625.js +0 -1
- package/dist/2625.js.map +0 -1
- package/dist/2652.js +0 -1
- package/dist/2652.js.map +0 -1
- package/dist/2685.js +0 -1
- package/dist/2685.js.map +0 -1
- package/dist/2948.js +0 -1
- package/dist/2948.js.map +0 -1
- package/dist/3089.js +0 -1
- package/dist/3089.js.map +0 -1
- package/dist/3190.js +0 -1
- package/dist/3190.js.map +0 -1
- package/dist/3224.js +0 -1
- package/dist/3224.js.map +0 -1
- package/dist/3366.js +0 -1
- package/dist/3366.js.map +0 -1
- package/dist/349.js +0 -1
- package/dist/349.js.map +0 -1
- package/dist/3548.js +0 -1
- package/dist/3548.js.map +0 -1
- package/dist/3571.js +0 -1
- package/dist/3571.js.map +0 -1
- package/dist/3691.js +0 -1
- package/dist/3691.js.map +0 -1
- package/dist/3775.js +0 -1
- package/dist/3775.js.map +0 -1
- package/dist/3816.js +0 -1
- package/dist/3816.js.map +0 -1
- package/dist/3852.js +0 -1
- package/dist/3852.js.map +0 -1
- package/dist/3906.js +0 -1
- package/dist/3906.js.map +0 -1
- package/dist/3963.js +0 -1
- package/dist/3963.js.map +0 -1
- package/dist/405.js +0 -1
- package/dist/405.js.map +0 -1
- package/dist/4296.js +0 -1
- package/dist/4296.js.map +0 -1
- package/dist/4337.js +0 -1
- package/dist/4337.js.map +0 -1
- package/dist/4735.js +0 -1
- package/dist/4735.js.map +0 -1
- package/dist/4744.js +0 -1
- package/dist/4744.js.map +0 -1
- package/dist/4813.js +0 -2
- package/dist/4813.js.map +0 -1
- package/dist/4858.js +0 -1
- package/dist/4858.js.map +0 -1
- package/dist/487.js +0 -1
- package/dist/487.js.map +0 -1
- package/dist/4970.js +0 -1
- package/dist/4970.js.map +0 -1
- package/dist/5202.js +0 -1
- package/dist/5202.js.map +0 -1
- package/dist/5294.js +0 -1
- package/dist/5294.js.map +0 -1
- package/dist/545.js +0 -1
- package/dist/545.js.map +0 -1
- package/dist/552.js +0 -1
- package/dist/552.js.map +0 -1
- package/dist/5592.js +0 -1
- package/dist/5592.js.map +0 -1
- package/dist/5669.js +0 -1
- package/dist/5669.js.map +0 -1
- package/dist/5884.js +0 -1
- package/dist/5884.js.map +0 -1
- package/dist/5940.js +0 -1
- package/dist/5940.js.map +0 -1
- package/dist/6092.js +0 -1
- package/dist/6092.js.map +0 -1
- package/dist/6155.js +0 -1
- package/dist/6155.js.map +0 -1
- package/dist/6178.js +0 -1
- package/dist/6178.js.map +0 -1
- package/dist/6399.js +0 -1
- package/dist/6399.js.map +0 -1
- package/dist/6456.js +0 -1
- package/dist/6466.js +0 -3
- package/dist/6466.js.map +0 -1
- package/dist/6492.js +0 -1
- package/dist/6492.js.map +0 -1
- package/dist/6676.js +0 -1
- package/dist/6676.js.map +0 -1
- package/dist/6800.js +0 -1
- package/dist/6800.js.map +0 -1
- package/dist/6976.js +0 -1
- package/dist/6976.js.map +0 -1
- package/dist/7005.js +0 -1
- package/dist/7005.js.map +0 -1
- package/dist/7201.js +0 -1
- package/dist/7201.js.map +0 -1
- package/dist/7210.js +0 -1
- package/dist/7210.js.map +0 -1
- package/dist/7234.js +0 -1
- package/dist/7234.js.map +0 -1
- package/dist/7261.js +0 -1
- package/dist/7261.js.map +0 -1
- package/dist/7326.js +0 -1
- package/dist/7463.js +0 -1
- package/dist/7463.js.map +0 -1
- package/dist/7528.js +0 -1
- package/dist/7528.js.map +0 -1
- package/dist/7584.js +0 -1
- package/dist/7584.js.map +0 -1
- package/dist/7607.js +0 -1
- package/dist/7717.js +0 -1
- package/dist/7717.js.map +0 -1
- package/dist/7737.js +0 -1
- package/dist/7737.js.map +0 -1
- package/dist/7739.js +0 -1
- package/dist/7739.js.map +0 -1
- package/dist/7765.js +0 -1
- package/dist/7765.js.map +0 -1
- package/dist/7820.js +0 -1
- package/dist/7820.js.map +0 -1
- package/dist/7844.js +0 -1
- package/dist/7844.js.map +0 -1
- package/dist/7866.js +0 -1
- package/dist/7866.js.map +0 -1
- package/dist/7916.js +0 -1
- package/dist/7916.js.map +0 -1
- package/dist/7971.js +0 -1
- package/dist/7971.js.map +0 -1
- package/dist/8159.js +0 -7
- package/dist/8159.js.map +0 -1
- package/dist/8244.js +0 -1
- package/dist/8244.js.map +0 -1
- package/dist/8262.js +0 -1
- package/dist/8262.js.map +0 -1
- package/dist/834.js +0 -1
- package/dist/834.js.map +0 -1
- package/dist/8376.js +0 -1
- package/dist/8376.js.map +0 -1
- package/dist/845.js +0 -1
- package/dist/845.js.map +0 -1
- package/dist/8546.js +0 -1
- package/dist/8546.js.map +0 -1
- package/dist/8570.js +0 -1
- package/dist/8570.js.map +0 -1
- package/dist/87.js +0 -1
- package/dist/87.js.map +0 -1
- package/dist/8727.js +0 -1
- package/dist/8828.js +0 -1
- package/dist/8828.js.map +0 -1
- package/dist/8860.js +0 -1
- package/dist/8860.js.map +0 -1
- package/dist/9036.js +0 -1
- package/dist/9036.js.map +0 -1
- package/dist/9124.js +0 -1
- package/dist/9124.js.map +0 -1
- package/dist/9182.js +0 -1
- package/dist/921.js +0 -1
- package/dist/921.js.map +0 -1
- package/dist/9404.js +0 -1
- package/dist/9404.js.map +0 -1
- package/dist/9406.js +0 -1
- package/dist/9406.js.map +0 -1
- package/dist/9446.js +0 -1
- package/dist/9446.js.map +0 -1
- package/dist/9449.js +0 -1
- package/dist/9449.js.map +0 -1
- package/dist/9566.js +0 -5
- package/dist/9566.js.map +0 -1
- package/dist/9641.js +0 -1
- package/dist/9641.js.map +0 -1
- package/dist/9711.js +0 -1
- package/dist/9711.js.map +0 -1
- package/dist/9801.js +0 -1
- package/dist/9801.js.map +0 -1
- package/dist/9835.js +0 -11
- package/dist/9835.js.map +0 -1
- package/dist/kenyaemr-esm-admin-app.js +0 -5
- package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +0 -3173
- package/dist/kenyaemr-esm-admin-app.js.map +0 -1
- package/dist/main.js +0 -5
- package/dist/main.js.map +0 -1
- package/dist/routes.json +0 -1
- package/src/components/facility-setup/facility-setup.resource.tsx +0 -7
- package/src/components/hook/healthWorkerAdapter.ts +0 -213
- package/src/components/hook/useFacilityInfo.tsx +0 -37
package/package.json
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
3
|
import '@testing-library/jest-dom';
|
|
4
|
-
import {
|
|
4
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
5
5
|
import OperationConfirmation from './confirmation-operation-modal.component';
|
|
6
6
|
|
|
7
|
-
jest.mock('react-i18next', () => ({
|
|
8
|
-
useTranslation: jest.fn(),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
7
|
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
8
|
it('renders the component with provided props', () => {
|
|
20
|
-
const closeMock =
|
|
21
|
-
const confirmMock =
|
|
9
|
+
const closeMock = vi.fn();
|
|
10
|
+
const confirmMock = vi.fn();
|
|
22
11
|
const operationName = 'refresh';
|
|
23
12
|
const operationType = 'refreshed';
|
|
24
13
|
|
|
@@ -32,14 +21,14 @@ describe('OperationConfirmation', () => {
|
|
|
32
21
|
);
|
|
33
22
|
|
|
34
23
|
expect(screen.getByRole('heading', { name: 'Confirmation' })).toBeInTheDocument();
|
|
35
|
-
expect(screen.getByText('Do you want to
|
|
24
|
+
expect(screen.getByText('Do you want to {{operationTypeOrName}}?')).toBeInTheDocument();
|
|
36
25
|
expect(screen.getByText('No')).toBeInTheDocument();
|
|
37
26
|
expect(screen.getByText('Yes')).toBeInTheDocument();
|
|
38
27
|
});
|
|
39
28
|
|
|
40
29
|
it('calls close when the No button is clicked', () => {
|
|
41
|
-
const closeMock =
|
|
42
|
-
const confirmMock =
|
|
30
|
+
const closeMock = vi.fn();
|
|
31
|
+
const confirmMock = vi.fn();
|
|
43
32
|
|
|
44
33
|
render(
|
|
45
34
|
<OperationConfirmation close={closeMock} confirm={confirmMock} operationName="delete" operationType="deleted" />,
|
|
@@ -53,8 +42,8 @@ describe('OperationConfirmation', () => {
|
|
|
53
42
|
});
|
|
54
43
|
|
|
55
44
|
it('calls confirm when the Yes button is clicked', () => {
|
|
56
|
-
const closeMock =
|
|
57
|
-
const confirmMock =
|
|
45
|
+
const closeMock = vi.fn();
|
|
46
|
+
const confirmMock = vi.fn();
|
|
58
47
|
|
|
59
48
|
render(
|
|
60
49
|
<OperationConfirmation close={closeMock} confirm={confirmMock} operationName="delete" operationType="deleted" />,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../logs-table/operation-log-resource';
|
|
13
13
|
import LogTable from '../logs-table/operation-log-table.component';
|
|
14
14
|
import { showModal, showSnackbar } from '@openmrs/esm-framework';
|
|
15
|
+
import { extractErrorMessagesFromResponse } from '../locations/helpers';
|
|
15
16
|
|
|
16
17
|
const Dashboard: React.FC = () => {
|
|
17
18
|
const { t } = useTranslation();
|
|
@@ -59,7 +60,9 @@ const Dashboard: React.FC = () => {
|
|
|
59
60
|
} catch (error) {
|
|
60
61
|
showSnackbar({
|
|
61
62
|
title: t('operationError', '{{operationName}} failed', { operationName }),
|
|
62
|
-
subtitle: t('operationErrorSubtitle', 'An error occurred during the operation.'
|
|
63
|
+
subtitle: t('operationErrorSubtitle', 'An error occurred during the operation. {{error}}', {
|
|
64
|
+
error: extractErrorMessagesFromResponse(error),
|
|
65
|
+
}),
|
|
63
66
|
kind: 'error',
|
|
64
67
|
isLowContrast: true,
|
|
65
68
|
});
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import '@testing-library/jest-dom';
|
|
3
|
-
import {
|
|
3
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
4
4
|
import EmptyState from './empty-state-log.components';
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
EmptyDataIllustration: jest.fn(() => <div>Mocked EmptyDataIllustration</div>),
|
|
7
|
+
vi.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
8
|
+
EmptyDataIllustration: vi.fn(() => <div>Mocked EmptyDataIllustration</div>),
|
|
10
9
|
}));
|
|
11
10
|
|
|
12
11
|
describe('EmptyState', () => {
|
|
@@ -1,91 +1,123 @@
|
|
|
1
|
-
import { Button, Column, Grid, InlineLoading,
|
|
1
|
+
import { Button, Column, Grid, InlineLoading, Tag, Tile } from '@carbon/react';
|
|
2
|
+
import { Renew, Phone, Email } from '@carbon/react/icons';
|
|
2
3
|
import { formatDate, parseDate, showSnackbar } from '@openmrs/esm-framework';
|
|
3
|
-
import React, { useCallback
|
|
4
|
+
import React, { useCallback } from 'react';
|
|
4
5
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { useLocalFacilityInfo, useShaFacilityInfo } from '../hook/useFacilityInfo';
|
|
6
6
|
import styles from './facility-info.scss';
|
|
7
|
-
import { syncPackagesAndInterventions } from './facility-setup.resource';
|
|
8
|
-
import { Renew, Phone, Email } from '@carbon/react/icons';
|
|
9
|
-
import { SectionCard } from './shared/custom-section-card.component';
|
|
10
7
|
import { InfoRow } from './shared/custom-info.component';
|
|
8
|
+
import { SectionCard } from './shared/custom-section-card.component';
|
|
11
9
|
import { StatusTag } from './shared/custom-status-tag.component';
|
|
10
|
+
import { syncFacilityRegistry, useFacilityRegistry } from './useFacilityRegistry';
|
|
11
|
+
import { FacilityRegistryRecord } from './type';
|
|
12
|
+
import { CardHeader } from '@openmrs/esm-patient-common-lib/src';
|
|
13
|
+
import EmptyState from '../empty-state/empty-state-log.components';
|
|
12
14
|
|
|
13
|
-
const
|
|
15
|
+
const FacilityRegistryView: React.FC = () => {
|
|
14
16
|
const { t } = useTranslation();
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const {
|
|
18
|
-
shaFacility,
|
|
19
|
-
isLoading: isShaLoading,
|
|
20
|
-
error: shaError,
|
|
21
|
-
mutate: mutateSha,
|
|
22
|
-
} = useShaFacilityInfo(shouldSynchronize);
|
|
23
|
-
const { localFacility, isLoading: isLocalLoading, mutate: mutateLocal } = useLocalFacilityInfo();
|
|
24
|
-
|
|
25
|
-
const mutateFacility = useCallback(async () => {
|
|
26
|
-
const [defaultFacility, sha] = await Promise.all([mutateLocal(), mutateSha()]);
|
|
27
|
-
return { shaFacility: sha, defaultFacility };
|
|
28
|
-
}, [mutateLocal, mutateSha]);
|
|
17
|
+
const { facility, isLoading, error, notYetSynced, mutate } = useFacilityRegistry();
|
|
18
|
+
const [isSyncing, setIsSyncing] = React.useState(false);
|
|
29
19
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const synchronizeFacilityData = useCallback(async () => {
|
|
20
|
+
const handleSync = useCallback(async () => {
|
|
21
|
+
setIsSyncing(true);
|
|
33
22
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
showSnackbar({ title: t('syncingHieSuccess', 'Synchronization complete'), kind: 'success', isLowContrast: true });
|
|
37
|
-
if (synced?.data?.source !== 'HIE') {
|
|
38
|
-
showSnackbar({
|
|
39
|
-
kind: 'warning',
|
|
40
|
-
title: t('hieSyncFailed', 'HIE sync failed. Pulling local info.'),
|
|
41
|
-
isLowContrast: true,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
await syncPackagesAndInterventions();
|
|
45
|
-
} catch (error) {
|
|
23
|
+
const result = await syncFacilityRegistry();
|
|
24
|
+
await mutate();
|
|
46
25
|
showSnackbar({
|
|
47
|
-
title: t('
|
|
26
|
+
title: t('facilityRegistrySyncSuccess', 'Facility synced'),
|
|
27
|
+
subtitle: t('facilityRegistrySyncedFrom', 'Pulled {{name}} from the facility registry', {
|
|
28
|
+
name: result.official_name,
|
|
29
|
+
}),
|
|
30
|
+
kind: 'success',
|
|
31
|
+
isLowContrast: true,
|
|
32
|
+
});
|
|
33
|
+
} catch (err: any) {
|
|
34
|
+
showSnackbar({
|
|
35
|
+
title: t('facilityRegistrySyncFailed', 'Sync failed'),
|
|
48
36
|
subtitle:
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
err?.responseBody?.error ??
|
|
38
|
+
err?.message ??
|
|
39
|
+
t('facilityRegistrySyncError', 'Unable to reach the facility registry'),
|
|
51
40
|
kind: 'error',
|
|
52
41
|
isLowContrast: true,
|
|
53
42
|
});
|
|
43
|
+
} finally {
|
|
44
|
+
setIsSyncing(false);
|
|
54
45
|
}
|
|
55
|
-
}, [
|
|
46
|
+
}, [mutate, t]);
|
|
47
|
+
|
|
48
|
+
if (isLoading) {
|
|
49
|
+
return (
|
|
50
|
+
<div className={styles.root}>
|
|
51
|
+
<InlineLoading description={t('loadingFacility', 'Loading facility…')} />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (error) {
|
|
57
|
+
return (
|
|
58
|
+
<div className={styles.root}>
|
|
59
|
+
<Tile className={styles.errorTile}>
|
|
60
|
+
<p>{t('facilityRegistryLoadError', 'Failed to load the facility record.')}</p>
|
|
61
|
+
<Button kind="secondary" size="sm" renderIcon={Renew} onClick={handleSync} disabled={isSyncing}>
|
|
62
|
+
{isSyncing ? t('syncing', 'Syncing…') : t('retry', 'Retry sync')}
|
|
63
|
+
</Button>
|
|
64
|
+
</Tile>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (notYetSynced || !facility) {
|
|
70
|
+
return (
|
|
71
|
+
<div>
|
|
72
|
+
<CardHeader title={t('facilityDetails', 'Facility details')}>
|
|
73
|
+
<Button kind="primary" size="sm" renderIcon={Renew} onClick={handleSync} disabled={isSyncing}>
|
|
74
|
+
{isSyncing ? t('syncing', 'Syncing…') : t('syncNow', 'Sync now')}
|
|
75
|
+
</Button>
|
|
76
|
+
</CardHeader>
|
|
77
|
+
<EmptyState
|
|
78
|
+
subTitle={t(
|
|
79
|
+
'facilityRegistryNotSyncedBody',
|
|
80
|
+
'This facility has not been pulled from the registry. Click sync to fetch the latest record.',
|
|
81
|
+
)}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
56
86
|
|
|
57
87
|
return (
|
|
58
88
|
<div className={styles.root}>
|
|
59
89
|
<div className={styles.pageHeader}>
|
|
60
90
|
<div className={styles.pageActions}>
|
|
61
|
-
{
|
|
62
|
-
<
|
|
91
|
+
{facility.last_synced_at && (
|
|
92
|
+
<span className={styles.lastSynced}>
|
|
93
|
+
{t('lastSynced', 'Last synced')}: {formatDate(parseDate(facility.last_synced_at))}
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
{isSyncing ? (
|
|
97
|
+
<InlineLoading description={t('syncing', 'Syncing…')} />
|
|
63
98
|
) : (
|
|
64
|
-
<Button kind="secondary" size="sm" renderIcon={Renew} onClick={
|
|
65
|
-
{t('
|
|
99
|
+
<Button kind="secondary" size="sm" renderIcon={Renew} onClick={handleSync}>
|
|
100
|
+
{t('syncWithRegistry', 'Sync with facility registry')}
|
|
66
101
|
</Button>
|
|
67
102
|
)}
|
|
68
103
|
</div>
|
|
69
104
|
</div>
|
|
105
|
+
|
|
70
106
|
<Grid narrow className={styles.grid}>
|
|
71
107
|
<Column sm={4} md={4} lg={8}>
|
|
72
108
|
<SectionCard title={t('generalInformation', 'General information')}>
|
|
73
|
-
<InfoRow label={t('facilityName', 'Facility name')} value={
|
|
74
|
-
<InfoRow label={t('registrationNumber', 'Registration no.')} value={
|
|
75
|
-
<InfoRow label={t('
|
|
76
|
-
<InfoRow label={t('
|
|
77
|
-
<InfoRow
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
/>
|
|
81
|
-
<InfoRow label={t('kephLevel', 'KEPH level')} value={shaFacility?.kephLevel} />
|
|
82
|
-
<InfoRow label={t('facilityType', 'Facility type')} value={shaFacility?.facilityType} />
|
|
83
|
-
<InfoRow label={t('facilityOwnership', 'Ownership')} value={shaFacility?.facilityOwnership} />
|
|
109
|
+
<InfoRow label={t('facilityName', 'Facility name')} value={facility.official_name} />
|
|
110
|
+
<InfoRow label={t('registrationNumber', 'Registration no.')} value={facility.registration_number} />
|
|
111
|
+
<InfoRow label={t('frCode', 'FR code')} value={facility.fr_code} />
|
|
112
|
+
<InfoRow label={t('fidCode', 'FID code')} value={facility.fid_code} />
|
|
113
|
+
<InfoRow label={t('kephLevel', 'KEPH level')} value={facility.keph_level} />
|
|
114
|
+
<InfoRow label={t('facilityType', 'Facility type')} value={facility.facility_type} />
|
|
115
|
+
<InfoRow label={t('facilityOwnership', 'Ownership')} value={facility.facility_ownership} />
|
|
84
116
|
<InfoRow
|
|
85
117
|
label={t('hubFacility', 'Hub facility')}
|
|
86
118
|
value={
|
|
87
|
-
<Tag type={
|
|
88
|
-
{
|
|
119
|
+
<Tag type={facility.is_hub ? 'blue' : 'gray'} size="sm">
|
|
120
|
+
{facility.is_hub ? t('yes', 'Yes') : t('no', 'No')}
|
|
89
121
|
</Tag>
|
|
90
122
|
}
|
|
91
123
|
/>
|
|
@@ -94,31 +126,33 @@ const FacilityInfo: React.FC = () => {
|
|
|
94
126
|
|
|
95
127
|
<Column sm={4} md={4} lg={8}>
|
|
96
128
|
<SectionCard title={t('licenseAndShaStatus', 'License & SHA status')}>
|
|
97
|
-
<InfoRow label={t('licenseNumber', 'License no.')} value={
|
|
98
|
-
<InfoRow label={t('regulatoryBody', 'Regulatory body')} value={
|
|
129
|
+
<InfoRow label={t('licenseNumber', 'License no.')} value={facility.license_number} />
|
|
130
|
+
<InfoRow label={t('regulatoryBody', 'Regulatory body')} value={facility.regulatory_body} />
|
|
99
131
|
<InfoRow
|
|
100
132
|
label={t('licenseStatus', 'License status')}
|
|
101
|
-
value={<StatusTag value={
|
|
133
|
+
value={<StatusTag value={facility.license_status} />}
|
|
102
134
|
/>
|
|
103
135
|
<InfoRow
|
|
104
136
|
label={t('licenseStart', 'License start')}
|
|
105
|
-
value={formatDate(parseDate(
|
|
137
|
+
value={facility.license_start_date ? formatDate(parseDate(facility.license_start_date)) : '—'}
|
|
106
138
|
/>
|
|
107
139
|
<InfoRow
|
|
108
140
|
label={t('licenseExpiry', 'License expiry')}
|
|
109
|
-
value={formatDate(parseDate(
|
|
141
|
+
value={facility.license_end_date ? formatDate(parseDate(facility.license_end_date)) : '—'}
|
|
110
142
|
/>
|
|
111
143
|
<InfoRow
|
|
112
|
-
label={t('
|
|
113
|
-
value={<StatusTag value={
|
|
144
|
+
label={t('regulatoryOperationalStatus', 'Regulatory status')}
|
|
145
|
+
value={<StatusTag value={facility.regulatory_operational_status} />}
|
|
146
|
+
/>
|
|
147
|
+
<InfoRow
|
|
148
|
+
label={t('shaOperationalStatus', 'SHA status')}
|
|
149
|
+
value={<StatusTag value={facility.sha_operational_status} />}
|
|
114
150
|
/>
|
|
115
151
|
<InfoRow
|
|
116
152
|
label={t('shaContractStatus', 'SHA contract')}
|
|
117
153
|
value={
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
shaFacility.shaContractStatus !== '' ? (
|
|
121
|
-
<StatusTag value={shaFacility.shaContractStatus} />
|
|
154
|
+
facility.sha_contract_status && facility.sha_contract_status !== '' ? (
|
|
155
|
+
<StatusTag value={facility.sha_contract_status} />
|
|
122
156
|
) : (
|
|
123
157
|
<Tag type="gray" size="sm">
|
|
124
158
|
{t('notContracted', 'Not contracted')}
|
|
@@ -126,17 +160,26 @@ const FacilityInfo: React.FC = () => {
|
|
|
126
160
|
)
|
|
127
161
|
}
|
|
128
162
|
/>
|
|
129
|
-
|
|
163
|
+
{facility.bed_occupancy && (
|
|
164
|
+
<InfoRow label={t('totalBeds', 'Total beds')} value={facility.bed_occupancy.totalBeds?.toString()} />
|
|
165
|
+
)}
|
|
130
166
|
</SectionCard>
|
|
131
167
|
</Column>
|
|
132
168
|
|
|
133
169
|
<Column sm={4} md={4} lg={8}>
|
|
134
170
|
<SectionCard title={t('location', 'Location')}>
|
|
135
|
-
<InfoRow label={t('
|
|
136
|
-
<InfoRow label={t('
|
|
137
|
-
<InfoRow label={t('
|
|
138
|
-
<InfoRow label={t('
|
|
139
|
-
<InfoRow label={t('
|
|
171
|
+
<InfoRow label={t('country', 'Country')} value={facility.address?.country} />
|
|
172
|
+
<InfoRow label={t('county', 'County')} value={facility.address?.county} />
|
|
173
|
+
<InfoRow label={t('subCounty', 'Sub-county')} value={facility.address?.sub_county} />
|
|
174
|
+
<InfoRow label={t('town', 'Town')} value={facility.address?.town} />
|
|
175
|
+
<InfoRow label={t('physicalLocation', 'Physical location')} value={facility.address?.physical_location} />
|
|
176
|
+
<InfoRow label={t('postalAddress', 'Postal address')} value={facility.address?.postal_address} />
|
|
177
|
+
{facility.address?.latitude != null && facility.address?.longitude != null && (
|
|
178
|
+
<InfoRow
|
|
179
|
+
label={t('coordinates', 'Coordinates')}
|
|
180
|
+
value={`${facility.address.latitude}, ${facility.address.longitude}`}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
140
183
|
</SectionCard>
|
|
141
184
|
</Column>
|
|
142
185
|
|
|
@@ -145,10 +188,10 @@ const FacilityInfo: React.FC = () => {
|
|
|
145
188
|
<InfoRow
|
|
146
189
|
label={t('facilityPhone', 'Phone')}
|
|
147
190
|
value={
|
|
148
|
-
|
|
191
|
+
facility.facility_phone_number ? (
|
|
149
192
|
<span className={styles.contactValue}>
|
|
150
193
|
<Phone size={14} />
|
|
151
|
-
{
|
|
194
|
+
{facility.facility_phone_number}
|
|
152
195
|
</span>
|
|
153
196
|
) : undefined
|
|
154
197
|
}
|
|
@@ -156,51 +199,39 @@ const FacilityInfo: React.FC = () => {
|
|
|
156
199
|
<InfoRow
|
|
157
200
|
label={t('facilityEmail', 'Email')}
|
|
158
201
|
value={
|
|
159
|
-
|
|
202
|
+
facility.facility_email ? (
|
|
160
203
|
<span className={styles.contactValue}>
|
|
161
204
|
<Email size={14} />
|
|
162
|
-
{
|
|
205
|
+
{facility.facility_email}
|
|
163
206
|
</span>
|
|
164
207
|
) : undefined
|
|
165
208
|
}
|
|
166
209
|
/>
|
|
167
|
-
{
|
|
210
|
+
{facility.facility_administrator_name && (
|
|
168
211
|
<div className={styles.adminCard}>
|
|
169
212
|
<div>
|
|
170
|
-
<p className={styles.adminName}>{
|
|
171
|
-
|
|
172
|
-
|
|
213
|
+
<p className={styles.adminName}>{facility.facility_administrator_name}</p>
|
|
214
|
+
{facility.facility_administrator_phone && (
|
|
215
|
+
<p className={styles.adminDetail}>{facility.facility_administrator_phone}</p>
|
|
216
|
+
)}
|
|
217
|
+
{facility.facility_administrator_email && (
|
|
218
|
+
<p className={styles.adminDetail}>{facility.facility_administrator_email}</p>
|
|
219
|
+
)}
|
|
173
220
|
</div>
|
|
174
221
|
</div>
|
|
175
222
|
)}
|
|
176
223
|
</SectionCard>
|
|
177
224
|
</Column>
|
|
178
225
|
|
|
226
|
+
<Column sm={4} md={8} lg={16}>
|
|
227
|
+
<SectionCard title={t('bedOccupancy', 'Bed occupancy')}>
|
|
228
|
+
<BedOccupancyView facility={facility} />
|
|
229
|
+
</SectionCard>
|
|
230
|
+
</Column>
|
|
231
|
+
|
|
179
232
|
<Column sm={4} md={8} lg={16}>
|
|
180
233
|
<SectionCard title={t('shaContractedServices', 'SHA contracted services')}>
|
|
181
|
-
{
|
|
182
|
-
try {
|
|
183
|
-
const services = JSON.parse(shaFacility?.shaContractedServices ?? '[]');
|
|
184
|
-
if (services.length === 0) {
|
|
185
|
-
return (
|
|
186
|
-
<p className={styles.emptyState}>
|
|
187
|
-
{t('noContractedServices', 'No contracted services on record for this facility.')}
|
|
188
|
-
</p>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
return (
|
|
192
|
-
<div className={styles.serviceList}>
|
|
193
|
-
{services.map((svc: any, idx: number) => (
|
|
194
|
-
<Tag key={idx} type="teal" size="sm">
|
|
195
|
-
{svc.name ?? svc}
|
|
196
|
-
</Tag>
|
|
197
|
-
))}
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
} catch {
|
|
201
|
-
return <p className={styles.emptyState}>—</p>;
|
|
202
|
-
}
|
|
203
|
-
})()}
|
|
234
|
+
<ServicesView facility={facility} />
|
|
204
235
|
</SectionCard>
|
|
205
236
|
</Column>
|
|
206
237
|
</Grid>
|
|
@@ -208,4 +239,43 @@ const FacilityInfo: React.FC = () => {
|
|
|
208
239
|
);
|
|
209
240
|
};
|
|
210
241
|
|
|
211
|
-
|
|
242
|
+
const BedOccupancyView: React.FC<{ facility: FacilityRegistryRecord }> = ({ facility }) => {
|
|
243
|
+
const { t } = useTranslation();
|
|
244
|
+
const bo = facility.bed_occupancy;
|
|
245
|
+
if (!bo) {
|
|
246
|
+
return <p className={styles.emptyState}>{t('noBedOccupancyData', 'No bed occupancy data')}</p>;
|
|
247
|
+
}
|
|
248
|
+
return (
|
|
249
|
+
<div className={styles.bedGrid}>
|
|
250
|
+
<InfoRow label={t('totalBeds', 'Total')} value={bo.totalBeds?.toString()} />
|
|
251
|
+
<InfoRow label={t('normalBeds', 'Normal')} value={bo.normalBeds?.toString()} />
|
|
252
|
+
<InfoRow label={t('icuBeds', 'ICU')} value={bo.icuBeds?.toString()} />
|
|
253
|
+
<InfoRow label={t('hduBeds', 'HDU')} value={bo.hduBeds?.toString()} />
|
|
254
|
+
<InfoRow label={t('dialysisBeds', 'Dialysis')} value={bo.dialysisBeds?.toString()} />
|
|
255
|
+
<InfoRow label={t('cots', 'Cots')} value={bo.numberOfCots?.toString()} />
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const ServicesView: React.FC<{ facility: FacilityRegistryRecord }> = ({ facility }) => {
|
|
261
|
+
const { t } = useTranslation();
|
|
262
|
+
const services = facility.sha_contracted_services ?? [];
|
|
263
|
+
if (services.length === 0) {
|
|
264
|
+
return (
|
|
265
|
+
<p className={styles.emptyState}>
|
|
266
|
+
{t('noContractedServices', 'No contracted services on record for this facility.')}
|
|
267
|
+
</p>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return (
|
|
271
|
+
<div className={styles.serviceList}>
|
|
272
|
+
{services.map((svc, idx) => (
|
|
273
|
+
<Tag key={idx} type="teal" size="sm">
|
|
274
|
+
{typeof svc === 'string' ? svc : svc.name ?? JSON.stringify(svc)}
|
|
275
|
+
</Tag>
|
|
276
|
+
))}
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export default FacilityRegistryView;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
align-items: center;
|
|
12
12
|
justify-content: space-between;
|
|
13
13
|
margin-bottom: layout.$spacing-05;
|
|
14
|
+
margin-right: layout.$spacing-08;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
.pageTitle {
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
align-items: center;
|
|
25
26
|
gap: layout.$spacing-03;
|
|
26
27
|
margin-left: auto;
|
|
27
|
-
margin-right: layout.$spacing-
|
|
28
|
+
margin-right: layout.$spacing-12;
|
|
28
29
|
}
|
|
29
30
|
.grid {
|
|
30
31
|
margin-top: 0;
|
|
@@ -128,3 +129,40 @@
|
|
|
128
129
|
font-style: italic;
|
|
129
130
|
margin: 0;
|
|
130
131
|
}
|
|
132
|
+
|
|
133
|
+
.pageMeta {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: 0.5rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.lastSynced {
|
|
140
|
+
font-size: 0.875rem;
|
|
141
|
+
color: var(--cds-text-secondary, #525252);
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: end;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.bedGrid {
|
|
147
|
+
display: grid;
|
|
148
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
149
|
+
gap: layout.$spacing-04;
|
|
150
|
+
|
|
151
|
+
.infoRow {
|
|
152
|
+
display: flex;
|
|
153
|
+
justify-content: flex-start;
|
|
154
|
+
gap: layout.$spacing-03;
|
|
155
|
+
padding: layout.$spacing-02 layout.$spacing-03;
|
|
156
|
+
background: colors.$gray-10;
|
|
157
|
+
border-radius: 4px;
|
|
158
|
+
border-bottom: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.infoLabel {
|
|
162
|
+
margin-right: 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.infoValue {
|
|
166
|
+
text-align: left;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Layer } from '@carbon/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import FacilityInfo from './facility-info.component';
|
|
5
4
|
import styles from './facility-setup.scss';
|
|
6
5
|
import Header from './header/header.component';
|
|
6
|
+
import FacilityRegistryView from './facility-info.component';
|
|
7
7
|
|
|
8
8
|
const FacilitySetup: React.FC = () => {
|
|
9
9
|
const { t } = useTranslation();
|
|
@@ -12,7 +12,7 @@ const FacilitySetup: React.FC = () => {
|
|
|
12
12
|
<Header title={t('facilityDetails', 'Facility Details')} />
|
|
13
13
|
|
|
14
14
|
<Layer className={styles.tableLayer}>
|
|
15
|
-
<
|
|
15
|
+
<FacilityRegistryView />
|
|
16
16
|
</Layer>
|
|
17
17
|
</div>
|
|
18
18
|
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type FacilityRegistryAddress = {
|
|
2
|
+
country?: string;
|
|
3
|
+
county_code?: string;
|
|
4
|
+
county?: string;
|
|
5
|
+
sub_county_code?: string;
|
|
6
|
+
sub_county?: string;
|
|
7
|
+
postal_address?: string;
|
|
8
|
+
physical_location?: string;
|
|
9
|
+
town?: string;
|
|
10
|
+
latitude?: number | null;
|
|
11
|
+
longitude?: number | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type FacilityRegistryBedOccupancy = {
|
|
15
|
+
totalBeds?: number;
|
|
16
|
+
normalBeds?: number;
|
|
17
|
+
icuBeds?: number;
|
|
18
|
+
hduBeds?: number;
|
|
19
|
+
dialysisBeds?: number;
|
|
20
|
+
numberOfCots?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type FacilityRegistryRecord = {
|
|
24
|
+
fr_code: string;
|
|
25
|
+
fid_code?: string;
|
|
26
|
+
registration_number?: string;
|
|
27
|
+
official_name?: string;
|
|
28
|
+
keph_level?: string;
|
|
29
|
+
facility_type?: string;
|
|
30
|
+
facility_ownership?: string;
|
|
31
|
+
license_status?: string;
|
|
32
|
+
license_number?: string;
|
|
33
|
+
license_start_date?: string | null;
|
|
34
|
+
license_end_date?: string | null;
|
|
35
|
+
regulatory_body?: string;
|
|
36
|
+
sha_contract_status?: string;
|
|
37
|
+
sha_contract_start_date?: string | null;
|
|
38
|
+
sha_contract_end_date?: string | null;
|
|
39
|
+
is_hub?: boolean;
|
|
40
|
+
facility_phone_number?: string;
|
|
41
|
+
facility_email?: string;
|
|
42
|
+
facility_administrator_name?: string;
|
|
43
|
+
facility_administrator_email?: string;
|
|
44
|
+
facility_administrator_phone?: string;
|
|
45
|
+
facility_administrator_identifier?: string;
|
|
46
|
+
address?: FacilityRegistryAddress;
|
|
47
|
+
regulatory_operational_status?: string;
|
|
48
|
+
sha_operational_status?: string;
|
|
49
|
+
bed_occupancy?: FacilityRegistryBedOccupancy;
|
|
50
|
+
sha_contracted_services?: Array<{ name?: string } | string>;
|
|
51
|
+
last_synced_at?: string;
|
|
52
|
+
last_sync_status?: 'SUCCESS' | 'FAILURE';
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type FacilityRegistrySyncResult = {
|
|
56
|
+
success: boolean;
|
|
57
|
+
fr_code: string;
|
|
58
|
+
official_name: string;
|
|
59
|
+
last_synced_at: string;
|
|
60
|
+
last_sync_status: 'SUCCESS' | 'FAILURE';
|
|
61
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import useSWR from 'swr';
|
|
2
|
+
import { openmrsFetch, restBaseUrl, type FetchResponse } from '@openmrs/esm-framework';
|
|
3
|
+
import { FacilityRegistryRecord, FacilityRegistrySyncResult } from './type';
|
|
4
|
+
import { facilityRegistryUrl } from './constant';
|
|
5
|
+
|
|
6
|
+
export const useFacilityRegistry = () => {
|
|
7
|
+
const { data, error, isLoading, mutate } = useSWR<FetchResponse<FacilityRegistryRecord>>(
|
|
8
|
+
facilityRegistryUrl,
|
|
9
|
+
openmrsFetch,
|
|
10
|
+
{
|
|
11
|
+
revalidateOnFocus: false,
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const notYetSynced = error?.response?.status === 404;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
facility: data?.data,
|
|
19
|
+
isLoading,
|
|
20
|
+
error: notYetSynced ? null : error,
|
|
21
|
+
notYetSynced,
|
|
22
|
+
mutate,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const syncFacilityRegistry = async (): Promise<FacilityRegistrySyncResult> => {
|
|
27
|
+
const response = await openmrsFetch<FacilityRegistrySyncResult>(facilityRegistryUrl + '/sync', { method: 'POST' });
|
|
28
|
+
return response.data;
|
|
29
|
+
};
|