Skip to content
Merged
113 changes: 75 additions & 38 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,51 @@ 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: 2025-09-17T10:34:29.970Z\n"
"PO-Revision-Date: 2025-09-17T10:34:29.970Z\n"

msgid ""
"Unable to load growth data. Please check that the configured programs and "
"stages exist and are accessible."
msgstr ""
"Unable to load growth data. Please check that the configured programs and "
"stages exist and are accessible."
"POT-Creation-Date: 2026-03-22T09:03:05.690Z\n"
"PO-Revision-Date: 2026-03-22T09:03:05.690Z\n"

msgid "Growth Chart"
msgstr "Growth Chart"

msgid "Growth Chart Configuration Issues"
msgstr "Growth Chart Configuration Issues"
msgid "How to Fix This Issue"
msgstr "How to Fix This Issue"

msgid "Use "
msgstr "Use "

msgid " to open namespace "
msgstr " to open namespace "

msgid "Go to the Datastore Management app in DHIS2"
msgstr "Go to the Datastore Management app in DHIS2"
msgid " and key "
msgstr " and key "

msgid "Find the namespace \"captureGrowthChart\" and key \"config\""
msgstr "Find the namespace \"captureGrowthChart\" and key \"config\""
msgid ". Edit the "
msgstr ". Edit the "

msgid " to address the errors above, save, and refresh this page."
msgstr " to address the errors above, save, and refresh this page."

msgid ""
"If you need to add or change attributes, program stage, data elements, or "
"option codes in DHIS2, "
msgstr ""
"If you need to add or change attributes, program stage, data elements, or "
"option codes in DHIS2, "

msgid "Update the configuration JSON to fix the issues listed above"
msgstr "Update the configuration JSON to fix the issues listed above"
msgid "do that in "
msgstr "do that in "

msgid "Save the changes and refresh this page"
msgstr "Save the changes and refresh this page"
msgid " first. Afterwards, update "
msgstr " first. Afterwards, update "

msgid " so your "
msgstr " so your "

msgid " and "
msgstr " and "

msgid " match what you configured."
msgstr " match what you configured."

msgid "Need help?"
msgstr "Need help?"
Expand All @@ -53,19 +71,6 @@ msgstr ""
"There was an error fetching the custom references for the growth chart. "
"Please check the configuration in Datastore Management and try again."

msgid "The default indicator"
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 "There was an error fetching the event."
msgstr "There was an error fetching the event."

Expand All @@ -76,12 +81,6 @@ msgstr ""
"Please check the configuration in Datastore Management application and try "
"again."

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 "Print"
msgstr "Print"

Expand Down Expand Up @@ -165,3 +164,41 @@ msgstr "Boy"

msgid "Girl"
msgstr "Girl"

msgid "Date of birth"
msgstr "Date of birth"

msgid "No value on this profile. Age-based axes and categories may be wrong."
msgstr "No value on this profile. Age-based axes and categories may be wrong."

msgid "Gender"
msgstr "Gender"

msgid ""
"No value on this profile. Reference curves default until gender is "
"available."
msgstr ""
"No value on this profile. Reference curves default until gender is "
"available."

msgid ""
"Could not load profile data for this person. Please try again or check your "
"access."
msgstr ""
"Could not load profile data for this person. Please try again or check your "
"access."

msgid ""
"Could not load growth measurements for this record. Please try again or "
"check your access."
msgstr ""
"Could not load growth measurements for this record. Please try again or "
"check your access."

msgctxt "Application title"
msgid "__MANIFEST_APP_TITLE"
msgstr "Capture Growth Chart"

