From a8f57fd51d1f5ba8bc8454522071bb226e786cc1 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Wed, 15 Jul 2026 23:06:36 +0200 Subject: [PATCH 01/11] Integrate final data --- backend/Makefile | 16 ++++++++-------- backend/configs/all4trees_config.json | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/backend/Makefile b/backend/Makefile index faa8336e..041dad81 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -34,14 +34,14 @@ catalog: coordo add foreignkey inv_bio.year bio_pop.year --package catalog/inventaire_bio # All 4 Trees - Enquête ménage - coordo add kobotoolbox data/all4trees/enquete/20260422_EnqueteMenage_DonneesK.csv --form data/all4trees/enquete/20260519_EnqueteMenage_QuestionnaireK.xlsx --package catalog/enquete - coordo add file data/all4trees/enquete/20260422_EnqueteMenage_DonneesExternes.xlsx --package catalog/enquete - - coordo add foreignkey enquete_menage.proj enq_samp.proj --package catalog/enquete - coordo add foreignkey enquete_menage.typ enq_pop.typ --package catalog/enquete - coordo add foreignkey enquete_menage.loc2 enq_gps.loc2 --package catalog/enquete - coordo add foreignkey enquete_menage.fw_cod enq_fw.fw_cod --package catalog/enquete - coordo add foreignkey enquete_menage.coal_unit enq_coal.coal_unit --package catalog/enquete + coordo add kobotoolbox data/all4trees/enquete/20260709_EnqueteMenage_DonneesK.xlsx --form data/all4trees/enquete/20260519_EnqueteMenage_QuestionnaireK.xlsx --package catalog/enquete + coordo add file data/all4trees/enquete/20260713_EnqueteMenage_DonneesExternes.xlsx --package catalog/enquete + + coordo add foreignkey enquete_menage.proj hh_samp.proj --package catalog/enquete + coordo add foreignkey enquete_menage.typ hh_pop.typ --package catalog/enquete + coordo add foreignkey enquete_menage.loc2 hh_gps.loc2 --package catalog/enquete + coordo add foreignkey enquete_menage.fw_cod hh_fw.fw_cod --package catalog/enquete + coordo add foreignkey enquete_menage.coal_unit hh_coal.coal_unit --package catalog/enquete # Seed - Survey coordo add file data/seed/survey.zip --package catalog/seed diff --git a/backend/configs/all4trees_config.json b/backend/configs/all4trees_config.json index 035acbab..51b70e13 100644 --- a/backend/configs/all4trees_config.json +++ b/backend/configs/all4trees_config.json @@ -140,11 +140,11 @@ "colors": ["rgba(161,181,245,0.7)", "rgba(20,71,230,0.7)", "rgba(14,50,161,0.7)"] }, "columns": { - "geom": "centroid(merge(point(enq_gps.long, enq_gps.latit)))", + "geom": "centroid(merge(point(hh_gps.long, hh_gps.latit)))", "household_nb": "count(_id)", "population": "sum(res)", "fuel_sources": "list_aggregate(flatten(list(ener)), 'histogram')", - "wood_fuel_cons": "sum( 12 * ( ifnull(fw_qty * enq_fw.fw_vol * fw_freq, 0) + ifnull(coal_qty * enq_coal.coal_kg * coal_freq * enq_coal.coal_fw, 0) / enq_coal.coal_dens )) / sum(res)", + "wood_fuel_cons": "sum( 12 * ( ifnull(fw_qty * hh_fw.fw_vol * fw_freq, 0) + ifnull(coal_qty * hh_coal.coal_kg * coal_freq * hh_coal.coal_fw, 0) / hh_coal.coal_dens )) / sum(res)", "firewood_collec_time": "avg(ifnull(coll_freq, 0) * ifnull(coll_dur, 0) / 30)", "firewood_satis": "histogram(wf_satis if int(wf_satis) > 0)", "firewood_satis_tot": "count(1 if int(wf_satis) > 0)", @@ -169,7 +169,6 @@ "assets_idx_raw": "avg( ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + ifnull(list_unique(ass),0) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + ( int( land_own ) + int( land_titl ) + int( land_size ) if int(land_own) > 0 else 0) ) if ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + list_unique(ass) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + int( land_own ) + int( land_titl ) + int( land_size ) ) <= int(ass_tot_max) )", "assets_idx": "10 / int(ass_tot_max) * avg( ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + ifnull(list_unique(ass),0) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + ( int( land_own ) + int( land_titl ) + int( land_size ) if int(land_own) > 0 else 0) ) if ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + list_unique(ass) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + int( land_own ) + int( land_titl ) + int( land_size ) ) <= int(ass_tot_max) )", "nb_income_sources" : "avg(int(inc_no))", - "nb_additional_incomes": "'N/A'", "benef_pract1": "count(1 if int(pract1) = 2) / count(1) * 100", "benef_pract2": "count(1 if int(pract2) = 2) / count(1) * 100", "benef_pract3": "count(1 if int(pract3) = 2) / count(1) * 100", From 2c72561c87e8b4e8875e7e572c148b5e50b96328 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Thu, 16 Jul 2026 09:29:56 +0200 Subject: [PATCH 02/11] Use external data for village labelling --- backend/configs/all4trees_config.json | 2 + webapp/src/features/indicators/labels.ts | 81 +++++++++++++++++++ .../popup/socio-eco/popup-socio-eco.tsx | 34 ++++---- webapp/src/features/popup/socio-eco/types.ts | 15 +++- 4 files changed, 114 insertions(+), 18 deletions(-) create mode 100644 webapp/src/features/indicators/labels.ts diff --git a/backend/configs/all4trees_config.json b/backend/configs/all4trees_config.json index 51b70e13..782b9659 100644 --- a/backend/configs/all4trees_config.json +++ b/backend/configs/all4trees_config.json @@ -143,6 +143,8 @@ "geom": "centroid(merge(point(hh_gps.long, hh_gps.latit)))", "household_nb": "count(_id)", "population": "sum(res)", + "project": "first(proj)", + "loc1": "first(loc1)", "fuel_sources": "list_aggregate(flatten(list(ener)), 'histogram')", "wood_fuel_cons": "sum( 12 * ( ifnull(fw_qty * hh_fw.fw_vol * fw_freq, 0) + ifnull(coal_qty * hh_coal.coal_kg * coal_freq * hh_coal.coal_fw, 0) / hh_coal.coal_dens )) / sum(res)", "firewood_collec_time": "avg(ifnull(coll_freq, 0) * ifnull(coll_dur, 0) / 30)", diff --git a/webapp/src/features/indicators/labels.ts b/webapp/src/features/indicators/labels.ts new file mode 100644 index 00000000..a40a09c2 --- /dev/null +++ b/webapp/src/features/indicators/labels.ts @@ -0,0 +1,81 @@ +import type { LabelData } from "@features/popup/forest-inventory/types"; +import type { LayerMetadata } from "coordo"; + + +export function findCategoricalLabel( + metadata: LayerMetadata, + fieldName: string, + fieldValue: any, +): string | undefined { + // Searching field category in main resource schema + const resourceLabel = metadata?.resource?.schema?.fields + .find((f) => f.name === fieldName) + ?.categories?.find((c) => c.value === fieldValue)?.label; + + if (resourceLabel) { + return resourceLabel; + } + + // Searching field category in main resource's references' schemas + return metadata?.references + ?.find((ref) => + ref.schema.fields + .find((f) => f.name === fieldName) + ?.categories?.some((c) => c.value === fieldValue), + ) + ?.schema.fields.find((f) => f.name === fieldName) + ?.categories?.find((c) => c.value === fieldValue)?.label; +} + +// Find status of corresponding taxon value. +export function findStatus( + resourceData: LabelData[], + project: string, + lang: string, + fieldName: string, + fieldValue: any, +): string | undefined { + return findMatchingRecord(resourceData, project, fieldName, fieldValue)?.[ + `stat::${lang}` + ]; +} + +export function findLabel( + resourceData: LabelData[], + project: string, + lang: string, + fieldName: string, + fieldValue: any, +): string | undefined { + return findMatchingRecord(resourceData, project, fieldName, fieldValue)?.[ + `label::${lang}` + ]; +} + +export function findMatchingRecord( + resourceData: LabelData[], + project: string, + fieldName: string, + fieldValue: any, +): any { + if (!resourceData || !Array.isArray(resourceData)) { + return undefined; + } + + // Find the record matching all criteria: project, list_name, and name + const record = resourceData.find((item: LabelData) => { + if (typeof item.name !== typeof fieldValue) { + console.warn( + `Checking field values with different types ! fieldName=${fieldName} fieldValue type=${typeof fieldValue}; item.name type= ${typeof item.name}`, + ); + } + return ( + item.proj?.trim() === project.trim() && + item.list_name?.trim() === fieldName.trim() && + item.name === fieldValue + ); + }); + + console.log("Found matching record", record) + return record; +} diff --git a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx index 07dac99b..bb2ee644 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -1,22 +1,21 @@ import { cx } from "class-variance-authority"; import { UsersIcon } from "lucide-react"; -import { Activity, type FC, useState } from "react"; +import { Activity, type FC, use, useState } from "react"; import { ICON_SIZE_HEADER } from "@features/indicators/components/constants"; import { IndicatorElements } from "@features/indicators/components/indicator-elements"; import { IndicatorScrollContainer } from "@features/indicators/components/indicator-scroll-container"; import { useEconomicIndicatorElements } from "@features/indicators/economy"; import { useSocialIndicatorElements } from "@features/indicators/social/use-social-indicator-elements"; -import { findCategoricalLabel } from "@features/indicators/utils"; import { IndicatorPopupHeader } from "@features/popup/components/indicator-popup-header"; -import type { LayerMetadata } from "@shared/lib/coordo"; import { formatDate } from "@shared/lib/utils"; import { GridSelector } from "@shared/ui/grid-selector"; -import { useTranslation } from "@i18n"; +import { i18nInstance, useTranslation } from "@i18n"; import type { RenderPopupProps } from "../renderPopup"; -import type { SocioEcoData } from "./types"; +import type { GPSData, SocioEcoData } from "./types"; + type SocioEcoIndicatorProps = RenderPopupProps; @@ -28,35 +27,38 @@ const TABS: Record = { } as const; const exctractVillageName = ( - metadata: LayerMetadata, + metadata: GPSData[], data: SocioEcoData, + lang: string, ): string => { - const label = findCategoricalLabel(metadata, "loc2", data.loc2); - let villageName: string | undefined; - if (label) { - const jsonLabel = JSON.parse(label); - villageName = - jsonLabel["Malagasy(mg)"] ?? jsonLabel["Fran\u00e7ais(fr)"] ?? undefined; - } + // Find the record matching all criteria: project, list_name, and name + const record = metadata.find((item: GPSData) => { + return ( + item.proj?.trim() === data.project.trim() && + item.loc2 === Number(data.loc2) + ); + }); - return villageName || data.loc2; + return record?.[`loc2::${lang}`] || data.loc2; }; export const SocioEcoIndicator: FC = ({ data, - metadata, + externalDataPromise, className, ...headerProps }) => { const { t } = useTranslation("all4trees"); const [selectedTab, setSelectedTab] = useState(TABS.RESOURCES); + const lang = i18nInstance.language; + const externalData = use(externalDataPromise) const socialElements = useSocialIndicatorElements(data); const economicElements = useEconomicIndicatorElements(data); const title = t("popup.socioEco.title", { - village: exctractVillageName(metadata, data), + village: exctractVillageName(externalData['hh_gps'], data, lang), }); const tabs = { [TABS.RESOURCES]: t("indicators.resources.title"), diff --git a/webapp/src/features/popup/socio-eco/types.ts b/webapp/src/features/popup/socio-eco/types.ts index efd9f65f..4c844309 100644 --- a/webapp/src/features/popup/socio-eco/types.ts +++ b/webapp/src/features/popup/socio-eco/types.ts @@ -2,8 +2,19 @@ import type { EconomicData } from "@features/indicators/economy/types"; import type { SocialData } from "@features/indicators/social/types"; export type SocioEcoData = { - household_nb: number; - population: number; + project: string; + year: number; + cohort: number; + type: number; + loc1: string; loc2: string; + population: number; + household_nb: number; } & SocialData & EconomicData; + +export type GPSData = { + proj: string; + loc2: number; + [key: string]: any; + } \ No newline at end of file From 13bdfe84aa03ec41873a7f78d526de7cfc557757 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Fri, 17 Jul 2026 15:38:36 +0200 Subject: [PATCH 03/11] Hamonize labelling --- webapp/src/entities/resources.ts | 3 +- .../biodiversity/chart-relative-abundance.tsx | 2 +- .../bioinventory/chart-species-status.tsx | 2 +- .../features/charts/components/lib/taxon.ts | 2 +- .../indicators/bio-inventory/format-data.ts | 2 +- webapp/src/features/indicators/labels.ts | 24 ++++-- webapp/src/features/indicators/utils.ts | 86 ------------------- .../bio-inventory/popup-bio-inventory.tsx | 2 +- .../popup-forest-inventory.tsx | 2 +- .../popup/socio-eco/popup-socio-eco.tsx | 32 +++---- webapp/src/features/popup/socio-eco/types.ts | 6 -- 11 files changed, 35 insertions(+), 128 deletions(-) diff --git a/webapp/src/entities/resources.ts b/webapp/src/entities/resources.ts index cae9ca9a..a77b4ccf 100644 --- a/webapp/src/entities/resources.ts +++ b/webapp/src/entities/resources.ts @@ -6,11 +6,12 @@ export const EXTERNAL_RESOURCES_BY_LAYER = new Map([ ["for_label", "for_mf_tax1", "for_mf_tax2", "for_mf_tax3", "for_score"], ], [LAYERS.INVENTORY_BIO, ["bio_label", "bio_sp"]], - [LAYERS.ENQUETE, [""]], + [LAYERS.ENQUETE, ["hh_label"]], [LAYERS.SEED_POINT, [""]], ]); export const LABEL_DATA = new Map([ [LAYERS.INVENTORY_FOR, "for_label"], [LAYERS.INVENTORY_BIO, "bio_label"], + [LAYERS.ENQUETE, "hh_label"], ]); diff --git a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx index 20bb4b55..7310a2b8 100644 --- a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx +++ b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx @@ -1,4 +1,4 @@ -import { findLabel } from "@features/indicators/utils"; +import { findLabel } from "@features/indicators/labels"; import type { LabelData } from "@entities/data"; diff --git a/webapp/src/features/charts/bioinventory/chart-species-status.tsx b/webapp/src/features/charts/bioinventory/chart-species-status.tsx index 0c1935d4..822b7ea4 100644 --- a/webapp/src/features/charts/bioinventory/chart-species-status.tsx +++ b/webapp/src/features/charts/bioinventory/chart-species-status.tsx @@ -1,4 +1,4 @@ -import { findStatus } from "@features/indicators/utils"; +import { findStatus } from "@features/indicators/labels"; import type { BioSpeciesData } from "@entities/data"; diff --git a/webapp/src/features/charts/components/lib/taxon.ts b/webapp/src/features/charts/components/lib/taxon.ts index c2450ce8..842469d7 100644 --- a/webapp/src/features/charts/components/lib/taxon.ts +++ b/webapp/src/features/charts/components/lib/taxon.ts @@ -1,4 +1,4 @@ -import { findLabel } from "@features/indicators/utils"; +import { findLabel } from "@features/indicators/labels"; import type { LabelData } from "@entities/data"; diff --git a/webapp/src/features/indicators/bio-inventory/format-data.ts b/webapp/src/features/indicators/bio-inventory/format-data.ts index 679dcade..937685b1 100644 --- a/webapp/src/features/indicators/bio-inventory/format-data.ts +++ b/webapp/src/features/indicators/bio-inventory/format-data.ts @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; +import { findLabel } from "@features/indicators/labels"; import { - findLabel, formatTaxonAbundance, preciseNumericIndicators, UNITS, diff --git a/webapp/src/features/indicators/labels.ts b/webapp/src/features/indicators/labels.ts index a40a09c2..bc929fda 100644 --- a/webapp/src/features/indicators/labels.ts +++ b/webapp/src/features/indicators/labels.ts @@ -1,6 +1,8 @@ -import type { LabelData } from "@features/popup/forest-inventory/types"; import type { LayerMetadata } from "coordo"; +import type { LabelData } from "@features/popup/forest-inventory/types"; + +import type { BioSpeciesData } from "@entities/data"; export function findCategoricalLabel( metadata: LayerMetadata, @@ -29,15 +31,21 @@ export function findCategoricalLabel( // Find status of corresponding taxon value. export function findStatus( - resourceData: LabelData[], + resourceData: BioSpeciesData[], project: string, lang: string, - fieldName: string, - fieldValue: any, + taxon: number, ): string | undefined { - return findMatchingRecord(resourceData, project, fieldName, fieldValue)?.[ - `stat::${lang}` - ]; + if (!resourceData || !Array.isArray(resourceData)) { + return undefined; + } + + // Find the record matching all criteria: project, list_name, and name + const record = resourceData.find((item: BioSpeciesData) => { + return item.proj?.trim() === project.trim() && item.tax3 === taxon; + }); + + return record?.[`stat::${lang}` as keyof BioSpeciesData] as string; } export function findLabel( @@ -76,6 +84,6 @@ export function findMatchingRecord( ); }); - console.log("Found matching record", record) + console.log("Found matching record", record); return record; } diff --git a/webapp/src/features/indicators/utils.ts b/webapp/src/features/indicators/utils.ts index fc8d9236..a05ee35d 100644 --- a/webapp/src/features/indicators/utils.ts +++ b/webapp/src/features/indicators/utils.ts @@ -1,7 +1,4 @@ -import type { BioSpeciesData, LabelData } from "@entities/data"; - import { useTranslation } from "@shared/i18n"; -import type { LayerMetadata } from "@shared/lib/coordo"; import { precise } from "@shared/lib/utils"; import type { NumericKeys } from "@shared/types"; @@ -170,86 +167,3 @@ export function computeScore(value: number): number { return 4 - Math.floor(Math.log10(value)); } - -export function findCategoricalLabel( - metadata: LayerMetadata, - fieldName: string, - fieldValue: any, -): string | undefined { - // Searching field category in main resource schema - const resourceLabel = metadata?.resource?.schema?.fields - .find((f) => f.name === fieldName) - ?.categories?.find((c) => c.value === fieldValue)?.label; - - if (resourceLabel) { - return resourceLabel; - } - - // Searching field category in main resource's references' schemas - return metadata?.references - ?.find((ref) => - ref.schema.fields - .find((f) => f.name === fieldName) - ?.categories?.some((c) => c.value === fieldValue), - ) - ?.schema.fields.find((f) => f.name === fieldName) - ?.categories?.find((c) => c.value === fieldValue)?.label; -} - -// Find status of corresponding taxon value. -export function findStatus( - resourceData: BioSpeciesData[], - project: string, - lang: string, - taxon: number, -): string | undefined { - if (!resourceData || !Array.isArray(resourceData)) { - return undefined; - } - - // Find the record matching all criteria: project, list_name, and name - const record = resourceData.find((item: BioSpeciesData) => { - return item.proj?.trim() === project.trim() && item.tax3 === taxon; - }); - - return record?.[`stat::${lang}` as keyof BioSpeciesData] as string; -} - -export function findLabel( - resourceData: LabelData[], - project: string, - lang: string, - fieldName: string, - fieldValue: any, -): string | undefined { - return findMatchingRecord(resourceData, project, fieldName, fieldValue)?.[ - `label::${lang}` - ]; -} - -export function findMatchingRecord( - resourceData: LabelData[], - project: string, - fieldName: string, - fieldValue: any, -): any { - if (!resourceData || !Array.isArray(resourceData)) { - return undefined; - } - - // Find the record matching all criteria: project, list_name, and name - const record = resourceData.find((item: LabelData) => { - if (typeof item.name !== typeof fieldValue) { - console.warn( - `Checking field values with different types ! fieldName=${fieldName} fieldValue type=${typeof fieldValue}; item.name type= ${typeof item.name}`, - ); - } - return ( - item.proj?.trim() === project.trim() && - item.list_name?.trim() === fieldName.trim() && - item.name === fieldValue - ); - }); - - return record; -} diff --git a/webapp/src/features/popup/bio-inventory/popup-bio-inventory.tsx b/webapp/src/features/popup/bio-inventory/popup-bio-inventory.tsx index 003ec52b..f2ab5b27 100644 --- a/webapp/src/features/popup/bio-inventory/popup-bio-inventory.tsx +++ b/webapp/src/features/popup/bio-inventory/popup-bio-inventory.tsx @@ -6,7 +6,7 @@ import { useBioInventoryIndicatorElements } from "@features/indicators/bio-inven import { ICON_SIZE_HEADER } from "@features/indicators/components/constants"; import { IndicatorElements } from "@features/indicators/components/indicator-elements"; import { IndicatorScrollContainer } from "@features/indicators/components/indicator-scroll-container"; -import { findLabel } from "@features/indicators/utils"; +import { findLabel } from "@features/indicators/labels"; import { IndicatorPopupHeader } from "@features/popup/components/indicator-popup-header"; import { formatDate } from "@shared/lib/utils"; diff --git a/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx b/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx index a744a0ca..c8687648 100644 --- a/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx +++ b/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx @@ -7,8 +7,8 @@ import { useBiodiversityIndicatorElements } from "@features/indicators/biodivers import { ICON_SIZE_HEADER } from "@features/indicators/components/constants"; import { IndicatorElements } from "@features/indicators/components/indicator-elements"; import { IndicatorScrollContainer } from "@features/indicators/components/indicator-scroll-container"; +import { findLabel } from "@features/indicators/labels"; import { useSoilIndicatorElements } from "@features/indicators/soil"; -import { findLabel } from "@features/indicators/utils"; import { i18nInstance } from "@shared/i18n"; import { formatDate } from "@shared/lib/utils"; diff --git a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx index bb2ee644..72f92f90 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -6,16 +6,20 @@ import { ICON_SIZE_HEADER } from "@features/indicators/components/constants"; import { IndicatorElements } from "@features/indicators/components/indicator-elements"; import { IndicatorScrollContainer } from "@features/indicators/components/indicator-scroll-container"; import { useEconomicIndicatorElements } from "@features/indicators/economy"; +import { findLabel } from "@features/indicators/labels"; import { useSocialIndicatorElements } from "@features/indicators/social/use-social-indicator-elements"; import { IndicatorPopupHeader } from "@features/popup/components/indicator-popup-header"; +import { LABEL_DATA } from "@entities/resources"; + +import { LAYERS } from "@shared/api/layers"; import { formatDate } from "@shared/lib/utils"; import { GridSelector } from "@shared/ui/grid-selector"; import { i18nInstance, useTranslation } from "@i18n"; +import type { LabelData } from "../forest-inventory/types"; import type { RenderPopupProps } from "../renderPopup"; -import type { GPSData, SocioEcoData } from "./types"; - +import type { SocioEcoData } from "./types"; type SocioEcoIndicatorProps = RenderPopupProps; @@ -26,23 +30,6 @@ const TABS: Record = { RESOURCES: "resources", } as const; -const exctractVillageName = ( - metadata: GPSData[], - data: SocioEcoData, - lang: string, -): string => { - - // Find the record matching all criteria: project, list_name, and name - const record = metadata.find((item: GPSData) => { - return ( - item.proj?.trim() === data.project.trim() && - item.loc2 === Number(data.loc2) - ); - }); - - return record?.[`loc2::${lang}`] || data.loc2; -}; - export const SocioEcoIndicator: FC = ({ data, externalDataPromise, @@ -53,12 +40,15 @@ export const SocioEcoIndicator: FC = ({ const [selectedTab, setSelectedTab] = useState(TABS.RESOURCES); const lang = i18nInstance.language; - const externalData = use(externalDataPromise) + const externalData = use(externalDataPromise); + const labelData = + externalData[LABEL_DATA.get(LAYERS.ENQUETE) || ""] || ([] as LabelData[]); + const socialElements = useSocialIndicatorElements(data); const economicElements = useEconomicIndicatorElements(data); const title = t("popup.socioEco.title", { - village: exctractVillageName(externalData['hh_gps'], data, lang), + village: findLabel(labelData, data.project, lang, "loc2", data.loc2), }); const tabs = { [TABS.RESOURCES]: t("indicators.resources.title"), diff --git a/webapp/src/features/popup/socio-eco/types.ts b/webapp/src/features/popup/socio-eco/types.ts index 4c844309..77358d8e 100644 --- a/webapp/src/features/popup/socio-eco/types.ts +++ b/webapp/src/features/popup/socio-eco/types.ts @@ -12,9 +12,3 @@ export type SocioEcoData = { household_nb: number; } & SocialData & EconomicData; - -export type GPSData = { - proj: string; - loc2: number; - [key: string]: any; - } \ No newline at end of file From e86dddea425e78b76493757b6507e8e5958190da Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Fri, 17 Jul 2026 15:44:15 +0200 Subject: [PATCH 04/11] Labelize charts --- webapp/src/features/charts/socio-eco/chart-energy-sources.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx b/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx index 9ac64fc6..c81b6bfd 100644 --- a/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx +++ b/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx @@ -2,6 +2,7 @@ import { useTranslation } from "@shared/i18n"; import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; import type { ChartComponentType } from "../components/chart-component"; +import type { ExternalData } from "@features/popup/forest-inventory/types"; type Data = { fw_coll: number; @@ -15,6 +16,8 @@ type Data = { }; type ChartEnergySourcesProps = { + project: string; + externalData: ExternalData benef: Data; temoin?: Data; }; From 2afc32949e9ed2c09c2f08fde93aec19dd6ba7b3 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Sun, 19 Jul 2026 23:06:25 +0200 Subject: [PATCH 05/11] fix import error --- webapp/src/features/indicators/labels.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/webapp/src/features/indicators/labels.ts b/webapp/src/features/indicators/labels.ts index bc929fda..ea780266 100644 --- a/webapp/src/features/indicators/labels.ts +++ b/webapp/src/features/indicators/labels.ts @@ -1,8 +1,6 @@ import type { LayerMetadata } from "coordo"; -import type { LabelData } from "@features/popup/forest-inventory/types"; - -import type { BioSpeciesData } from "@entities/data"; +import type { BioSpeciesData, LabelData } from "@entities/data"; export function findCategoricalLabel( metadata: LayerMetadata, From 8fad537311058214c4a299484eff4422d1bcd46f Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Mon, 20 Jul 2026 00:12:01 +0200 Subject: [PATCH 06/11] Labelize EnergySource & FirewoodNeeds charts --- webapp/src/entities/data.ts | 1 + .../charts/socio-eco/chart-energy-sources.tsx | 79 ++++++------------- .../charts/socio-eco/chart-firewood-needs.tsx | 65 ++++++--------- webapp/src/features/charts/socio-eco/utils.ts | 7 ++ webapp/src/features/indicators/labels.ts | 1 - .../features/indicators/social/format-data.ts | 10 +-- .../social/use-social-indicator-elements.tsx | 15 +++- .../popup/socio-eco/popup-socio-eco.tsx | 4 +- 8 files changed, 75 insertions(+), 107 deletions(-) create mode 100644 webapp/src/features/charts/socio-eco/utils.ts diff --git a/webapp/src/entities/data.ts b/webapp/src/entities/data.ts index 157d78b4..b7df6c65 100644 --- a/webapp/src/entities/data.ts +++ b/webapp/src/entities/data.ts @@ -1,6 +1,7 @@ export type ExternalData = { for_label: LabelData[]; bio_label: LabelData[]; + hh_label: LabelData[]; bio_sp: BioSpeciesData[]; for_mf_tax1: any[]; for_mf_tax2: any[]; diff --git a/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx b/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx index c81b6bfd..8d0ae24b 100644 --- a/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx +++ b/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx @@ -1,8 +1,11 @@ -import { useTranslation } from "@shared/i18n"; +import { findLabel } from "@features/indicators/labels"; + +import type { LabelData } from "@entities/data"; + +import { i18nInstance, useTranslation } from "@shared/i18n"; import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; import type { ChartComponentType } from "../components/chart-component"; -import type { ExternalData } from "@features/popup/forest-inventory/types"; type Data = { fw_coll: number; @@ -17,7 +20,7 @@ type Data = { type ChartEnergySourcesProps = { project: string; - externalData: ExternalData + metadata: LabelData[]; benef: Data; temoin?: Data; }; @@ -25,65 +28,29 @@ type ChartEnergySourcesProps = { export const ChartFuelSources: ChartComponentType = ({ benef, temoin, + project, + metadata, }) => { - const { t } = useTranslation("all4trees"); + const { t } = useTranslation(["common", "all4trees"]); + const lang = i18nInstance.language; - const chartData = [ - { - benef: benef.fw_coll, - indicator: t( - "indicators.socioEco.sections.wood.energySources.collectedWood", - ), - temoin: temoin?.fw_coll, - }, - { - benef: benef.fw_pur, - indicator: t( - "indicators.socioEco.sections.wood.energySources.boughtWood", - ), - temoin: temoin?.fw_pur, - }, - { - benef: benef.fw_rec, - indicator: t("indicators.socioEco.sections.wood.energySources.recWood"), - temoin: temoin?.fw_rec, - }, - { - benef: benef.coal, - indicator: t("indicators.socioEco.sections.wood.energySources.coal"), - temoin: temoin?.coal, - }, - { - benef: benef.crop, - indicator: t( - "indicators.socioEco.sections.wood.energySources.organicWaste", - ), - temoin: temoin?.crop, - }, - { - benef: benef.man, - indicator: t( - "indicators.socioEco.sections.wood.energySources.animalWaste", - ), - temoin: temoin?.man, - }, - { - benef: benef.gaz, - indicator: t("indicators.socioEco.sections.wood.energySources.gas"), - temoin: temoin?.gaz, - }, - { - benef: benef.oth, - indicator: t("indicators.socioEco.sections.wood.energySources.other"), - temoin: temoin?.oth, - }, - ]; + const chartData = Object.entries(benef).map(([key, value]) => ({ + benef: value, + indicator: + findLabel(metadata, project, lang, "ener", key) || + t("common:dataManagement.undefined"), + temoin: temoin?.[key as keyof typeof temoin], + })); return ( diff --git a/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx b/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx index a6303378..1258d211 100644 --- a/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx @@ -1,4 +1,8 @@ -import { useTranslation } from "@shared/i18n"; +import { findLabel } from "@features/indicators/labels"; + +import type { LabelData } from "@entities/data"; + +import { i18nInstance, useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; import type { ChartComponentType } from "../components/chart-component"; @@ -6,57 +10,36 @@ import { PieChartCategorical, renderLabel, } from "../components/pie-chart-categorical"; +import { satisfactionColorMap } from "./utils"; type PieChartProps = { + project: string; + metadata: LabelData[]; data: { - easyToMeet: number; - moderateToMeet: number; - difficultToMeet: number; - dontKnow: number; + [key: string]: number; }; }; export const ChartFireWoodNeeds: ChartComponentType = ({ data, + metadata, + project, }) => { const { t } = useTranslation("all4trees"); - const chartData = [ - { - fill: "var(--chart-1)", - name: "easyToMeet", - value: data.easyToMeet, - }, - { - fill: "var(--chart-3)", - name: "moderateToMeet", - value: data.moderateToMeet, - }, - { - fill: "var(--chart-6)", - name: "difficultToMeet", - value: data.difficultToMeet, - }, - { - fill: "var(--chart-2)", - name: "dontKnow", - value: data.dontKnow, - }, - ]; + const lang = i18nInstance.language; + + const chartData = Object.entries(data).map(([key, value]) => ({ + fill: satisfactionColorMap.get(key) || "var(--chart-5)", + name: key, + value, + })); - const chartConfig = { - difficultToMeet: { - label: t("indicators.socioEco.sections.wood.needs.difficultToMeet"), - }, - dontKnow: { - label: t("indicators.socioEco.sections.wood.needs.dontKnow"), - }, - easyToMeet: { - label: t("indicators.socioEco.sections.wood.needs.easyToMeet"), - }, - moderateToMeet: { - label: t("indicators.socioEco.sections.wood.needs.moderateToMeet"), - }, - } satisfies ChartConfig; + const chartConfig = chartData.reduce((acc, item) => { + acc[item.name as keyof typeof acc] = { + label: findLabel(metadata, project, lang, "satis", item.name), + }; + return acc; + }, {} as ChartConfig) satisfies ChartConfig; return ( ([ + ["1", "var(--chart-1)"], + ["2", "var(--chart-3)"], + ["3", "var(--chart-6)"], + ["98", "var(--chart-2)"], + ["99", "var(--chart-1)"], +]); diff --git a/webapp/src/features/indicators/labels.ts b/webapp/src/features/indicators/labels.ts index ea780266..cd0a48eb 100644 --- a/webapp/src/features/indicators/labels.ts +++ b/webapp/src/features/indicators/labels.ts @@ -82,6 +82,5 @@ export function findMatchingRecord( ); }); - console.log("Found matching record", record); return record; } diff --git a/webapp/src/features/indicators/social/format-data.ts b/webapp/src/features/indicators/social/format-data.ts index ca68e901..2090c92f 100644 --- a/webapp/src/features/indicators/social/format-data.ts +++ b/webapp/src/features/indicators/social/format-data.ts @@ -74,11 +74,11 @@ export const useFormatSocialData = (data: SocioEcoData) => { UNITS.m3PerHabPerYear, ), firewoodNeeds: { - difficultToMeet: Number(safeData.firewood_satis3), - dontKnow: Number(safeData.firewood_satis98), - easyToMeet: Number(safeData.firewood_satis1), - moderateToMeet: Number(safeData.firewood_satis2), - refuse: Number(safeData.firewood_satis99), + "1": Number(safeData.firewood_satis1), + "2": Number(safeData.firewood_satis2), + "3": Number(safeData.firewood_satis3), + "98": Number(safeData.firewood_satis98), + "99": Number(safeData.firewood_satis99), }, fuelSources: safeData.fuel_sources, timberNeeds: { diff --git a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx index bee0574a..fec55d01 100644 --- a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx +++ b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx @@ -10,6 +10,8 @@ import { IndicatorRawValue } from "@features/indicators/components/indicator-raw import type { UseIndicatorReturnType } from "@features/indicators/components/types"; import type { SocioEcoData } from "@features/popup/socio-eco"; +import type { ExternalData } from "@entities/data"; + import { useTranslation } from "@i18n"; import { ICON_SIZE } from "../components/constants"; @@ -17,6 +19,7 @@ import { useFormatSocialData } from "./format-data"; export const useSocialIndicatorElements = ( rawData: SocioEcoData, + externalData: ExternalData, ): UseIndicatorReturnType => { const { t } = useTranslation("all4trees"); const data = useFormatSocialData(rawData); @@ -25,13 +28,21 @@ export const useSocialIndicatorElements = ( { children: ( <> - + } value={data.wood.energyConsumption} /> - + } diff --git a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx index 72f92f90..3429847b 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -10,6 +10,7 @@ import { findLabel } from "@features/indicators/labels"; import { useSocialIndicatorElements } from "@features/indicators/social/use-social-indicator-elements"; import { IndicatorPopupHeader } from "@features/popup/components/indicator-popup-header"; +import type { LabelData } from "@entities/data"; import { LABEL_DATA } from "@entities/resources"; import { LAYERS } from "@shared/api/layers"; @@ -17,7 +18,6 @@ import { formatDate } from "@shared/lib/utils"; import { GridSelector } from "@shared/ui/grid-selector"; import { i18nInstance, useTranslation } from "@i18n"; -import type { LabelData } from "../forest-inventory/types"; import type { RenderPopupProps } from "../renderPopup"; import type { SocioEcoData } from "./types"; @@ -44,7 +44,7 @@ export const SocioEcoIndicator: FC = ({ const labelData = externalData[LABEL_DATA.get(LAYERS.ENQUETE) || ""] || ([] as LabelData[]); - const socialElements = useSocialIndicatorElements(data); + const socialElements = useSocialIndicatorElements(data, externalData); const economicElements = useEconomicIndicatorElements(data); const title = t("popup.socioEco.title", { From 32064fa83c63026a6119fbbb97da4d27a18d5692 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Mon, 20 Jul 2026 08:09:25 +0200 Subject: [PATCH 07/11] Labelize TimberNeeds & LivingConditions charts --- backend/Makefile | 1 - backend/configs/all4trees_config.json | 6 +- .../charts/socio-eco/chart-firewood-needs.tsx | 12 +-- .../socio-eco/chart-living-condition.tsx | 88 +++++++------------ .../charts/socio-eco/chart-timber-needs.tsx | 71 ++++++--------- .../features/charts/socio-eco/constants.ts | 15 ++++ webapp/src/features/charts/socio-eco/utils.ts | 7 -- .../indicators/economy/format-data.ts | 14 ++- .../use-economic-indicator-elements.tsx | 5 ++ .../features/indicators/social/format-data.ts | 10 +-- .../social/use-social-indicator-elements.tsx | 6 +- .../popup/socio-eco/popup-socio-eco.tsx | 2 +- 12 files changed, 105 insertions(+), 132 deletions(-) create mode 100644 webapp/src/features/charts/socio-eco/constants.ts delete mode 100644 webapp/src/features/charts/socio-eco/utils.ts diff --git a/backend/Makefile b/backend/Makefile index 041dad81..240f1446 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -38,7 +38,6 @@ catalog: coordo add file data/all4trees/enquete/20260713_EnqueteMenage_DonneesExternes.xlsx --package catalog/enquete coordo add foreignkey enquete_menage.proj hh_samp.proj --package catalog/enquete - coordo add foreignkey enquete_menage.typ hh_pop.typ --package catalog/enquete coordo add foreignkey enquete_menage.loc2 hh_gps.loc2 --package catalog/enquete coordo add foreignkey enquete_menage.fw_cod hh_fw.fw_cod --package catalog/enquete coordo add foreignkey enquete_menage.coal_unit hh_coal.coal_unit --package catalog/enquete diff --git a/backend/configs/all4trees_config.json b/backend/configs/all4trees_config.json index 782b9659..41e4bbae 100644 --- a/backend/configs/all4trees_config.json +++ b/backend/configs/all4trees_config.json @@ -132,7 +132,7 @@ "path": "../catalog/enquete", "resource": "enquete_menage", "layerType": "symbol", - "groupby": ["loc2"], + "groupby": ["loc2", "year"], "cluster": { "radius": 50, "maxZoom": 12, @@ -141,10 +141,10 @@ }, "columns": { "geom": "centroid(merge(point(hh_gps.long, hh_gps.latit)))", - "household_nb": "count(_id)", - "population": "sum(res)", "project": "first(proj)", "loc1": "first(loc1)", + "household_nb": "count(_id)", + "population": "sum(res)", "fuel_sources": "list_aggregate(flatten(list(ener)), 'histogram')", "wood_fuel_cons": "sum( 12 * ( ifnull(fw_qty * hh_fw.fw_vol * fw_freq, 0) + ifnull(coal_qty * hh_coal.coal_kg * coal_freq * hh_coal.coal_fw, 0) / hh_coal.coal_dens )) / sum(res)", "firewood_collec_time": "avg(ifnull(coll_freq, 0) * ifnull(coll_dur, 0) / 30)", diff --git a/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx b/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx index 1258d211..b8a63481 100644 --- a/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx @@ -10,7 +10,7 @@ import { PieChartCategorical, renderLabel, } from "../components/pie-chart-categorical"; -import { satisfactionColorMap } from "./utils"; +import { SATISFACTION_COLOR_MAP } from "./constants"; type PieChartProps = { project: string; @@ -25,18 +25,20 @@ export const ChartFireWoodNeeds: ChartComponentType = ({ metadata, project, }) => { - const { t } = useTranslation("all4trees"); + const { t } = useTranslation(["common", "all4trees"]); const lang = i18nInstance.language; const chartData = Object.entries(data).map(([key, value]) => ({ - fill: satisfactionColorMap.get(key) || "var(--chart-5)", + fill: SATISFACTION_COLOR_MAP.get(key) || "var(--chart-4)", name: key, value, })); const chartConfig = chartData.reduce((acc, item) => { acc[item.name as keyof typeof acc] = { - label: findLabel(metadata, project, lang, "satis", item.name), + label: + findLabel(metadata, project, lang, "satis", item.name) || + t("common:dataManagement.undefined"), }; return acc; }, {} as ChartConfig) satisfies ChartConfig; @@ -45,7 +47,7 @@ export const ChartFireWoodNeeds: ChartComponentType = ({ renderLabel({ ...props, chartConfig, linebreak: 18 }) diff --git a/webapp/src/features/charts/socio-eco/chart-living-condition.tsx b/webapp/src/features/charts/socio-eco/chart-living-condition.tsx index ce8203d3..6ccb1d0f 100644 --- a/webapp/src/features/charts/socio-eco/chart-living-condition.tsx +++ b/webapp/src/features/charts/socio-eco/chart-living-condition.tsx @@ -1,80 +1,52 @@ -import { useTranslation } from "@shared/i18n"; +import { findLabel } from "@features/indicators/labels"; + +import type { LabelData } from "@entities/data"; + +import { i18nInstance, useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; import type { ChartComponentType } from "../components/chart-component"; import { PieChartCategorical } from "../components/pie-chart-categorical"; +import { EVOLUTION_COLOR_MAP } from "./constants"; type PieChartProps = { + project: string; + metadata: LabelData[]; data: { - improvement: number; - stable: number; - regression: number; - dontKnow: number; - refuse: number; + [key: string]: number; }; }; export const ChartLivingCondition: ChartComponentType = ({ data, + metadata, + project, }) => { - const { t } = useTranslation("all4trees"); - const chartData = [ - { - fill: "var(--chart-1)", - name: "improvement", - value: data.improvement, - }, - { - fill: "var(--chart-5)", - name: "stable", - value: data.stable, - }, - { - fill: "var(--chart-6)", - name: "regression", - value: data.regression, - }, - { - fill: "var(--chart-3)", - name: "refuse", - value: data.refuse, - }, - { - fill: "var(--chart-2)", - name: "dontKnow", - value: data.dontKnow, - }, - ]; + const { t } = useTranslation(["common", "all4trees"]); + const lang = i18nInstance.language; + + const chartData = Object.entries(data).map(([key, value]) => ({ + fill: EVOLUTION_COLOR_MAP.get(key) || "var(--chart-4)", + name: key, + value, + })); - const chartConfig = { - dontKnow: { - label: t( - "indicators.socioEco.sections.economy.livingPerception.dontKnow", - ), - }, - improvement: { - label: t( - "indicators.socioEco.sections.economy.livingPerception.improvement", - ), - }, - refuse: { - label: t("indicators.socioEco.sections.economy.livingPerception.refuse"), - }, - regression: { - label: t( - "indicators.socioEco.sections.economy.livingPerception.regression", - ), - }, - stable: { - label: t("indicators.socioEco.sections.economy.livingPerception.stable"), - }, - } satisfies ChartConfig; + const chartConfig = chartData.reduce((acc, item) => { + acc[item.name as keyof typeof acc] = { + label: + findLabel(metadata, project, lang, "evol", item.name) || + t("common:dataManagement.undefined"), + }; + return acc; + }, {} as ChartConfig) satisfies ChartConfig; return ( diff --git a/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx b/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx index 47d26a2c..495ff58b 100644 --- a/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx @@ -1,4 +1,8 @@ -import { useTranslation } from "@shared/i18n"; +import { findLabel } from "@features/indicators/labels"; + +import type { LabelData } from "@entities/data"; + +import { i18nInstance, useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; import type { ChartComponentType } from "../components/chart-component"; @@ -6,63 +10,44 @@ import { PieChartCategorical, renderLabel, } from "../components/pie-chart-categorical"; +import { SATISFACTION_COLOR_MAP } from "./constants"; type PieChartProps = { + project: string; + metadata: LabelData[]; data: { - easyToMeet: number; - moderateToMeet: number; - difficultToMeet: number; - dontKnow: number; + [key: string]: number; }; }; export const ChartTimberNeeds: ChartComponentType = ({ data, + metadata, + project, }) => { - const { t } = useTranslation("all4trees"); - const chartData = [ - { - fill: "var(--chart-1)", - name: "easyToMeet", - value: data.easyToMeet, - }, - { - fill: "var(--chart-3)", - name: "moderateToMeet", - value: data.moderateToMeet, - }, - { - fill: "var(--chart-6)", - name: "difficultToMeet", - value: data.difficultToMeet, - }, - { - fill: "var(--chart-2)", - name: "dontKnow", - value: data.dontKnow, - }, - ]; + const { t } = useTranslation(["common", "all4trees"]); + const lang = i18nInstance.language; + + const chartData = Object.entries(data).map(([key, value]) => ({ + fill: SATISFACTION_COLOR_MAP.get(key) || "var(--chart-4)", + name: key, + value, + })); - const chartConfig = { - difficultToMeet: { - label: t("indicators.socioEco.sections.wood.needs.difficultToMeet"), - }, - dontKnow: { - label: t("indicators.socioEco.sections.wood.needs.dontKnow"), - }, - easyToMeet: { - label: t("indicators.socioEco.sections.wood.needs.easyToMeet"), - }, - moderateToMeet: { - label: t("indicators.socioEco.sections.wood.needs.moderateToMeet"), - }, - } satisfies ChartConfig; + const chartConfig = chartData.reduce((acc, item) => { + acc[item.name as keyof typeof acc] = { + label: + findLabel(metadata, project, lang, "satis", item.name) || + t("common:dataManagement.undefined"), + }; + return acc; + }, {} as ChartConfig) satisfies ChartConfig; return ( renderLabel({ ...props, chartConfig, linebreak: 18 }) diff --git a/webapp/src/features/charts/socio-eco/constants.ts b/webapp/src/features/charts/socio-eco/constants.ts new file mode 100644 index 00000000..3e85b6a4 --- /dev/null +++ b/webapp/src/features/charts/socio-eco/constants.ts @@ -0,0 +1,15 @@ +export const SATISFACTION_COLOR_MAP = new Map([ + ["1", "var(--chart-1)"], + ["2", "var(--chart-3)"], + ["3", "var(--chart-6)"], + ["98", "var(--chart-2)"], + ["99", "var(--chart-5)"], +]); + +export const EVOLUTION_COLOR_MAP = new Map([ + ["1", "var(--chart-1)"], + ["2", "var(--chart-6)"], + ["3", "var(--chart-5)"], + ["98", "var(--chart-2)"], + ["99", "var(--chart-3)"], +]); diff --git a/webapp/src/features/charts/socio-eco/utils.ts b/webapp/src/features/charts/socio-eco/utils.ts deleted file mode 100644 index 92fcbeae..00000000 --- a/webapp/src/features/charts/socio-eco/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const satisfactionColorMap = new Map([ - ["1", "var(--chart-1)"], - ["2", "var(--chart-3)"], - ["3", "var(--chart-6)"], - ["98", "var(--chart-2)"], - ["99", "var(--chart-1)"], -]); diff --git a/webapp/src/features/indicators/economy/format-data.ts b/webapp/src/features/indicators/economy/format-data.ts index 8e278ce6..e5617ab8 100644 --- a/webapp/src/features/indicators/economy/format-data.ts +++ b/webapp/src/features/indicators/economy/format-data.ts @@ -46,14 +46,12 @@ export const useFormatEconomicData = (data: SocioEcoData) => { economy: { assetsIndex: `${safeData.assets_idx}/10`, incomeSourceNb: safeData.nb_income_sources, - livingConditionsPerception: { - dontKnow: safeData.living_cond_perception[HISTOGRAM_KEYS.DONT_KNOW], - improvement: - safeData.living_cond_perception[HISTOGRAM_KEYS.IMPROVEMENT], - refuse: safeData.living_cond_perception[HISTOGRAM_KEYS.REFUSE], - regression: safeData.living_cond_perception[HISTOGRAM_KEYS.REGRESSION], - stable: safeData.living_cond_perception[HISTOGRAM_KEYS.STABLE], - }, + livingConditionsPerception: Object.fromEntries( + Object.entries(HISTOGRAM_KEYS).map(([_, value]) => [ + value, + safeData.living_cond_perception[value], + ]), + ), revenueChange: `${safeData.revenue_change} %`, }, governance: { diff --git a/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx b/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx index e816e870..df37932b 100644 --- a/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx +++ b/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx @@ -6,6 +6,8 @@ import { } from "@features/charts/socio-eco"; import type { SocioEcoData } from "@features/popup/socio-eco"; +import type { ExternalData } from "@entities/data"; + import { useTranslation } from "@i18n"; import { ICON_SIZE } from "../components/constants"; @@ -15,6 +17,7 @@ import { useFormatEconomicData } from "./format-data"; export const useEconomicIndicatorElements = ( rawData: SocioEcoData, + externalData: ExternalData, ): UseIndicatorReturnType => { const { t } = useTranslation("all4trees"); const data = useFormatEconomicData(rawData); @@ -40,6 +43,8 @@ export const useEconomicIndicatorElements = ( /> ), diff --git a/webapp/src/features/indicators/social/format-data.ts b/webapp/src/features/indicators/social/format-data.ts index 2090c92f..c181e17e 100644 --- a/webapp/src/features/indicators/social/format-data.ts +++ b/webapp/src/features/indicators/social/format-data.ts @@ -82,11 +82,11 @@ export const useFormatSocialData = (data: SocioEcoData) => { }, fuelSources: safeData.fuel_sources, timberNeeds: { - difficultToMeet: Number(safeData.timber_satis3), - dontKnow: Number(safeData.timber_satis98), - easyToMeet: Number(safeData.timber_satis1), - moderateToMeet: Number(safeData.timber_satis2), - refuse: Number(safeData.timber_satis99), + "1": Number(safeData.timber_satis1), + "2": Number(safeData.timber_satis2), + "3": Number(safeData.timber_satis3), + "98": Number(safeData.timber_satis98), + "99": Number(safeData.timber_satis99), }, }, }; diff --git a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx index fec55d01..d56f4a2f 100644 --- a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx +++ b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx @@ -48,7 +48,11 @@ export const useSocialIndicatorElements = ( iconStart={} value={data.wood.collectionTime} /> - + ), identifier: "indicator-wood", diff --git a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx index 3429847b..84460dbc 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -45,7 +45,7 @@ export const SocioEcoIndicator: FC = ({ externalData[LABEL_DATA.get(LAYERS.ENQUETE) || ""] || ([] as LabelData[]); const socialElements = useSocialIndicatorElements(data, externalData); - const economicElements = useEconomicIndicatorElements(data); + const economicElements = useEconomicIndicatorElements(data, externalData); const title = t("popup.socioEco.title", { village: findLabel(labelData, data.project, lang, "loc2", data.loc2), From db6e22771d11b58ca750aded7ab9ac4c35be6815 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Tue, 21 Jul 2026 23:51:09 +0200 Subject: [PATCH 08/11] Labelize Practice & Food charts + Factorize code --- webapp/src/entities/socio-eco.ts | 7 ++ .../charts/components/labeled-bar-chart.tsx | 55 ++++++++++++ .../labeled-pie-chart.tsx} | 32 +++---- webapp/src/features/charts/index.ts | 7 ++ .../socio-eco/chart-beneficial-practices.tsx | 77 ---------------- .../charts/socio-eco/chart-energy-sources.tsx | 60 ------------- .../charts/socio-eco/chart-firewood-needs.tsx | 59 ------------- .../charts/socio-eco/chart-food-diversity.tsx | 87 ------------------- .../socio-eco/chart-living-condition.tsx | 56 ------------ webapp/src/features/charts/socio-eco/index.ts | 6 -- .../use-bioinventory-indicator-elements.tsx | 2 +- .../use-economic-indicator-elements.tsx | 38 ++++---- .../social/use-social-indicator-elements.tsx | 37 +++++--- .../soil/use-soil-indicator-elements.tsx | 2 +- 14 files changed, 136 insertions(+), 389 deletions(-) create mode 100644 webapp/src/entities/socio-eco.ts create mode 100644 webapp/src/features/charts/components/labeled-bar-chart.tsx rename webapp/src/features/charts/{socio-eco/chart-timber-needs.tsx => components/labeled-pie-chart.tsx} (57%) create mode 100644 webapp/src/features/charts/index.ts delete mode 100644 webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx delete mode 100644 webapp/src/features/charts/socio-eco/chart-energy-sources.tsx delete mode 100644 webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx delete mode 100644 webapp/src/features/charts/socio-eco/chart-food-diversity.tsx delete mode 100644 webapp/src/features/charts/socio-eco/chart-living-condition.tsx delete mode 100644 webapp/src/features/charts/socio-eco/index.ts diff --git a/webapp/src/entities/socio-eco.ts b/webapp/src/entities/socio-eco.ts new file mode 100644 index 00000000..55daa8bf --- /dev/null +++ b/webapp/src/entities/socio-eco.ts @@ -0,0 +1,7 @@ +export const SOCIO_LABEL_FIELDS = { + BENEFICIAL_PRACTICES: "pract", + ENERGY_SOURCES: "ener", + EVOLUTION: "evol", + FOOD_CONS: "food_cons", + SATISFACTION: "satis", +}; diff --git a/webapp/src/features/charts/components/labeled-bar-chart.tsx b/webapp/src/features/charts/components/labeled-bar-chart.tsx new file mode 100644 index 00000000..59624e87 --- /dev/null +++ b/webapp/src/features/charts/components/labeled-bar-chart.tsx @@ -0,0 +1,55 @@ +import { findLabel } from "@features/indicators/labels"; + +import type { LabelData } from "@entities/data"; + +import { i18nInstance, useTranslation } from "@shared/i18n"; + +import { BarCharWithBenefAndControl } from "./bar-chart-benef-control"; +import type { ChartComponentType } from "./chart-component"; + +type Data = { + [key: string]: number; +}; + +type LabeledBarChartProps = { + benef: Data; + temoin?: Data; + metadata: LabelData[]; + project: string; + labelField: string; + title: string; + legendLabel: string; +}; + +export const LabeledBarChart: ChartComponentType = ({ + benef, + temoin, + metadata, + project, + labelField, + legendLabel, + title, +}) => { + const { t } = useTranslation("common"); + const lang = i18nInstance.language; + + const chartData = Object.entries(benef).map(([key, value]) => ({ + benef: value, + indicator: + findLabel(metadata, project, lang, labelField, key) || + t("dataManagement.undefined"), + temoin: temoin?.[key as keyof typeof temoin], + })); + + return ( + + ); +}; + +LabeledBarChart.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx b/webapp/src/features/charts/components/labeled-pie-chart.tsx similarity index 57% rename from webapp/src/features/charts/socio-eco/chart-timber-needs.tsx rename to webapp/src/features/charts/components/labeled-pie-chart.tsx index 495ff58b..f09a6f97 100644 --- a/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx +++ b/webapp/src/features/charts/components/labeled-pie-chart.tsx @@ -5,31 +5,33 @@ import type { LabelData } from "@entities/data"; import { i18nInstance, useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; -import type { ChartComponentType } from "../components/chart-component"; -import { - PieChartCategorical, - renderLabel, -} from "../components/pie-chart-categorical"; -import { SATISFACTION_COLOR_MAP } from "./constants"; - -type PieChartProps = { +import type { ChartComponentType } from "./chart-component"; +import { PieChartCategorical, renderLabel } from "./pie-chart-categorical"; + +type LabeledPieChartProps = { project: string; metadata: LabelData[]; data: { [key: string]: number; }; + title: string; + labelField: string; + colorMap: Map; }; -export const ChartTimberNeeds: ChartComponentType = ({ +export const LabeledPieChart: ChartComponentType = ({ data, metadata, project, + title, + colorMap, + labelField, }) => { - const { t } = useTranslation(["common", "all4trees"]); + const { t } = useTranslation("common"); const lang = i18nInstance.language; const chartData = Object.entries(data).map(([key, value]) => ({ - fill: SATISFACTION_COLOR_MAP.get(key) || "var(--chart-4)", + fill: colorMap.get(key) || "var(--chart-4)", name: key, value, })); @@ -37,8 +39,8 @@ export const ChartTimberNeeds: ChartComponentType = ({ const chartConfig = chartData.reduce((acc, item) => { acc[item.name as keyof typeof acc] = { label: - findLabel(metadata, project, lang, "satis", item.name) || - t("common:dataManagement.undefined"), + findLabel(metadata, project, lang, labelField, item.name) || + t("dataManagement.undefined"), }; return acc; }, {} as ChartConfig) satisfies ChartConfig; @@ -47,7 +49,7 @@ export const ChartTimberNeeds: ChartComponentType = ({ renderLabel({ ...props, chartConfig, linebreak: 18 }) @@ -56,4 +58,4 @@ export const ChartTimberNeeds: ChartComponentType = ({ ); }; -ChartTimberNeeds.isChartComponent = true; +LabeledPieChart.isChartComponent = true; diff --git a/webapp/src/features/charts/index.ts b/webapp/src/features/charts/index.ts new file mode 100644 index 00000000..81721df3 --- /dev/null +++ b/webapp/src/features/charts/index.ts @@ -0,0 +1,7 @@ +export { BarCharWithBenefAndControl } from "./components/bar-chart-benef-control"; +export { ChartComponent } from "./components/chart-component"; +export { ChartTaxonAbundance } from "./components/chart-taxon-abundance"; +export { LabeledBarChart } from "./components/labeled-bar-chart"; +export { LabeledPieChart } from "./components/labeled-pie-chart"; +export { PieChartCategorical } from "./components/pie-chart-categorical"; +export { ChartRadarWithBenefAndControl } from "./components/radar-benef-control"; diff --git a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx b/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx deleted file mode 100644 index aae67f92..00000000 --- a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useTranslation } from "@shared/i18n"; - -import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; -import type { ChartComponentType } from "../components/chart-component"; - -type Data = { - pract1: number; - pract2: number; - pract3: number; - pract4: number; - pract5: number; -}; - -type ChartBeneficialPracticesProps = { - benef: Data; - temoin?: Data; -}; - -export const ChartBeneficialPractices: ChartComponentType< - ChartBeneficialPracticesProps -> = ({ benef, temoin }) => { - const { t } = useTranslation("all4trees"); - - const chartData = [ - { - benef: benef.pract1, - indicator: t( - "indicators.socioEco.sections.governance.beneficialPractices.pract1", - ), - temoin: temoin?.pract1, - }, - { - benef: benef.pract2, - indicator: t( - "indicators.socioEco.sections.governance.beneficialPractices.pract2", - ), - temoin: temoin?.pract2, - }, - { - benef: benef.pract3, - indicator: t( - "indicators.socioEco.sections.governance.beneficialPractices.pract3", - ), - temoin: temoin?.pract3, - }, - { - benef: benef.pract4, - indicator: t( - "indicators.socioEco.sections.governance.beneficialPractices.pract4", - ), - temoin: temoin?.pract4, - }, - { - benef: benef.pract5, - indicator: t( - "indicators.socioEco.sections.governance.beneficialPractices.pract5", - ), - temoin: temoin?.pract5, - }, - ]; - - return ( - - ); -}; - -ChartBeneficialPractices.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx b/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx deleted file mode 100644 index 8d0ae24b..00000000 --- a/webapp/src/features/charts/socio-eco/chart-energy-sources.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { findLabel } from "@features/indicators/labels"; - -import type { LabelData } from "@entities/data"; - -import { i18nInstance, useTranslation } from "@shared/i18n"; - -import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; -import type { ChartComponentType } from "../components/chart-component"; - -type Data = { - fw_coll: number; - fw_pur: number; - fw_rec: number; - coal: number; - crop: number; - man: number; - gaz: number; - oth: number; -}; - -type ChartEnergySourcesProps = { - project: string; - metadata: LabelData[]; - benef: Data; - temoin?: Data; -}; - -export const ChartFuelSources: ChartComponentType = ({ - benef, - temoin, - project, - metadata, -}) => { - const { t } = useTranslation(["common", "all4trees"]); - const lang = i18nInstance.language; - - const chartData = Object.entries(benef).map(([key, value]) => ({ - benef: value, - indicator: - findLabel(metadata, project, lang, "ener", key) || - t("common:dataManagement.undefined"), - temoin: temoin?.[key as keyof typeof temoin], - })); - - return ( - - ); -}; - -ChartFuelSources.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx b/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx deleted file mode 100644 index b8a63481..00000000 --- a/webapp/src/features/charts/socio-eco/chart-firewood-needs.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { findLabel } from "@features/indicators/labels"; - -import type { LabelData } from "@entities/data"; - -import { i18nInstance, useTranslation } from "@shared/i18n"; -import type { ChartConfig } from "@shared/ui/chart"; - -import type { ChartComponentType } from "../components/chart-component"; -import { - PieChartCategorical, - renderLabel, -} from "../components/pie-chart-categorical"; -import { SATISFACTION_COLOR_MAP } from "./constants"; - -type PieChartProps = { - project: string; - metadata: LabelData[]; - data: { - [key: string]: number; - }; -}; - -export const ChartFireWoodNeeds: ChartComponentType = ({ - data, - metadata, - project, -}) => { - const { t } = useTranslation(["common", "all4trees"]); - const lang = i18nInstance.language; - - const chartData = Object.entries(data).map(([key, value]) => ({ - fill: SATISFACTION_COLOR_MAP.get(key) || "var(--chart-4)", - name: key, - value, - })); - - const chartConfig = chartData.reduce((acc, item) => { - acc[item.name as keyof typeof acc] = { - label: - findLabel(metadata, project, lang, "satis", item.name) || - t("common:dataManagement.undefined"), - }; - return acc; - }, {} as ChartConfig) satisfies ChartConfig; - - return ( - - renderLabel({ ...props, chartConfig, linebreak: 18 }) - } - /> - ); -}; - -ChartFireWoodNeeds.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/chart-food-diversity.tsx b/webapp/src/features/charts/socio-eco/chart-food-diversity.tsx deleted file mode 100644 index 11b94b31..00000000 --- a/webapp/src/features/charts/socio-eco/chart-food-diversity.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useTranslation } from "@shared/i18n"; - -import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; -import type { ChartComponentType } from "../components/chart-component"; - -type Data = { - cer: number; - egg: number; - fish: number; - fru: number; - tub: number; - meat: number; - puls: number; - veg: number; - dairy: number; -}; - -type ChartFoodDiversityProps = { - benef: Data; - temoin?: Data; -}; - -export const ChartFoodDiversity: ChartComponentType< - ChartFoodDiversityProps -> = ({ benef, temoin }) => { - const { t } = useTranslation("all4trees"); - - const chartData = [ - { - benef: benef.cer, - indicator: t("indicators.socioEco.sections.food.diversity.cereals"), - temoin: temoin?.cer, - }, - { - benef: benef.egg, - indicator: t("indicators.socioEco.sections.food.diversity.eggs"), - temoin: temoin?.egg, - }, - { - benef: benef.fish, - indicator: t("indicators.socioEco.sections.food.diversity.fish"), - temoin: temoin?.fish, - }, - { - benef: benef.meat, - indicator: t("indicators.socioEco.sections.food.diversity.meat"), - temoin: temoin?.meat, - }, - { - benef: benef.fru, - indicator: t("indicators.socioEco.sections.food.diversity.fruits"), - temoin: temoin?.fru, - }, - { - benef: benef.veg, - indicator: t("indicators.socioEco.sections.food.diversity.vegetables"), - temoin: temoin?.veg, - }, - { - benef: benef.tub, - indicator: t("indicators.socioEco.sections.food.diversity.roots"), - temoin: temoin?.tub, - }, - { - benef: benef.puls, - indicator: t("indicators.socioEco.sections.food.diversity.seeds"), - temoin: temoin?.puls, - }, - { - benef: benef.dairy, - indicator: t("indicators.socioEco.sections.food.diversity.dairy"), - temoin: temoin?.dairy, - }, - ]; - - return ( - - ); -}; - -ChartFoodDiversity.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/chart-living-condition.tsx b/webapp/src/features/charts/socio-eco/chart-living-condition.tsx deleted file mode 100644 index 6ccb1d0f..00000000 --- a/webapp/src/features/charts/socio-eco/chart-living-condition.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { findLabel } from "@features/indicators/labels"; - -import type { LabelData } from "@entities/data"; - -import { i18nInstance, useTranslation } from "@shared/i18n"; -import type { ChartConfig } from "@shared/ui/chart"; - -import type { ChartComponentType } from "../components/chart-component"; -import { PieChartCategorical } from "../components/pie-chart-categorical"; -import { EVOLUTION_COLOR_MAP } from "./constants"; - -type PieChartProps = { - project: string; - metadata: LabelData[]; - data: { - [key: string]: number; - }; -}; - -export const ChartLivingCondition: ChartComponentType = ({ - data, - metadata, - project, -}) => { - const { t } = useTranslation(["common", "all4trees"]); - const lang = i18nInstance.language; - - const chartData = Object.entries(data).map(([key, value]) => ({ - fill: EVOLUTION_COLOR_MAP.get(key) || "var(--chart-4)", - name: key, - value, - })); - - const chartConfig = chartData.reduce((acc, item) => { - acc[item.name as keyof typeof acc] = { - label: - findLabel(metadata, project, lang, "evol", item.name) || - t("common:dataManagement.undefined"), - }; - return acc; - }, {} as ChartConfig) satisfies ChartConfig; - - return ( - - ); -}; - -ChartLivingCondition.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/index.ts b/webapp/src/features/charts/socio-eco/index.ts deleted file mode 100644 index b0f690c9..00000000 --- a/webapp/src/features/charts/socio-eco/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { ChartBeneficialPractices } from "./chart-beneficial-practices"; -export { ChartFuelSources } from "./chart-energy-sources"; -export { ChartFireWoodNeeds } from "./chart-firewood-needs"; -export { ChartFoodDiversity } from "./chart-food-diversity"; -export { ChartLivingCondition } from "./chart-living-condition"; -export { ChartTimberNeeds } from "./chart-timber-needs"; diff --git a/webapp/src/features/indicators/bio-inventory/use-bioinventory-indicator-elements.tsx b/webapp/src/features/indicators/bio-inventory/use-bioinventory-indicator-elements.tsx index 6cd90fec..5bbd79cd 100644 --- a/webapp/src/features/indicators/bio-inventory/use-bioinventory-indicator-elements.tsx +++ b/webapp/src/features/indicators/bio-inventory/use-bioinventory-indicator-elements.tsx @@ -1,7 +1,7 @@ import { Gem, PawPrint, Ruler, SearchAlert, VectorSquare } from "lucide-react"; +import { ChartTaxonAbundance } from "@features/charts"; import { ChartSpeciesStatus } from "@features/charts/bioinventory/chart-species-status"; -import { ChartTaxonAbundance } from "@features/charts/components/chart-taxon-abundance"; import type { BioInventoryData } from "@features/popup/bio-inventory/types"; import type { ExternalData } from "@entities/data"; diff --git a/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx b/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx index df37932b..732862b4 100644 --- a/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx +++ b/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx @@ -1,12 +1,11 @@ import { ChartLine, HandCoins, Scroll } from "lucide-react"; -import { - ChartBeneficialPractices, - ChartLivingCondition, -} from "@features/charts/socio-eco"; +import { LabeledBarChart, LabeledPieChart } from "@features/charts"; +import { EVOLUTION_COLOR_MAP } from "@features/charts/socio-eco/constants"; import type { SocioEcoData } from "@features/popup/socio-eco"; import type { ExternalData } from "@entities/data"; +import { SOCIO_LABEL_FIELDS } from "@entities/socio-eco"; import { useTranslation } from "@i18n"; @@ -41,10 +40,15 @@ export const useEconomicIndicatorElements = ( iconStart={} value={data.economy.assetsIndex} /> - ), @@ -54,18 +58,18 @@ export const useEconomicIndicatorElements = ( }, { children: ( - <> - {/* Not implemented yet, waiting for All4Trees to better define this indicator. - */} - - + ), identifier: "indicator-governance", title: t("indicators.socioEco.sections.governance.title"), diff --git a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx index d56f4a2f..8710e03d 100644 --- a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx +++ b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx @@ -1,16 +1,13 @@ import { Calendar, Hourglass, Soup, Wheat, Zap } from "lucide-react"; -import { - ChartFireWoodNeeds, - ChartFoodDiversity, - ChartFuelSources, - ChartTimberNeeds, -} from "@features/charts/socio-eco"; +import { LabeledBarChart, LabeledPieChart } from "@features/charts"; +import { SATISFACTION_COLOR_MAP } from "@features/charts/socio-eco/constants"; import { IndicatorRawValue } from "@features/indicators/components/indicator-raw-value"; import type { UseIndicatorReturnType } from "@features/indicators/components/types"; import type { SocioEcoData } from "@features/popup/socio-eco"; import type { ExternalData } from "@entities/data"; +import { SOCIO_LABEL_FIELDS } from "@entities/socio-eco"; import { useTranslation } from "@i18n"; @@ -28,30 +25,41 @@ export const useSocialIndicatorElements = ( { children: ( <> - } value={data.wood.energyConsumption} /> - } value={data.wood.collectionTime} /> - ), @@ -67,7 +75,16 @@ export const useSocialIndicatorElements = ( iconStart={} value={data.food.foodDiversityScore} /> - + } diff --git a/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx b/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx index 6780dfa8..00aa4a1b 100644 --- a/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx +++ b/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx @@ -1,6 +1,6 @@ import { Bug, Gem, Sprout } from "lucide-react"; -import { ChartTaxonAbundance } from "@features/charts/components/chart-taxon-abundance"; +import { ChartTaxonAbundance } from "@features/charts"; import { ChartAquaticErosion } from "@features/charts/soil/ui/chart-aquatic-erosion"; import { ChartWindErosion } from "@features/charts/soil/ui/chart-wind-erosion"; import type { UseIndicatorReturnType } from "@features/indicators//components/types"; From 57e40829ffe135176b043e2bee495dd4b315ff5f Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Tue, 21 Jul 2026 23:56:47 +0200 Subject: [PATCH 09/11] Remove deprecated translations --- .../i18n/translations/en/all4trees.json | 35 +------------------ .../i18n/translations/fr/all4trees.json | 35 +------------------ 2 files changed, 2 insertions(+), 68 deletions(-) diff --git a/webapp/src/shared/i18n/translations/en/all4trees.json b/webapp/src/shared/i18n/translations/en/all4trees.json index c33a3dd1..27f6a41b 100644 --- a/webapp/src/shared/i18n/translations/en/all4trees.json +++ b/webapp/src/shared/i18n/translations/en/all4trees.json @@ -93,11 +93,6 @@ "assetsIndex": "Material assets index", "incomeSourceNb": "Number of sources of income per household", "livingPerception": { - "dontKnow": "Don't know", - "improvement": "Improvement", - "refuse": "Refuse to respond", - "regression": "Regression", - "stable": "No change", "title": "Perception of changes in living conditions" }, "revenueChange": "Revenue changes", @@ -106,17 +101,8 @@ "food": { "autoConso": "Food self-sufficiency", "diversity": { - "cereals": "Cereals", - "dairy": "Dairy", - "eggs": "Eggs", - "fish": "Fish", - "fruits": "Fruits", "legend": "of households consume this food group", - "meat": "Meat", - "roots": "Roots", - "seeds": "Seeds", - "title": "Food groups consumed", - "vegetables": "Vegetables" + "title": "Food groups consumed" }, "diversityScore": "Dietary diversity score", "leanPeriod": "Duration of the lean season", @@ -125,11 +111,6 @@ "governance": { "beneficialPractices": { "legend": "of households adopted this practice", - "pract1": "Tree planting", - "pract2": "Contour farming", - "pract3": "Intercropping", - "pract4": "Crop rotation", - "pract5": "Composting", "title": "Adoption of good practices" }, "conflictIndex": "Conflict Index", @@ -142,23 +123,9 @@ "title": "Satisfaction of wood energy needs" }, "energySources": { - "animalWaste": "Animal waste", - "boughtWood": "Bought wood", - "coal": "Coal", - "collectedWood": "Collected wood", - "gas": "Gas", "legend": "of households use the resource", - "organicWaste": "Organic waste", - "other": "Other", - "recWood": "Received wood", "title": "Energy sources usage" }, - "needs": { - "difficultToMeet": "Difficult to meet", - "dontKnow": "Don't know", - "easyToMeet": "Easy to meet", - "moderateToMeet": "Moderate to meet" - }, "timberNeeds": { "title": "Satisfaction of timber needs" }, diff --git a/webapp/src/shared/i18n/translations/fr/all4trees.json b/webapp/src/shared/i18n/translations/fr/all4trees.json index 762165d8..9c22f113 100644 --- a/webapp/src/shared/i18n/translations/fr/all4trees.json +++ b/webapp/src/shared/i18n/translations/fr/all4trees.json @@ -92,11 +92,6 @@ "assetsIndex": "Indice de patrimoine", "incomeSourceNb": "Nombre de sources de revenus par ménage", "livingPerception": { - "dontKnow": "Ne sait pas", - "improvement": "Amélioration", - "refuse": "Refuse de répondre", - "regression": "Dégradation", - "stable": "Pas de changement", "title": "Perception de l’évolution des conditions de vie" }, "revenueChange": "Evolution du montant des revenus", @@ -105,17 +100,8 @@ "food": { "autoConso": "Satisfaction des besoins en auto-consommation", "diversity": { - "cereals": "Céréales", - "dairy": "Produits Laitiers", - "eggs": "Œufs", - "fish": "Poisson", - "fruits": "Fruits", "legend": "des ménages consomment ce groupe alimentaire", - "meat": "Viande", - "roots": "Racines", - "seeds": "Graines", - "title": "Groupes alimentaires consommés", - "vegetables": "Légumes" + "title": "Groupes alimentaires consommés" }, "diversityScore": "Score de diversité alimentaire", "leanPeriod": "Durée de la pérode de soudure", @@ -124,11 +110,6 @@ "governance": { "beneficialPractices": { "legend": "des ménages ont adopté cette pratique", - "pract1": "Plantation", - "pract2": "Cultures en courbes de niveau", - "pract3": "Association de cultures", - "pract4": "Rotation des cultures", - "pract5": "Fabrication de compost", "title": "Pratiques bénéfiques adoptées" }, "conflictIndex": "Indice de conflictualité", @@ -141,23 +122,9 @@ "title": "Satisfaction des besoins en bois-énergie" }, "energySources": { - "animalWaste": "Déchets animaux", - "boughtWood": "Bois acheté", - "coal": "Charbon", - "collectedWood": "Bois collecté", - "gas": "Gaz", "legend": "des ménages utilisent la ressource", - "organicWaste": "Déchets végétaux", - "other": "Autres", - "recWood": "Bois offert", "title": "Sources d’énergie utilisées" }, - "needs": { - "difficultToMeet": "Difficile à satisfaire", - "dontKnow": "Ne sait pas", - "easyToMeet": "Facile à satisfaire", - "moderateToMeet": "Moyennement facile à satisfaire" - }, "timberNeeds": { "title": "Satisfaction des besoins en bois d’œuvre" }, From 7c3b11743b900caaf0f0e345ef60db29e7b35ac3 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Wed, 22 Jul 2026 07:56:28 +0200 Subject: [PATCH 10/11] Add Lean month chart --- backend/configs/all4trees_config.json | 1 + webapp/src/entities/socio-eco.ts | 3 +- .../components/bar-chart-benef-control.tsx | 16 ++++++--- .../charts/components/labeled-bar-chart.tsx | 14 ++++++-- .../components/pie-chart-categorical.tsx | 2 -- .../features/indicators/social/format-data.ts | 31 ++++++++++++++++ .../src/features/indicators/social/types.ts | 1 + .../social/use-social-indicator-elements.tsx | 35 ++++++++++++------- webapp/src/features/indicators/utils.ts | 2 +- .../i18n/translations/en/all4trees.json | 6 +++- .../i18n/translations/fr/all4trees.json | 6 +++- 11 files changed, 92 insertions(+), 25 deletions(-) diff --git a/backend/configs/all4trees_config.json b/backend/configs/all4trees_config.json index 41e4bbae..d00e7a13 100644 --- a/backend/configs/all4trees_config.json +++ b/backend/configs/all4trees_config.json @@ -163,6 +163,7 @@ "timber_satis": "histogram(timb_satis if timb_yn = '1')", "timber_satis_tot": "count(1 if timb_yn = '1')", "lean_period": "avg(len(lean) if lean_yn = 1)", + "lean_months": "list_aggregate(flatten(list(lean)), 'histogram')", "food_diversity": "list_aggregate(flatten(list(food_cons)), 'histogram')", "food_diversity_score": "avg(list_unique(food_cons) / int(food_cons_max) * 10)", "food_self_suff_score": "avg(ifnull(list_unique(food_self), 0) / list_unique(food_cons) * 100)", diff --git a/webapp/src/entities/socio-eco.ts b/webapp/src/entities/socio-eco.ts index 55daa8bf..7ede8b47 100644 --- a/webapp/src/entities/socio-eco.ts +++ b/webapp/src/entities/socio-eco.ts @@ -2,6 +2,7 @@ export const SOCIO_LABEL_FIELDS = { BENEFICIAL_PRACTICES: "pract", ENERGY_SOURCES: "ener", EVOLUTION: "evol", - FOOD_CONS: "food_cons", + FOOD_CONSOMMATION: "food_cons", + LEAN_MONTHS: "lean", SATISFACTION: "satis", }; diff --git a/webapp/src/features/charts/components/bar-chart-benef-control.tsx b/webapp/src/features/charts/components/bar-chart-benef-control.tsx index 1f5d7d94..9de2f698 100644 --- a/webapp/src/features/charts/components/bar-chart-benef-control.tsx +++ b/webapp/src/features/charts/components/bar-chart-benef-control.tsx @@ -1,3 +1,4 @@ +import type { PropsWithChildren } from "react"; import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { useTranslation } from "@shared/i18n"; @@ -10,14 +11,15 @@ import { import type { ChartComponentType } from "./chart-component"; import { ChartComponent } from "./chart-component"; -type BarChartProps = { +type BarChartProps = PropsWithChildren<{ title: string; chartData: Array<{ indicator: string; benef: unknown; temoin?: unknown }>; legendLabel: string; unit?: string; withTemoin?: boolean; layout?: { chartHeight: number; chartXAxisHeight: number }; -}; + description?: string; +}>; export const BarCharWithBenefAndControl: ChartComponentType = ({ title, @@ -25,6 +27,8 @@ export const BarCharWithBenefAndControl: ChartComponentType = ({ legendLabel, unit = "", withTemoin = false, + description, + children, }) => { const { t } = useTranslation("all4trees"); const chartConfig = { @@ -56,10 +60,14 @@ export const BarCharWithBenefAndControl: ChartComponentType = ({ }; return ( - + + {children} ; export const LabeledBarChart: ChartComponentType = ({ benef, @@ -29,6 +32,8 @@ export const LabeledBarChart: ChartComponentType = ({ labelField, legendLabel, title, + description, + children, }) => { const { t } = useTranslation("common"); const lang = i18nInstance.language; @@ -44,11 +49,14 @@ export const LabeledBarChart: ChartComponentType = ({ return ( + > + {children} + ); }; diff --git a/webapp/src/features/charts/components/pie-chart-categorical.tsx b/webapp/src/features/charts/components/pie-chart-categorical.tsx index c50f6495..d2839217 100644 --- a/webapp/src/features/charts/components/pie-chart-categorical.tsx +++ b/webapp/src/features/charts/components/pie-chart-categorical.tsx @@ -7,8 +7,6 @@ import { lineBreakLabel } from "../utils"; import type { ChartComponentType } from "./chart-component"; import { ChartComponent } from "./chart-component"; -export const description = "A pie chart with a label"; - export type PieChartCategoricalProps = { title: string; chartData: Array<{ name: string; value: number; fill: string }>; diff --git a/webapp/src/features/indicators/social/format-data.ts b/webapp/src/features/indicators/social/format-data.ts index c181e17e..c0e48fab 100644 --- a/webapp/src/features/indicators/social/format-data.ts +++ b/webapp/src/features/indicators/social/format-data.ts @@ -30,6 +30,33 @@ const indicatorKeys: NumericKeys[] = [ "food_self_suff_score", ]; +// Lean months order mapping +const monthOrder: Record = { + apr: 3, + aug: 7, + dec: 11, + feb: 1, + jan: 0, + jull: 6, + jun: 5, + mar: 2, + may: 4, + nov: 10, + oct: 9, + sep: 8, +}; + +export const sortByMonth = (monthDict: Record) => { + return Object.keys(monthDict) + .sort((a, b) => monthOrder[a] - monthOrder[b]) + .reduce( + (acc, key) => { + acc[key] = monthDict[key]; + return acc; + }, + {} as Record, + ); +}; /** * Return data in a convenient way for UI rendering, handling units and fixing */ @@ -53,6 +80,9 @@ export const useFormatSocialData = (data: SocioEcoData) => { data.household_nb, "0", ); + safeData.lean_months = sortByMonth( + convertDictToPercentage(safeData.lean_months, data.household_nb, "0"), + ); return { food: { @@ -62,6 +92,7 @@ export const useFormatSocialData = (data: SocioEcoData) => { safeData.food_self_suff_score, UNITS.percentFoodRequirements, ), + leanMonths: safeData.lean_months, leanPeriod: formatWithUnit(safeData.lean_period, UNITS.monthPerYear), }, wood: { diff --git a/webapp/src/features/indicators/social/types.ts b/webapp/src/features/indicators/social/types.ts index ad9baebb..abbc9de8 100644 --- a/webapp/src/features/indicators/social/types.ts +++ b/webapp/src/features/indicators/social/types.ts @@ -13,6 +13,7 @@ export type SocialData = { timber_satis98: number; timber_satis99: number; lean_period: number; + lean_months: any; food_diversity: any; food_diversity_score: number; food_self_suff_score: number; diff --git a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx index 8710e03d..00c21d84 100644 --- a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx +++ b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx @@ -70,31 +70,42 @@ export const useSocialIndicatorElements = ( { children: ( <> - } - value={data.food.foodDiversityScore} - /> + > + } + value={data.food.foodDiversityScore} + /> + } value={data.food.foodSelfSufficiency} /> - } - value={data.food.leanPeriod} - /> + + } + value={data.food.leanPeriod} + /> + ), identifier: "indicator-food", diff --git a/webapp/src/features/indicators/utils.ts b/webapp/src/features/indicators/utils.ts index a05ee35d..9104a044 100644 --- a/webapp/src/features/indicators/utils.ts +++ b/webapp/src/features/indicators/utils.ts @@ -27,7 +27,7 @@ export const useFormatterWithUnit = () => { function formatWithUnit( value: number | string | null | undefined, unit: Unit, - ): string | null { + ): string { const noDataStr = t("dataManagement.noData", { ns: "common" }); if (value == null || value === noDataStr) { return noDataStr; diff --git a/webapp/src/shared/i18n/translations/en/all4trees.json b/webapp/src/shared/i18n/translations/en/all4trees.json index 27f6a41b..579ac653 100644 --- a/webapp/src/shared/i18n/translations/en/all4trees.json +++ b/webapp/src/shared/i18n/translations/en/all4trees.json @@ -105,7 +105,11 @@ "title": "Food groups consumed" }, "diversityScore": "Dietary diversity score", - "leanPeriod": "Duration of the lean season", + "lean": { + "duration": "Mean duration of the lean season", + "legend": "of households", + "title": "Lean period" + }, "title": "Food resources" }, "governance": { diff --git a/webapp/src/shared/i18n/translations/fr/all4trees.json b/webapp/src/shared/i18n/translations/fr/all4trees.json index 9c22f113..fe66b6b8 100644 --- a/webapp/src/shared/i18n/translations/fr/all4trees.json +++ b/webapp/src/shared/i18n/translations/fr/all4trees.json @@ -104,7 +104,11 @@ "title": "Groupes alimentaires consommés" }, "diversityScore": "Score de diversité alimentaire", - "leanPeriod": "Durée de la pérode de soudure", + "lean": { + "duration": "Durée moyenne de la période de soudure", + "legend": "des ménages", + "title": "Pérode de soudure" + }, "title": "Ressources alimentaires" }, "governance": { From 1fdb0c60dca72a6b408b9365fdb34e501d71c407 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Thu, 23 Jul 2026 00:31:49 +0200 Subject: [PATCH 11/11] Fix food self sufficient score indicator --- backend/configs/all4trees_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/configs/all4trees_config.json b/backend/configs/all4trees_config.json index d00e7a13..42d66af5 100644 --- a/backend/configs/all4trees_config.json +++ b/backend/configs/all4trees_config.json @@ -166,7 +166,7 @@ "lean_months": "list_aggregate(flatten(list(lean)), 'histogram')", "food_diversity": "list_aggregate(flatten(list(food_cons)), 'histogram')", "food_diversity_score": "avg(list_unique(food_cons) / int(food_cons_max) * 10)", - "food_self_suff_score": "avg(ifnull(list_unique(food_self), 0) / list_unique(food_cons) * 100)", + "food_self_suff_score": "avg( ((0 if list_contains(food_self, '') else list_unique(food_self)) / list_unique(food_cons) * 100) if list_contains(food_cons, '') != 'true')", "living_cond_perception": "histogram(cond_evol)", "revenue_change": "avg(0 - inc_dif if inc_evol = '2' else ifnull(inc_dif, 0))", "assets_idx_raw": "avg( ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + ifnull(list_unique(ass),0) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + ( int( land_own ) + int( land_titl ) + int( land_size ) if int(land_own) > 0 else 0) ) if ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + list_unique(ass) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + int( land_own ) + int( land_titl ) + int( land_size ) ) <= int(ass_tot_max) )",