Skip to content

Commit

Permalink
feat: [CGC-65] Automatic time interval selection (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
edvinstava authored Sep 20, 2024
1 parent d9472ca commit ee37c00
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 31 deletions.
15 changes: 11 additions & 4 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"

Expand Down
9 changes: 8 additions & 1 deletion src/Plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -84,7 +85,7 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
);
}

if (isErrorTei || isErrorEvents) {
if (isErrorEvents) {
return (
<GenericError
errorMessage={i18n.t('Failed to load data. Please check that you have selected the correct programStageId in the configuration.')}
Expand All @@ -104,6 +105,12 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
);
}

if (isErrorTei) {
return (
<TrackedEntityError />
);
}

return (
<QueryClientProvider client={queryClient}>
<div className='bg-white w-screen flex m-0 p-0'>
Expand Down
10 changes: 10 additions & 0 deletions src/UI/FeedbackComponents/TrackedEntityError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import i18n from '@dhis2/d2-i18n';
import { GenericError } from '../GenericError';

export const TrackedEntityError = () => (
<GenericError
/* eslint-disable-next-line max-len */
errorMessage={i18n.t('There was an error fetching the tracked entity for the growth chart. Please check the configuration in Datastore Management and try again.')}
/>
);
54 changes: 28 additions & 26 deletions src/components/GrowthChart/GrowthChart.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -25,38 +27,38 @@ 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<string>(trackedEntityGender !== undefined ? trackedEntityGender : GenderCodes.CGC_Female);
const { chartDataForGender } = useChartDataForGender({
gender,
chartData,
});

const [category, setCategory] = useState<keyof typeof CategoryCodes>();
const [dataset, setDataset] = useState<string>();

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;
Expand All @@ -82,8 +84,8 @@ export const GrowthChart = ({
<>
<div className='flex justify-between px-14'>
<ChartSelector
category={category}
dataset={dataset}
category={selectedCategory}
dataset={selectedDataset}
setCategory={setCategory}
setDataset={setDataset}
chartData={chartDataForGender}
Expand All @@ -93,8 +95,8 @@ export const GrowthChart = ({
/>
<div>
<ChartSettingsButton
category={category}
dataset={dataset}
category={selectedCategory}
dataset={selectedDataset}
gender={gender}
trackedEntity={trackedEntity}
/>
Expand All @@ -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}
/>
</div>
Expand Down
83 changes: 83 additions & 0 deletions src/utils/Hooks/Calculations/useAppropriateChartData.ts
Original file line number Diff line number Diff line change
@@ -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<keyof typeof CategoryCodes>();
const [selectedDataset, setSelectedDataset] = useState<string>();

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,
};
};

0 comments on commit ee37c00

Please sign in to comment.