msgctxt "Application description"
msgid "__MANIFEST_APP_DESCRIPTION"
msgstr "A plugin for displaying growth charts in the Capture app"
183 changes: 97 additions & 86 deletions src/Plugin.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';
import './Plugin.css';
import './tailwind.css';
import './index.css';
Expand All @@ -7,31 +7,29 @@ import i18n from '@dhis2/d2-i18n';
import { WidgetCollapsible } from './components/WidgetCollapsible';
import { GrowthChart } from './components/GrowthChart/GrowthChart';
import { EnrollmentOverviewProps } from './Plugin.types';
import { useChartConfig, useTrackedEntityForProgram } from './utils/DataFetching/Hooks';
import { useChartConfig, usePluginErrorHandling, useProgramTrackedEntityAttributes } from './utils/DataFetching/Hooks';
import { useEvents } from './utils/DataFetching/Hooks/useEvents';
import { useProgramStageDataElements } from './utils/DataFetching/Hooks/useProgramStageDataElements';
import { useGenderAttributeOptionCodes } from './utils/DataFetching/Hooks/useGenderAttributeOptionCodes';
import { useProgramStageMappingValidation } from './utils/DataFetching/Hooks/useProgramStageMappingValidation';
import { useConfigValidation } from './utils/DataFetching/Hooks/useConfigValidation';
import { useRuntimeValidation } from './utils/DataFetching/Hooks/useRuntimeValidation';
import { useMappedGrowthVariables } from './utils/DataFetching/Sorting/useMappedGrowthVariables';
import { useMappedTrackedEntityVariables } from './utils/DataFetching/Sorting/useMappedTrackedEntity';
import { GenericLoading } from './UI/GenericLoading';
import { useCustomReferences } from './utils/DataFetching/Hooks/useCustomReferences';
import { useTrackedEntityInstanceAttributes } from './utils/DataFetching/Hooks/useTrackedEntityInstanceAttributes';
import { useMappedTrackedEntityVariables } from './utils/DataFetching/Sorting/useMappedTrackedEntity';
import { useInvalidProfileValidation } from './utils/DataFetching/Hooks/useInvalidProfileValidation';
import { InvalidProfileValidationWarning } from './UI/InvalidProfileValidationWarning';
import { chartData as chartDataWHO } from './DataSets/WhoStandardDataSets/ChartData';
import { useFilterByMissingData } from './utils/DataFetching/Sorting';
import { MissingGrowthVariablesError } from './UI/GenericError/MissingGrowthVariablesError';
import { ConfigError, CustomReferenceError, DefaultIndicatorError } from './UI/FeedbackComponents';
import { GenericError } from './UI/GenericError';
import { ConfigValidationError } from './UI/ConfigValidationError';

const queryClient = new QueryClient();

const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
const [defaultIndicatorError, setDefaultIndicatorError] = useState<boolean>(false);
const [open, setOpen] = useState(true);

const {
teiId,
orgUnitId,
programId,
teiId,
} = propsFromParent;

const {
Expand All @@ -46,16 +44,13 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
isError: isErrorRef,
} = useCustomReferences(chartConfig?.settings?.customReferences || false);

const configValidation = useConfigValidation(chartConfig, isLoading, isError);
const runtimeValidation = useRuntimeValidation(teiId, orgUnitId);

const {
trackedEntity,
isLoading: isLoadingTei,
isError: isErrorTei,
} = useTrackedEntityForProgram({ teiId, programId });
programTrackedEntityAttributeIds,
isLoading: isLoadingProgramTrackedEntityAttributes,
isError: isErrorProgramTrackedEntityAttributes,
} = useProgramTrackedEntityAttributes(programId);

console.log('trackedEntity', trackedEntity);
const runtimeValidation = useRuntimeValidation(isErrorProgramTrackedEntityAttributes);

const {
events,
Expand All @@ -64,15 +59,42 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
} = useEvents({
Comment thread
henrikmv marked this conversation as resolved.
programStageId: chartConfig?.metadata?.programStageForGrowthChart?.[programId],
programId,
orgUnitId,
teiId,
});

