@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.
Files changed (69) hide show
  1. package/.turbo/turbo-build.log +79 -0
  2. package/README.md +7 -0
  3. package/dist/1.js +1 -0
  4. package/dist/1.js.map +1 -0
  5. package/dist/144.js +2 -0
  6. package/dist/144.js.LICENSE.txt +19 -0
  7. package/dist/144.js.map +1 -0
  8. package/dist/225.js +1 -0
  9. package/dist/225.js.map +1 -0
  10. package/dist/300.js +1 -0
  11. package/dist/372.js +1 -0
  12. package/dist/372.js.map +1 -0
  13. package/dist/405.js +1 -0
  14. package/dist/405.js.map +1 -0
  15. package/dist/41.js +2 -0
  16. package/dist/41.js.LICENSE.txt +9 -0
  17. package/dist/41.js.map +1 -0
  18. package/dist/507.js +2 -0
  19. package/dist/507.js.LICENSE.txt +30 -0
  20. package/dist/507.js.map +1 -0
  21. package/dist/831.js +2 -0
  22. package/dist/831.js.LICENSE.txt +5 -0
  23. package/dist/831.js.map +1 -0
  24. package/dist/909.js +2 -0
  25. package/dist/909.js.LICENSE.txt +9 -0
  26. package/dist/909.js.map +1 -0
  27. package/dist/913.js +2 -0
  28. package/dist/913.js.LICENSE.txt +32 -0
  29. package/dist/913.js.map +1 -0
  30. package/dist/kenyaemr-esm-facility-dashboard-app.js +1 -0
  31. package/dist/kenyaemr-esm-facility-dashboard-app.js.buildmanifest.json +365 -0
  32. package/dist/kenyaemr-esm-facility-dashboard-app.js.map +1 -0
  33. package/dist/main.js +2 -0
  34. package/dist/main.js.LICENSE.txt +9 -0
  35. package/dist/main.js.map +1 -0
  36. package/dist/routes.json +1 -0
  37. package/jest.config.js +8 -0
  38. package/package.json +55 -0
  39. package/src/above-site/above-site-dashboard.component.tsx +32 -0
  40. package/src/above-site/above-site.scss +13 -0
  41. package/src/components/empty-state/empty-state-log.components.tsx +20 -0
  42. package/src/components/empty-state/empty-state-log.scss +28 -0
  43. package/src/components/empty-state/empty-state-log.test.tsx +24 -0
  44. package/src/components/header/header-illustration.component.tsx +13 -0
  45. package/src/components/header/header.component.tsx +34 -0
  46. package/src/components/header/header.scss +72 -0
  47. package/src/components/side-menu/left-panel.scss +42 -0
  48. package/src/components/side-menu/left-pannel.component.tsx +25 -0
  49. package/src/config-schema.ts +16 -0
  50. package/src/constants.ts +15 -0
  51. package/src/declarations.d.ts +6 -0
  52. package/src/facility-dashboard.resource.ts +0 -0
  53. package/src/hooks/useFacilityDashboardSurveillance.ts +29 -0
  54. package/src/index.ts +28 -0
  55. package/src/left-pannel-link.component.tsx +47 -0
  56. package/src/root.component.tsx +34 -0
  57. package/src/root.scss +12 -0
  58. package/src/routes.json +26 -0
  59. package/src/setup-tests.ts +1 -0
  60. package/src/surveillance/summary-cards/summary-card.component.tsx +30 -0
  61. package/src/surveillance/summary-cards/summary-card.scss +49 -0
  62. package/src/surveillance/summary-cards/surveillance-summary-cards.component.tsx +78 -0
  63. package/src/surveillance/surveillance-dashboard.component.tsx +17 -0
  64. package/src/surveillance/surveillance-filters.component.tsx +22 -0
  65. package/src/surveillance/surveillance.scss +16 -0
  66. package/src/types/index.ts +13 -0
  67. package/translations/en.json +22 -0
  68. package/tsconfig.json +5 -0
  69. package/webpack.config.js +1 -0
package/jest.config.js ADDED
@@ -0,0 +1,8 @@
1
+ const rootConfig = require('../../jest.config.js');
2
+
3
+ const packageConfig = {
4
+ ...rootConfig,
5
+ collectCoverage: false,
6
+ };
7
+
8
+ module.exports = packageConfig;
package/package.json ADDED
@@ -0,0 +1,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,13 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+
5
+ .dashboard {
6
+ width: 100%;
7
+ height: 85vh;
8
+ padding: 0 layout.$spacing-05 0 layout.$spacing-05;
9
+ }
10
+
11
+ .supersetBtn {
12
+ margin-left: layout.$spacing-05;
13
+ }
@@ -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}>&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
+ };
@@ -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
+ };
@@ -0,0 +1,6 @@
1
+ declare module '@carbon/react';
2
+ declare module '*.css';
3
+ declare module '*.scss';
4
+ declare module '*.png';
5
+
6
+ declare type SideNavProps = object;
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
+ }
@@ -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;