From 642773bf72555b492757b22600e14583c9b553cd Mon Sep 17 00:00:00 2001 From: 9sneha-n <9sneha.n@gmail.com> Date: Sun, 6 Oct 2024 23:42:35 +0530 Subject: [PATCH] feat: cases, deaths and risk history charts --- src/CompositionRoot.ts | 10 ++ .../repositories/ChartConfigD2Repository.ts | 53 ++++++ .../test/ChartConfigTestRepository.ts | 15 ++ .../repositories/ChartConfigRepository.ts | 7 + .../usecases/GetChartConfigByTypeUseCase.ts | 19 +++ src/webapp/components/chart/Chart.tsx | 40 +++++ src/webapp/components/chart/useChart.ts | 25 +++ src/webapp/components/map/Map.tsx | 140 ---------------- src/webapp/components/map/MapSection.tsx | 45 ++++- .../visualisation/Visualisation.tsx | 158 +++++++++++++++--- .../pages/event-tracker/EventTrackerPage.tsx | 77 +++++---- 11 files changed, 383 insertions(+), 206 deletions(-) create mode 100644 src/data/repositories/ChartConfigD2Repository.ts create mode 100644 src/data/repositories/test/ChartConfigTestRepository.ts create mode 100644 src/domain/repositories/ChartConfigRepository.ts create mode 100644 src/domain/usecases/GetChartConfigByTypeUseCase.ts create mode 100644 src/webapp/components/chart/Chart.tsx create mode 100644 src/webapp/components/chart/useChart.ts delete mode 100644 src/webapp/components/map/Map.tsx diff --git a/src/CompositionRoot.ts b/src/CompositionRoot.ts index 0d6325e5..006d3611 100644 --- a/src/CompositionRoot.ts +++ b/src/CompositionRoot.ts @@ -42,6 +42,10 @@ import { AlertSyncDataStoreTestRepository } from "./data/repositories/test/Alert import { AlertSyncRepository } from "./domain/repositories/AlertSyncRepository"; import { DataStoreClient } from "./data/DataStoreClient"; import { GetTotalCardCountsUseCase } from "./domain/usecases/GetTotalCardCountsUseCase"; +import { ChartConfigRepository } from "./domain/repositories/ChartConfigRepository"; +import { GetChartConfigByTypeUseCase } from "./domain/usecases/GetChartConfigByTypeUseCase"; +import { ChartConfigTestRepository } from "./data/repositories/test/ChartConfigTestRepository"; +import { ChartConfigD2Repository } from "./data/repositories/ChartConfigD2Repository"; export type CompositionRoot = ReturnType; @@ -56,6 +60,7 @@ type Repositories = { riskAssessmentRepository: RiskAssessmentRepository; mapConfigRepository: MapConfigRepository; performanceOverviewRepository: PerformanceOverviewRepository; + chartConfigRepository: ChartConfigRepository; }; function getCompositionRoot(repositories: Repositories) { @@ -91,6 +96,9 @@ function getCompositionRoot(repositories: Repositories) { getAll: new GetAllOrgUnitsUseCase(repositories.orgUnitRepository), getProvinces: new GetProvincesOrgUnits(repositories.orgUnitRepository), }, + charts: { + getCases: new GetChartConfigByTypeUseCase(repositories.chartConfigRepository), + }, }; } @@ -107,6 +115,7 @@ export function getWebappCompositionRoot(api: D2Api) { riskAssessmentRepository: new RiskAssessmentD2Repository(api), mapConfigRepository: new MapConfigD2Repository(api), performanceOverviewRepository: new PerformanceOverviewD2Repository(api, dataStoreClient), + chartConfigRepository: new ChartConfigD2Repository(dataStoreClient), }; return getCompositionRoot(repositories); @@ -124,6 +133,7 @@ export function getTestCompositionRoot() { riskAssessmentRepository: new RiskAssessmentTestRepository(), mapConfigRepository: new MapConfigTestRepository(), performanceOverviewRepository: new PerformanceOverviewTestRepository(), + chartConfigRepository: new ChartConfigTestRepository(), }; return getCompositionRoot(repositories); diff --git a/src/data/repositories/ChartConfigD2Repository.ts b/src/data/repositories/ChartConfigD2Repository.ts new file mode 100644 index 00000000..17ac2418 --- /dev/null +++ b/src/data/repositories/ChartConfigD2Repository.ts @@ -0,0 +1,53 @@ +import { DataStoreClient } from "../DataStoreClient"; +import { FutureData } from "../api-futures"; +import { ChartConfigRepository } from "../../domain/repositories/ChartConfigRepository"; +import { Id } from "../../domain/entities/Ref"; + +type ChartConfig = { + key: string; + casesId: Id; + deathsId: Id; + riskAssessmentHistoryId: Id; +}; + +const chartConfigDatastoreKey = "charts-config"; + +export class ChartConfigD2Repository implements ChartConfigRepository { + constructor(private dataStoreClient: DataStoreClient) {} + + public getCases(chartKey: string): FutureData { + return this.dataStoreClient + .getObject(chartConfigDatastoreKey) + .map(chartConfigs => { + const currentChart = chartConfigs?.find( + chartConfig => chartConfig.key === chartKey + ); + if (currentChart) return currentChart.casesId; + else throw new Error(`Chart id not found for ${chartKey}`); + }); + } + + public getDeaths(chartKey: string): FutureData { + return this.dataStoreClient + .getObject(chartConfigDatastoreKey) + .map(chartConfigs => { + const currentChart = chartConfigs?.find( + chartConfig => chartConfig.key === chartKey + ); + if (currentChart) return currentChart.deathsId; + else throw new Error(`Chart id not found for ${chartKey}`); + }); + } + + public getRiskAssessmentHistory(chartKey: string): FutureData { + return this.dataStoreClient + .getObject(chartConfigDatastoreKey) + .map(chartConfigs => { + const currentChart = chartConfigs?.find( + chartConfig => chartConfig.key === chartKey + ); + if (currentChart) return currentChart.riskAssessmentHistoryId; + else throw new Error(`Chart id not found for ${chartKey}`); + }); + } +} diff --git a/src/data/repositories/test/ChartConfigTestRepository.ts b/src/data/repositories/test/ChartConfigTestRepository.ts new file mode 100644 index 00000000..8942a9bd --- /dev/null +++ b/src/data/repositories/test/ChartConfigTestRepository.ts @@ -0,0 +1,15 @@ +import { Future } from "../../../domain/entities/generic/Future"; +import { ChartConfigRepository } from "../../../domain/repositories/ChartConfigRepository"; +import { FutureData } from "../../api-futures"; + +export class ChartConfigTestRepository implements ChartConfigRepository { + getRiskAssessmentHistory(_chartKey: string): FutureData { + return Future.success("1"); + } + getCases(_chartkey: string): FutureData { + return Future.success("1"); + } + getDeaths(_chartKey: string): FutureData { + return Future.success("1"); + } +} diff --git a/src/domain/repositories/ChartConfigRepository.ts b/src/domain/repositories/ChartConfigRepository.ts new file mode 100644 index 00000000..5f61084a --- /dev/null +++ b/src/domain/repositories/ChartConfigRepository.ts @@ -0,0 +1,7 @@ +import { FutureData } from "../../data/api-futures"; + +export interface ChartConfigRepository { + getCases(chartkey: string): FutureData; + getDeaths(chartKey: string): FutureData; + getRiskAssessmentHistory(chartKey: string): FutureData; +} diff --git a/src/domain/usecases/GetChartConfigByTypeUseCase.ts b/src/domain/usecases/GetChartConfigByTypeUseCase.ts new file mode 100644 index 00000000..0527ccbd --- /dev/null +++ b/src/domain/usecases/GetChartConfigByTypeUseCase.ts @@ -0,0 +1,19 @@ +import { FutureData } from "../../data/api-futures"; +import { ChartConfigRepository } from "../repositories/ChartConfigRepository"; + +export type ChartType = "deaths" | "cases" | "risk-assessment-history"; +export class GetChartConfigByTypeUseCase { + constructor(private chartConfigRepository: ChartConfigRepository) {} + + public execute(chartType: ChartType, chartKey: string): FutureData { + if (chartType === "deaths") { + return this.chartConfigRepository.getDeaths(chartKey); + } else if (chartType === "cases") { + return this.chartConfigRepository.getCases(chartKey); + } else if (chartType === "risk-assessment-history") { + return this.chartConfigRepository.getRiskAssessmentHistory(chartKey); + } else { + throw new Error(`Invalid chart type: ${chartType}`); + } + } +} diff --git a/src/webapp/components/chart/Chart.tsx b/src/webapp/components/chart/Chart.tsx new file mode 100644 index 00000000..7a8e9580 --- /dev/null +++ b/src/webapp/components/chart/Chart.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { Section } from "../section/Section"; +import { Visualisation } from "../visualisation/Visualisation"; +import { useAppContext } from "../../contexts/app-context"; +import { useChart } from "./useChart"; +import { Maybe } from "../../../utils/ts-utils"; +import LoaderContainer from "../loader/LoaderContainer"; +import { ChartType } from "../../../domain/usecases/GetChartConfigByTypeUseCase"; + +type ChartProps = { + title: string; + chartType: ChartType; + chartKey: Maybe; + hasSeparator?: boolean; + lastUpdated?: string; +}; +export const Chart: React.FC = React.memo(props => { + const { api } = useAppContext(); + const { title, hasSeparator, lastUpdated, chartType, chartKey } = props; + + const { id } = useChart(chartType, chartKey); + + const chartUrl = + chartType === "risk-assessment-history" + ? `${api.baseUrl}/dhis-web-event-visualizer/?id=${id}` + : `${api.baseUrl}/dhis-web-data-visualizer/#/${id}`; + + return ( + +
+ +
+
+ ); +}); diff --git a/src/webapp/components/chart/useChart.ts b/src/webapp/components/chart/useChart.ts new file mode 100644 index 00000000..f803749d --- /dev/null +++ b/src/webapp/components/chart/useChart.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from "react"; +import { useAppContext } from "../../contexts/app-context"; +import { Maybe } from "../../../utils/ts-utils"; +import { ChartType } from "../../../domain/usecases/GetChartConfigByTypeUseCase"; + +export function useChart(chartType: ChartType, chartKey: Maybe) { + const { compositionRoot } = useAppContext(); + const [id, setId] = useState(); + + useEffect(() => { + if (!chartKey) { + return; + } + compositionRoot.charts.getCases.execute(chartType, chartKey).run( + chartId => { + setId(chartId); + }, + error => { + console.error(error); + } + ); + }, [chartKey, chartType, compositionRoot.charts.getCases]); + + return { id }; +} diff --git a/src/webapp/components/map/Map.tsx b/src/webapp/components/map/Map.tsx deleted file mode 100644 index 6f3e16fe..00000000 --- a/src/webapp/components/map/Map.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import { useAppContext } from "../../contexts/app-context"; -import { FilteredMapConfig } from "./useMap"; -import LoaderContainer from "../loader/LoaderContainer"; - -type MapProps = { - config: FilteredMapConfig; -}; - -type State = { - type: "loading" | "loaded"; -}; - -export const Map: React.FC = React.memo(props => { - const { config } = props; - const { api } = useAppContext(); - - const [state, setState] = React.useState({ type: "loading" }); - - const baseUrl = `${api.baseUrl}/api/apps/zebra-custom-maps-app/index.html`; - - const params = { - currentApp: config.currentApp, - currentPage: config.currentPage, - zebraNamespace: config.zebraNamespace, - dashboardDatastoreKey: config.dashboardDatastoreKey, - id: config.mapId, - orgUnits: config.orgUnits.join(","), - programIndicatorId: config.programIndicatorId, - programIndicatorName: config.programIndicatorName, - programId: config.programId, - programName: config.programName, - startDate: config.startDate, - endDate: config.endDate, - timeField: config.timeField, - }; - - const srcUrl = - baseUrl + "?" + new URLSearchParams(removeUndefinedProperties(params)).toString(); - - const iframeRef: React.RefObject = React.createRef(); - - React.useEffect(() => { - const iframe = iframeRef.current; - - if (iframe !== null) { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - iframe.addEventListener("load", async () => { - await setMapStyling(iframe); - setState(prevState => ({ ...prevState, type: "loaded" })); - }); - } - }, [iframeRef]); - - const isLoading = state.type === "loading"; - - return ( - - -
- -
-
-
- ); -}); - -const MapEditorIFrame = styled.iframe``; - -const styles: Record = { - wrapperVisible: { width: "100%", height: "80vh" }, - wrapperHidden: { visibility: "hidden", width: "100%" }, -}; - -function removeUndefinedProperties(obj: T): Partial { - return Object.entries(obj).reduce((acc, [key, value]) => { - return value === undefined ? acc : { ...acc, [key]: value }; - }, {} as Partial); -} - -function waitforDocumentToLoad(iframeDocument: Document, selector: string) { - return new Promise(resolve => { - const check = () => { - if (iframeDocument.querySelector(selector)) { - resolve(undefined); - } else { - setTimeout(check, 1000); - } - }; - check(); - }); -} - -function waitforElementToLoad(element: HTMLElement | Document, selector: string) { - return new Promise(resolve => { - const check = () => { - if (element.querySelector(selector)) { - resolve(undefined); - } else { - setTimeout(check, 1000); - } - }; - check(); - }); -} - -async function setMapStyling(iframe: HTMLIFrameElement) { - if (!iframe.contentWindow) return; - const iframeDocument = iframe.contentWindow.document; - - await waitforDocumentToLoad(iframeDocument, "#dhis2-app-root"); - await waitforElementToLoad(iframeDocument, "header"); - await waitforElementToLoad(iframeDocument, ".dhis2-map-container-wrapper"); - - const iFrameRoot = iframeDocument.querySelector("#dhis2-app-root"); - - iframeDocument.querySelectorAll("header").forEach(el => el.remove()); - iFrameRoot?.querySelectorAll("header").forEach(el => el.remove()); - - iframeDocument.querySelectorAll(".app-menu-container").forEach(el => el.remove()); - iFrameRoot?.querySelectorAll(".app-menu-container").forEach(el => el.remove()); - - iframeDocument.querySelectorAll(".layers-toggle-container").forEach(el => el.remove()); - iFrameRoot?.querySelectorAll(".layers-toggle-container").forEach(el => el.remove()); - - iframeDocument.querySelectorAll(".layers-panel-drawer").forEach(el => el.remove()); - iFrameRoot?.querySelectorAll(".layers-panel-drawer").forEach(el => el.remove()); - - const mapContainerWrapper = iframeDocument.querySelector( - ".dhis2-map-container-wrapper" - ); - if (mapContainerWrapper) mapContainerWrapper.style.inset = "0px"; -} diff --git a/src/webapp/components/map/MapSection.tsx b/src/webapp/components/map/MapSection.tsx index ebf58741..8c966ece 100644 --- a/src/webapp/components/map/MapSection.tsx +++ b/src/webapp/components/map/MapSection.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from "react"; import styled from "styled-components"; import { useSnackbar } from "@eyeseetea/d2-ui-components"; -import { Map } from "./Map"; +import { Visualisation } from "../visualisation/Visualisation"; import { useMap } from "./useMap"; import { MapKey } from "../../../domain/entities/MapConfig"; import LoaderContainer from "../loader/LoaderContainer"; @@ -18,6 +18,7 @@ type MapSectionProps = { }; export const MapSection: React.FC = React.memo(props => { + const { api } = useAppContext(); const { mapKey, singleSelectFilters, @@ -44,27 +45,59 @@ export const MapSection: React.FC = React.memo(props => { eventHazardCode: eventHazardCode, }); + const baseUrl = `${api.baseUrl}/api/apps/zebra-custom-maps-app/index.html`; + const [mapUrl, setMapUrl] = React.useState(); + useEffect(() => { if (mapConfigState.kind === "error") { snackbar.error(mapConfigState.message); + } else if (mapConfigState.kind === "loaded") { + const config = mapConfigState.data; + const params = { + currentApp: config.currentApp, + currentPage: config.currentPage, + zebraNamespace: config.zebraNamespace, + dashboardDatastoreKey: config.dashboardDatastoreKey, + id: config.mapId, + orgUnits: config.orgUnits.join(","), + programIndicatorId: config.programIndicatorId, + programIndicatorName: config.programIndicatorName, + programId: config.programId, + programName: config.programName, + startDate: config.startDate, + endDate: config.endDate, + timeField: config.timeField, + }; + const srcUrl = + baseUrl + "?" + new URLSearchParams(removeUndefinedProperties(params)).toString(); + setMapUrl(srcUrl); } - }, [mapConfigState, snackbar]); + }, [baseUrl, mapConfigState, snackbar]); if (mapConfigState.kind === "error") { return
{mapConfigState.message}
; } + function removeUndefinedProperties(obj: T): Partial { + return Object.entries(obj).reduce((acc, [key, value]) => { + return value === undefined ? acc : { ...acc, [key]: value }; + }, {} as Partial); + } + return ( - {mapConfigState.kind === "loaded" && allProvincesIds.length !== 0 ? ( - ) : null} diff --git a/src/webapp/components/visualisation/Visualisation.tsx b/src/webapp/components/visualisation/Visualisation.tsx index 7f6bf966..733df25c 100644 --- a/src/webapp/components/visualisation/Visualisation.tsx +++ b/src/webapp/components/visualisation/Visualisation.tsx @@ -1,36 +1,142 @@ import React from "react"; -import { VisualizationTypes } from "../../pages/event-tracker/EventTrackerPage"; import styled from "styled-components"; -import { Section } from "../section/Section"; +import LoaderContainer from "../loader/LoaderContainer"; type VisualisationProps = { - type: VisualizationTypes; - title: string; - hasSeparator?: boolean; - lastUpdated?: string; + type: "map" | "chart"; + srcUrl: string; }; + +type State = { + type: "loading" | "loaded"; +}; + export const Visualisation: React.FC = React.memo(props => { - const { title, hasSeparator, lastUpdated } = props; + const { srcUrl, type } = props; + + const [state, setState] = React.useState({ type: "loading" }); + + const iframeRef: React.RefObject = React.createRef(); + + React.useEffect(() => { + const iframe = iframeRef.current; + + if (iframe !== null) { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + iframe.addEventListener("load", async () => { + if (type === "map") { + await setMapStyling(iframe); + setState(prevState => ({ ...prevState, type: "loaded" })); + } else { + await setChartStyling(iframe); + setState(prevState => ({ ...prevState, type: "loaded" })); + } + }); + } + }, [iframeRef, type]); + + const isLoading = state.type === "loading"; + return ( -
- {`Coming soon!`} -
+ + +
+ +
+
+
); }); -const VisualisationContainer = styled.div` - width: 100%; - height: 25rem; - border: 0.1rem solid ${props => props.theme.palette.divider}; - background: ${props => props.theme.palette.background.paper}; - color: ${props => props.theme.palette.text.disabled}; - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 2rem; -`; +const VisualisationIFrame = styled.iframe``; + +const styles: Record = { + wrapperVisible: { width: "100%", height: "80vh" }, + wrapperHidden: { visibility: "hidden", width: "100%" }, +}; + +function waitforDocumentToLoad(iframeDocument: Document, selector: string) { + return new Promise(resolve => { + const check = () => { + if (iframeDocument.querySelector(selector)) { + resolve(undefined); + } else { + setTimeout(check, 1000); + } + }; + check(); + }); +} + +function waitforElementToLoad(element: HTMLElement | Document, selector: string) { + return new Promise(resolve => { + const check = () => { + if (element.querySelector(selector)) { + resolve(undefined); + } else { + setTimeout(check, 1000); + } + }; + check(); + }); +} + +async function setMapStyling(iframe: HTMLIFrameElement) { + if (!iframe.contentWindow) return; + const iframeDocument = iframe.contentWindow.document; + + await waitforDocumentToLoad(iframeDocument, "#dhis2-app-root"); + await waitforElementToLoad(iframeDocument, "header"); + await waitforElementToLoad(iframeDocument, ".dhis2-map-container-wrapper"); + + const iFrameRoot = iframeDocument.querySelector("#dhis2-app-root"); + + iframeDocument.querySelectorAll("header").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll("header").forEach(el => el.remove()); + + iframeDocument.querySelectorAll(".app-menu-container").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll(".app-menu-container").forEach(el => el.remove()); + + iframeDocument.querySelectorAll(".layers-toggle-container").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll(".layers-toggle-container").forEach(el => el.remove()); + + iframeDocument.querySelectorAll(".layers-panel-drawer").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll(".layers-panel-drawer").forEach(el => el.remove()); + + const mapContainerWrapper = iframeDocument.querySelector( + ".dhis2-map-container-wrapper" + ); + if (mapContainerWrapper) mapContainerWrapper.style.inset = "0px"; +} + +async function setChartStyling(iframe: HTMLIFrameElement) { + if (!iframe.contentWindow) return; + const iframeDocument = iframe.contentWindow.document; + + await waitforDocumentToLoad(iframeDocument, "#dhis2-app-root"); + await waitforElementToLoad(iframeDocument, "header"); + await waitforElementToLoad(iframeDocument, ".data-visualizer-app"); + + const iFrameRoot = iframeDocument.querySelector("#dhis2-app-root"); + + iframeDocument.querySelectorAll("header").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll("header").forEach(el => el.remove()); + + iframeDocument.querySelectorAll(".main-left").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll(".main-left").forEach(el => el.remove()); + + iframeDocument.querySelectorAll(".section-toolbar").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll(".section-toolbar").forEach(el => el.remove()); + + iframeDocument.querySelectorAll(".main-center-layout").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll(".main-center-layout").forEach(el => el.remove()); + + iframeDocument.querySelectorAll(".main-center-titlebar").forEach(el => el.remove()); + iFrameRoot?.querySelectorAll(".main-center-titlebar").forEach(el => el.remove()); +} diff --git a/src/webapp/pages/event-tracker/EventTrackerPage.tsx b/src/webapp/pages/event-tracker/EventTrackerPage.tsx index 85b1484b..10aab740 100644 --- a/src/webapp/pages/event-tracker/EventTrackerPage.tsx +++ b/src/webapp/pages/event-tracker/EventTrackerPage.tsx @@ -7,7 +7,7 @@ import { AddCircleOutline, EditOutlined } from "@material-ui/icons"; import i18n from "../../../utils/i18n"; import { Layout } from "../../components/layout/Layout"; import { FormSummary } from "../../components/form/form-summary/FormSummary"; -import { Visualisation } from "../../components/visualisation/Visualisation"; +import { Chart } from "../../components/chart/Chart"; import { Section } from "../../components/section/Section"; import { BasicTable, TableColumn } from "../../components/table/BasicTable"; import { getDateAsLocaleDateTimeString } from "../../../data/repositories/utils/DateTimeHelper"; @@ -18,16 +18,7 @@ import { MapSection } from "../../components/map/MapSection"; import LoaderContainer from "../../components/loader/LoaderContainer"; import { useMapFilters } from "./useMapFilters"; import { DateRangePicker } from "../../components/date-picker/DateRangePicker"; - -// TODO: Add every section here -export type VisualizationTypes = - | "ALL_EVENTS_MAP" - | "EVENT_TRACKER_AREAS_AFFECTED_MAP" - | "RISK_ASSESSMENT_HISTORY_LINE_CHART" - | "EVENT_TRACKER_CASES_BAR_CHART" - | "EVENT_TRACKER_DEATHS_BAR_CHART" - | "EVENT_TRACKER_OVERVIEW_CARDS" - | "EVENT_TRACKER_717_CARDS"; +import { NoticeBox } from "../../components/notice-box/NoticeBox"; //TO DO : Create Risk assessment section export const riskAssessmentColumns: TableColumn[] = [ @@ -52,6 +43,7 @@ export const EventTrackerPage: React.FC = React.memo(() => { useDiseaseOutbreakEvent(id); const { changeCurrentEventTracker: changeCurrentEventTrackerId, getCurrentEventTracker } = useCurrentEventTracker(); + const currentEventTracker = getCurrentEventTracker(); const { dateRangeFilter } = useMapFilters(); @@ -90,14 +82,14 @@ export const EventTrackerPage: React.FC = React.memo(() => { @@ -131,28 +123,45 @@ export const EventTrackerPage: React.FC = React.memo(() => { ) } titleVariant="secondary" - lastUpdated={lastUpdated} > - + {riskAssessmentRows.length > 0 ? ( + + ) : ( + + {i18n.t("Risks associated with this event have not yet been assessed.")} + + )} + {!!currentEventTracker?.riskAssessment?.grading?.length && ( + + )} + + +
+ +
- - - - - ); });