Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [CGC-53] Handle missing growth variables #56

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
16 changes: 14 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ 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-04-23T10:55:58.423Z\n"
"PO-Revision-Date: 2024-04-23T10:55:58.423Z\n"
"POT-Creation-Date: 2024-04-24T11:33:57.683Z\n"
"PO-Revision-Date: 2024-04-24T11:33:57.683Z\n"

msgid "Growth Chart"
msgstr "Growth Chart"
Expand All @@ -17,12 +17,24 @@ msgstr "There was an error fetching the config for the growth chart."
msgid "Please check the configuration in Datastore Management and try again."
msgstr "Please check the configuration in Datastore Management and try again."

msgid "There was an error fetching the custom references for the growth chart."
msgstr "There was an error fetching the custom references for the growth chart."

msgid "No growth variables were found."
msgstr "No growth variables were found."

msgid "Please add growth variables and try again."
msgstr "Please add growth variables and try again."

msgid "Date"
msgstr "Date"

msgid "Age"
msgstr "Age"

msgid "Year"
msgstr "Year"

msgid "Years"
msgstr "Years"

Expand Down
23 changes: 16 additions & 7 deletions src/Plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ import { useMappedTrackedEntityVariables } from './utils/DataFetching/Sorting/us
import { ChartConfigError } from './UI/GenericError/ChartConfigError';
import { GenericLoading } from './UI/GenericLoading';
import { useCustomReferences } from './utils/DataFetching/Hooks/useCustomReferences';
import { chartData } from './DataSets/WhoStandardDataSets/ChartData';
import { chartData as chartDataWHO } from './DataSets/WhoStandardDataSets/ChartData';
import { CustomReferencesError } from './UI/GenericError/CustomReferencesError';
import { useFilterByMissingData } from './utils/DataFetching/Sorting';
import { MissingGrowthVariablesError } from './UI/GenericError/MissingGrowthVariablesError';

const queryClient = new QueryClient();

const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
const { chartConfig, isLoading, isError } = useChartConfig();
const { customReferences, isLoading: isLoadingRef, isError: isErrorRef } = useCustomReferences();
const { teiId, programId, orgUnitId } = propsFromParent;
const { teiId, programId } = propsFromParent;
const { trackedEntity } = useTeiById({ teiId });
const { events } = useEvents({
orgUnitId,
programStageId: chartConfig?.metadata.program.programStageId,
const { events, isLoading: isLoadingEvents } = useEvents({
programId,
teiId,
});
Expand All @@ -42,11 +42,16 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
isWeightInGrams: chartConfig?.settings.weightInGrams || false,
});

const { chartData, measurementDataExist } = useFilterByMissingData(
mappedGrowthVariables,
chartConfig && customReferences && chartConfig?.settings.customReferences ? customReferences : chartDataWHO,
);

const isPercentiles = chartConfig?.settings.usePercentiles || false;

const [open, setOpen] = useState(true);

if (isLoading || isLoadingRef) {
if (isLoading || isLoadingRef || isLoadingEvents) {
return <GenericLoading />;
}

Expand All @@ -58,6 +63,10 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
return <CustomReferencesError />;
}

if (measurementDataExist.headCircumference === false && measurementDataExist.height === false && measurementDataExist.weight === false) {
return <MissingGrowthVariablesError />;
}

return (
<QueryClientProvider
client={queryClient}
Expand All @@ -83,7 +92,7 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
<GrowthChart
trackedEntity={mappedTrackedEntity}
measurementData={mappedGrowthVariables}
chartData={chartConfig.settings.customReferences ? customReferences : chartData}
chartData={chartData}
isPercentiles={isPercentiles}
/>
</WidgetCollapsible>
Expand Down
33 changes: 33 additions & 0 deletions src/UI/GenericError/EventError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useState } from 'react';
import i18n from '@dhis2/d2-i18n';
import { WidgetCollapsible } from '../../components/WidgetCollapsible';
import { Warning } from '../Icons';

export const ChartConfigError = () => {
const [open, setOpen] = useState(true);
return (
<div style={{
width: '100vw',
margin: 0,
padding: 0,
}}
>
<WidgetCollapsible
header={i18n.t('Growth Chart')}
borderless={false}
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
>
<div className='flex justify-center'>
<Warning className='w-12 h-12' />
<p className='flex p-5 pt-2'>
{i18n.t('There was an error fetching the event.')}
<br />
{i18n.t('Please check the configuration in Datastore Management application and try again.')}
</p>
</div>
</WidgetCollapsible>
</div>
);
};
33 changes: 33 additions & 0 deletions src/UI/GenericError/MissingGrowthVariablesError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useState } from 'react';
import i18n from '@dhis2/d2-i18n';
import { WidgetCollapsible } from '../../components/WidgetCollapsible';
import { Warning } from '../Icons';