const mappedTrackedEntity = useMappedTrackedEntityVariables({
variableMappings: chartConfig?.metadata?.attributes,
attributes: trackedEntity?.attributes,
const programStageId = chartConfig?.metadata?.programStageForGrowthChart?.[programId];
const {
programStageDataElementIds,
isLoading: isLoadingProgramStage,
isError: isErrorProgramStage,
} = useProgramStageDataElements(programStageId);

const genderAttributeId = chartConfig?.metadata?.attributes?.gender;
const {
genderOptionCodes,
isLoading: isLoadingGenderOptions,
} = useGenderAttributeOptionCodes(genderAttributeId);

const {
programStageIdsByProgramId,
programIdsFailedToLoad,
isLoading: isLoadingProgramMappings,
} = useProgramStageMappingValidation(chartConfig?.metadata?.programStageForGrowthChart);

const configValidation = useConfigValidation(chartConfig, isLoading, isError, {
programTrackedEntityAttributeIds,
isLoadingProgramTrackedEntityAttributes,
programStageDataElementIds,
isLoadingProgramStage,
isErrorProgramStage,
genderOptionCodes,
isLoadingGenderOptions,
programStageIdsByProgramId,
programIdsFailedToLoad,
isLoadingProgramMappings,
parentProgramId: programId,
});

const mappedGrowthVariables = useMappedGrowthVariables({
growthVariables: chartConfig?.metadata?.dataElements ? {
headCircumference: chartConfig.metadata.dataElements.headCircumference,
Expand All @@ -83,64 +105,47 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
isWeightInGrams: chartConfig?.settings?.weightInGrams || false,
});

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

const isPercentiles = chartConfig?.settings?.usePercentiles || false;
const defaultIndicator = chartConfig?.settings?.defaultIndicator || 'wfa';

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

if (!configValidation.isValid || !runtimeValidation.isValid) {
return (
<ConfigValidationError
errors={[...configValidation.errors, ...runtimeValidation.errors]}
warnings={configValidation.warnings}
/>
);
}

if (isError) {
return (
<ConfigError />
);
}

if (isErrorEvents) {
return (
<GenericError
errorMessage={i18n.t('Unable to load growth data. Please check that the configured programs and stages exist and are accessible.')}
/>
);
}

if (chartConfig?.settings?.customReferences && isErrorRef) {
return (
<CustomReferenceError />
);
}

if (defaultIndicatorError) {
return (
<DefaultIndicatorError defaultIndicator={defaultIndicator} />
);
}

if (isErrorTei) {
return (
<GenericError
errorMessage={i18n.t('Unable to load tracked entity data. Please check that the program attributes are accessible.')}
/>
);
}

if (measurementDataExist.headCircumference === false && measurementDataExist.height === false && measurementDataExist.weight === false) {
return <MissingGrowthVariablesError />;
}
const {
attributes: teiAttributes,
isLoading: isLoadingTeiAttributes,
isError: isErrorTeiAttributes,
} = useTrackedEntityInstanceAttributes({
teiId,
programId,
});

const mappedTrackedEntity = useMappedTrackedEntityVariables({
attributes: teiAttributes,
variableMappings: chartConfig?.metadata?.attributes,
});

const invalidProfileValidation = useInvalidProfileValidation(mappedTrackedEntity);

const errorView = usePluginErrorHandling({
isLoading,
isLoadingRef,
isLoadingProgramTrackedEntityAttributes,
isLoadingEvents,
isLoadingTeiAttributes,
isErrorTeiAttributes,
isErrorEvents,
configValidation,
runtimeValidation,
isError,
customReferencesEnabled: chartConfig?.settings?.customReferences,
isErrorRef,
});

return (
<QueryClientProvider client={queryClient}>
Expand All @@ -152,14 +157,20 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => {
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
>
<GrowthChart
trackedEntity={mappedTrackedEntity}
measurementData={mappedGrowthVariables}
chartData={chartData}
defaultIndicator={defaultIndicator}
isPercentiles={isPercentiles}
setDefaultIndicatorError={setDefaultIndicatorError}
/>
{errorView ?? (
<>
{!invalidProfileValidation.isValid && (
<InvalidProfileValidationWarning warnings={invalidProfileValidation.warnings} />
)}
<GrowthChart
trackedEntity={mappedTrackedEntity}
measurementData={mappedGrowthVariables ?? []}
chartData={chartData}
defaultIndicator={defaultIndicator}
isPercentiles={isPercentiles}
/>
</>
)}
</WidgetCollapsible>
</div>
</QueryClientProvider>
Expand Down
1 change: 0 additions & 1 deletion src/Plugin.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export type EnrollmentOverviewProps = {
programId: string;
orgUnitId: string;
enrollmentId: string;
teiId: string;
programStageId?: string;
Expand Down
Loading
Loading