@kenyaemr/esm-facility-dashboard-app 5.4.1-pre.1867
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 +79 -0
- package/README.md +7 -0
- package/dist/1.js +1 -0
- package/dist/1.js.map +1 -0
- package/dist/144.js +2 -0
- package/dist/144.js.LICENSE.txt +19 -0
- package/dist/144.js.map +1 -0
- package/dist/225.js +1 -0
- package/dist/225.js.map +1 -0
- package/dist/300.js +1 -0
- package/dist/372.js +1 -0
- package/dist/372.js.map +1 -0
- package/dist/405.js +1 -0
- package/dist/405.js.map +1 -0
- package/dist/41.js +2 -0
- package/dist/41.js.LICENSE.txt +9 -0
- package/dist/41.js.map +1 -0
- package/dist/507.js +2 -0
- package/dist/507.js.LICENSE.txt +30 -0
- package/dist/507.js.map +1 -0
- package/dist/831.js +2 -0
- package/dist/831.js.LICENSE.txt +5 -0
- package/dist/831.js.map +1 -0
- package/dist/909.js +2 -0
- package/dist/909.js.LICENSE.txt +9 -0
- package/dist/909.js.map +1 -0
- package/dist/913.js +2 -0
- package/dist/913.js.LICENSE.txt +32 -0
- package/dist/913.js.map +1 -0
- package/dist/kenyaemr-esm-facility-dashboard-app.js +1 -0
- package/dist/kenyaemr-esm-facility-dashboard-app.js.buildmanifest.json +365 -0
- package/dist/kenyaemr-esm-facility-dashboard-app.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +9 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +8 -0
- package/package.json +55 -0
- package/src/above-site/above-site-dashboard.component.tsx +32 -0
- package/src/above-site/above-site.scss +13 -0
- package/src/components/empty-state/empty-state-log.components.tsx +20 -0
- package/src/components/empty-state/empty-state-log.scss +28 -0
- package/src/components/empty-state/empty-state-log.test.tsx +24 -0
- package/src/components/header/header-illustration.component.tsx +13 -0
- package/src/components/header/header.component.tsx +34 -0
- package/src/components/header/header.scss +72 -0
- package/src/components/side-menu/left-panel.scss +42 -0
- package/src/components/side-menu/left-pannel.component.tsx +25 -0
- package/src/config-schema.ts +16 -0
- package/src/constants.ts +15 -0
- package/src/declarations.d.ts +6 -0
- package/src/facility-dashboard.resource.ts +0 -0
- package/src/hooks/useFacilityDashboardSurveillance.ts +29 -0
- package/src/index.ts +28 -0
- package/src/left-pannel-link.component.tsx +47 -0
- package/src/root.component.tsx +34 -0
- package/src/root.scss +12 -0
- package/src/routes.json +26 -0
- package/src/setup-tests.ts +1 -0
- package/src/surveillance/summary-cards/summary-card.component.tsx +30 -0
- package/src/surveillance/summary-cards/summary-card.scss +49 -0
- package/src/surveillance/summary-cards/surveillance-summary-cards.component.tsx +78 -0
- package/src/surveillance/surveillance-dashboard.component.tsx +17 -0
- package/src/surveillance/surveillance-filters.component.tsx +22 -0
- package/src/surveillance/surveillance.scss +16 -0
- package/src/types/index.ts +13 -0
- package/translations/en.json +22 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kenyaemr/esm-facility-dashboard-app",
|
|
3
|
+
"version": "5.4.1-pre.1867",
|
|
4
|
+
"description": "Facility dashboard app",
|
|
5
|
+
"browser": "dist/kenyaemr-esm-facility-dashboard-app.js",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"source": true,
|
|
8
|
+
"license": "MPL-2.0",
|
|
9
|
+
"homepage": "https://github.com/palladiumkenya/kenyaemr-esm-3.x#readme",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "openmrs develop",
|
|
12
|
+
"serve": "webpack serve --mode=development",
|
|
13
|
+
"debug": "npm run serve",
|
|
14
|
+
"build": "webpack --mode production",
|
|
15
|
+
"analyze": "webpack --mode=production --env.analyze=true",
|
|
16
|
+
"lint": "eslint src --ext ts,tsx",
|
|
17
|
+
"typescript": "tsc",
|
|
18
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js",
|
|
19
|
+
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests",
|
|
20
|
+
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js",
|
|
21
|
+
"coverage": "yarn test --coverage"
|
|
22
|
+
},
|
|
23
|
+
"browserslist": [
|
|
24
|
+
"extends browserslist-config-openmrs"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"openmrs"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/palladiumkenya/kenyaemr-esm-3.x#readme"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/palladiumkenya/kenyaemr-esm-3.x/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"lodash-es": "^4.17.15",
|
|
41
|
+
"react-to-print": "^2.14.13"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@carbon/react": "1.x",
|
|
45
|
+
"@openmrs/esm-framework": "5.x",
|
|
46
|
+
"react": "^18.1.0",
|
|
47
|
+
"react-i18next": "11.x",
|
|
48
|
+
"react-router-dom": "6.x",
|
|
49
|
+
"swr": "2.x"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"webpack": "^5.74.0"
|
|
53
|
+
},
|
|
54
|
+
"stableVersion": "5.4.0"
|
|
55
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Button, Layer } from '@carbon/react';
|
|
2
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import FacilityDashboardHeader from '../components/header/header.component';
|
|
6
|
+
import { FacilityDashboardConfigObject } from '../config-schema';
|
|
7
|
+
import styles from './above-site.scss';
|
|
8
|
+
|
|
9
|
+
const AboveSiteDashboard = () => {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
const { facilityDashboardAboveSiteUrl } = useConfig<FacilityDashboardConfigObject>();
|
|
12
|
+
return (
|
|
13
|
+
<Layer>
|
|
14
|
+
<FacilityDashboardHeader title={t('aboveSiteFacilityDashboard', 'Above site facility Dashboard')} />
|
|
15
|
+
|
|
16
|
+
<Button
|
|
17
|
+
className={styles.supersetBtn}
|
|
18
|
+
onClick={() => {
|
|
19
|
+
window.open(facilityDashboardAboveSiteUrl, '_blank');
|
|
20
|
+
}}>
|
|
21
|
+
{t('viewOnSuperset', 'View on Superset')}
|
|
22
|
+
</Button>
|
|
23
|
+
|
|
24
|
+
<iframe
|
|
25
|
+
className={styles.dashboard}
|
|
26
|
+
src={`${facilityDashboardAboveSiteUrl}?standalone=true`}
|
|
27
|
+
title={t('aboveSiteDashboard', 'Above Site Dashboard')}></iframe>
|
|
28
|
+
</Layer>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default AboveSiteDashboard;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import styles from './empty-state-log.scss';
|
|
4
|
+
import { DataEnrichment } from '@carbon/react/icons';
|
|
5
|
+
import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
|
|
6
|
+
|
|
7
|
+
interface EmptyStateProps {
|
|
8
|
+
subTitle: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const EmptyState: React.FC<EmptyStateProps> = ({ subTitle }) => {
|
|
12
|
+
return (
|
|
13
|
+
<div className={styles.emptyStateContainer}>
|
|
14
|
+
<EmptyDataIllustration />
|
|
15
|
+
<p className={styles.subTitle}>{subTitle}</p>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default EmptyState;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.emptyStateContainer {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
min-height: 300px;
|
|
11
|
+
background-color: colors.$gray-10;
|
|
12
|
+
row-gap: layout.$spacing-02;
|
|
13
|
+
|
|
14
|
+
& form {
|
|
15
|
+
border: none;
|
|
16
|
+
}
|
|
17
|
+
.subTitle {
|
|
18
|
+
@include type.type-style('body-compact-01');
|
|
19
|
+
color: colors.$cool-gray-70;
|
|
20
|
+
margin-top: layout.$spacing-04;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
svg.iconOverrides {
|
|
25
|
+
width: layout.$spacing-11;
|
|
26
|
+
height: layout.$spacing-11;
|
|
27
|
+
fill: var(--brand-03);
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
|
|
4
|
+
import EmptyState from './empty-state-log.components';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
// Mock the EmptyDataIllustration component
|
|
8
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
9
|
+
EmptyDataIllustration: jest.fn(() => <div>Mocked EmptyDataIllustration</div>),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('EmptyState', () => {
|
|
13
|
+
it('renders the EmptyState component with the given subtitle', () => {
|
|
14
|
+
const testSubtitle = 'No data available';
|
|
15
|
+
|
|
16
|
+
render(<EmptyState subTitle={testSubtitle} />);
|
|
17
|
+
|
|
18
|
+
const subtitleElement = screen.getByText(testSubtitle);
|
|
19
|
+
expect(subtitleElement).toBeInTheDocument();
|
|
20
|
+
|
|
21
|
+
const illustrationElement = screen.getByText('Mocked EmptyDataIllustration');
|
|
22
|
+
expect(illustrationElement).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './header.scss';
|
|
3
|
+
import { ChartColumn } from '@carbon/react/icons';
|
|
4
|
+
|
|
5
|
+
const FacilityDashboardIllustration: React.FC = () => {
|
|
6
|
+
return (
|
|
7
|
+
<div className={styles.svgContainer}>
|
|
8
|
+
<ChartColumn className={styles.iconOveriders} />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default FacilityDashboardIllustration;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Calendar, Location } from '@carbon/react/icons';
|
|
2
|
+
import { formatDate, useSession } from '@openmrs/esm-framework';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import FacilityDashboardIllustration from './header-illustration.component';
|
|
6
|
+
import styles from './header.scss';
|
|
7
|
+
|
|
8
|
+
const FacilityDashboardHeader: React.FC<{ title: string }> = ({ title }) => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const userSession = useSession();
|
|
11
|
+
const userLocation = userSession?.sessionLocation?.display;
|
|
12
|
+
return (
|
|
13
|
+
<div className={styles.header}>
|
|
14
|
+
<div className={styles.leftJustifiedItems}>
|
|
15
|
+
<FacilityDashboardIllustration />
|
|
16
|
+
<div className={styles.pageLabels}>
|
|
17
|
+
<p>{t('facilitydashboard', 'Facility Dashboard')}</p>
|
|
18
|
+
<p className={styles.pageName}>{title}</p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div className={styles.rightJustifiedItems}>
|
|
22
|
+
<div className={styles.dateAndLocation}>
|
|
23
|
+
<Location size={16} />
|
|
24
|
+
<span className={styles.value}>{userLocation}</span>
|
|
25
|
+
<span className={styles.middot}>·</span>
|
|
26
|
+
<Calendar size={16} />
|
|
27
|
+
<span className={styles.value}>{formatDate(new Date(), { mode: 'standard' })}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default FacilityDashboardHeader;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
5
|
+
|
|
6
|
+
.header {
|
|
7
|
+
@include type.type-style('body-compact-02');
|
|
8
|
+
color: $text-02;
|
|
9
|
+
height: layout.$spacing-12;
|
|
10
|
+
background-color: $ui-02;
|
|
11
|
+
border: 1px solid $ui-03;
|
|
12
|
+
border-left: 0px;
|
|
13
|
+
display: flex;
|
|
14
|
+
justify-content: space-between;
|
|
15
|
+
margin-bottom: 2rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.leftJustifiedItems {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: row;
|
|
21
|
+
align-items: center;
|
|
22
|
+
margin-left: 0.75rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.rightJustifiedItems {
|
|
26
|
+
@include type.type-style('body-compact-02');
|
|
27
|
+
color: $text-02;
|
|
28
|
+
padding-top: 1rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.pageName {
|
|
32
|
+
@include type.type-style('heading-04');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.pageLabels {
|
|
36
|
+
margin-left: 1rem;
|
|
37
|
+
|
|
38
|
+
p:first-of-type {
|
|
39
|
+
margin-bottom: 0.25rem;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dateAndLocation {
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: flex-end;
|
|
46
|
+
align-items: center;
|
|
47
|
+
margin-right: 1rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.value {
|
|
51
|
+
margin-left: 0.25rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.middot {
|
|
55
|
+
margin: 0 layout.$spacing-03;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.view {
|
|
59
|
+
@include type.type-style('label-01');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
svg.iconOverrides {
|
|
63
|
+
width: 72 !important;
|
|
64
|
+
height: 72 !important;
|
|
65
|
+
fill: var(--brand-03);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.svgContainer svg {
|
|
69
|
+
width: 72px;
|
|
70
|
+
height: 72px;
|
|
71
|
+
fill: var(--brand-03);
|
|
72
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.leftPanel {
|
|
6
|
+
border-right: 1px solid colors.$gray-20;
|
|
7
|
+
padding-top: layout.$spacing-05;
|
|
8
|
+
|
|
9
|
+
// Left nav menu item
|
|
10
|
+
:global(.cds--side-nav__link) {
|
|
11
|
+
color: colors.$gray-70;
|
|
12
|
+
@include type.type-style('heading-compact-01');
|
|
13
|
+
|
|
14
|
+
&:focus,
|
|
15
|
+
&:hover {
|
|
16
|
+
background-color: colors.$gray-10-hover;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Active menu item
|
|
20
|
+
&:global(.active-left-nav-link) {
|
|
21
|
+
color: colors.$gray-100;
|
|
22
|
+
border-left: layout.$spacing-02 solid var(--brand-01);
|
|
23
|
+
outline: none;
|
|
24
|
+
padding: 0 layout.$spacing-04;
|
|
25
|
+
background-color: colors.$gray-20;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Desktop */
|
|
31
|
+
:global(.omrs-breakpoint-gt-tablet) {
|
|
32
|
+
:global(.cds--side-nav__link) {
|
|
33
|
+
height: layout.$spacing-07;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Tablet */
|
|
38
|
+
:global(.omrs-breakpoint-lt-desktop) {
|
|
39
|
+
:global(.cds--side-nav__link) {
|
|
40
|
+
height: layout.$spacing-09;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { SideNav } from '@carbon/react';
|
|
4
|
+
import { attach, ExtensionSlot, isDesktop, useLayoutType } from '@openmrs/esm-framework';
|
|
5
|
+
import styles from './left-panel.scss';
|
|
6
|
+
|
|
7
|
+
attach('nav-menu-slot', 'admin-left-panel');
|
|
8
|
+
|
|
9
|
+
const LeftPanel: React.FC = () => {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
const layout = useLayoutType();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
isDesktop(layout) && (
|
|
15
|
+
<SideNav
|
|
16
|
+
aria-label={t('facilitydashboardLeftPannel', 'facility Dashboard Left Panel')}
|
|
17
|
+
className={styles.leftPanel}
|
|
18
|
+
expanded>
|
|
19
|
+
<ExtensionSlot name="facility-dashboard-left-panel-slot" />
|
|
20
|
+
</SideNav>
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default LeftPanel;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Type } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export const configSchema = {
|
|
4
|
+
facilityDashboardAboveSiteUrl: {
|
|
5
|
+
_type: Type.String,
|
|
6
|
+
_description: 'Facility dashboard link for superset analytics',
|
|
7
|
+
// links for testing above site superset dashbord embeding
|
|
8
|
+
// _default: 'https://odoosuperset.kenyahmis.org/superset/dashboard/11/', // Actual facility link
|
|
9
|
+
_default: 'https://superset.datatest.ch/superset/dashboard/10/', // Public accessible test link
|
|
10
|
+
// _default: 'http://192.168.1.102:8088/superset/dashboard/1/', // Local test instance link
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type FacilityDashboardConfigObject = {
|
|
15
|
+
facilityDashboardAboveSiteUrl: string;
|
|
16
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const moduleName = '@kenyaemr/esm-facility-dashboard-app';
|
|
2
|
+
export const etlBasePath = `${window.spaBase}`;
|
|
3
|
+
|
|
4
|
+
export const today = () => {
|
|
5
|
+
const date = new Date();
|
|
6
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const DATE_PICKER_CONTROL_FORMAT = 'd/m/Y';
|
|
10
|
+
|
|
11
|
+
export const DATE_PICKER_FORMAT = 'DD/MM/YYYY';
|
|
12
|
+
|
|
13
|
+
export const formatNewDate = (date: Date | null | undefined) => {
|
|
14
|
+
return date ? new Date(date) : '';
|
|
15
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { IndicationMode, type SurveillanceSummary } from '../types';
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
|
+
|
|
6
|
+
const useFacilityDashboardSurveillance = () => {
|
|
7
|
+
const url = `${restBaseUrl}/kenyaemr/facility-dashboard`;
|
|
8
|
+
const { data, error, isLoading, mutate } = useSWR<FetchResponse<SurveillanceSummary>>(url, openmrsFetch);
|
|
9
|
+
const getIndication = useCallback((indicator: number, denominator: number, threshold: number): IndicationMode => {
|
|
10
|
+
if (denominator === 0) {
|
|
11
|
+
return 'decreasing';
|
|
12
|
+
}
|
|
13
|
+
if (indicator > denominator * threshold) {
|
|
14
|
+
return 'increasing';
|
|
15
|
+
} else {
|
|
16
|
+
return 'decreasing';
|
|
17
|
+
}
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
surveillanceSummary: data?.data,
|
|
22
|
+
getIndication,
|
|
23
|
+
isLoading,
|
|
24
|
+
mutate,
|
|
25
|
+
error,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default useFacilityDashboardSurveillance;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineConfigSchema, getSyncLifecycle } from '@openmrs/esm-framework';
|
|
2
|
+
import { configSchema } from './config-schema';
|
|
3
|
+
import { moduleName } from './constants';
|
|
4
|
+
import Root from './root.component';
|
|
5
|
+
import { createLeftPanelLink } from './left-pannel-link.component';
|
|
6
|
+
|
|
7
|
+
const options = {
|
|
8
|
+
featureName: 'esm-facility-dashboard-app',
|
|
9
|
+
moduleName,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
13
|
+
|
|
14
|
+
export const root = getSyncLifecycle(Root, options);
|
|
15
|
+
|
|
16
|
+
export function startupApp() {
|
|
17
|
+
defineConfigSchema(moduleName, configSchema);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const surveillanceDashboardLink = getSyncLifecycle(
|
|
21
|
+
createLeftPanelLink({ title: 'Surveillance', name: '' }),
|
|
22
|
+
options,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const aboveSiteDashboardLink = getSyncLifecycle(
|
|
26
|
+
createLeftPanelLink({ title: 'Above site Dashboard', name: 'above-site' }),
|
|
27
|
+
options,
|
|
28
|
+
);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import last from 'lodash-es/last';
|
|
3
|
+
import { BrowserRouter, useLocation } from 'react-router-dom';
|
|
4
|
+
import { ConfigurableLink } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
export interface LinkConfig {
|
|
7
|
+
name: string;
|
|
8
|
+
title: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function LinkExtension({ config }: { config: LinkConfig }) {
|
|
12
|
+
const { name, title } = config;
|
|
13
|
+
const location = useLocation();
|
|
14
|
+
|
|
15
|
+
let urlSegment = useMemo(() => decodeURIComponent(last(location.pathname.split('/'))), [location.pathname]);
|
|
16
|
+
|
|
17
|
+
const isUUID = (value) => {
|
|
18
|
+
const regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
|
|
19
|
+
return regex.test(value);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (isUUID(urlSegment)) {
|
|
23
|
+
urlSegment = 'facility-dashboard';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getName = (name: string) => {
|
|
27
|
+
if (!name) {
|
|
28
|
+
return 'facility-dashboard';
|
|
29
|
+
}
|
|
30
|
+
return name;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<ConfigurableLink
|
|
35
|
+
to={`${window.getOpenmrsSpaBase()}facility-dashboard${name && name !== 'facility-dashboard' ? `/${name}` : ''}`}
|
|
36
|
+
className={`cds--side-nav__link ${getName(name) === urlSegment && 'active-left-nav-link'}`}>
|
|
37
|
+
{title}
|
|
38
|
+
</ConfigurableLink>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const createLeftPanelLink = (config: LinkConfig) => () =>
|
|
43
|
+
(
|
|
44
|
+
<BrowserRouter>
|
|
45
|
+
<LinkExtension config={config} />
|
|
46
|
+
</BrowserRouter>
|
|
47
|
+
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { setLeftNav, unsetLeftNav } from '@openmrs/esm-framework';
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
4
|
+
import LeftPanel from './components/side-menu/left-pannel.component';
|
|
5
|
+
import styles from './root.scss';
|
|
6
|
+
import SurveillanceDashboard from './surveillance/surveillance-dashboard.component';
|
|
7
|
+
import AboveSiteDashboard from './above-site/above-site-dashboard.component';
|
|
8
|
+
|
|
9
|
+
const Root: React.FC = () => {
|
|
10
|
+
const spaBasePath = window.spaBase;
|
|
11
|
+
const facilityDashboardBaseName = window.getOpenmrsSpaBase() + 'facility-dashboard';
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setLeftNav({
|
|
15
|
+
name: 'facility-dashboard-left-panel-slot',
|
|
16
|
+
basePath: spaBasePath,
|
|
17
|
+
});
|
|
18
|
+
return () => unsetLeftNav('facility-dashboard-left-panel-slot');
|
|
19
|
+
}, [spaBasePath]);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<BrowserRouter basename={facilityDashboardBaseName}>
|
|
23
|
+
<LeftPanel />
|
|
24
|
+
<main className={styles.container}>
|
|
25
|
+
<Routes>
|
|
26
|
+
<Route path="/" element={<SurveillanceDashboard />} />
|
|
27
|
+
<Route path="/above-site" element={<AboveSiteDashboard />} />
|
|
28
|
+
</Routes>
|
|
29
|
+
</main>
|
|
30
|
+
</BrowserRouter>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default Root;
|
package/src/root.scss
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
background-color: colors.$white-0;
|
|
7
|
+
height: calc(100vh - layout.$spacing-09);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
:global(.omrs-breakpoint-gt-tablet) .container {
|
|
11
|
+
margin-left: var(--omrs-sidenav-width);
|
|
12
|
+
}
|
package/src/routes.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
3
|
+
"backendDependencies": {
|
|
4
|
+
"kenyaemrCharts": "^1.6.7"
|
|
5
|
+
},
|
|
6
|
+
"extensions": [
|
|
7
|
+
{
|
|
8
|
+
"component": "surveillanceDashboardLink",
|
|
9
|
+
"name": "surveillance-dashboard-link",
|
|
10
|
+
"slot": "facility-dashboard-left-panel-slot"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"component": "aboveSiteDashboardLink",
|
|
14
|
+
"name": "above-site-dashboard-link",
|
|
15
|
+
"slot": "facility-dashboard-left-panel-slot"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"workspaces": [],
|
|
19
|
+
"modals": [],
|
|
20
|
+
"pages": [
|
|
21
|
+
{
|
|
22
|
+
"component": "root",
|
|
23
|
+
"route": "facility-dashboard"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Tile } from '@carbon/react';
|
|
2
|
+
import { ArrowUp, ArrowDown } from '@carbon/react/icons';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import styles from './summary-card.scss';
|
|
5
|
+
import { type IndicationMode } from '../../types';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
title: string;
|
|
9
|
+
value?: string;
|
|
10
|
+
header?: string;
|
|
11
|
+
mode?: IndicationMode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const SummaryCard: React.FC<Props> = ({ title, value, header, mode }) => {
|
|
15
|
+
return (
|
|
16
|
+
<Tile className={styles.summaryCard}>
|
|
17
|
+
<span>
|
|
18
|
+
{mode === 'increasing' && <ArrowUp className={styles.upIcon} />}
|
|
19
|
+
{mode === 'decreasing' && <ArrowDown className={styles.downIcon} />}
|
|
20
|
+
{mode === 'increasing' && <ArrowUp className={styles.upIcon} />}
|
|
21
|
+
{mode === 'decreasing' && <ArrowDown className={styles.downIcon} />}
|
|
22
|
+
</span>
|
|
23
|
+
{header && <strong>{header}</strong>}
|
|
24
|
+
<h4>{value ?? '--'}</h4>
|
|
25
|
+
<p>{title}</p>
|
|
26
|
+
</Tile>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default SummaryCard;
|