export const MissingGrowthVariablesError = () => {
const [open, setOpen] = useState(true);
return (
<div style={{
width: '100vw',
margin: 0,
padding: 0,
}}
>
<WidgetCollapsible
header={i18n.t('Growth Chart')}
borderless={false}
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
>
<div className='flex justify-center'>
<Warning className='w-12 h-12' />
<p className='flex p-5 pt-2'>
{i18n.t('No growth variables were found.')}
<br />
{i18n.t('Please add growth variables and try again.')}
</p>
</div>
</WidgetCollapsible>
</div>
);
};
1 change: 0 additions & 1 deletion src/components/GrowthChart/GrowthChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const GrowthChart = ({
chartData,
}: GrowthChartProps) => {
const trackedEntityGender = trackedEntity?.gender;

const [gender, setGender] = useState<string>(trackedEntityGender !== undefined ? trackedEntityGender : GenderCodes.CGC_Female);
const { chartDataForGender } = useChartDataForGender({ gender, chartData });

Expand Down
6 changes: 3 additions & 3 deletions src/types/chartDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import i18n from '@dhis2/d2-i18n';
export interface MeasurementData {
eventDate: string;
dataValues: {
weight: string;
headCircumference: string;
height: string;
weight: number;
headCircumference: number;
height: number;
};
}

Expand Down
15 changes: 2 additions & 13 deletions src/utils/DataFetching/Hooks/useEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { ServerEvent } from '../../../types/Event.types';
import { RequestedEntities, handleAPIResponse } from './handleAPIResponse';

type UseEventsByProgramStageProps = {
programStageId: string | undefined;
orgUnitId: string | undefined;
programId: string | undefined;
teiId: string | undefined;
};
Expand Down Expand Up @@ -38,38 +36,29 @@ interface UseEventsByProgramStageReturn {
}

export const useEvents = ({
orgUnitId,
programId,
programStageId,
teiId,
}: UseEventsByProgramStageProps): UseEventsByProgramStageReturn => {
const dataEngine = useDataEngine();
const {
data,
isLoading,
isError,
} = useQuery(['eventsByProgramStage', orgUnitId, programStageId, programId, teiId], (): any => dataEngine.query({
} = useQuery(['eventsByProgramStage', programId, teiId], (): any => dataEngine.query({
eventsByProgramStage: {
resource: 'tracker/events',
params: ({
orgUnitId,
programStageId,
programId,
teiId,
}) => ({
fields: 'event,status,program,dataValues,occurredAt,programStage',
program: programId,
programStage: programStageId,
orgUnit: orgUnitId,
trackedEntity: teiId,
}),
},
}, {
variables: {
orgUnitId,
programStageId,
programId,
teiId,
programId,
},
}), { staleTime: 5000 });

Expand Down
1 change: 1 addition & 0 deletions src/utils/DataFetching/Sorting/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { useChartDataForGender } from './useChartDataForGender';
export { useFilterByMissingData } from './useFilterByMissingData';
56 changes: 56 additions & 0 deletions src/utils/DataFetching/Sorting/useFilterByMissingData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useMemo } from 'react';
import { ChartData, MeasurementData } from '../../../types/chartDataTypes';

export const useFilterByMissingData = (measurementData: MeasurementData[], chartData: ChartData) => {
if (!chartData || !measurementData) {
return {};
}
const requiredData = Object.freeze({
hcfa_b: { headCircumference: true },
hcfa_g: { headCircumference: true },
lhfa_b: { height: true },
lhfa_g: { height: true },
wfa_b: { weight: true },
wfa_g: { weight: true },
wflh_b: { weight: true, height: true },
wflh_g: { weight: true, height: true },
});

const measurementDataExist = useMemo(() => {
if (!measurementData) {
return {};
}
return {
weight: measurementData?.some((entry) => entry.dataValues.weight !== undefined),
headCircumference: measurementData?.some((entry) => entry.dataValues.headCircumference !== undefined),
height: measurementData?.some((entry) => entry.dataValues.height !== undefined),
};
}, [measurementData]);

const filteredChartData = useMemo(() => {
if (!chartData) {
return {};
}
const filteredData = Object.entries(chartData).reduce(
(acc: ChartData, [key, value]: [keyof typeof requiredData, any]) => {
const requiredMeasurements = requiredData[key];
if (requiredMeasurements) {
const allRequiredMeasurementsExist = Object.entries(requiredMeasurements).every(
([measurementKey, required]: [keyof typeof measurementDataExist, boolean]) =>
!required || measurementDataExist[measurementKey],
);
if (!allRequiredMeasurementsExist) {
return acc;
}
}
acc[key] = value;
return acc;
},
{},
);

return filteredData;
}, [chartData, measurementDataExist, requiredData]);

return { chartData: filteredChartData, measurementDataExist };
};
22 changes: 11 additions & 11 deletions src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { Event } from '../Hooks/useEvents';

interface UseMappedGrowthVariablesProps {
events: Event[];
growthVariables: { [key: string]: string };
growthVariables: { [key: string]: number | undefined };
isWeightInGrams: boolean;
}

export interface MappedDataValue {
eventDate: string;
dataValues: {
weight: string;
headCircumference: string;
height: string;
weight: number;
headCircumference: number;
height: number;
};
}

Expand All @@ -21,17 +21,17 @@ export const useMappedGrowthVariables = ({
isWeightInGrams,
}: UseMappedGrowthVariablesProps): MappedDataValue[] | undefined => {
const mappedData = events?.map((event: Event) => {
const dataValueMap: { weight: string; headCircumference: string; height: string; [key: string]: string } = {
weight: '',
headCircumference: '',
height: '',
const dataValueMap: { weight: number; headCircumference: number; height: number; [key: string]: number } = {
weight: undefined,
headCircumference: undefined,
height: undefined,
};

if (growthVariables && event.dataValues) {
Object.entries(growthVariables).reduce((acc, [key, value]: [string, string]) => {
const dataValue = String(Object.entries(event.dataValues).find(([dataElement]) => dataElement === value)?.[1]);
Object.entries(growthVariables).reduce((acc, [key, value]: [string, number | undefined]) => {
const dataValue = Number(Object.entries(event.dataValues).find(([dataElement]) => dataElement === String(value))?.[1]);
if (dataValue && value) {
acc[key] = (key === 'weight' && (isWeightInGrams || Number(dataValue) > 1000)) ? String(Number(dataValue) / 1000) : dataValue;
acc[key] = (key === 'weight' && (isWeightInGrams || dataValue > 1000)) ? dataValue / 1000 : dataValue;
}
return acc;
}, dataValueMap);
Expand Down
Loading