From f0de1f8ffa960749f1ffecfd9b25c87668d32c54 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Fri, 1 May 2026 00:41:36 +0200 Subject: [PATCH 1/8] Fix(UI): All4Trees Feedback --- backend/configs/config.json | 4 +- webapp/src/app/styles/all4trees.css | 2 +- webapp/src/app/styles/index.css | 13 +-- .../biodiversity/chart-relative-abundance.tsx | 3 - .../components/bar-chart-benef-control.tsx | 73 ++++++++-------- .../charts/components/chart-component.tsx | 41 +++++++++ .../components/pie-chart-categorical.tsx | 46 ++++------ .../charts/components/radar-benef-control.tsx | 87 +++++++++---------- .../charts/soil/ui/chart-taxon-abundance.tsx | 20 +++-- .../indicators/biodiversity/format-data.ts | 11 +-- .../components/indicator-raw-value.tsx | 9 +- .../components/indicator-section.tsx | 75 +++++++++++++++- .../features/indicators/soil/format-data.ts | 1 - webapp/src/features/indicators/soil/types.ts | 1 - .../soil/use-soil-indicator-elements.tsx | 4 - webapp/src/features/indicators/utils.ts | 5 ++ .../i18n/translations/en/translations.json | 9 +- .../i18n/translations/fr/translations.json | 9 +- 18 files changed, 253 insertions(+), 160 deletions(-) create mode 100644 webapp/src/features/charts/components/chart-component.tsx diff --git a/backend/configs/config.json b/backend/configs/config.json index f9349960..80808d7e 100644 --- a/backend/configs/config.json +++ b/backend/configs/config.json @@ -41,9 +41,11 @@ "resource": "inventaire_id", "layerType": "symbol", "columns": { + "geom": "gps.merge().centroid()", "for": "for", "cod": "cod", - "geom": "gps.merge().centroid()", + "taille_placette": "20^2*pi()/10000", + "projet": "trim('A Kob Ale')", "biomass_volume": "ind.sum(0.0673 * (dens_bois.value * (dhp1 + dhp2 + dhp3 + dhp4 + dhp5 + dhp6 + dhp7 + dhp8 + dhp9 + dhp10)^2 * haut)^0.976 if etat = 1) / (20^2 * pi() / 10000) / 1000", "tree_density": "count(1 if ind.etat = 1) / (20^2 * pi() / 10000)", "tree_pop": "count(1 if ind.etat = 1)", diff --git a/webapp/src/app/styles/all4trees.css b/webapp/src/app/styles/all4trees.css index 0116b857..8df5c010 100644 --- a/webapp/src/app/styles/all4trees.css +++ b/webapp/src/app/styles/all4trees.css @@ -36,7 +36,7 @@ /* ======================================================================== COULEURS AJUSTEES - POUR UNE MEILLEURE UI ======================================================================== */ - --custom-color-green-dark: #1a2420; + --custom-color-green-dark: #102017; --custom-color-green-main: #007a55; --custom-color-green-pastel: #d0fae5; --custom-color-grey-light: #e5e7eb; diff --git a/webapp/src/app/styles/index.css b/webapp/src/app/styles/index.css index 8943ea69..6b2c5286 100644 --- a/webapp/src/app/styles/index.css +++ b/webapp/src/app/styles/index.css @@ -55,11 +55,12 @@ --input: #ffffff; --ring: var(--a4t-color-vert-kelly); - --chart-1: var(--a4t-color-bleu); - --chart-2: var(--a4t-color-vert-de-gris); - --chart-3: var(--a4t-color-vert-kelly); - --chart-4: var(--a4t-color-citrouille); - --chart-5: var(--a4t-color-jaune-vert); + + --chart-1: var(--a4t-color-vert-kelly); + --chart-2: var(--a4t-color-citrouille); + --chart-3: var(--custom-color-inventaire); + --chart-4: var(--a4t-color-brunswick-green); + --chart-5: var(--a4t-color-onyx); --sidebar: var(--a4t-color-alabaster); --sidebar-foreground: var(--a4t-color-alabaster); @@ -75,7 +76,7 @@ --background: var(--custom-color-green-dark); --foreground: var(--a4t-color-alabaster); - --card: var(--a4t-color-nuit); + --card: var(--custom-color-green-dark); --card-foreground: var(--a4t-color-alabaster); --popover: var(--a4t-color-nuit); diff --git a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx index 0bee77fd..3056be6e 100644 --- a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx +++ b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx @@ -41,9 +41,6 @@ export const ChartRelativeAbundance: FC = ({ = ({ } satisfies ChartConfig; return ( - - - {title} - - - + + - - - value.slice(0, 4)} - tickLine={false} - tickMargin={10} - /> - } - cursor={false} - /> + + value.slice(0, 4)} + tickLine={false} + tickMargin={10} + /> + } + cursor={false} + /> + + + {withTemoin && ( - - {withTemoin && ( - - )} - - - - + )} + + + ); }; diff --git a/webapp/src/features/charts/components/chart-component.tsx b/webapp/src/features/charts/components/chart-component.tsx new file mode 100644 index 00000000..320e99e5 --- /dev/null +++ b/webapp/src/features/charts/components/chart-component.tsx @@ -0,0 +1,41 @@ +import type { ComponentType, FC, PropsWithChildren } from "react"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@ui/card"; + +type ChartComponentProps = PropsWithChildren<{ + title?: string; + description?: string; + className?: string; +}>; + +export const ChartComponent: FC = ({ + title, + description, + className, + children, +}) => { + return ( + + {(title || description) && ( + + {title ? {title} : null} + {description ? {description} : null} + + )} + {children} + + ); +}; + +ChartComponent.displayName = "ChartComponent"; + +export const markChartComponent =

( + component: ComponentType

, +): ComponentType

& { isChartComponent: boolean } => + Object.assign(component, { isChartComponent: true }); diff --git a/webapp/src/features/charts/components/pie-chart-categorical.tsx b/webapp/src/features/charts/components/pie-chart-categorical.tsx index 1d3c8f72..28085a66 100644 --- a/webapp/src/features/charts/components/pie-chart-categorical.tsx +++ b/webapp/src/features/charts/components/pie-chart-categorical.tsx @@ -1,13 +1,7 @@ import type { FC } from "react"; import { Pie, PieChart, type PieLabel } from "recharts"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@ui/card"; +import { ChartComponent } from "./chart-component"; import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@ui/chart"; export const description = "A pie chart with a label"; @@ -28,27 +22,21 @@ export const PieChartCategorical: FC = ({ withLabel, }) => { return ( - - - {title} - {description} - - - - - } /> - - - - - + + + + } /> + + + + ); }; diff --git a/webapp/src/features/charts/components/radar-benef-control.tsx b/webapp/src/features/charts/components/radar-benef-control.tsx index c1ac5077..f2a57d62 100644 --- a/webapp/src/features/charts/components/radar-benef-control.tsx +++ b/webapp/src/features/charts/components/radar-benef-control.tsx @@ -9,7 +9,7 @@ import { import { useTranslation } from "@shared/i18n"; -import { Card, CardContent, CardDescription, CardHeader } from "@ui/card"; +import { ChartComponent } from "./chart-component"; import { type ChartConfig, ChartContainer, @@ -45,53 +45,50 @@ export const ChartRadarWithBenefAndControl: FC< }; return ( - - - {title} - - - + + - - } - cursor={true} - /> - - - + } + cursor={true} + /> + + + + + {withTemoin && (<> - {withTemoin && ( - - )} - } - /> - - - - + + } + /> + + )} + + + ); }; diff --git a/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx b/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx index 98bf13e5..3186502e 100644 --- a/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx +++ b/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx @@ -3,7 +3,7 @@ import type { FC } from "react"; import Plot from "react-plotly.js"; import { useTranslation } from "@shared/i18n"; -import { Card, CardContent, CardHeader, CardTitle } from "@shared/ui/card"; +import { ChartComponent } from "@features/charts/components/chart-component"; import { SUNBURST_LAYOUT } from "../config"; import { buildNodeColors, buildSunburstNodes } from "../lib/sunburst"; @@ -20,7 +20,9 @@ export const ChartTaxonAbundance: FC = ({ let sunburstData: PlotlyData[] = []; const cardHeight = hasTaxonData ? "min-h-105" : "min-h-40"; if (hasTaxonData) { - const nodes = buildSunburstNodes(dataEntries, metadata, dataType); + // Remove taxon entries with "Aucun" value + const filteredDataEntries = dataEntries.filter(([key]) => key.trim() !== "0"); + const nodes = buildSunburstNodes(filteredDataEntries, metadata, dataType); const nodeColors = buildNodeColors(nodes); sunburstData = [ @@ -39,11 +41,11 @@ export const ChartTaxonAbundance: FC = ({ ] as unknown as PlotlyData[]; } return ( - - - {t("indicators.common.abundance")} - - + +

{hasTaxonData && ( = ({ {t("indicators.common.noData")}
)} - - + + ); }; diff --git a/webapp/src/features/indicators/biodiversity/format-data.ts b/webapp/src/features/indicators/biodiversity/format-data.ts index 9daed1be..de64b884 100644 --- a/webapp/src/features/indicators/biodiversity/format-data.ts +++ b/webapp/src/features/indicators/biodiversity/format-data.ts @@ -70,21 +70,12 @@ export const useFormatBiodiversityData = (data: BiodiversityData) => { verticalDistribution: safeData.epf_vertical_distribution, }, }, - // replace hardcoded value when data will be available - indicatorSpecies: { - abundanceTaxon1: 43, - abundanceTaxon2: 56, - abundanceTaxon3: 33, - speciesRichnessTaxon1: 47, - speciesRichnessTaxon2: 23, - speciesRichnessTaxon3: 24, - }, treeDiversity: { relative_abundance: formatRelativeAbundance( data.relative_abundance, data.tree_pop, ), - speciesRichness: data.richness, + speciesRichness: formatWithUnit(safeData.richness, UNITS.essenceCount), tree_pop: data.tree_pop, }, }; diff --git a/webapp/src/features/indicators/components/indicator-raw-value.tsx b/webapp/src/features/indicators/components/indicator-raw-value.tsx index 39832d45..34d447a6 100644 --- a/webapp/src/features/indicators/components/indicator-raw-value.tsx +++ b/webapp/src/features/indicators/components/indicator-raw-value.tsx @@ -1,3 +1,4 @@ +import { Card, CardContent } from "@shared/ui/card"; import type { FC, ReactNode } from "react"; type IndicatorRawValueProps = { @@ -16,7 +17,9 @@ export const IndicatorRawValue: FC = ({ } return ( -
+ + +
{iconStart}

{dataName}

@@ -25,5 +28,9 @@ export const IndicatorRawValue: FC = ({ {typeof value === "string" ? value : String(value)}

+ + + + ); }; diff --git a/webapp/src/features/indicators/components/indicator-section.tsx b/webapp/src/features/indicators/components/indicator-section.tsx index 2e462ab2..9b27e2e4 100644 --- a/webapp/src/features/indicators/components/indicator-section.tsx +++ b/webapp/src/features/indicators/components/indicator-section.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { FC, PropsWithChildren, ReactNode } from "react"; type IndicatorSectionProps = PropsWithChildren<{ @@ -5,18 +6,88 @@ type IndicatorSectionProps = PropsWithChildren<{ iconStart?: ReactNode; }>; +const isChartElement = (node: ReactNode): boolean => { + if (!React.isValidElement(node)) { + return false; + } + + const element = node as React.ReactElement<{ children?: ReactNode }>; + const type = element.type as { + displayName?: string; + name?: string; + isChartComponent?: boolean; + }; + + const componentName = type.displayName || type.name; + if (type.isChartComponent || componentName?.includes("Chart")) { + return true; + } + + const children = element.props.children; + if (!children) { + return false; + } + + return React.Children.toArray(children).some(isChartElement); +}; + +const flattenChildren = (node: ReactNode): ReactNode[] => { + if (!React.isValidElement(node)) { + return [node]; + } + + if (node.type === React.Fragment) { + const element = node as React.ReactElement<{ children?: ReactNode }>; + return React.Children.toArray(element.props.children).flatMap(flattenChildren); + } + + return [node]; +}; + export const IndicatorSection: FC = ({ title, iconStart, children, }) => { + const childrenArray = React.Children.toArray(children).flatMap(flattenChildren); + const [valueChildren, chartChildren] = childrenArray.reduce<[ + ReactNode[], + ReactNode[], + ]>( + ([values, charts], child) => { + return isChartElement(child) + ? [values, [...charts, child]] + : [[...values, child], charts]; + }, + [[], []], + ); + return (
{iconStart} -
{title}
+
{title}
- {children} + + {valueChildren.length > 0 && ( +
+ {valueChildren.map((child, index) => ( +
+ {child} +
+ ))} +
+ )} + + {chartChildren.length > 0 && ( +
+ {chartChildren.map((child, index) => ( +
+ {child} +
+ ))} +
+ )}
); }; diff --git a/webapp/src/features/indicators/soil/format-data.ts b/webapp/src/features/indicators/soil/format-data.ts index 3e9597ea..cc662554 100644 --- a/webapp/src/features/indicators/soil/format-data.ts +++ b/webapp/src/features/indicators/soil/format-data.ts @@ -12,7 +12,6 @@ import type { SoilData } from "./types"; const indicatorsToPreciseWithFallBack: NumericKeys[] = [ "soil_structure", - "soil_composition", "soil_fauna_density", "surface_fauna_density", ] as const; diff --git a/webapp/src/features/indicators/soil/types.ts b/webapp/src/features/indicators/soil/types.ts index b4822a44..1f7bca0f 100644 --- a/webapp/src/features/indicators/soil/types.ts +++ b/webapp/src/features/indicators/soil/types.ts @@ -4,7 +4,6 @@ export type SoilData = { soil_structure: number; - soil_composition: number; ero_rainfall: number; ero_wind: number; ero_soil_cover: number; 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 da52010a..be8e1a3b 100644 --- a/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx +++ b/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx @@ -23,10 +23,6 @@ export const useSoilIndicatorElements = ( { children: ( <> - { return t("indicators.units.speciesCount", { count: parseInt(formattedValue, 10), }); + case UNITS.essenceCount: + return t("indicators.units.essenceCount", { + count: parseInt(formattedValue, 10), + }); case UNITS.tonPerHectare: return t("indicators.units.tonPerHectare", { value }); case UNITS.m3PerHabPerYear: diff --git a/webapp/src/shared/i18n/translations/en/translations.json b/webapp/src/shared/i18n/translations/en/translations.json index 41bbc85a..06c9ab49 100644 --- a/webapp/src/shared/i18n/translations/en/translations.json +++ b/webapp/src/shared/i18n/translations/en/translations.json @@ -92,7 +92,7 @@ "title": "Tree Diversity" } }, - "title": "Biodiversity Data" + "title": "Biodiversity" }, "common": { "abundance": "Relative abundance", @@ -218,7 +218,7 @@ }, "unknownTaxon": "Unknown taxon" }, - "title": "Soil data" + "title": "Soil" }, "units": { "individualPerCubicMeter": "{{ value }} ind/m³", @@ -227,9 +227,8 @@ "m3PerHabPerYear": "{{ value }} m³/inhab/year", "monthPerYear": "{{ value }} month/year", "percentFoodRequirements": "{{ value }} % of food needs", - "speciesCount": "", - "speciesCount_one": "{{ count }} species inventoried", - "speciesCount_other": "{{ count }} species inventoried", + "speciesCount": "{{ count }} species inventoried", + "essenceCount": "{{ count }} essences inventoried", "tonPerHectare": "{{ value }} t/ha" } }, diff --git a/webapp/src/shared/i18n/translations/fr/translations.json b/webapp/src/shared/i18n/translations/fr/translations.json index e0bd7aef..0ccaa9bb 100644 --- a/webapp/src/shared/i18n/translations/fr/translations.json +++ b/webapp/src/shared/i18n/translations/fr/translations.json @@ -92,7 +92,7 @@ "title": "Diversité arborée" } }, - "title": "Données biodiversité" + "title": "Biodiversité" }, "common": { "abundance": "Abondance relative", @@ -126,7 +126,7 @@ "title": "Modèle économique" }, "food": { - "autoConso": "Satsifaction des besoins en auto-consommation", + "autoConso": "Satisfaction des besoins en auto-consommation", "diversity": { "cereals": "Céréales", "eggs": "Œufs", @@ -218,7 +218,7 @@ }, "unknownTaxon": "Taxon inconnu" }, - "title": "Données sol" + "title": "Sol" }, "units": { "individualPerCubicMeter": "{{ value }} ind/m³", @@ -230,6 +230,9 @@ "speciesCount": "", "speciesCount_one": "{{ count }} espèce inventoriée", "speciesCount_other": "{{ count }} espèces inventoriées", + "essenceCount": "", + "essenceCount_one": "{{ count }} essence inventoriée", + "essenceCount_other": "{{ count }} essences inventoriées", "tonPerHectare": "{{ value }} t/ha" } }, From 65cec7572333a2206c173e240ef8c80b6ccd9ea9 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Fri, 1 May 2026 13:08:37 +0200 Subject: [PATCH 2/8] Continue applying feedback --- backend/configs/config.json | 4 +++- .../use-economic-indicator-elements.tsx | 3 ++- .../features/indicators/social/format-data.ts | 3 +-- .../src/features/indicators/social/types.ts | 2 -- .../social/use-social-indicator-elements.tsx | 1 + webapp/src/features/indicators/utils.ts | 4 ++++ .../components/indicator-popup-header.tsx | 4 ++-- .../popup-forest-inventory.tsx | 22 ++++++++++-------- .../features/popup/forest-inventory/types.ts | 3 +++ .../popup/socio-eco/popup-socio-eco.tsx | 18 ++++++++++----- webapp/src/features/popup/socio-eco/types.ts | 2 ++ .../i18n/translations/en/translations.json | 23 ++++++++++++++----- .../i18n/translations/fr/translations.json | 21 +++++++++++++---- webapp/src/shared/lib/utils.ts | 3 +++ 14 files changed, 78 insertions(+), 35 deletions(-) diff --git a/backend/configs/config.json b/backend/configs/config.json index 80808d7e..9a516c6f 100644 --- a/backend/configs/config.json +++ b/backend/configs/config.json @@ -42,6 +42,7 @@ "layerType": "symbol", "columns": { "geom": "gps.merge().centroid()", + "id": "_id", "for": "for", "cod": "cod", "taille_placette": "20^2*pi()/10000", @@ -93,6 +94,7 @@ "groupby": ["admi2"], "columns": { "geom": "centroid(merge(point(socio_eco_gps.long, socio_eco_gps.latit)))", + "household_nb": "count(_id)", "population": "sum(hab)", "energyType": "count(ener)", "boughtWoodEnergy": "count(1)", @@ -103,7 +105,7 @@ "otherEnergy": "count(1)", "woodEnergyConsumption": "count(1)", "woodEnergyNeeds": "count(1)", - "woodCollectionTime:": "count(1)", + "woodCollectionTime": "count(1)", "timberNeeds": "count(1)", "foodDiversity": "count(1)", "autoConsumtionNeeds": "count(1)", 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 cc3e3185..66d8ed7d 100644 --- a/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx +++ b/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx @@ -42,12 +42,13 @@ export const useEconomicIndicatorElements = ( { children: ( <> + { /* Not implemented yet, waiting for All4Trees to better define this indicator. + /> */ } diff --git a/webapp/src/features/indicators/social/format-data.ts b/webapp/src/features/indicators/social/format-data.ts index 9e63ddb2..60fb28aa 100644 --- a/webapp/src/features/indicators/social/format-data.ts +++ b/webapp/src/features/indicators/social/format-data.ts @@ -10,7 +10,6 @@ import { import type { SocialData } from "./types"; const indicatorKeys: NumericKeys[] = [ - "population", "collectedWoodEnergy", "boughtWoodEnergy", "coalEnergy", @@ -56,7 +55,7 @@ export const useFormatSocialData = (data: SocialData) => { leanPeriod: formatWithUnit(3, UNITS.monthPerYear), }, wood: { - collectionTime: safeData.woodCollectionTime, + collectionTime: formatWithUnit(safeData.woodCollectionTime, UNITS.minPerHouseholdPerDay), energyConsumption: formatWithUnit(0, UNITS.m3PerHabPerYear), energyNeeds: { difficultToMeet: 3, diff --git a/webapp/src/features/indicators/social/types.ts b/webapp/src/features/indicators/social/types.ts index 4b059904..7a9510f9 100644 --- a/webapp/src/features/indicators/social/types.ts +++ b/webapp/src/features/indicators/social/types.ts @@ -1,6 +1,4 @@ export type SocialData = { - admi2: string; - population: number; collectedWoodEnergy: number; boughtWoodEnergy: number; coalEnergy: 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 2173a4b9..0337c309 100644 --- a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx +++ b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx @@ -18,6 +18,7 @@ export const useSocialIndicatorElements = ( const { t } = useTranslation("translations"); const data = useFormatSocialData(rawData); + console.log("Formatted Data", data); return [ { children: ( diff --git a/webapp/src/features/indicators/utils.ts b/webapp/src/features/indicators/utils.ts index 482c7700..d833424b 100644 --- a/webapp/src/features/indicators/utils.ts +++ b/webapp/src/features/indicators/utils.ts @@ -12,6 +12,7 @@ export const UNITS = { speciesCount: "speciesCount", essenceCount: "essenceCount", tonPerHectare: "tonPerHectare", + minPerHouseholdPerDay: "minPerHouseholdPerDay", } as const; export type Unit = keyof typeof UNITS; @@ -30,6 +31,7 @@ export const useFormatterWithUnit = () => { return null; } + console.log(`Formatting value ${value} with unit ${unit}`) const formattedValue = typeof value === "number" ? precise(value) : value; switch (unit) { @@ -55,6 +57,8 @@ export const useFormatterWithUnit = () => { return t("indicators.units.monthPerYear", { value }); case UNITS.percentFoodRequirements: return t("indicators.units.percentFoodRequirements", { value }); + case UNITS.minPerHouseholdPerDay: + return t("indicators.units.minPerHhPerDay", { value }); default: return null; } diff --git a/webapp/src/features/popup/components/indicator-popup-header.tsx b/webapp/src/features/popup/components/indicator-popup-header.tsx index 510a90d6..4046fc00 100644 --- a/webapp/src/features/popup/components/indicator-popup-header.tsx +++ b/webapp/src/features/popup/components/indicator-popup-header.tsx @@ -1,5 +1,5 @@ import { XIcon } from "lucide-react"; -import type { FC, ReactNode } from "react"; +import type { FC, ReactElement, ReactNode } from "react"; import { cn } from "@shared/lib/utils"; import { @@ -15,7 +15,7 @@ import { ICON_SIZE_HEADER } from "../../indicators/components/constants"; type IndicatorPopupHeaderProps = { icon: ReactNode; title: string; - subtitle?: string; + subtitle?: string | ReactElement; onCrossClick: () => void; }; 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 445cb4a8..77996157 100644 --- a/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx +++ b/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx @@ -39,13 +39,15 @@ export const ForestInventoryPopupContent: FC< const biodiversityElements = useBiodiversityIndicatorElements(data, metadata); const soilElements = useSoilIndicatorElements(data, metadata); - const title = t("popup.forestInventory", { - code: data.cod, - label: - findCategoricalLabel(metadata, "for", data.for) || t("popup.undefined"), - }); - - const subtitles = { + const title = t("popup.forestInventory.title", { id: data.id }); + const subtitle = ( +
+ {t("popup.forestInventory.subtitle", { for: findCategoricalLabel(metadata, "for", data.for) || t('popup.undefined') })} +
+ {t("popup.forestInventory.date", { date: formatDate(new Date()) })} +
+ ) + const tabs = { [TABS.BIODIVERSITY]: t("indicators.biodiversity.title"), [TABS.SOIL]: t("indicators.soil.title"), }; @@ -55,7 +57,7 @@ export const ForestInventoryPopupContent: FC< } onCrossClick={onClose} - subtitle={formatDate(new Date())} + subtitle={subtitle} title={title} /> @@ -65,11 +67,11 @@ export const ForestInventoryPopupContent: FC< options={[ { id: TABS.BIODIVERSITY, - label: subtitles[TABS.BIODIVERSITY], + label: tabs[TABS.BIODIVERSITY], }, { id: TABS.SOIL, - label: subtitles[TABS.SOIL], + label: tabs[TABS.SOIL], }, ]} value={selectedTab} diff --git a/webapp/src/features/popup/forest-inventory/types.ts b/webapp/src/features/popup/forest-inventory/types.ts index cf554b39..16b9fd10 100644 --- a/webapp/src/features/popup/forest-inventory/types.ts +++ b/webapp/src/features/popup/forest-inventory/types.ts @@ -2,7 +2,10 @@ import type { BiodiversityData } from "@features/indicators/biodiversity/types"; import type { SoilData } from "@features/indicators/soil/types"; export type ForestInventoryData = { + id: string; for: string; cod: number; + projet: string; + taille_placette: number; } & SoilData & BiodiversityData; 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 cc3b23fe..50a9e05e 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -57,11 +57,17 @@ export const SocioEcoIndicator: FC = ({ const socialElements = useSocialIndicatorElements(data); const economicElements = useEconomicIndicatorElements(data); - const title = t("popup.socioEco", { + const title = t("popup.socioEco.title", { village: exctractVillageName(metadata, data), }); - - const subtitles = { + const subtitle = ( +
+ {t("popup.socioEco.subtitleCount", { count: data.household_nb })} +
+ {t("popup.socioEco.date", { date: formatDate(new Date()) })} +
+ ) + const tabs = { [TABS.RESOURCES]: t("indicators.resources.title"), [TABS.ECONOMY]: t("indicators.economy.title"), }; @@ -71,7 +77,7 @@ export const SocioEcoIndicator: FC = ({ } onCrossClick={onClose} - subtitle={formatDate(new Date())} + subtitle={subtitle} title={title} /> @@ -81,11 +87,11 @@ export const SocioEcoIndicator: FC = ({ options={[ { id: TABS.RESOURCES, - label: subtitles[TABS.RESOURCES], + label: tabs[TABS.RESOURCES], }, { id: TABS.ECONOMY, - label: subtitles[TABS.ECONOMY], + label: tabs[TABS.ECONOMY], }, ]} value={selectedTab} diff --git a/webapp/src/features/popup/socio-eco/types.ts b/webapp/src/features/popup/socio-eco/types.ts index af29fa2b..8cf78cd9 100644 --- a/webapp/src/features/popup/socio-eco/types.ts +++ b/webapp/src/features/popup/socio-eco/types.ts @@ -2,6 +2,8 @@ import type { EconomicData } from "@features/indicators/economy/types"; import type { SocialData } from "@features/indicators/social/types"; export type SocioEcoData = { + household_nb: number; + population: number; admi2: string; } & SocialData & EconomicData; diff --git a/webapp/src/shared/i18n/translations/en/translations.json b/webapp/src/shared/i18n/translations/en/translations.json index 06c9ab49..a998ab44 100644 --- a/webapp/src/shared/i18n/translations/en/translations.json +++ b/webapp/src/shared/i18n/translations/en/translations.json @@ -104,10 +104,10 @@ "speciesRichness": "Species richness" }, "economy": { - "title": "Economy and governance" + "title": "Local Population" }, "resources": { - "title": "Households resources" + "title": "Resources & Territory" }, "socioEco": { "sections": { @@ -228,14 +228,25 @@ "monthPerYear": "{{ value }} month/year", "percentFoodRequirements": "{{ value }} % of food needs", "speciesCount": "{{ count }} species inventoried", - "essenceCount": "{{ count }} essences inventoried", - "tonPerHectare": "{{ value }} t/ha" + "essenceCount": "{{ count }} species inventoried", + "tonPerHectare": "{{ value }} t/ha", + "minPerHhPerDay": "min/household/day" } }, "popup": { - "forestInventory": "Plot n°{{ code }} in {{ label }} forest", + "forestInventory": { + "title": "Plot n°{{ id }}", + "subtitle": "{{ for }} forest", + "date": "Site survey date : {{ date }}" + }, "seed": "Plot n°{{ id}}", - "socioEco": "Village {{ village }}", + "socioEco": { + "title": "{{ village }}", + "subtitleCount": "", + "subtitleCount_one": "{{ count }} household surveyed", + "subtitleCount_other": "{{ count }} households surveyed", + "date":"Survey date: {{ date }}" + }, "undefined": "not found" }, "seed": { diff --git a/webapp/src/shared/i18n/translations/fr/translations.json b/webapp/src/shared/i18n/translations/fr/translations.json index 0ccaa9bb..9469954b 100644 --- a/webapp/src/shared/i18n/translations/fr/translations.json +++ b/webapp/src/shared/i18n/translations/fr/translations.json @@ -104,10 +104,10 @@ "speciesRichness": "Richesse spécifique" }, "economy": { - "title": "Modèle économique et gouvernance" + "title": "Population locale" }, "resources": { - "title": "Ressources des ménages" + "title": "Ressources et territoires" }, "socioEco": { "sections": { @@ -233,13 +233,24 @@ "essenceCount": "", "essenceCount_one": "{{ count }} essence inventoriée", "essenceCount_other": "{{ count }} essences inventoriées", - "tonPerHectare": "{{ value }} t/ha" + "tonPerHectare": "{{ value }} t/ha", + "minPerHhPerDay": "min/ménage/jour" } }, "popup": { - "forestInventory": "Placette n°{{ code }} dans la forêt {{ label }}", + "forestInventory": { + "title": "Placette n°{{ id }}", + "subtitle": "Forêt {{ for }}", + "date":"Date relevé: {{ date }}" + }, "seed": "Placette n°{{ id}}", - "socioEco": "Village {{ village }}", + "socioEco": { + "title": "{{ village }}", + "subtitleCount": "", + "subtitleCount_one": "{{ count }} ménage enquêté", + "subtitleCount_other": "{{ count }} ménages enquêtés", + "date":"Date relevé: {{ date }}" + }, "undefined": "non trouvée" }, "seed": { diff --git a/webapp/src/shared/lib/utils.ts b/webapp/src/shared/lib/utils.ts index 013177c1..da55b391 100644 --- a/webapp/src/shared/lib/utils.ts +++ b/webapp/src/shared/lib/utils.ts @@ -29,6 +29,8 @@ export function findCategoricalLabel( fieldName: string, fieldValue: any, ): string | undefined { + + // Search field category in main resource schema const resourceLabel = metadata?.resource?.schema?.fields .find((f) => f.name === fieldName) ?.categories?.find((c) => c.value === fieldValue)?.label; @@ -37,6 +39,7 @@ export function findCategoricalLabel( return resourceLabel; } + // Search field category in main resource's references' schemas return metadata?.references ?.find((ref) => ref.schema.fields From 5630802f719182e335c208fdb886dce9d393fa39 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Sat, 2 May 2026 11:17:31 +0200 Subject: [PATCH 3/8] Improve pie & bar charts rendering --- backend/configs/config.json | 2 +- webapp/src/app/styles/index.css | 1 - .../biodiversity/chart-relative-abundance.tsx | 19 ++-- .../components/bar-chart-benef-control.tsx | 41 ++++++- .../charts/components/chart-component.tsx | 4 +- .../components/pie-chart-categorical.tsx | 101 ++++++++++++++++-- .../charts/components/radar-benef-control.tsx | 53 ++++++--- .../components/render-polar-angle-tick.tsx | 27 ----- .../socio-eco/chart-living-perception.tsx | 41 +------ .../charts/socio-eco/chart-timber-needs.tsx | 3 +- .../socio-eco/chart-wood-energy-needs.tsx | 3 +- .../charts/soil/ui/chart-taxon-abundance.tsx | 11 +- .../components/indicator-raw-value.tsx | 27 +++-- .../components/indicator-section.tsx | 26 +++-- .../use-economic-indicator-elements.tsx | 4 +- .../features/indicators/social/format-data.ts | 5 +- .../social/use-social-indicator-elements.tsx | 1 - webapp/src/features/indicators/utils.ts | 5 +- .../popup-forest-inventory.tsx | 14 ++- webapp/src/features/popup/renderPopup.tsx | 4 +- .../popup/socio-eco/popup-socio-eco.tsx | 6 +- .../i18n/translations/en/translations.json | 18 ++-- .../i18n/translations/fr/translations.json | 18 ++-- webapp/src/shared/lib/utils.ts | 5 +- 24 files changed, 275 insertions(+), 164 deletions(-) delete mode 100644 webapp/src/features/charts/components/render-polar-angle-tick.tsx diff --git a/backend/configs/config.json b/backend/configs/config.json index 9a516c6f..70679900 100644 --- a/backend/configs/config.json +++ b/backend/configs/config.json @@ -55,7 +55,7 @@ "epf_tree_density": "count(1 if ind.etat = 1) / (20^2 * pi() / 10000) / 100", "epf_necromass_pied": "ind.sum(0.0673 * (dens_bois_mort.dens_bois * (dhp1 + dhp2 + dhp3 + dhp4 + dhp5 + dhp6 + dhp7 + dhp8 + dhp9 + dhp10)^2 * haut)^0.976 if etat = 2)", "epf_necromass_sol": "ind.sum(3.1415926535 / 3 * ((dhp_b / 2)^2 + ((dhp_e / 2)^2 + dhp_b / 2 + dhp_e / 2) * longueur * dens_bois_mort.dens_bois) if etat = 3)", - "epf_deadWood": "((ind.sum(0.0673 * (dens_bois_mort.dens_bois * (dhp1 + dhp2 + dhp3 + dhp4 + dhp5 + dhp6 + dhp7 + dhp8 + dhp9 + dhp10)^2 * haut)^0.976 if etat = 2) + ind.sum(3.1415926535 / 3 * ((dhp_b / 2)^2 + ((dhp_e / 2)^2 + dhp_b / 2 + dhp_e / 2) * longueur * dens_bois_mort.dens_bois) if etat = 3)) / ind.sum(0.0673 * (dens_bois.value * (dhp1 + dhp2 + dhp3 + dhp4 + dhp5 + dhp6 + dhp7 + dhp8 + dhp9 + dhp10)^2 * haut)^0.976 if etat = 1)) * 10", + "epf_deadWood": "((ind.sum(0.0673 * (dens_bois_mort.dens_bois * (dhp1 + dhp2 + dhp3 + dhp4 + dhp5 + dhp6 + dhp7 + dhp8 + dhp9 + dhp10)^2 * haut)^0.976 if etat = 2) + ind.sum(pi() / 3 * ((dhp_b / 2)^2 + ((dhp_e / 2)^2 + dhp_b / 2 + dhp_e / 2) * longueur * dens_bois_mort.dens_bois) if etat = 3)) / ind.sum(0.0673 * (dens_bois.value * (dhp1 + dhp2 + dhp3 + dhp4 + dhp5 + dhp6 + dhp7 + dhp8 + dhp9 + dhp10)^2 * haut)^0.976 if etat = 1)) * 10", "epf_tree_diversity": "shannon(ind.ess_arb) / 5 * 10", "epf_spatial_distribution": "ind.categorical_gini(zon) * 10", "epf_diameter_distribution": "ind.gini(dhp1 + dhp2 + dhp3 + dhp4 + dhp5 + dhp6 + dhp7 + dhp8 + dhp9 + dhp10) * 10", diff --git a/webapp/src/app/styles/index.css b/webapp/src/app/styles/index.css index 6b2c5286..6fa8976d 100644 --- a/webapp/src/app/styles/index.css +++ b/webapp/src/app/styles/index.css @@ -55,7 +55,6 @@ --input: #ffffff; --ring: var(--a4t-color-vert-kelly); - --chart-1: var(--a4t-color-vert-kelly); --chart-2: var(--a4t-color-citrouille); --chart-3: var(--custom-color-inventaire); diff --git a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx index 3056be6e..779a362c 100644 --- a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx +++ b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx @@ -17,19 +17,21 @@ export const ChartRelativeAbundance: FC = ({ metadata, }) => { const { t } = useTranslation("translations"); - const chartData = data.map((element, index) => ({ - fill: `var(--chart-${(index % 4) + 1})`, - name: element[0], - value: element[1], - })); + const chartData = data + .filter(([name, _]) => name != "0") + .map((element, index) => ({ + fill: `var(--chart-${(index % 4) + 1})`, + name: element[0], + value: element[1], + })); let chartConfig: ChartConfig = {}; - data.forEach((element) => { + chartData.forEach((element) => { chartConfig = { ...chartConfig, - [element[0]]: { + [element.name]: { label: - findCategoricalLabel(metadata, "ess_arb", element[0]) || + findCategoricalLabel(metadata, "ess_arb", element.name) || t( "indicators.biodiversity.sections.treeDiversity.relativeAbundance.other", ), @@ -44,6 +46,7 @@ export const ChartRelativeAbundance: FC = ({ title={t( "indicators.biodiversity.sections.treeDiversity.relativeAbundance.title", )} + unit="%" withLabel /> ); 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 2769c89f..160ecdbc 100644 --- a/webapp/src/features/charts/components/bar-chart-benef-control.tsx +++ b/webapp/src/features/charts/components/bar-chart-benef-control.tsx @@ -1,8 +1,7 @@ import type { FC } from "react"; -import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"; +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { useTranslation } from "@shared/i18n"; -import { ChartComponent } from "./chart-component"; import { type ChartConfig, ChartContainer, @@ -10,6 +9,8 @@ import { ChartTooltipContent, } from "@shared/ui/chart"; +import { ChartComponent } from "./chart-component"; + type BarChartProps = { title: string; chartData: Array<{ indicator: string; benef: unknown; temoin?: unknown }>; @@ -38,7 +39,7 @@ export const BarCharWithBenefAndControl: FC = ({ return ( = ({ value.slice(0, 4)} + height={90} + interval={0} + tick={renderXAxisTick} tickLine={false} tickMargin={10} /> + `${value}%`} + tickLine={false} + tickMargin={-2} + width={24} + /> } cursor={false} @@ -75,3 +86,25 @@ export const BarCharWithBenefAndControl: FC = ({ ); }; + +const renderXAxisTick = ({ + x, + y, + payload, +}: { + x: number; + y: number; + payload: { value: string | number }; +}) => ( + + {payload.value} + +); diff --git a/webapp/src/features/charts/components/chart-component.tsx b/webapp/src/features/charts/components/chart-component.tsx index 320e99e5..cf4552b2 100644 --- a/webapp/src/features/charts/components/chart-component.tsx +++ b/webapp/src/features/charts/components/chart-component.tsx @@ -25,7 +25,9 @@ export const ChartComponent: FC = ({ {(title || description) && ( {title ? {title} : null} - {description ? {description} : null} + {description ? ( + {description} + ) : null} )} {children} diff --git a/webapp/src/features/charts/components/pie-chart-categorical.tsx b/webapp/src/features/charts/components/pie-chart-categorical.tsx index 28085a66..dee248f1 100644 --- a/webapp/src/features/charts/components/pie-chart-categorical.tsx +++ b/webapp/src/features/charts/components/pie-chart-categorical.tsx @@ -1,8 +1,10 @@ -import type { FC } from "react"; +import type { FC, JSX } from "react"; import { Pie, PieChart, type PieLabel } from "recharts"; +import { ChartContainer, ChartTooltip } from "@ui/chart"; + +import { lineBreakLabel } from "../utils"; import { ChartComponent } from "./chart-component"; -import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@ui/chart"; export const description = "A pie chart with a label"; @@ -12,6 +14,12 @@ export type PieChartCategoricalProps = { chartConfig: any; description?: string; withLabel?: PieLabel; + unit?: string; + innerRadius?: number; + outerRadius?: number; + paddingAngle?: number; + minAngle?: number; + labelLine?: boolean; }; export const PieChartCategorical: FC = ({ @@ -20,23 +28,104 @@ export const PieChartCategorical: FC = ({ title, description, withLabel, + unit, }) => { + const CustomTooltip = ({ active, payload }: any) => { + if (active && payload && payload.length) { + return ( +
+

+ {chartConfig[payload[0].name]?.label || payload[0].name}:{" "} + {payload[0].value} {unit} +

+
+ ); + } + return null; + }; return ( - + - } /> + } /> renderLabel({ ...props, chartConfig }) + : false + } + labelLine={false} nameKey="name" + outerRadius={140} /> ); }; + +const renderLabel = ({ + payload, + cx, + cy, + midAngle, + outerRadius, + chartConfig, +}: { + payload: any; + cx: number; + cy: number; + midAngle: number; + outerRadius: number; + chartConfig: any; +}): JSX.Element => { + const label = chartConfig[payload.name]?.label || payload.name; + const RADIAN = Math.PI / 180; + const angle = -midAngle * RADIAN; + const lineStartX = cx + outerRadius * Math.cos(angle); + const lineStartY = cy + outerRadius * Math.sin(angle); + const lineMidX = cx + (outerRadius + 10) * Math.cos(angle); + const lineMidY = cy + (outerRadius + 10) * Math.sin(angle); + const labelX = lineMidX + (lineMidX > cx ? 14 : -14); + const labelY = lineMidY; + const textAnchor = lineMidX > cx ? "start" : "end"; + const lines = lineBreakLabel(label, 13); + + return ( + + + + {lines.map((line, index) => ( + + {line} + + ))} + + + ); +}; diff --git a/webapp/src/features/charts/components/radar-benef-control.tsx b/webapp/src/features/charts/components/radar-benef-control.tsx index f2a57d62..84ba2818 100644 --- a/webapp/src/features/charts/components/radar-benef-control.tsx +++ b/webapp/src/features/charts/components/radar-benef-control.tsx @@ -9,7 +9,6 @@ import { import { useTranslation } from "@shared/i18n"; -import { ChartComponent } from "./chart-component"; import { type ChartConfig, ChartContainer, @@ -20,7 +19,8 @@ import { } from "@ui/chart"; import { RADAR_CONFIG } from "../constants"; -import { renderPolarAngleTick } from "./render-polar-angle-tick"; +import { lineBreakLabel } from "../utils"; +import { ChartComponent } from "./chart-component"; type ChartRadarWithBenefAndControlProps = { title: string; @@ -73,18 +73,19 @@ export const ChartRadarWithBenefAndControl: FC< stroke="var(--color-benef)" {...RADAR_CONFIG} /> - {withTemoin && (<> - + {withTemoin && ( + <> + - } - /> + } + /> )} @@ -92,3 +93,29 @@ export const ChartRadarWithBenefAndControl: FC< ); }; + +const renderPolarAngleTick = ({ payload, x, y, textAnchor }: any) => { + const label = String(payload?.value ?? ""); + const lines = lineBreakLabel(label); + + return ( + + {lines.map((line, index) => ( + + key={index} + x={x} + > + {line} + + ))} + + ); +}; diff --git a/webapp/src/features/charts/components/render-polar-angle-tick.tsx b/webapp/src/features/charts/components/render-polar-angle-tick.tsx deleted file mode 100644 index d77f7f91..00000000 --- a/webapp/src/features/charts/components/render-polar-angle-tick.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { lineBreakLabel } from "../utils"; - -export const renderPolarAngleTick = ({ payload, x, y, textAnchor }: any) => { - const label = String(payload?.value ?? ""); - const lines = lineBreakLabel(label); - - return ( - - {lines.map((line, index) => ( - - key={index} - x={x} - > - {line} - - ))} - - ); -}; diff --git a/webapp/src/features/charts/socio-eco/chart-living-perception.tsx b/webapp/src/features/charts/socio-eco/chart-living-perception.tsx index 46003552..2ac9595b 100644 --- a/webapp/src/features/charts/socio-eco/chart-living-perception.tsx +++ b/webapp/src/features/charts/socio-eco/chart-living-perception.tsx @@ -4,7 +4,6 @@ import { useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; import { PieChartCategorical } from "../components/pie-chart-categorical"; -import { lineBreakLabel } from "../utils"; type PieChartProps = { data: { @@ -75,44 +74,8 @@ export const ChartLivingPerception: FC = ({ data }) => { chartConfig={chartConfig} chartData={chartData} title={t("indicators.socioEco.sections.economy.livingPerception.title")} - withLabel={/*renderLabel(chartConfig)*/ false} + unit="%" + withLabel /> ); }; - -// TODO: Would be nice to display labels correctly but there is not enough space in chart card. -//@ts-expect-error -const _renderLabel = - (chartConfig: any) => - ({ - payload, - ...props - }: { - payload: { name: string; value: number }; - [key: string]: any; - }) => { - const name = payload.name as keyof typeof chartConfig; - const label = `${chartConfig[name]?.label || payload.name}: ${payload.value}%`; - const lines = lineBreakLabel(label); - - return ( - - {lines.map((line, index) => ( - - key={index} - x={props.x} - > - {line} - - ))} - - ); - }; 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 c564d544..4630e720 100644 --- a/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx @@ -59,7 +59,8 @@ export const ChartTimberNeeds: FC = ({ data }) => { chartConfig={chartConfig} chartData={chartData} title={t("indicators.socioEco.sections.wood.timberNeeds.title")} - withLabel={/*renderLabel(chartConfig)*/ false} + unit="%" + withLabel /> ); }; diff --git a/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx b/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx index fe113105..afcafceb 100644 --- a/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx @@ -59,7 +59,8 @@ export const ChartWoodEnergyNeeds: FC = ({ data }) => { chartConfig={chartConfig} chartData={chartData} title={t("indicators.socioEco.sections.wood.timberNeeds.title")} - withLabel={/*renderLabel(chartConfig)*/ false} + unit="%" + withLabel /> ); }; diff --git a/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx b/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx index 3186502e..b8039e8d 100644 --- a/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx +++ b/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx @@ -2,9 +2,10 @@ import type { Data as PlotlyData } from "plotly.js"; import type { FC } from "react"; import Plot from "react-plotly.js"; -import { useTranslation } from "@shared/i18n"; import { ChartComponent } from "@features/charts/components/chart-component"; +import { useTranslation } from "@shared/i18n"; + import { SUNBURST_LAYOUT } from "../config"; import { buildNodeColors, buildSunburstNodes } from "../lib/sunburst"; import type { PieChartProps, SunburstTrace } from "../types"; @@ -20,8 +21,10 @@ export const ChartTaxonAbundance: FC = ({ let sunburstData: PlotlyData[] = []; const cardHeight = hasTaxonData ? "min-h-105" : "min-h-40"; if (hasTaxonData) { - // Remove taxon entries with "Aucun" value - const filteredDataEntries = dataEntries.filter(([key]) => key.trim() !== "0"); + // Remove taxon entries with "Aucun" value + const filteredDataEntries = dataEntries.filter( + ([key]) => key.trim() !== "0", + ); const nodes = buildSunburstNodes(filteredDataEntries, metadata, dataType); const nodeColors = buildNodeColors(nodes); @@ -42,8 +45,8 @@ export const ChartTaxonAbundance: FC = ({ } return (
{hasTaxonData && ( diff --git a/webapp/src/features/indicators/components/indicator-raw-value.tsx b/webapp/src/features/indicators/components/indicator-raw-value.tsx index 34d447a6..01a81034 100644 --- a/webapp/src/features/indicators/components/indicator-raw-value.tsx +++ b/webapp/src/features/indicators/components/indicator-raw-value.tsx @@ -1,6 +1,7 @@ -import { Card, CardContent } from "@shared/ui/card"; import type { FC, ReactNode } from "react"; +import { Card, CardContent } from "@shared/ui/card"; + type IndicatorRawValueProps = { dataName: string; value?: string | number | boolean | null; @@ -17,20 +18,18 @@ export const IndicatorRawValue: FC = ({ } return ( - + -
-
- {iconStart} -

{dataName}

-
-

- {typeof value === "string" ? value : String(value)} -

-
+
+
+ {iconStart} +

{dataName}

+
+

+ {typeof value === "string" ? value : String(value)} +

+
- -
- +
); }; diff --git a/webapp/src/features/indicators/components/indicator-section.tsx b/webapp/src/features/indicators/components/indicator-section.tsx index 9b27e2e4..95f82b8b 100644 --- a/webapp/src/features/indicators/components/indicator-section.tsx +++ b/webapp/src/features/indicators/components/indicator-section.tsx @@ -1,5 +1,5 @@ -import React from "react"; import type { FC, PropsWithChildren, ReactNode } from "react"; +import React from "react"; type IndicatorSectionProps = PropsWithChildren<{ title: string; @@ -38,7 +38,9 @@ const flattenChildren = (node: ReactNode): ReactNode[] => { if (node.type === React.Fragment) { const element = node as React.ReactElement<{ children?: ReactNode }>; - return React.Children.toArray(element.props.children).flatMap(flattenChildren); + return React.Children.toArray(element.props.children).flatMap( + flattenChildren, + ); } return [node]; @@ -49,11 +51,11 @@ export const IndicatorSection: FC = ({ iconStart, children, }) => { - const childrenArray = React.Children.toArray(children).flatMap(flattenChildren); - const [valueChildren, chartChildren] = childrenArray.reduce<[ - ReactNode[], - ReactNode[], - ]>( + const childrenArray = + React.Children.toArray(children).flatMap(flattenChildren); + const [valueChildren, chartChildren] = childrenArray.reduce< + [ReactNode[], ReactNode[]] + >( ([values, charts], child) => { return isChartElement(child) ? [values, [...charts, child]] @@ -72,7 +74,10 @@ export const IndicatorSection: FC = ({ {valueChildren.length > 0 && (
{valueChildren.map((child, index) => ( -
+
{child}
))} @@ -82,7 +87,10 @@ export const IndicatorSection: FC = ({ {chartChildren.length > 0 && (
{chartChildren.map((child, index) => ( -
+
{child}
))} 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 66d8ed7d..d7bfe94a 100644 --- a/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx +++ b/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx @@ -42,13 +42,13 @@ export const useEconomicIndicatorElements = ( { children: ( <> - { /* Not implemented yet, waiting for All4Trees to better define this indicator. + {/* Not implemented yet, waiting for All4Trees to better define this indicator. */ } + /> */} diff --git a/webapp/src/features/indicators/social/format-data.ts b/webapp/src/features/indicators/social/format-data.ts index 60fb28aa..80670144 100644 --- a/webapp/src/features/indicators/social/format-data.ts +++ b/webapp/src/features/indicators/social/format-data.ts @@ -55,7 +55,10 @@ export const useFormatSocialData = (data: SocialData) => { leanPeriod: formatWithUnit(3, UNITS.monthPerYear), }, wood: { - collectionTime: formatWithUnit(safeData.woodCollectionTime, UNITS.minPerHouseholdPerDay), + collectionTime: formatWithUnit( + safeData.woodCollectionTime, + UNITS.minPerHouseholdPerDay, + ), energyConsumption: formatWithUnit(0, UNITS.m3PerHabPerYear), energyNeeds: { difficultToMeet: 3, 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 0337c309..2173a4b9 100644 --- a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx +++ b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx @@ -18,7 +18,6 @@ export const useSocialIndicatorElements = ( const { t } = useTranslation("translations"); const data = useFormatSocialData(rawData); - console.log("Formatted Data", data); return [ { children: ( diff --git a/webapp/src/features/indicators/utils.ts b/webapp/src/features/indicators/utils.ts index d833424b..43f305d1 100644 --- a/webapp/src/features/indicators/utils.ts +++ b/webapp/src/features/indicators/utils.ts @@ -3,16 +3,16 @@ import { precise } from "@shared/lib/utils"; import type { NumericKeys } from "@shared/types"; export const UNITS = { + essenceCount: "essenceCount", individualPerCubicMeter: "individualPerCubicMeter", individualPerHectare: "individualPerHectare", individualPerTrap: "individualPerTrap", m3PerHabPerYear: "m3PerHabPerYear", + minPerHouseholdPerDay: "minPerHouseholdPerDay", monthPerYear: "monthPerYear", percentFoodRequirements: "percentFoodRequirements", speciesCount: "speciesCount", - essenceCount: "essenceCount", tonPerHectare: "tonPerHectare", - minPerHouseholdPerDay: "minPerHouseholdPerDay", } as const; export type Unit = keyof typeof UNITS; @@ -31,7 +31,6 @@ export const useFormatterWithUnit = () => { return null; } - console.log(`Formatting value ${value} with unit ${unit}`) const formattedValue = typeof value === "number" ? precise(value) : value; switch (unit) { 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 77996157..20a8963b 100644 --- a/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx +++ b/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx @@ -42,11 +42,19 @@ export const ForestInventoryPopupContent: FC< const title = t("popup.forestInventory.title", { id: data.id }); const subtitle = (
- {t("popup.forestInventory.subtitle", { for: findCategoricalLabel(metadata, "for", data.for) || t('popup.undefined') })} + + {t("popup.forestInventory.subtitle", { + for: + findCategoricalLabel(metadata, "for", data.for) || + t("popup.undefined"), + })} +
- {t("popup.forestInventory.date", { date: formatDate(new Date()) })} + + {t("popup.forestInventory.date", { date: formatDate(new Date()) })} +
- ) + ); const tabs = { [TABS.BIODIVERSITY]: t("indicators.biodiversity.title"), [TABS.SOIL]: t("indicators.soil.title"), diff --git a/webapp/src/features/popup/renderPopup.tsx b/webapp/src/features/popup/renderPopup.tsx index e8a40d97..826a9dcc 100644 --- a/webapp/src/features/popup/renderPopup.tsx +++ b/webapp/src/features/popup/renderPopup.tsx @@ -8,7 +8,7 @@ export const DEFAULT_POPUP_CONFIG: PopupOptions = { closeButton: false, closeOnClick: true, closeOnMove: false, - maxWidth: "500px", + maxWidth: "560px", }; export function getRenderPopupLayer( @@ -24,7 +24,7 @@ export function getRenderPopupLayer( const root = createRoot(container); root.render( root.unmount()} 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 50a9e05e..cfbdec89 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -62,11 +62,13 @@ export const SocioEcoIndicator: FC = ({ }); const subtitle = (
- {t("popup.socioEco.subtitleCount", { count: data.household_nb })} + + {t("popup.socioEco.subtitleCount", { count: data.household_nb })} +
{t("popup.socioEco.date", { date: formatDate(new Date()) })}
- ) + ); const tabs = { [TABS.RESOURCES]: t("indicators.resources.title"), [TABS.ECONOMY]: t("indicators.economy.title"), diff --git a/webapp/src/shared/i18n/translations/en/translations.json b/webapp/src/shared/i18n/translations/en/translations.json index a998ab44..80c94545 100644 --- a/webapp/src/shared/i18n/translations/en/translations.json +++ b/webapp/src/shared/i18n/translations/en/translations.json @@ -104,10 +104,10 @@ "speciesRichness": "Species richness" }, "economy": { - "title": "Local Population" + "title": "Local Residents" }, "resources": { - "title": "Resources & Territory" + "title": "Resources & Territories" }, "socioEco": { "sections": { @@ -221,31 +221,31 @@ "title": "Soil" }, "units": { + "essenceCount": "{{ count }} species inventoried", "individualPerCubicMeter": "{{ value }} ind/m³", "individualPerHectare": "{{ value }} ind/ha", "individualPerTrap": "{{ value }} ind/trap", "m3PerHabPerYear": "{{ value }} m³/inhab/year", + "minPerHhPerDay": "min/household/day", "monthPerYear": "{{ value }} month/year", "percentFoodRequirements": "{{ value }} % of food needs", "speciesCount": "{{ count }} species inventoried", - "essenceCount": "{{ count }} species inventoried", - "tonPerHectare": "{{ value }} t/ha", - "minPerHhPerDay": "min/household/day" + "tonPerHectare": "{{ value }} t/ha" } }, "popup": { "forestInventory": { - "title": "Plot n°{{ id }}", + "date": "Site survey date : {{ date }}", "subtitle": "{{ for }} forest", - "date": "Site survey date : {{ date }}" + "title": "Plot n°{{ id }}" }, "seed": "Plot n°{{ id}}", "socioEco": { - "title": "{{ village }}", + "date": "Survey date: {{ date }}", "subtitleCount": "", "subtitleCount_one": "{{ count }} household surveyed", "subtitleCount_other": "{{ count }} households surveyed", - "date":"Survey date: {{ date }}" + "title": "{{ village }}" }, "undefined": "not found" }, diff --git a/webapp/src/shared/i18n/translations/fr/translations.json b/webapp/src/shared/i18n/translations/fr/translations.json index 9469954b..dcc41455 100644 --- a/webapp/src/shared/i18n/translations/fr/translations.json +++ b/webapp/src/shared/i18n/translations/fr/translations.json @@ -221,35 +221,35 @@ "title": "Sol" }, "units": { + "essenceCount": "", + "essenceCount_one": "{{ count }} essence inventoriée", + "essenceCount_other": "{{ count }} essences inventoriées", "individualPerCubicMeter": "{{ value }} ind/m³", "individualPerHectare": "{{ value }} ind/ha", "individualPerTrap": "{{ value }} ind/piège", "m3PerHabPerYear": "{{ value }} m³/hab/an", + "minPerHhPerDay": "min/ménage/jour", "monthPerYear": "{{ value }} mois/an", "percentFoodRequirements": "{{ value }} % des besoins alimentaires", "speciesCount": "", "speciesCount_one": "{{ count }} espèce inventoriée", "speciesCount_other": "{{ count }} espèces inventoriées", - "essenceCount": "", - "essenceCount_one": "{{ count }} essence inventoriée", - "essenceCount_other": "{{ count }} essences inventoriées", - "tonPerHectare": "{{ value }} t/ha", - "minPerHhPerDay": "min/ménage/jour" + "tonPerHectare": "{{ value }} t/ha" } }, "popup": { "forestInventory": { - "title": "Placette n°{{ id }}", + "date": "Date relevé: {{ date }}", "subtitle": "Forêt {{ for }}", - "date":"Date relevé: {{ date }}" + "title": "Placette n°{{ id }}" }, "seed": "Placette n°{{ id}}", "socioEco": { - "title": "{{ village }}", + "date": "Date relevé: {{ date }}", "subtitleCount": "", "subtitleCount_one": "{{ count }} ménage enquêté", "subtitleCount_other": "{{ count }} ménages enquêtés", - "date":"Date relevé: {{ date }}" + "title": "{{ village }}" }, "undefined": "non trouvée" }, diff --git a/webapp/src/shared/lib/utils.ts b/webapp/src/shared/lib/utils.ts index da55b391..06bc3f27 100644 --- a/webapp/src/shared/lib/utils.ts +++ b/webapp/src/shared/lib/utils.ts @@ -29,8 +29,7 @@ export function findCategoricalLabel( fieldName: string, fieldValue: any, ): string | undefined { - - // Search field category in main resource schema + // 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; @@ -39,7 +38,7 @@ export function findCategoricalLabel( return resourceLabel; } - // Search field category in main resource's references' schemas + // Searching field category in main resource's references' schemas return metadata?.references ?.find((ref) => ref.schema.fields From c054e6b83a1c0e6beb362a195d782f863e3a973d Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Sun, 3 May 2026 23:35:54 +0200 Subject: [PATCH 4/8] Last fixes --- webapp/src/app/styles/all4trees.css | 2 +- webapp/src/app/styles/index.css | 2 +- .../biodiversity/chart-relative-abundance.tsx | 28 +++++++++++++---- .../components/bar-chart-benef-control.tsx | 8 +++-- .../components/pie-chart-categorical.tsx | 1 + .../socio-eco/chart-beneficial-practices.tsx | 1 + .../charts/soil/ui/chart-taxon-abundance.tsx | 5 ++-- .../use-biodiversity-indicator-elements.tsx | 7 +++-- .../components/indicator-raw-value.tsx | 24 ++++++--------- .../components/indicator-section.tsx | 18 +++++------ .../use-economic-indicator-elements.tsx | 6 ++++ .../social/use-social-indicator-elements.tsx | 8 +++++ .../soil/use-soil-indicator-elements.tsx | 7 +++++ .../components/indicator-popup-header.tsx | 30 +++++++++++++++---- .../popup-forest-inventory.tsx | 22 ++++---------- webapp/src/features/popup/renderPopup.tsx | 2 +- .../popup/socio-eco/popup-socio-eco.tsx | 14 +++------ .../i18n/translations/en/translations.json | 2 +- .../i18n/translations/fr/translations.json | 2 +- 19 files changed, 113 insertions(+), 76 deletions(-) diff --git a/webapp/src/app/styles/all4trees.css b/webapp/src/app/styles/all4trees.css index 8df5c010..82d5c054 100644 --- a/webapp/src/app/styles/all4trees.css +++ b/webapp/src/app/styles/all4trees.css @@ -40,7 +40,7 @@ --custom-color-green-main: #007a55; --custom-color-green-pastel: #d0fae5; --custom-color-grey-light: #e5e7eb; - --custom-color-blue-dark: #192637; + --custom-color-blue-dark: #192430; --custom-color-blue-light: #52a4ff; --custom-color-socio-eco: #1447e6; /* Same as in socio-eco-icon */ diff --git a/webapp/src/app/styles/index.css b/webapp/src/app/styles/index.css index 6fa8976d..4f7198a6 100644 --- a/webapp/src/app/styles/index.css +++ b/webapp/src/app/styles/index.css @@ -75,7 +75,7 @@ --background: var(--custom-color-green-dark); --foreground: var(--a4t-color-alabaster); - --card: var(--custom-color-green-dark); + --card: var(--custom-color-blue-dark); --card-foreground: var(--a4t-color-alabaster); --popover: var(--a4t-color-nuit); diff --git a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx index 779a362c..346c40e2 100644 --- a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx +++ b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx @@ -2,7 +2,7 @@ import type { LayerMetadata } from "node_modules/coordo/coordo-ts/src/types"; import type { FC } from "react"; import { useTranslation } from "@shared/i18n"; -import { findCategoricalLabel } from "@shared/lib/utils"; +import { findCategoricalLabel, precise } from "@shared/lib/utils"; import type { ChartConfig } from "@shared/ui/chart"; import { PieChartCategorical } from "../components/pie-chart-categorical"; @@ -17,8 +17,15 @@ export const ChartRelativeAbundance: FC = ({ metadata, }) => { const { t } = useTranslation("translations"); + const smallCategoriesSum = Number( + precise( + data + .filter(([_, value]) => value < 5) + .reduce((acc, [_, value]) => acc + value, 0), + ), + ); const chartData = data - .filter(([name, _]) => name != "0") + .filter(([name, value]) => name !== "0" && (data.length < 6 || value >= 5)) .map((element, index) => ({ fill: `var(--chart-${(index % 4) + 1})`, name: element[0], @@ -32,13 +39,24 @@ export const ChartRelativeAbundance: FC = ({ [element.name]: { label: findCategoricalLabel(metadata, "ess_arb", element.name) || - t( - "indicators.biodiversity.sections.treeDiversity.relativeAbundance.other", - ), + element.name, + }, + other: { + label: t( + "indicators.biodiversity.sections.treeDiversity.relativeAbundance.other", + ), }, }; }); + if (data.length >= 6 && smallCategoriesSum > 0) { + chartData.push({ + fill: `var(--chart-5)`, + name: "other", + value: smallCategoriesSum, + }); + } + return ( ; legendLabel: string; withTemoin?: boolean; + layout?: { chartHeight: number; chartXAxisHeight: number }; }; export const BarCharWithBenefAndControl: FC = ({ title, chartData, legendLabel, - withTemoin, + withTemoin = false, + layout = { chartHeight: 80, chartXAxisHeight: 90 }, }) => { const { t } = useTranslation("translations"); const chartConfig = { @@ -39,7 +41,7 @@ export const BarCharWithBenefAndControl: FC = ({ return ( = ({ ( key={index} x={labelX} > diff --git a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx b/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx index fb9cb072..ab448057 100644 --- a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx +++ b/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx @@ -56,6 +56,7 @@ export const ChartBeneficialPractices: FC = ({ return ( = ({ className={cardHeight} title={t("indicators.common.abundance")} > -
+
{hasTaxonData && ( = ({ )} {!hasTaxonData && (
{t("indicators.common.noData")} diff --git a/webapp/src/features/indicators/biodiversity/use-biodiversity-indicator-elements.tsx b/webapp/src/features/indicators/biodiversity/use-biodiversity-indicator-elements.tsx index aaa1841f..db6e1454 100644 --- a/webapp/src/features/indicators/biodiversity/use-biodiversity-indicator-elements.tsx +++ b/webapp/src/features/indicators/biodiversity/use-biodiversity-indicator-elements.tsx @@ -1,5 +1,5 @@ import type { LayerMetadata } from "coordo"; -import { HeartPulseIcon, InfoIcon } from "lucide-react"; +import { Gem, TreePine, Trees } from "lucide-react"; import { ChartForestPotential } from "@features/charts/biodiversity/chart-forest-potential"; import { ChartRelativeAbundance } from "@features/charts/biodiversity/chart-relative-abundance"; @@ -25,11 +25,12 @@ export const useBiodiversityIndicatorElements = ( <> } value={data.biomass.volume} /> } + iconStart={} value={data.biomass.density} /> @@ -46,7 +47,7 @@ export const useBiodiversityIndicatorElements = ( dataName={t( "indicators.biodiversity.sections.treeDiversity.speciesRichness", )} - iconStart={} + iconStart={} value={data.treeDiversity.speciesRichness} /> = ({ } return ( - - -
-
- {iconStart} -

{dataName}

-
-

- {typeof value === "string" ? value : String(value)} -

-
-
-
+
+
+ {iconStart} +

{dataName}

+
+

+ {typeof value === "string" ? value : String(value)} +

+
); }; diff --git a/webapp/src/features/indicators/components/indicator-section.tsx b/webapp/src/features/indicators/components/indicator-section.tsx index 95f82b8b..9d48d8f1 100644 --- a/webapp/src/features/indicators/components/indicator-section.tsx +++ b/webapp/src/features/indicators/components/indicator-section.tsx @@ -1,6 +1,8 @@ import type { FC, PropsWithChildren, ReactNode } from "react"; import React from "react"; +import { Card, CardContent } from "@shared/ui/card"; + type IndicatorSectionProps = PropsWithChildren<{ title: string; iconStart?: ReactNode; @@ -68,20 +70,13 @@ export const IndicatorSection: FC = ({
{iconStart} -
{title}
+
{title}
{valueChildren.length > 0 && ( -
- {valueChildren.map((child, index) => ( -
- {child} -
- ))} -
+ + {valueChildren} + )} {chartChildren.length > 0 && ( @@ -89,6 +84,7 @@ export const IndicatorSection: FC = ({ {chartChildren.map((child, index) => (
key={`chart-indicator-${index}`} > {child} 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 d7bfe94a..343e9053 100644 --- a/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx +++ b/webapp/src/features/indicators/economy/use-economic-indicator-elements.tsx @@ -1,8 +1,11 @@ +import { ChartLine, HandCoins, Scroll } from "lucide-react"; + import { ChartBeneficialPractices } from "@features/charts/socio-eco/chart-beneficial-practices"; import { ChartLivingPerception } from "@features/charts/socio-eco/chart-living-perception"; import { useTranslation } from "@i18n"; +import { ICON_SIZE } from "../components/constants"; import { IndicatorRawValue } from "../components/indicator-raw-value"; import type { UseIndicatorReturnType } from "../components/types"; import { useFormatEconomicData } from "./format-data"; @@ -20,14 +23,17 @@ export const useEconomicIndicatorElements = ( <> } value={data.economy.incomeSourceNb} /> } value={data.economy.revenueChange} /> } value={data.economy.assetsIndex} /> } value={data.wood.energyConsumption} /> } value={data.wood.collectionTime} /> @@ -44,15 +49,18 @@ export const useSocialIndicatorElements = ( <> } value={data.food.foodDiversityScore} /> } value={data.food.autoConsumptionNeeds} /> } value={data.food.leanPeriod} /> 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 be8e1a3b..704e1d44 100644 --- a/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx +++ b/webapp/src/features/indicators/soil/use-soil-indicator-elements.tsx @@ -1,4 +1,5 @@ import type { LayerMetadata } from "coordo"; +import { Bug, Gem, Sprout } from "lucide-react"; import { ChartAquaticErosion } from "@features/charts/soil/ui/chart-aquatic-erosion"; import { ChartTaxonAbundance } from "@features/charts/soil/ui/chart-taxon-abundance"; @@ -8,6 +9,7 @@ import { IndicatorRawValue } from "@features/indicators/components/indicator-raw import { useTranslation } from "@i18n"; +import { ICON_SIZE } from "../components/constants"; import { useFormatSoilData } from "./format-data"; import type { SoilData } from "./types"; @@ -25,6 +27,7 @@ export const useSoilIndicatorElements = ( <> } value={data.soil_structure} /> @@ -65,10 +68,12 @@ export const useSoilIndicatorElements = ( <> } value={data.soil_fauna_density} /> } value={data.soil_fauna_diversity} /> } value={data.surface_fauna_density} /> } value={data.surface_fauna_diversity} /> void; }; @@ -23,6 +27,7 @@ export const IndicatorPopupHeader: FC = ({ icon, title, subtitle, + date, onCrossClick, }) => { return ( @@ -33,9 +38,22 @@ export const IndicatorPopupHeader: FC = ({ {icon} {title} - {subtitle && ( + {(subtitle || date) && ( - {subtitle} +
+ {subtitle && ( + <> + {subtitle} +
+ + )} + {date && ( +
+ +

{date}

+
+ )} +
)} 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 20a8963b..d90a324d 100644 --- a/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx +++ b/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx @@ -40,21 +40,6 @@ export const ForestInventoryPopupContent: FC< const soilElements = useSoilIndicatorElements(data, metadata); const title = t("popup.forestInventory.title", { id: data.id }); - const subtitle = ( -
- - {t("popup.forestInventory.subtitle", { - for: - findCategoricalLabel(metadata, "for", data.for) || - t("popup.undefined"), - })} - -
- - {t("popup.forestInventory.date", { date: formatDate(new Date()) })} - -
- ); const tabs = { [TABS.BIODIVERSITY]: t("indicators.biodiversity.title"), [TABS.SOIL]: t("indicators.soil.title"), @@ -63,9 +48,14 @@ export const ForestInventoryPopupContent: FC< return (
} onCrossClick={onClose} - subtitle={subtitle} + subtitle={t("popup.forestInventory.subtitle", { + for: + findCategoricalLabel(metadata, "for", data.for) || + t("popup.undefined"), + })} title={title} /> diff --git a/webapp/src/features/popup/renderPopup.tsx b/webapp/src/features/popup/renderPopup.tsx index 826a9dcc..c8f0e8ec 100644 --- a/webapp/src/features/popup/renderPopup.tsx +++ b/webapp/src/features/popup/renderPopup.tsx @@ -24,7 +24,7 @@ export function getRenderPopupLayer( const root = createRoot(container); root.render( root.unmount()} 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 cfbdec89..399e0500 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -60,15 +60,6 @@ export const SocioEcoIndicator: FC = ({ const title = t("popup.socioEco.title", { village: exctractVillageName(metadata, data), }); - const subtitle = ( -
- - {t("popup.socioEco.subtitleCount", { count: data.household_nb })} - -
- {t("popup.socioEco.date", { date: formatDate(new Date()) })} -
- ); const tabs = { [TABS.RESOURCES]: t("indicators.resources.title"), [TABS.ECONOMY]: t("indicators.economy.title"), @@ -77,9 +68,12 @@ export const SocioEcoIndicator: FC = ({ return (
} onCrossClick={onClose} - subtitle={subtitle} + subtitle={t("popup.socioEco.subtitleCount", { + count: data.household_nb, + })} title={title} /> diff --git a/webapp/src/shared/i18n/translations/en/translations.json b/webapp/src/shared/i18n/translations/en/translations.json index 80c94545..022ff997 100644 --- a/webapp/src/shared/i18n/translations/en/translations.json +++ b/webapp/src/shared/i18n/translations/en/translations.json @@ -226,7 +226,7 @@ "individualPerHectare": "{{ value }} ind/ha", "individualPerTrap": "{{ value }} ind/trap", "m3PerHabPerYear": "{{ value }} m³/inhab/year", - "minPerHhPerDay": "min/household/day", + "minPerHhPerDay": "{{ value }} min/household/day", "monthPerYear": "{{ value }} month/year", "percentFoodRequirements": "{{ value }} % of food needs", "speciesCount": "{{ count }} species inventoried", diff --git a/webapp/src/shared/i18n/translations/fr/translations.json b/webapp/src/shared/i18n/translations/fr/translations.json index dcc41455..fd09baba 100644 --- a/webapp/src/shared/i18n/translations/fr/translations.json +++ b/webapp/src/shared/i18n/translations/fr/translations.json @@ -228,7 +228,7 @@ "individualPerHectare": "{{ value }} ind/ha", "individualPerTrap": "{{ value }} ind/piège", "m3PerHabPerYear": "{{ value }} m³/hab/an", - "minPerHhPerDay": "min/ménage/jour", + "minPerHhPerDay": "{{ value }} min/ménage/jour", "monthPerYear": "{{ value }} mois/an", "percentFoodRequirements": "{{ value }} % des besoins alimentaires", "speciesCount": "", From c791826f7399f4436f6a80522154099fb318d0f0 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Mon, 4 May 2026 00:23:04 +0200 Subject: [PATCH 5/8] Update Coordo and some fake indicators --- backend/configs/config.json | 2 +- backend/requirements.txt | 2 +- webapp/package-lock.json | 4 +-- webapp/package.json | 2 +- .../charts/components/chart-component.tsx | 2 +- .../socio-eco/chart-beneficial-practices.tsx | 2 +- .../components/indicator-raw-value.tsx | 2 +- .../components/indicator-section.tsx | 2 +- .../indicators/economy/format-data.ts | 18 +++++------ .../features/indicators/social/format-data.ts | 30 +++++++++---------- .../components/indicator-popup-header.tsx | 4 +-- .../popup/socio-eco/popup-socio-eco.tsx | 2 +- 12 files changed, 36 insertions(+), 36 deletions(-) diff --git a/backend/configs/config.json b/backend/configs/config.json index 70679900..d3d9b694 100644 --- a/backend/configs/config.json +++ b/backend/configs/config.json @@ -62,7 +62,7 @@ "epf_vertical_distribution": "gini(ind.haut) * 10", "epf_dominant_height": "ind.avg(haut if haut > percentile(haut, 80)) * 10 / 40", "epf_microhabitats": "ind.list(dmh).flatten().list_unique() * 10 / 16", - "soil_structure": "sum(ep1*int(not1) + ep2*int(not2) + ep3*int(not3) + ep4*int(not4) + ep5*int(not5)) / sum(ep1 + ep2 + ep3 + ep4 + ep5) * 2", + "soil_structure": "sum(ep1*int(not1) + ep2*int(not2) + ep3*int(not3) + ep4*int(not4) + ep5*int(not5)) / (sum(ep1 + ep2 + ep3 + ep4 + ep5) if sum(ep1 + ep2 + ep3 + ep4 + ep5) > 0 else 1) * 2", "soil_composition": "count(1)", "ero_rainfall_and_wind": "meteo.concat_ws('-', pluv, vent)", "ero_couv_slope_and_cover": "inventaire_external.concat_ws('-', pent, couv)", diff --git a/backend/requirements.txt b/backend/requirements.txt index 966113ba..91848379 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,5 +1,5 @@ asgiref==3.8.1 -coordo @ git+https://github.com/dataforgoodfr/Coordonnees.git@0.3.0#subdirectory=coordo-py +coordo @ git+https://github.com/dataforgoodfr/Coordonnees.git@arnaudfnr/fix-null-values-in-formulas#subdirectory=coordo-py Django>=4.2.27 djangorestframework==3.16.0 djangorestframework_simplejwt==5.5.1 diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 7d921573..20128456 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -22,7 +22,7 @@ "chart.js": "^4.5.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "coordo": "github:dataforgoodfr/Coordonnees#0.3.0", + "coordo": "github:dataforgoodfr/Coordonnees#arnaudfnr/fix-null-values-in-formulas", "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", @@ -4120,7 +4120,7 @@ }, "node_modules/coordo": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/dataforgoodfr/Coordonnees.git#9231c239c3877f1175fdd373f69b60f4e869ec2b", + "resolved": "git+ssh://git@github.com/dataforgoodfr/Coordonnees.git#337be638e156e1dc536b35bb4741747fa9708566", "license": "ISC", "dependencies": { "maplibre-gl": "^5.16.0" diff --git a/webapp/package.json b/webapp/package.json index 53b704d0..ac683f11 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -39,7 +39,7 @@ "chart.js": "^4.5.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "coordo": "github:dataforgoodfr/Coordonnees#0.3.0", + "coordo": "github:dataforgoodfr/Coordonnees#arnaudfnr/fix-null-values-in-formulas", "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", diff --git a/webapp/src/features/charts/components/chart-component.tsx b/webapp/src/features/charts/components/chart-component.tsx index cf4552b2..54b70d03 100644 --- a/webapp/src/features/charts/components/chart-component.tsx +++ b/webapp/src/features/charts/components/chart-component.tsx @@ -23,7 +23,7 @@ export const ChartComponent: FC = ({ return ( {(title || description) && ( - + {title ? {title} : null} {description ? ( {description} diff --git a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx b/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx index ab448057..4edaadb2 100644 --- a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx +++ b/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx @@ -56,7 +56,7 @@ export const ChartBeneficialPractices: FC = ({ return ( = ({ } return ( -
+
{iconStart}

{dataName}

diff --git a/webapp/src/features/indicators/components/indicator-section.tsx b/webapp/src/features/indicators/components/indicator-section.tsx index 9d48d8f1..423f4f5b 100644 --- a/webapp/src/features/indicators/components/indicator-section.tsx +++ b/webapp/src/features/indicators/components/indicator-section.tsx @@ -70,7 +70,7 @@ export const IndicatorSection: FC = ({
{iconStart} -
{title}
+
{title}
{valueChildren.length > 0 && ( diff --git a/webapp/src/features/indicators/economy/format-data.ts b/webapp/src/features/indicators/economy/format-data.ts index 3190457a..b0272742 100644 --- a/webapp/src/features/indicators/economy/format-data.ts +++ b/webapp/src/features/indicators/economy/format-data.ts @@ -31,11 +31,11 @@ export const useFormatEconomicData = (data: EconomicData) => { assetsIndex: `${safeData.estateIndex}/10 (±${1})`, incomeSourceNb: `${1} (±${1})`, livingConditionsPerception: { - dontKnow: 2, - improvement: 1, - refuse: 1, - regression: 5, - stable: 3, + dontKnow: 4, + improvement: 28, + refuse: 3, + regression: 38, + stable: 27, }, nbAdditionalIncomes: 1, revenueChange: `${-5} % (±${1})`, @@ -44,10 +44,10 @@ export const useFormatEconomicData = (data: EconomicData) => { }, governance: { beneficialPractices: { - defense: 4, - improvedHousehold: 6, - rna: 3, - treePlanting: 8, + defense: 71, + improvedHousehold: 57, + rna: 100, + treePlanting: 100, }, conflictIndex: `${6}/10 (±${1})`, }, diff --git a/webapp/src/features/indicators/social/format-data.ts b/webapp/src/features/indicators/social/format-data.ts index 80670144..7d4b2712 100644 --- a/webapp/src/features/indicators/social/format-data.ts +++ b/webapp/src/features/indicators/social/format-data.ts @@ -61,25 +61,25 @@ export const useFormatSocialData = (data: SocialData) => { ), energyConsumption: formatWithUnit(0, UNITS.m3PerHabPerYear), energyNeeds: { - difficultToMeet: 3, - dontKnow: 2, - easyToMeet: 4, - moderateToMeet: 7, + difficultToMeet: 48, + dontKnow: 4, + easyToMeet: 10, + moderateToMeet: 38, }, energySources: { - animalWaste: 4, - boughtWood: 10, - coal: 5, - collectedWood: 18, - gas: 14, - organicWaste: 3, - other: 1, + animalWaste: 14, + boughtWood: 14, + coal: 57, + collectedWood: 100, + gas: 71, + organicWaste: 86, + other: 57, }, timberNeeds: { - difficultToMeet: 2, - dontKnow: 1, - easyToMeet: 4, - moderateToMeet: 8, + difficultToMeet: 38, + dontKnow: 6, + easyToMeet: 16, + moderateToMeet: 40, }, }, }; diff --git a/webapp/src/features/popup/components/indicator-popup-header.tsx b/webapp/src/features/popup/components/indicator-popup-header.tsx index c296c36b..5418c59e 100644 --- a/webapp/src/features/popup/components/indicator-popup-header.tsx +++ b/webapp/src/features/popup/components/indicator-popup-header.tsx @@ -32,14 +32,14 @@ export const IndicatorPopupHeader: FC = ({ }) => { return ( {icon} {title} {(subtitle || date) && ( - +
{subtitle && ( <> 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 399e0500..ab17d642 100644 --- a/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx +++ b/webapp/src/features/popup/socio-eco/popup-socio-eco.tsx @@ -78,7 +78,7 @@ export const SocioEcoIndicator: FC = ({ /> setSelectedTab(value as TabKind)} options={[ { From 16ae571fd51147b7faee64d580bded001147bc71 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Wed, 6 May 2026 15:08:54 +0200 Subject: [PATCH 6/8] Fix all4trees & David reviews part 1 --- webapp/src/app/styles/globals.css | 1 - webapp/src/app/styles/index.css | 7 ++-- .../biodiversity/chart-relative-abundance.tsx | 4 +- .../components/bar-chart-benef-control.tsx | 6 +-- .../charts/components/chart-component.tsx | 9 +---- .../components/pie-chart-categorical.tsx | 17 +++++---- .../charts/components/radar-benef-control.tsx | 3 +- .../socio-eco/chart-living-perception.tsx | 8 ++-- .../charts/socio-eco/chart-timber-needs.tsx | 17 ++++++--- .../socio-eco/chart-wood-energy-needs.tsx | 19 ++++++---- .../components/indicator-raw-value.tsx | 2 +- .../components/indicator-section.tsx | 5 +-- .../social/use-social-indicator-elements.tsx | 8 ++-- .../components/indicator-popup-header.tsx | 38 +++++++++---------- .../popup-forest-inventory.tsx | 9 ++--- .../i18n/translations/en/translations.json | 1 - .../i18n/translations/fr/translations.json | 3 +- webapp/src/shared/lib/palette.ts | 11 +++--- 18 files changed, 83 insertions(+), 85 deletions(-) diff --git a/webapp/src/app/styles/globals.css b/webapp/src/app/styles/globals.css index f936bfa7..53b1f34f 100644 --- a/webapp/src/app/styles/globals.css +++ b/webapp/src/app/styles/globals.css @@ -44,7 +44,6 @@ overflow: hidden !important; color: var(--popover-foreground) !important; background: var(--popover) !important; - border: 1px solid var(--border) !important; border-radius: var(--radius) !important; box-shadow: 0 40px 120px rgba(0, 0, 0, 0.25) !important; } diff --git a/webapp/src/app/styles/index.css b/webapp/src/app/styles/index.css index 4f7198a6..d0b35e4e 100644 --- a/webapp/src/app/styles/index.css +++ b/webapp/src/app/styles/index.css @@ -57,9 +57,10 @@ --chart-1: var(--a4t-color-vert-kelly); --chart-2: var(--a4t-color-citrouille); - --chart-3: var(--custom-color-inventaire); - --chart-4: var(--a4t-color-brunswick-green); - --chart-5: var(--a4t-color-onyx); + --chart-3: var(--a4t-color-bleu); + --chart-4: #895bf5; + --chart-5: #f04646; + --chart-6: var(--a4t-color-onyx); --sidebar: var(--a4t-color-alabaster); --sidebar-foreground: var(--a4t-color-alabaster); diff --git a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx index 346c40e2..36bde1c6 100644 --- a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx +++ b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx @@ -27,7 +27,7 @@ export const ChartRelativeAbundance: FC = ({ const chartData = data .filter(([name, value]) => name !== "0" && (data.length < 6 || value >= 5)) .map((element, index) => ({ - fill: `var(--chart-${(index % 4) + 1})`, + fill: `var(--chart-${(index % 5) + 1})`, name: element[0], value: element[1], })); @@ -51,7 +51,7 @@ export const ChartRelativeAbundance: FC = ({ if (data.length >= 6 && smallCategoriesSum > 0) { chartData.push({ - fill: `var(--chart-5)`, + fill: `var(--chart-6)`, name: "other", value: smallCategoriesSum, }); 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 a05ffd5f..142916fd 100644 --- a/webapp/src/features/charts/components/bar-chart-benef-control.tsx +++ b/webapp/src/features/charts/components/bar-chart-benef-control.tsx @@ -33,7 +33,7 @@ export const BarCharWithBenefAndControl: FC = ({ label: `(${t("indicators.common.beneficiary")}) ` + legendLabel, }, temoin: { - color: "var(--chart-2)", + color: "var(--chart-3)", label: `(${t("indicators.common.control")}) ` + legendLabel, }, } satisfies ChartConfig; @@ -72,14 +72,14 @@ export const BarCharWithBenefAndControl: FC = ({ /> {withTemoin && ( )} diff --git a/webapp/src/features/charts/components/chart-component.tsx b/webapp/src/features/charts/components/chart-component.tsx index 54b70d03..9a79da1a 100644 --- a/webapp/src/features/charts/components/chart-component.tsx +++ b/webapp/src/features/charts/components/chart-component.tsx @@ -1,4 +1,4 @@ -import type { ComponentType, FC, PropsWithChildren } from "react"; +import type { FC, PropsWithChildren } from "react"; import { Card, @@ -34,10 +34,3 @@ export const ChartComponent: FC = ({ ); }; - -ChartComponent.displayName = "ChartComponent"; - -export const markChartComponent =

( - component: ComponentType

, -): ComponentType

& { isChartComponent: boolean } => - Object.assign(component, { isChartComponent: true }); diff --git a/webapp/src/features/charts/components/pie-chart-categorical.tsx b/webapp/src/features/charts/components/pie-chart-categorical.tsx index ade7e271..c5ae70ce 100644 --- a/webapp/src/features/charts/components/pie-chart-categorical.tsx +++ b/webapp/src/features/charts/components/pie-chart-categorical.tsx @@ -32,12 +32,11 @@ export const PieChartCategorical: FC = ({ }) => { const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { + const name = chartConfig[payload[0].name]?.label || payload[0].name; + const tooltip = `${name}: ${payload[0].value}${unit}`; return (

-

- {chartConfig[payload[0].name]?.label || payload[0].name}:{" "} - {payload[0].value} {unit} -

+

{tooltip}

); } @@ -58,9 +57,9 @@ export const PieChartCategorical: FC = ({ data={chartData} dataKey="value" label={ - withLabel + withLabel === true ? (props) => renderLabel({ ...props, chartConfig }) - : false + : withLabel } labelLine={false} nameKey="name" @@ -72,13 +71,14 @@ export const PieChartCategorical: FC = ({ ); }; -const renderLabel = ({ +export const renderLabel = ({ payload, cx, cy, midAngle, outerRadius, chartConfig, + linebreak = 13, }: { payload: any; cx: number; @@ -86,6 +86,7 @@ const renderLabel = ({ midAngle: number; outerRadius: number; chartConfig: any; + linebreak: number; }): JSX.Element => { const label = chartConfig[payload.name]?.label || payload.name; const RADIAN = Math.PI / 180; @@ -97,7 +98,7 @@ const renderLabel = ({ const labelX = lineMidX + (lineMidX > cx ? 14 : -14); const labelY = lineMidY; const textAnchor = lineMidX > cx ? "start" : "end"; - const lines = lineBreakLabel(label, 13); + const lines = lineBreakLabel(label, linebreak); return ( diff --git a/webapp/src/features/charts/components/radar-benef-control.tsx b/webapp/src/features/charts/components/radar-benef-control.tsx index 84ba2818..df43a333 100644 --- a/webapp/src/features/charts/components/radar-benef-control.tsx +++ b/webapp/src/features/charts/components/radar-benef-control.tsx @@ -39,7 +39,7 @@ export const ChartRadarWithBenefAndControl: FC< label: t("indicators.common.beneficiary"), }, temoin: { - color: "var(--chart-4)", + color: "var(--chart-3)", label: t("indicators.common.control"), }, }; @@ -100,7 +100,6 @@ const renderPolarAngleTick = ({ payload, x, y, textAnchor }: any) => { return ( = ({ data }) => { const { t } = useTranslation("translations"); const chartData = [ { - fill: "var(--chart-4)", + fill: "var(--chart-1)", name: "improvement", value: data.improvement, }, @@ -29,17 +29,17 @@ export const ChartLivingPerception: FC = ({ data }) => { value: data.stable, }, { - fill: "var(--chart-2)", + fill: "var(--chart-5)", name: "regression", value: data.regression, }, { - fill: "var(--chart-5)", + fill: "var(--chart-6)", name: "refuse", value: data.refuse, }, { - fill: "var(--chart-1)", + fill: "var(--chart-2)", name: "dontKnow", value: data.dontKnow, }, 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 4630e720..9dcdd22b 100644 --- a/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx @@ -3,7 +3,10 @@ import type { FC } from "react"; import { useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; -import { PieChartCategorical } from "../components/pie-chart-categorical"; +import { + PieChartCategorical, + renderLabel, +} from "../components/pie-chart-categorical"; type PieChartProps = { data: { @@ -18,22 +21,22 @@ export const ChartTimberNeeds: FC = ({ data }) => { const { t } = useTranslation("translations"); const chartData = [ { - fill: "var(--chart-4)", + fill: "var(--chart-1)", name: "easyToMeet", value: data.easyToMeet, }, { - fill: "var(--chart-3)", + fill: "var(--chart-2)", name: "moderateToMeet", value: data.moderateToMeet, }, { - fill: "var(--chart-2)", + fill: "var(--chart-5)", name: "difficultToMeet", value: data.difficultToMeet, }, { - fill: "var(--chart-1)", + fill: "var(--chart-6)", name: "dontKnow", value: data.dontKnow, }, @@ -60,7 +63,9 @@ export const ChartTimberNeeds: FC = ({ data }) => { chartData={chartData} title={t("indicators.socioEco.sections.wood.timberNeeds.title")} unit="%" - withLabel + withLabel={(props) => + renderLabel({ ...props, chartConfig, linebreak: 18 }) + } /> ); }; diff --git a/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx b/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx index afcafceb..a5740e51 100644 --- a/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx @@ -3,7 +3,10 @@ import type { FC } from "react"; import { useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; -import { PieChartCategorical } from "../components/pie-chart-categorical"; +import { + PieChartCategorical, + renderLabel, +} from "../components/pie-chart-categorical"; type PieChartProps = { data: { @@ -18,22 +21,22 @@ export const ChartWoodEnergyNeeds: FC = ({ data }) => { const { t } = useTranslation("translations"); const chartData = [ { - fill: "var(--chart-4)", + fill: "var(--chart-1)", name: "easyToMeet", value: data.easyToMeet, }, { - fill: "var(--chart-3)", + fill: "var(--chart-2)", name: "moderateToMeet", value: data.moderateToMeet, }, { - fill: "var(--chart-2)", + fill: "var(--chart-5)", name: "difficultToMeet", value: data.difficultToMeet, }, { - fill: "var(--chart-1)", + fill: "var(--chart-6)", name: "dontKnow", value: data.dontKnow, }, @@ -58,9 +61,11 @@ export const ChartWoodEnergyNeeds: FC = ({ data }) => { + renderLabel({ ...props, chartConfig, linebreak: 18 }) + } /> ); }; diff --git a/webapp/src/features/indicators/components/indicator-raw-value.tsx b/webapp/src/features/indicators/components/indicator-raw-value.tsx index a5d21f91..b2fde9c1 100644 --- a/webapp/src/features/indicators/components/indicator-raw-value.tsx +++ b/webapp/src/features/indicators/components/indicator-raw-value.tsx @@ -16,7 +16,7 @@ export const IndicatorRawValue: FC = ({ } return ( -
+
{iconStart}

{dataName}

diff --git a/webapp/src/features/indicators/components/indicator-section.tsx b/webapp/src/features/indicators/components/indicator-section.tsx index 423f4f5b..94d0516a 100644 --- a/webapp/src/features/indicators/components/indicator-section.tsx +++ b/webapp/src/features/indicators/components/indicator-section.tsx @@ -53,8 +53,7 @@ export const IndicatorSection: FC = ({ iconStart, children, }) => { - const childrenArray = - React.Children.toArray(children).flatMap(flattenChildren); + const childrenArray = flattenChildren(children); const [valueChildren, chartChildren] = childrenArray.reduce< [ReactNode[], ReactNode[]] >( @@ -75,7 +74,7 @@ export const IndicatorSection: FC = ({ {valueChildren.length > 0 && ( - {valueChildren} + {valueChildren} )} 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 18e25048..ac431686 100644 --- a/webapp/src/features/indicators/social/use-social-indicator-elements.tsx +++ b/webapp/src/features/indicators/social/use-social-indicator-elements.tsx @@ -1,4 +1,4 @@ -import { Hourglass, InfoIcon, UserCheck, Wheat, Zap } from "lucide-react"; +import { Calendar, Hourglass, Soup, Wheat, Zap } from "lucide-react"; import { ChartEnergySources, @@ -49,18 +49,18 @@ export const useSocialIndicatorElements = ( <> } + iconStart={} value={data.food.foodDiversityScore} /> } + iconStart={} value={data.food.autoConsumptionNeeds} /> } + iconStart={} value={data.food.leanPeriod} /> diff --git a/webapp/src/features/popup/components/indicator-popup-header.tsx b/webapp/src/features/popup/components/indicator-popup-header.tsx index 5418c59e..28d0ab2e 100644 --- a/webapp/src/features/popup/components/indicator-popup-header.tsx +++ b/webapp/src/features/popup/components/indicator-popup-header.tsx @@ -1,6 +1,11 @@ import { Calendar, XIcon } from "lucide-react"; import type { FC, ReactNode } from "react"; +import { + ICON_SIZE, + ICON_SIZE_HEADER, +} from "@features/indicators/components/constants"; + import { cn } from "@shared/lib/utils"; import { Alert, @@ -10,11 +15,6 @@ import { } from "@shared/ui/alert"; import { Button } from "@shared/ui/button"; -import { - ICON_SIZE, - ICON_SIZE_HEADER, -} from "../../indicators/components/constants"; - type IndicatorPopupHeaderProps = { icon: ReactNode; title: string; @@ -39,21 +39,19 @@ export const IndicatorPopupHeader: FC = ({ {title} {(subtitle || date) && ( - -
- {subtitle && ( - <> - {subtitle} -
- - )} - {date && ( -
- -

{date}

-
- )} -
+ + {subtitle && {subtitle}} + + {date && ( +
+ +

{date}

+
+ )}
)} 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 d90a324d..31bb1627 100644 --- a/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx +++ b/webapp/src/features/popup/forest-inventory/popup-forest-inventory.tsx @@ -51,11 +51,10 @@ export const ForestInventoryPopupContent: FC< date={t("popup.forestInventory.date", { date: formatDate(new Date()) })} icon={} onCrossClick={onClose} - subtitle={t("popup.forestInventory.subtitle", { - for: - findCategoricalLabel(metadata, "for", data.for) || - t("popup.undefined"), - })} + subtitle={ + findCategoricalLabel(metadata, "for", data.for) || + t("popup.undefined") + } title={title} /> diff --git a/webapp/src/shared/i18n/translations/en/translations.json b/webapp/src/shared/i18n/translations/en/translations.json index 022ff997..05eb27a4 100644 --- a/webapp/src/shared/i18n/translations/en/translations.json +++ b/webapp/src/shared/i18n/translations/en/translations.json @@ -236,7 +236,6 @@ "popup": { "forestInventory": { "date": "Site survey date : {{ date }}", - "subtitle": "{{ for }} forest", "title": "Plot n°{{ id }}" }, "seed": "Plot n°{{ id}}", diff --git a/webapp/src/shared/i18n/translations/fr/translations.json b/webapp/src/shared/i18n/translations/fr/translations.json index fd09baba..132330a8 100644 --- a/webapp/src/shared/i18n/translations/fr/translations.json +++ b/webapp/src/shared/i18n/translations/fr/translations.json @@ -176,7 +176,7 @@ "difficultToMeet": "Difficile à satisfaire", "dontKnow": "Ne sait pas", "easyToMeet": "Facile à satisfaire", - "moderateToMeet": "Modéré à satisfaire" + "moderateToMeet": "Moyennement facile à satisfaire" }, "timberNeeds": { "title": "Satisfaction des besoins en bois d’œuvre" @@ -240,7 +240,6 @@ "popup": { "forestInventory": { "date": "Date relevé: {{ date }}", - "subtitle": "Forêt {{ for }}", "title": "Placette n°{{ id }}" }, "seed": "Placette n°{{ id}}", diff --git a/webapp/src/shared/lib/palette.ts b/webapp/src/shared/lib/palette.ts index 80a5e334..ff7b4bc6 100644 --- a/webapp/src/shared/lib/palette.ts +++ b/webapp/src/shared/lib/palette.ts @@ -7,9 +7,10 @@ const getCssVarColor = (name: string, fallback: string) => { }; export const getChartPalette = () => [ - getCssVarColor("--chart-1", "#091c2d"), - getCssVarColor("--chart-2", "#f8f8f8"), - getCssVarColor("--chart-3", "#26a65d"), - getCssVarColor("--chart-4", "#f5911f"), - getCssVarColor("--chart-5", "#f5f27b"), + getCssVarColor("--chart-1", "#97cf17"), + getCssVarColor("--chart-2", "#f98038"), + getCssVarColor("--chart-3", "#2d6db4"), + getCssVarColor("--chart-4", "#895bf5"), + getCssVarColor("--chart-5", "#f04646"), + getCssVarColor("--chart-6", "#424242"), ]; From c9122edf2c4420041a328ce57b2007815d77b1cc Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Wed, 6 May 2026 20:42:48 +0200 Subject: [PATCH 7/8] increase icon size --- webapp/src/features/indicators/components/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/features/indicators/components/constants.ts b/webapp/src/features/indicators/components/constants.ts index e17c241a..b9bd3131 100644 --- a/webapp/src/features/indicators/components/constants.ts +++ b/webapp/src/features/indicators/components/constants.ts @@ -1,2 +1,2 @@ -export const ICON_SIZE = 12; +export const ICON_SIZE = 18; export const ICON_SIZE_HEADER = 18; From 2cc7ef8aa90783a491d5d5a95a9015b746734c9c Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Thu, 7 May 2026 14:43:06 +0200 Subject: [PATCH 8/8] Improve indicator-section readability, mark chart components --- .../biodiversity/chart-forest-potential.tsx | 12 +++--- .../biodiversity/chart-relative-abundance.tsx | 6 ++- .../components/bar-chart-benef-control.tsx | 6 ++- .../charts/components/chart-component.tsx | 14 ++++++- .../components/pie-chart-categorical.tsx | 16 ++++--- .../charts/components/radar-benef-control.tsx | 6 ++- .../socio-eco/chart-beneficial-practices.tsx | 12 +++--- .../charts/socio-eco/chart-energy-sources.tsx | 12 +++--- .../charts/socio-eco/chart-food-diversity.tsx | 12 +++--- .../socio-eco/chart-living-perception.tsx | 9 ++-- .../charts/socio-eco/chart-timber-needs.tsx | 9 ++-- .../socio-eco/chart-wood-energy-needs.tsx | 9 ++-- .../charts/soil/ui/chart-aquatic-erosion.tsx | 11 ++--- .../charts/soil/ui/chart-taxon-abundance.tsx | 10 +++-- .../charts/soil/ui/chart-wind-erosion.tsx | 6 ++- webapp/src/features/charts/utils.ts | 25 +++++++++++ .../components/indicator-section.tsx | 42 ++++++------------- 17 files changed, 127 insertions(+), 90 deletions(-) diff --git a/webapp/src/features/charts/biodiversity/chart-forest-potential.tsx b/webapp/src/features/charts/biodiversity/chart-forest-potential.tsx index 5929075d..ae23a3eb 100644 --- a/webapp/src/features/charts/biodiversity/chart-forest-potential.tsx +++ b/webapp/src/features/charts/biodiversity/chart-forest-potential.tsx @@ -1,7 +1,6 @@ -import type { FC } from "react"; - import { useTranslation } from "@i18n"; +import type { ChartComponentType } from "../components/chart-component"; import { ChartRadarWithBenefAndControl } from "../components/radar-benef-control"; type Data = { @@ -20,10 +19,9 @@ type ChartForestPotentialProps = { temoin?: Data; }; -export const ChartForestPotential: FC = ({ - benef, - temoin, -}) => { +export const ChartForestPotential: ChartComponentType< + ChartForestPotentialProps +> = ({ benef, temoin }) => { const { t } = useTranslation("translations"); const chartData: Array<{ @@ -99,3 +97,5 @@ export const ChartForestPotential: FC = ({ /> ); }; + +ChartForestPotential.isChartComponent = true; diff --git a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx index 36bde1c6..a63829bb 100644 --- a/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx +++ b/webapp/src/features/charts/biodiversity/chart-relative-abundance.tsx @@ -1,10 +1,10 @@ import type { LayerMetadata } from "node_modules/coordo/coordo-ts/src/types"; -import type { FC } from "react"; import { useTranslation } from "@shared/i18n"; import { findCategoricalLabel, precise } from "@shared/lib/utils"; import type { ChartConfig } from "@shared/ui/chart"; +import type { ChartComponentType } from "../components/chart-component"; import { PieChartCategorical } from "../components/pie-chart-categorical"; type PieChartProps = { @@ -12,7 +12,7 @@ type PieChartProps = { metadata: LayerMetadata; }; -export const ChartRelativeAbundance: FC = ({ +export const ChartRelativeAbundance: ChartComponentType = ({ data, metadata, }) => { @@ -69,3 +69,5 @@ export const ChartRelativeAbundance: FC = ({ /> ); }; + +ChartRelativeAbundance.isChartComponent = true; 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 142916fd..12f2533f 100644 --- a/webapp/src/features/charts/components/bar-chart-benef-control.tsx +++ b/webapp/src/features/charts/components/bar-chart-benef-control.tsx @@ -1,4 +1,3 @@ -import type { FC } from "react"; import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { useTranslation } from "@shared/i18n"; @@ -9,6 +8,7 @@ import { ChartTooltipContent, } from "@shared/ui/chart"; +import type { ChartComponentType } from "./chart-component"; import { ChartComponent } from "./chart-component"; type BarChartProps = { @@ -19,7 +19,7 @@ type BarChartProps = { layout?: { chartHeight: number; chartXAxisHeight: number }; }; -export const BarCharWithBenefAndControl: FC = ({ +export const BarCharWithBenefAndControl: ChartComponentType = ({ title, chartData, legendLabel, @@ -89,6 +89,8 @@ export const BarCharWithBenefAndControl: FC = ({ ); }; +BarCharWithBenefAndControl.isChartComponent = true; + const renderXAxisTick = ({ x, y, diff --git a/webapp/src/features/charts/components/chart-component.tsx b/webapp/src/features/charts/components/chart-component.tsx index 9a79da1a..8ba82c92 100644 --- a/webapp/src/features/charts/components/chart-component.tsx +++ b/webapp/src/features/charts/components/chart-component.tsx @@ -1,4 +1,4 @@ -import type { FC, PropsWithChildren } from "react"; +import type { PropsWithChildren } from "react"; import { Card, @@ -14,7 +14,15 @@ type ChartComponentProps = PropsWithChildren<{ className?: string; }>; -export const ChartComponent: FC = ({ +export type MarkedComponent

= React.ComponentType

& { + isChartComponent?: true; +}; + +export type ChartComponentType

= React.FC

& { + isChartComponent?: true; +}; + +export const ChartComponent: ChartComponentType = ({ title, description, className, @@ -34,3 +42,5 @@ export const ChartComponent: FC = ({ ); }; + +ChartComponent.isChartComponent = true; diff --git a/webapp/src/features/charts/components/pie-chart-categorical.tsx b/webapp/src/features/charts/components/pie-chart-categorical.tsx index c5ae70ce..b353c06e 100644 --- a/webapp/src/features/charts/components/pie-chart-categorical.tsx +++ b/webapp/src/features/charts/components/pie-chart-categorical.tsx @@ -1,9 +1,10 @@ -import type { FC, JSX } from "react"; +import type { JSX } from "react"; import { Pie, PieChart, type PieLabel } from "recharts"; import { ChartContainer, ChartTooltip } from "@ui/chart"; import { lineBreakLabel } from "../utils"; +import type { ChartComponentType } from "./chart-component"; import { ChartComponent } from "./chart-component"; export const description = "A pie chart with a label"; @@ -22,14 +23,9 @@ export type PieChartCategoricalProps = { labelLine?: boolean; }; -export const PieChartCategorical: FC = ({ - chartData, - chartConfig, - title, - description, - withLabel, - unit, -}) => { +export const PieChartCategorical: ChartComponentType< + PieChartCategoricalProps +> = ({ chartData, chartConfig, title, description, withLabel, unit }) => { const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const name = chartConfig[payload[0].name]?.label || payload[0].name; @@ -71,6 +67,8 @@ export const PieChartCategorical: FC = ({ ); }; +PieChartCategorical.isChartComponent = true; + export const renderLabel = ({ payload, cx, diff --git a/webapp/src/features/charts/components/radar-benef-control.tsx b/webapp/src/features/charts/components/radar-benef-control.tsx index df43a333..f97287e0 100644 --- a/webapp/src/features/charts/components/radar-benef-control.tsx +++ b/webapp/src/features/charts/components/radar-benef-control.tsx @@ -1,4 +1,3 @@ -import type { FC } from "react"; import { PolarAngleAxis, PolarGrid, @@ -20,6 +19,7 @@ import { import { RADAR_CONFIG } from "../constants"; import { lineBreakLabel } from "../utils"; +import type { ChartComponentType } from "./chart-component"; import { ChartComponent } from "./chart-component"; type ChartRadarWithBenefAndControlProps = { @@ -28,7 +28,7 @@ type ChartRadarWithBenefAndControlProps = { chartData: Array<{ indicator: string; benef: unknown; temoin?: unknown }>; }; -export const ChartRadarWithBenefAndControl: FC< +export const ChartRadarWithBenefAndControl: ChartComponentType< ChartRadarWithBenefAndControlProps > = ({ chartData, title, withTemoin }) => { const { t } = useTranslation("translations"); @@ -94,6 +94,8 @@ export const ChartRadarWithBenefAndControl: FC< ); }; +ChartRadarWithBenefAndControl.isChartComponent = true; + const renderPolarAngleTick = ({ payload, x, y, textAnchor }: any) => { const label = String(payload?.value ?? ""); const lines = lineBreakLabel(label); diff --git a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx b/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx index 4edaadb2..b87a04d9 100644 --- a/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx +++ b/webapp/src/features/charts/socio-eco/chart-beneficial-practices.tsx @@ -1,8 +1,7 @@ -import type { FC } from "react"; - import { useTranslation } from "@shared/i18n"; import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; +import type { ChartComponentType } from "../components/chart-component"; type Data = { defense: number; @@ -16,10 +15,9 @@ type ChartBeneficialPracticesProps = { temoin?: Data; }; -export const ChartBeneficialPractices: FC = ({ - benef, - temoin, -}) => { +export const ChartBeneficialPractices: ChartComponentType< + ChartBeneficialPracticesProps +> = ({ benef, temoin }) => { const { t } = useTranslation("translations"); const chartData = [ @@ -67,3 +65,5 @@ export const ChartBeneficialPractices: FC = ({ /> ); }; + +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 index 007cc9b2..d0b3c6e8 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,7 @@ -import type { FC } from "react"; - import { useTranslation } from "@shared/i18n"; import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; +import type { ChartComponentType } from "../components/chart-component"; type Data = { collectedWood: number; @@ -19,10 +18,9 @@ type ChartEnergySourcesProps = { temoin?: Data; }; -export const ChartEnergySources: FC = ({ - benef, - temoin, -}) => { +export const ChartEnergySources: ChartComponentType< + ChartEnergySourcesProps +> = ({ benef, temoin }) => { const { t } = useTranslation("translations"); const chartData = [ @@ -80,3 +78,5 @@ export const ChartEnergySources: FC = ({ /> ); }; + +ChartEnergySources.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 index 0ed49661..a1a619bc 100644 --- a/webapp/src/features/charts/socio-eco/chart-food-diversity.tsx +++ b/webapp/src/features/charts/socio-eco/chart-food-diversity.tsx @@ -1,8 +1,7 @@ -import type { FC } from "react"; - import { useTranslation } from "@shared/i18n"; import { BarCharWithBenefAndControl } from "../components/bar-chart-benef-control"; +import type { ChartComponentType } from "../components/chart-component"; type Data = { cereals: number; @@ -20,10 +19,9 @@ type ChartFoodDiversityProps = { temoin?: Data; }; -export const ChartFoodDiversity: FC = ({ - benef, - temoin, -}) => { +export const ChartFoodDiversity: ChartComponentType< + ChartFoodDiversityProps +> = ({ benef, temoin }) => { const { t } = useTranslation("translations"); const chartData = [ @@ -78,3 +76,5 @@ export const ChartFoodDiversity: FC = ({ /> ); }; + +ChartFoodDiversity.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/chart-living-perception.tsx b/webapp/src/features/charts/socio-eco/chart-living-perception.tsx index a84afe71..e8a0e981 100644 --- a/webapp/src/features/charts/socio-eco/chart-living-perception.tsx +++ b/webapp/src/features/charts/socio-eco/chart-living-perception.tsx @@ -1,8 +1,7 @@ -import type { FC } from "react"; - import { 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"; type PieChartProps = { @@ -15,7 +14,9 @@ type PieChartProps = { }; }; -export const ChartLivingPerception: FC = ({ data }) => { +export const ChartLivingPerception: ChartComponentType = ({ + data, +}) => { const { t } = useTranslation("translations"); const chartData = [ { @@ -79,3 +80,5 @@ export const ChartLivingPerception: FC = ({ data }) => { /> ); }; + +ChartLivingPerception.isChartComponent = true; 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 9dcdd22b..9d9b7ed8 100644 --- a/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-timber-needs.tsx @@ -1,8 +1,7 @@ -import type { FC } from "react"; - import { useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; +import type { ChartComponentType } from "../components/chart-component"; import { PieChartCategorical, renderLabel, @@ -17,7 +16,9 @@ type PieChartProps = { }; }; -export const ChartTimberNeeds: FC = ({ data }) => { +export const ChartTimberNeeds: ChartComponentType = ({ + data, +}) => { const { t } = useTranslation("translations"); const chartData = [ { @@ -69,3 +70,5 @@ export const ChartTimberNeeds: FC = ({ data }) => { /> ); }; + +ChartTimberNeeds.isChartComponent = true; diff --git a/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx b/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx index a5740e51..bcaa2b64 100644 --- a/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx +++ b/webapp/src/features/charts/socio-eco/chart-wood-energy-needs.tsx @@ -1,8 +1,7 @@ -import type { FC } from "react"; - import { useTranslation } from "@shared/i18n"; import type { ChartConfig } from "@shared/ui/chart"; +import type { ChartComponentType } from "../components/chart-component"; import { PieChartCategorical, renderLabel, @@ -17,7 +16,9 @@ type PieChartProps = { }; }; -export const ChartWoodEnergyNeeds: FC = ({ data }) => { +export const ChartWoodEnergyNeeds: ChartComponentType = ({ + data, +}) => { const { t } = useTranslation("translations"); const chartData = [ { @@ -69,3 +70,5 @@ export const ChartWoodEnergyNeeds: FC = ({ data }) => { /> ); }; + +ChartWoodEnergyNeeds.isChartComponent = true; diff --git a/webapp/src/features/charts/soil/ui/chart-aquatic-erosion.tsx b/webapp/src/features/charts/soil/ui/chart-aquatic-erosion.tsx index 46926067..5d66fe18 100644 --- a/webapp/src/features/charts/soil/ui/chart-aquatic-erosion.tsx +++ b/webapp/src/features/charts/soil/ui/chart-aquatic-erosion.tsx @@ -1,4 +1,4 @@ -import type { FC } from "react"; +import type { ChartComponentType } from "@features/charts/components/chart-component"; import { useTranslation } from "@shared/i18n"; @@ -17,10 +17,9 @@ type ChartAquaticErosionProps = { temoin?: Data; }; -export const ChartAquaticErosion: FC = ({ - benef, - temoin, -}) => { +export const ChartAquaticErosion: ChartComponentType< + ChartAquaticErosionProps +> = ({ benef, temoin }) => { const { t } = useTranslation("translations"); const chartData: Array<{ @@ -63,3 +62,5 @@ export const ChartAquaticErosion: FC = ({ /> ); }; + +ChartAquaticErosion.isChartComponent = true; diff --git a/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx b/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx index f7486443..595a74a2 100644 --- a/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx +++ b/webapp/src/features/charts/soil/ui/chart-taxon-abundance.tsx @@ -1,8 +1,10 @@ import type { Data as PlotlyData } from "plotly.js"; -import type { FC } from "react"; import Plot from "react-plotly.js"; -import { ChartComponent } from "@features/charts/components/chart-component"; +import { + ChartComponent, + type ChartComponentType, +} from "@features/charts/components/chart-component"; import { useTranslation } from "@shared/i18n"; @@ -10,7 +12,7 @@ import { SUNBURST_LAYOUT } from "../config"; import { buildNodeColors, buildSunburstNodes } from "../lib/sunburst"; import type { PieChartProps, SunburstTrace } from "../types"; -export const ChartTaxonAbundance: FC = ({ +export const ChartTaxonAbundance: ChartComponentType = ({ data, metadata, dataType, @@ -70,3 +72,5 @@ export const ChartTaxonAbundance: FC = ({ ); }; + +ChartTaxonAbundance.isChartComponent = true; diff --git a/webapp/src/features/charts/soil/ui/chart-wind-erosion.tsx b/webapp/src/features/charts/soil/ui/chart-wind-erosion.tsx index 8e92e32b..4b4f0092 100644 --- a/webapp/src/features/charts/soil/ui/chart-wind-erosion.tsx +++ b/webapp/src/features/charts/soil/ui/chart-wind-erosion.tsx @@ -1,4 +1,4 @@ -import type { FC } from "react"; +import type { ChartComponentType } from "@features/charts/components/chart-component"; import { useTranslation } from "@shared/i18n"; @@ -15,7 +15,7 @@ type ChartWindErosionProps = { temoin?: Data; }; -export const ChartWindErosion: FC = ({ +export const ChartWindErosion: ChartComponentType = ({ benef, temoin, }) => { @@ -51,3 +51,5 @@ export const ChartWindErosion: FC = ({ /> ); }; + +ChartWindErosion.isChartComponent = true; diff --git a/webapp/src/features/charts/utils.ts b/webapp/src/features/charts/utils.ts index f0d78ec6..d9acb6cf 100644 --- a/webapp/src/features/charts/utils.ts +++ b/webapp/src/features/charts/utils.ts @@ -1,3 +1,28 @@ +import React, { type ReactNode } from "react"; + +import type { MarkedComponent } from "./components/chart-component"; + +// Check if node or one of its children has been marked as Chart Component +export const isChartElement = (node: ReactNode): boolean => { + if (!React.isValidElement(node)) { + return false; + } + + const element = node as React.ReactElement<{ children?: ReactNode }>; + const type = element.type as MarkedComponent; + + if (type.isChartComponent) { + return true; + } + + const children = element.props.children; + if (!children) { + return false; + } + + return React.Children.toArray(children).some(isChartElement); +}; + export const lineBreakLabel = (label: string, maxChars = 14) => { const words = label.split(" "); const lines: string[] = []; diff --git a/webapp/src/features/indicators/components/indicator-section.tsx b/webapp/src/features/indicators/components/indicator-section.tsx index 94d0516a..dc0320b9 100644 --- a/webapp/src/features/indicators/components/indicator-section.tsx +++ b/webapp/src/features/indicators/components/indicator-section.tsx @@ -1,6 +1,8 @@ import type { FC, PropsWithChildren, ReactNode } from "react"; import React from "react"; +import { isChartElement } from "@features/charts/utils"; + import { Card, CardContent } from "@shared/ui/card"; type IndicatorSectionProps = PropsWithChildren<{ @@ -8,31 +10,7 @@ type IndicatorSectionProps = PropsWithChildren<{ iconStart?: ReactNode; }>; -const isChartElement = (node: ReactNode): boolean => { - if (!React.isValidElement(node)) { - return false; - } - - const element = node as React.ReactElement<{ children?: ReactNode }>; - const type = element.type as { - displayName?: string; - name?: string; - isChartComponent?: boolean; - }; - - const componentName = type.displayName || type.name; - if (type.isChartComponent || componentName?.includes("Chart")) { - return true; - } - - const children = element.props.children; - if (!children) { - return false; - } - - return React.Children.toArray(children).some(isChartElement); -}; - +// Put a node and all its children in the same array const flattenChildren = (node: ReactNode): ReactNode[] => { if (!React.isValidElement(node)) { return [node]; @@ -54,7 +32,11 @@ export const IndicatorSection: FC = ({ children, }) => { const childrenArray = flattenChildren(children); - const [valueChildren, chartChildren] = childrenArray.reduce< + + // Divide indicator elements in 2 categories + // valueIndicators will be displayed inside a Card Component to improve visibility + // chartIndicators are already put (individually) inside a Card. + const [valueIndicators, chartIndicators] = childrenArray.reduce< [ReactNode[], ReactNode[]] >( ([values, charts], child) => { @@ -72,15 +54,15 @@ export const IndicatorSection: FC = ({

{title}
- {valueChildren.length > 0 && ( + {valueIndicators.length > 0 && ( - {valueChildren} + {valueIndicators} )} - {chartChildren.length > 0 && ( + {chartIndicators.length > 0 && (
- {chartChildren.map((child, index) => ( + {chartIndicators.map((child, index) => (