diff --git a/i18n/en.pot b/i18n/en.pot index a13b6c7..f89f6be 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,15 +5,15 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-07-31T11:25:42.922Z\n" -"PO-Revision-Date: 2024-07-31T11:25:42.922Z\n" +"POT-Creation-Date: 2024-09-20T08:45:15.376Z\n" +"PO-Revision-Date: 2024-09-20T08:45:15.376Z\n" msgid "" "Failed to load data. Please check that you have selected the correct " -"program stage ID in the configuration." +"programStageId in the configuration." msgstr "" "Failed to load data. Please check that you have selected the correct " -"program stage ID in the configuration." +"programStageId in the configuration." msgid "Growth Chart" msgstr "Growth Chart" @@ -38,6 +38,13 @@ msgstr "The default indicator" msgid "is not a valid. Please select a valid indicator in the configuration" msgstr "is not a valid. Please select a valid indicator in the configuration" +msgid "" +"There was an error fetching the tracked entity for the growth chart. Please " +"check the configuration in Datastore Management and try again." +msgstr "" +"There was an error fetching the tracked entity for the growth chart. Please " +"check the configuration in Datastore Management and try again." + msgid "Print" msgstr "Print" diff --git a/src/Plugin.tsx b/src/Plugin.tsx index 8836e6e..f775474 100644 --- a/src/Plugin.tsx +++ b/src/Plugin.tsx @@ -14,6 +14,7 @@ import { GenericLoading } from './UI/GenericLoading'; import { useCustomReferences } from './utils/DataFetching/Hooks/useCustomReferences'; import { chartData } from './DataSets/WhoStandardDataSets/ChartData'; import { ConfigError, CustomReferenceError, DefaultIndicatorError } from './UI/FeedbackComponents'; +import { TrackedEntityError } from './UI/FeedbackComponents/TrackedEntityError'; import { GenericError } from './UI/GenericError'; const queryClient = new QueryClient(); @@ -84,7 +85,7 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { ); } - if (isErrorTei || isErrorEvents) { + if (isErrorEvents) { return ( { ); } + if (isErrorTei) { + return ( + + ); + } + return (
diff --git a/src/UI/FeedbackComponents/TrackedEntityError.tsx b/src/UI/FeedbackComponents/TrackedEntityError.tsx new file mode 100644 index 0000000..43a36a0 --- /dev/null +++ b/src/UI/FeedbackComponents/TrackedEntityError.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { GenericError } from '../GenericError'; + +export const TrackedEntityError = () => ( + +); diff --git a/src/components/GrowthChart/GrowthChart.tsx b/src/components/GrowthChart/GrowthChart.tsx index 9b6a7ca..60022ef 100644 --- a/src/components/GrowthChart/GrowthChart.tsx +++ b/src/components/GrowthChart/GrowthChart.tsx @@ -1,11 +1,13 @@ import React, { useEffect, useMemo, useState } from 'react'; +import { differenceInMonths, differenceInWeeks } from 'date-fns'; import { GrowthChartBuilder } from './GrowthChartBuilder'; import { ChartSelector } from './GrowthChartSelector'; -import { CategoryCodes, ChartData, GenderCodes, MeasurementData } from '../../types/chartDataTypes'; +import { ChartData, GenderCodes, MeasurementData } from '../../types/chartDataTypes'; import { useCalculateMinMaxValues } from '../../utils/Hooks/Calculations'; import { ChartSettingsButton } from './ChartSettingsButton'; import { useChartDataForGender } from '../../utils/DataFetching/Sorting'; import { MappedEntityValues } from '../../utils/DataFetching/Sorting/useMappedTrackedEntity'; +import { useAppropriateChartData } from '../../utils/Hooks/Calculations/useAppropriateChartData'; interface GrowthChartProps { trackedEntity: MappedEntityValues; @@ -25,6 +27,9 @@ export const GrowthChart = ({ setDefaultIndicatorError, }: GrowthChartProps) => { const trackedEntityGender = trackedEntity?.gender; + const dateOfBirth = useMemo(() => new Date(trackedEntity.dateOfBirth), [trackedEntity.dateOfBirth]); + const childAgeInWeeks = useMemo(() => differenceInWeeks(new Date(), dateOfBirth), [dateOfBirth]); + const childAgeInMonths = useMemo(() => differenceInMonths(new Date(), dateOfBirth), [dateOfBirth]); const [gender, setGender] = useState(trackedEntityGender !== undefined ? trackedEntityGender : GenderCodes.CGC_Female); const { chartDataForGender } = useChartDataForGender({ @@ -32,31 +37,28 @@ export const GrowthChart = ({ chartData, }); - const [category, setCategory] = useState(); - const [dataset, setDataset] = useState(); - - const isKeyOfCategoryCodes = (key: string): key is keyof typeof CategoryCodes => key in CategoryCodes; - - useEffect(() => { - const key = `${defaultIndicator}_${gender.charAt(0).toLowerCase()}`; - if (!isKeyOfCategoryCodes(key)) { - setDefaultIndicatorError(true); - } - if (isKeyOfCategoryCodes(key) && chartDataForGender[key]) { - const newCategory = CategoryCodes[key]; - setCategory(newCategory); - const newDataset = Object.keys(chartDataForGender[newCategory].datasets)[0]; - setDataset(newDataset); - } - }, [chartDataForGender, defaultIndicator, gender, setDefaultIndicatorError]); + const { + selectedCategory, + selectedDataset, + setSelectedCategory: setCategory, + setSelectedDataset: setDataset, + } = useAppropriateChartData( + chartDataForGender, + defaultIndicator, + gender, + setDefaultIndicatorError, + childAgeInWeeks, + childAgeInMonths, + ); useEffect(() => { - if (trackedEntity && Object.values(GenderCodes).includes(trackedEntity.gender)) { + if (trackedEntity && Object.values(GenderCodes) + .includes(trackedEntity.gender)) { setGender(trackedEntity.gender); } }, [trackedEntity]); - const dataSetEntry = chartDataForGender[category]?.datasets[dataset]; + const dataSetEntry = chartDataForGender[selectedCategory]?.datasets[selectedDataset]; const dataSetValues = isPercentiles ? dataSetEntry?.percentileDatasetValues : dataSetEntry?.zScoreDatasetValues; const dataSetMetadata = dataSetEntry?.metadata; @@ -82,8 +84,8 @@ export const GrowthChart = ({ <>
@@ -109,8 +111,8 @@ export const GrowthChart = ({ yAxisValues={yAxisValues} keysDataSet={keysDataSet} dateOfBirth={new Date(trackedEntity?.dateOfBirth)} - category={category} - dataset={dataset} + category={selectedCategory} + dataset={selectedDataset} isPercentiles={isPercentiles} />
diff --git a/src/utils/Hooks/Calculations/useAppropriateChartData.ts b/src/utils/Hooks/Calculations/useAppropriateChartData.ts new file mode 100644 index 0000000..668f410 --- /dev/null +++ b/src/utils/Hooks/Calculations/useAppropriateChartData.ts @@ -0,0 +1,83 @@ +import { useEffect, useRef, useState } from 'react'; +import { CategoryCodes, ChartData, MeasurementTypeCodesLabel, TimeUnitCodes } from '../../../types/chartDataTypes'; + +export const useAppropriateChartData = ( + chartDataForGender: ChartData, + defaultIndicator: string, + gender: string, + setDefaultIndicatorError: (value: boolean) => void, + childAgeInWeeks: number, + childAgeInMonths: number, +) => { + const [selectedCategory, setSelectedCategory] = useState(); + const [selectedDataset, setSelectedDataset] = useState(); + + const selectDatasetForCategoryRef = useRef<(category: keyof typeof CategoryCodes) => void>(); + selectDatasetForCategoryRef.current = (category: keyof typeof CategoryCodes) => { + const { datasets } = chartDataForGender[category]; + + const isMeasurementType = (xAxis: string) => + Object.values(MeasurementTypeCodesLabel) + .includes(xAxis); + + const isWeeksInRange = (xAxis: string) => + xAxis === TimeUnitCodes.weeks && childAgeInWeeks < 13; + + const isMonthsInRange = (xAxis: string, range: { start: number, end: number }) => + xAxis === TimeUnitCodes.months && childAgeInMonths >= range.start && childAgeInMonths < range.end; + + const getMaxRangeDataset = (datasets: ChartData[0]['datasets']) => + Object.entries(datasets) + .reduce((max, [key, value]) => + ((!max || value.metadata.range.end > max[1].metadata.range.end) ? [key, value] : max)); + + const isAboveRange = (xAxis: string, range: { start: number, end: number }) => + xAxis === TimeUnitCodes.months && childAgeInMonths >= range.end; + Object.entries(datasets) + .some(([key, value]) => { + const { range } = value.metadata; + const xAxis = value.metadata.xAxisLabel; + + if (isMeasurementType(xAxis) || isWeeksInRange(xAxis) || isMonthsInRange(xAxis, range)) { + setSelectedDataset((prevDataset) => (prevDataset !== key ? key : prevDataset)); + return true; + } + + if (isAboveRange(xAxis, range)) { + const [newDatasetKey] = getMaxRangeDataset(datasets); + setSelectedDataset(newDatasetKey); + return true; + } + return false; + }); + }; + + useEffect(() => { + if (selectedCategory && chartDataForGender[selectedCategory]) { + selectDatasetForCategoryRef.current?.(selectedCategory); + } + }, [selectedCategory, chartDataForGender]); + + const isKeyOfCategoryCodes = (key: string): key is keyof typeof CategoryCodes => key in CategoryCodes; + + useEffect(() => { + const key = `${defaultIndicator}_${gender.charAt(0) + .toLowerCase()}`; + if (!isKeyOfCategoryCodes(key)) { + setDefaultIndicatorError(true); + } + if (isKeyOfCategoryCodes(key) && chartDataForGender[key]) { + const newCategory = CategoryCodes[key]; + setSelectedCategory(newCategory); + const newDataset = Object.keys(chartDataForGender[newCategory].datasets)[0]; + setSelectedDataset(newDataset); + } + }, [chartDataForGender, defaultIndicator, gender, setDefaultIndicatorError]); + + return { + selectedCategory, + selectedDataset, + setSelectedCategory, + setSelectedDataset, + }; +};