From 40a835f042db7d7558689f2dbb76d44f170ba757 Mon Sep 17 00:00:00 2001 From: edvinstava Date: Thu, 21 Mar 2024 13:30:27 +0100 Subject: [PATCH 1/6] fix: Improved useEvents query --- src/Plugin.tsx | 8 +++----- src/utils/DataFetching/Hooks/useEvents.ts | 17 +++-------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Plugin.tsx b/src/Plugin.tsx index a2c1f37..b445131 100644 --- a/src/Plugin.tsx +++ b/src/Plugin.tsx @@ -10,7 +10,7 @@ import { EnrollmentOverviewProps } from './Plugin.types'; import { useTeiById } from './utils/DataFetching/Hooks'; import { useChartConfig } from './utils/DataFetching/Hooks/useChartConfig'; import { useMappedGrowthVariables } from './utils/DataFetching/Sorting/useMappedGrowthVariables'; -import { useEventsByProgramStage } from './utils/DataFetching/Hooks/useEvents'; +import { useEvents } from './utils/DataFetching/Hooks/useEvents'; import { useMappedTrackedEntityVariables } from './utils/DataFetching/Sorting/useMappedTrackedEntity'; import { ChartConfigError } from './UI/GenericError/ChartConfigError'; import { GenericLoading } from './UI/GenericLoading'; @@ -19,11 +19,9 @@ const queryClient = new QueryClient(); const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { const { chartConfig, isLoading, isError } = useChartConfig(); - const { teiId, programId, orgUnitId } = propsFromParent; + const { teiId, programId } = propsFromParent; const { trackedEntity } = useTeiById({ teiId }); - const { events } = useEventsByProgramStage({ - orgUnitId, - programStageId: chartConfig?.metadata.program.programStageId, + const { events } = useEvents({ programId, teiId, }); diff --git a/src/utils/DataFetching/Hooks/useEvents.ts b/src/utils/DataFetching/Hooks/useEvents.ts index 9bec8b6..46f1539 100644 --- a/src/utils/DataFetching/Hooks/useEvents.ts +++ b/src/utils/DataFetching/Hooks/useEvents.ts @@ -5,8 +5,6 @@ import { convertDataElementToValues } from '../Convert'; import { ServerEvent } from '../../../types/Event.types'; type UseEventsByProgramStageProps = { - programStageId: string | undefined; - orgUnitId: string | undefined; programId: string | undefined; teiId: string | undefined; }; @@ -36,10 +34,8 @@ interface UseEventsByProgramStageReturn { stageHasEvents: boolean; } -export const useEventsByProgramStage = ({ - orgUnitId, +export const useEvents = ({ programId, - programStageId, teiId, }: UseEventsByProgramStageProps): UseEventsByProgramStageReturn => { const dataEngine = useDataEngine(); @@ -47,28 +43,21 @@ export const useEventsByProgramStage = ({ 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 }); From 6855cc2c12072c376aa7bb59f5644888a8efec8a Mon Sep 17 00:00:00 2001 From: edvinstava Date: Thu, 21 Mar 2024 18:31:18 +0100 Subject: [PATCH 2/6] feat: basic functioning filter for missing growth variables --- src/Components/GrowthChart/GrowthChart.tsx | 17 +++--- src/types/chartDataTypes.ts | 6 +-- .../Sorting/useMappedGrowthVariables.ts | 22 ++++---- src/utils/Hooks/index.ts | 1 + src/utils/Hooks/useFilterByMissingData.ts | 53 +++++++++++++++++++ 5 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 src/utils/Hooks/index.ts create mode 100644 src/utils/Hooks/useFilterByMissingData.ts diff --git a/src/Components/GrowthChart/GrowthChart.tsx b/src/Components/GrowthChart/GrowthChart.tsx index 5af70d6..48a9294 100644 --- a/src/Components/GrowthChart/GrowthChart.tsx +++ b/src/Components/GrowthChart/GrowthChart.tsx @@ -8,6 +8,7 @@ import { GrowthChartAnnotations } from './GrowthChartOptions'; import { ChartSettingsButton } from './ChartSettingsButton'; import { useChartDataForGender } from '../../utils/DataFetching/Sorting/useChartDataForGender'; import { MappedEntityValues } from '../../utils/DataFetching/Sorting/useMappedTrackedEntity'; +import { useFilterByMissingData } from '../../utils/Hooks'; interface GrowthChartProps { trackedEntity: MappedEntityValues; @@ -22,23 +23,25 @@ export const GrowthChart = ({ const [gender, setGender] = useState(trackedEntityGender !== undefined ? trackedEntityGender : GenderCodes.CGC_Female); const { chartDataForGender } = useChartDataForGender({ gender }); + const { chartData } = useFilterByMissingData(measurementData, chartDataForGender); + const [category, setCategory] = useState(); const [dataset, setDataset] = useState(); useEffect(() => { - if (Object.keys(chartDataForGender).length > 0) { - const newCategory = Object.keys(chartDataForGender)[0] as keyof typeof CategoryCodes; + if (Object.keys(chartData).length > 0) { + const newCategory = Object.keys(chartData)[0] as keyof typeof CategoryCodes; setCategory(newCategory); - const newDataset = Object.keys(chartDataForGender[newCategory].datasets)[0] as keyof ChartData; + const newDataset = Object.keys(chartData[newCategory].datasets)[0] as keyof ChartData; setDataset(newDataset); } - }, [chartDataForGender]); + }, [chartData]); useEffect(() => { Object.values(GenderCodes).includes(trackedEntity.gender) && setGender(trackedEntity?.gender); }, [trackedEntity]); - const dataSetEntry = chartDataForGender[category]?.datasets[dataset]; + const dataSetEntry = chartData[category]?.datasets[dataset]; const dataSetValues = dataSetEntry?.datasetValues; const dataSetMetadata = dataSetEntry?.metadata; @@ -55,7 +58,7 @@ export const GrowthChart = ({ const annotations = GrowthChartAnnotations(xAxisValues, dataSetMetadata?.xAxisLabel); - if (!chartDataForGender || !dataSetValues) { + if (!chartData || !dataSetValues) { return null; } @@ -75,7 +78,7 @@ export const GrowthChart = ({ dataset={dataset} setCategory={setCategory} setDataset={setDataset} - chartData={chartDataForGender} + chartData={chartData} isDisabled={trackedEntityGender !== undefined} gender={gender} setGender={setGender} diff --git a/src/types/chartDataTypes.ts b/src/types/chartDataTypes.ts index d79a920..8cbb1f9 100644 --- a/src/types/chartDataTypes.ts +++ b/src/types/chartDataTypes.ts @@ -4,9 +4,9 @@ import { AnnotationLabelType } from '../Components/GrowthChart/GrowthChartOption export interface MeasurementData { eventDate: string; dataValues: { - weight: string; - headCircumference: string; - height: string; + weight: number; + headCircumference: number; + height: number; }; } diff --git a/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts b/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts index b838947..e344a6b 100644 --- a/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts +++ b/src/utils/DataFetching/Sorting/useMappedGrowthVariables.ts @@ -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; }; } @@ -20,17 +20,17 @@ export const useMappedGrowthVariables = ({ growthVariables, isWeightInGrams, }: UseMappedGrowthVariablesProps): MappedDataValue[] | undefined => 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); diff --git a/src/utils/Hooks/index.ts b/src/utils/Hooks/index.ts new file mode 100644 index 0000000..fb84234 --- /dev/null +++ b/src/utils/Hooks/index.ts @@ -0,0 +1 @@ +export { useFilterByMissingData } from './useFilterByMissingData'; diff --git a/src/utils/Hooks/useFilterByMissingData.ts b/src/utils/Hooks/useFilterByMissingData.ts new file mode 100644 index 0000000..a3d9963 --- /dev/null +++ b/src/utils/Hooks/useFilterByMissingData.ts @@ -0,0 +1,53 @@ +import { useMemo } from 'react'; +import { ChartData, MeasurementData } from '../../types/chartDataTypes'; + +export const useFilterByMissingData = (measurementData: MeasurementData[], chartData: ChartData) => { + 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 }; +}; From 007d927db197c6a04f2258d37d7aba4a86a7d485 Mon Sep 17 00:00:00 2001 From: edvinstava Date: Wed, 24 Apr 2024 13:14:36 +0200 Subject: [PATCH 3/6] feat: Filter chartData based on measurmentData --- i18n/en.pot | 10 ++++++++-- src/Plugin.tsx | 10 ++++++++-- src/components/GrowthChart/GrowthChart.tsx | 1 - .../DataFetching/Sorting/useFilterByMissingData.ts | 3 +++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 3c358fe..429a790 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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-24T10:57:45.778Z\n" +"PO-Revision-Date: 2024-04-24T10:57:45.778Z\n" msgid "Growth Chart" msgstr "Growth Chart" @@ -17,12 +17,18 @@ 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 "Date" msgstr "Date" msgid "Age" msgstr "Age" +msgid "Year" +msgstr "Year" + msgid "Years" msgstr "Years" diff --git a/src/Plugin.tsx b/src/Plugin.tsx index 404e6f4..baaded8 100644 --- a/src/Plugin.tsx +++ b/src/Plugin.tsx @@ -15,8 +15,9 @@ 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'; const queryClient = new QueryClient(); @@ -40,6 +41,11 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { isWeightInGrams: chartConfig?.settings.weightInGrams || false, }); + const { chartData } = useFilterByMissingData( + mappedGrowthVariables, + chartConfig?.settings.customReferences ? customReferences : chartDataWHO, + ); + const isPercentiles = chartConfig?.settings.usePercentiles || false; const [open, setOpen] = useState(true); @@ -81,7 +87,7 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { diff --git a/src/components/GrowthChart/GrowthChart.tsx b/src/components/GrowthChart/GrowthChart.tsx index efd3f05..b23f5fc 100644 --- a/src/components/GrowthChart/GrowthChart.tsx +++ b/src/components/GrowthChart/GrowthChart.tsx @@ -21,7 +21,6 @@ export const GrowthChart = ({ chartData, }: GrowthChartProps) => { const trackedEntityGender = trackedEntity?.gender; - const [gender, setGender] = useState(trackedEntityGender !== undefined ? trackedEntityGender : GenderCodes.CGC_Female); const { chartDataForGender } = useChartDataForGender({ gender, chartData }); diff --git a/src/utils/DataFetching/Sorting/useFilterByMissingData.ts b/src/utils/DataFetching/Sorting/useFilterByMissingData.ts index f286427..bc9924c 100644 --- a/src/utils/DataFetching/Sorting/useFilterByMissingData.ts +++ b/src/utils/DataFetching/Sorting/useFilterByMissingData.ts @@ -2,6 +2,9 @@ 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 }, From c9ad75b999bd6b61e5657ba83e085062f3202de8 Mon Sep 17 00:00:00 2001 From: edvinstava Date: Wed, 24 Apr 2024 13:29:36 +0200 Subject: [PATCH 4/6] fix: Added error for missing growth variables --- src/Plugin.tsx | 7 +++- .../MissingGrowthVariablesError.tsx | 33 +++++++++++++++++++ .../Sorting/useFilterByMissingData.ts | 2 +- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/UI/GenericError/MissingGrowthVariablesError.tsx diff --git a/src/Plugin.tsx b/src/Plugin.tsx index baaded8..5726200 100644 --- a/src/Plugin.tsx +++ b/src/Plugin.tsx @@ -18,6 +18,7 @@ import { useCustomReferences } from './utils/DataFetching/Hooks/useCustomReferen 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(); @@ -41,7 +42,7 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { isWeightInGrams: chartConfig?.settings.weightInGrams || false, }); - const { chartData } = useFilterByMissingData( + const { chartData, measurementDataExist } = useFilterByMissingData( mappedGrowthVariables, chartConfig?.settings.customReferences ? customReferences : chartDataWHO, ); @@ -62,6 +63,10 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { return ; } + if (measurementDataExist.headCircumference === false && measurementDataExist.height === false && measurementDataExist.weight === false) { + return ; + } + return ( { + const [open, setOpen] = useState(true); + return ( +
+ setOpen(true)} + onClose={() => setOpen(false)} + > +
+ +

+ {i18n.t('No growth variables were found.')} +
+ {i18n.t('Please add growth variables and try again.')} +

+
+
+
+ ); +}; diff --git a/src/utils/DataFetching/Sorting/useFilterByMissingData.ts b/src/utils/DataFetching/Sorting/useFilterByMissingData.ts index bc9924c..ff76ff4 100644 --- a/src/utils/DataFetching/Sorting/useFilterByMissingData.ts +++ b/src/utils/DataFetching/Sorting/useFilterByMissingData.ts @@ -52,5 +52,5 @@ export const useFilterByMissingData = (measurementData: MeasurementData[], chart return filteredData; }, [chartData, measurementDataExist, requiredData]); - return { chartData: filteredChartData }; + return { chartData: filteredChartData, measurementDataExist }; }; From d0963b9ae420d44e2c734226f9d812e3cebdabca Mon Sep 17 00:00:00 2001 From: edvinstava Date: Wed, 24 Apr 2024 13:34:56 +0200 Subject: [PATCH 5/6] fix: Updated en.pot file --- i18n/en.pot | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 429a790..91f5b7c 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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-24T10:57:45.778Z\n" -"PO-Revision-Date: 2024-04-24T10:57:45.778Z\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" @@ -20,6 +20,12 @@ 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" From 0536207ca0212f792803942e9f7ed795568054a3 Mon Sep 17 00:00:00 2001 From: edvinstava Date: Wed, 24 Apr 2024 13:49:11 +0200 Subject: [PATCH 6/6] fix: Handle missing variables for custom references --- src/Plugin.tsx | 6 +++--- src/UI/GenericError/EventError.tsx | 33 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/UI/GenericError/EventError.tsx diff --git a/src/Plugin.tsx b/src/Plugin.tsx index 5726200..d15b63c 100644 --- a/src/Plugin.tsx +++ b/src/Plugin.tsx @@ -27,7 +27,7 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { const { customReferences, isLoading: isLoadingRef, isError: isErrorRef } = useCustomReferences(); const { teiId, programId } = propsFromParent; const { trackedEntity } = useTeiById({ teiId }); - const { events } = useEvents({ + const { events, isLoading: isLoadingEvents } = useEvents({ programId, teiId, }); @@ -44,14 +44,14 @@ const PluginInner = (propsFromParent: EnrollmentOverviewProps) => { const { chartData, measurementDataExist } = useFilterByMissingData( mappedGrowthVariables, - chartConfig?.settings.customReferences ? customReferences : chartDataWHO, + 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 ; } diff --git a/src/UI/GenericError/EventError.tsx b/src/UI/GenericError/EventError.tsx new file mode 100644 index 0000000..15d659f --- /dev/null +++ b/src/UI/GenericError/EventError.tsx @@ -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 ( +
+ setOpen(true)} + onClose={() => setOpen(false)} + > +
+ +

+ {i18n.t('There was an error fetching the event.')} +
+ {i18n.t('Please check the configuration in Datastore Management application and try again.')} +

+
+
+
+ ); +};