Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions webapp/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"!@pages/**",
"!@widgets/**",
"!@features/**",
"!@entities/**",
"!@shared/**",
"!@lib/**",
"!@i18n",
Expand All @@ -31,6 +32,8 @@
":BLANK_LINE:",
"@features/**",
":BLANK_LINE:",
"@entities/**",
":BLANK_LINE:",
"@shared/**",
"@lib/**",
"@i18n",
Expand Down
22 changes: 21 additions & 1 deletion webapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@
"react": "^19.2.0",
"react-chartjs-2": "^5.3.1",
"react-dom": "^19.2.0",
"react-error-boundary": "^6.1.2",
"react-i18next": "^16.5.4",
"react-plotly.js": "^2.6.0",
"react-resizable-panels": "^3.0.6",
"react-router-dom": "^7.13.0",
"react-spinners": "^0.17.0",
"recharts": "^2.15.4"
"recharts": "^2.15.4",
"zod": "^4.4.3"
},
"devDependencies": {
"@biomejs/biome": "^2.4.4",
Expand Down
5 changes: 2 additions & 3 deletions webapp/src/app/providers/ApiProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { type ReactNode, useMemo } from "react";

import { createApiClient } from "@features/api/client";
import { useAuth } from "@features/auth/useAuth";

import { createApiClient } from "@shared/api/client";
import { ApiContext } from "@shared/contexts/ApiContext";
import { ApiContext } from "@features/contexts/ApiContext";
Comment thread
severo marked this conversation as resolved.

interface ApiProviderProps {
children: ReactNode;
Expand Down
18 changes: 18 additions & 0 deletions webapp/src/entities/dashboard/biodiversity-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Tropical Biodiversity Index

import * as z from "zod";

import { ValueAndErrorSchema } from "@entities/dashboard/generic";

export const BiodiversityIndexSchema = z.object({
bio_idx_deadWood: ValueAndErrorSchema,
bio_idx_diametric_distribution: ValueAndErrorSchema,
bio_idx_dominant_height: ValueAndErrorSchema,
bio_idx_microhabitats: ValueAndErrorSchema,
bio_idx_spatial_distribution: ValueAndErrorSchema,
bio_idx_tree_density: ValueAndErrorSchema,
bio_idx_tree_diversity: ValueAndErrorSchema,
bio_idx_vertical_distribution: ValueAndErrorSchema,
});

export type BiodiversityIndex = z.infer<typeof BiodiversityIndexSchema>;
30 changes: 30 additions & 0 deletions webapp/src/entities/dashboard/generic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as z from "zod";

export const ValueAndErrorSchema = z
.object({
error: z.number().nullable(),
value: z.number().nullable(),
})
.default(() => ({
error: null,
value: null,
}));

const DictionaryDataSchema = z.record(z.string(), ValueAndErrorSchema);

export type DictionaryData = z.infer<typeof DictionaryDataSchema>;

const MIN_YEAR = 1900;
const MAX_YEAR = 2100;
const YearSchema = z.coerce.number().int().min(MIN_YEAR).max(MAX_YEAR);

const YearDataSchema = z.object({
beneficiary: DictionaryDataSchema,
control: DictionaryDataSchema,
});

export type YearData = z.infer<typeof YearDataSchema>;

export const DashboardDataSchema = z.record(YearSchema, YearDataSchema);

export type DashboardData = z.infer<typeof DashboardDataSchema>;
20 changes: 20 additions & 0 deletions webapp/src/features/api/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
type DashboardData,
DashboardDataSchema,
} from "@entities/dashboard/generic";

import { fetchJSONWithAuth } from "@shared/api/client";

export const createApiClient = (authToken: string | null) => ({
// Bases
fetchDashboardData: async (layerId: string): Promise<DashboardData> => {
const json = await fetchJSONWithAuth(
`/maps/dashboard/${layerId}`,
{},
authToken,
);
return DashboardDataSchema.parse(json);
},
});

export type ApiClient = ReturnType<typeof createApiClient>;
22 changes: 19 additions & 3 deletions webapp/src/features/charts/biodiversity/chart-forest-potential.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useTranslation } from "@i18n";
import type { ChartComponentType } from "@features/charts/components/chart-component";
import { ChartRadarWithBenefAndControl } from "@features/charts/components/radar-benef-control";

import type { BiodiversityIndex } from "@entities/dashboard/biodiversity-index";

import type { ChartComponentType } from "../components/chart-component";
import { ChartRadarWithBenefAndControl } from "../components/radar-benef-control";
import { useTranslation } from "@i18n";

export type ChartForestPotentialData = {
density: number;
Expand All @@ -19,6 +21,20 @@ type ChartForestPotentialProps = {
temoin?: ChartForestPotentialData;
};

export function fromBiodiversityIndex(
data: BiodiversityIndex,
): ChartForestPotentialData {
return {
deadWood: data.bio_idx_deadWood.value ?? 0,
density: data.bio_idx_tree_density.value ?? 0,
diameterDistribution: data.bio_idx_diametric_distribution.value ?? 0,
diversity: data.bio_idx_tree_diversity.value ?? 0,
dominantHeight: data.bio_idx_dominant_height.value ?? 0,
microHabitat: data.bio_idx_microhabitats.value ?? 0,
spatialDistribution: data.bio_idx_spatial_distribution.value ?? 0,
verticalDistribution: data.bio_idx_vertical_distribution.value ?? 0,
};
}
export const ChartForestPotential: ChartComponentType<
ChartForestPotentialProps
> = ({ benef, temoin }) => {
Expand Down
11 changes: 9 additions & 2 deletions webapp/src/features/charts/soil/lib/sunburst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,18 @@ export function buildSunburstNodes(
return nodes;
}

export const getLevelPalettes = () => {
type ThreeColorsPalette = [string, string, string];
type ThreePalettes = [
ThreeColorsPalette,
ThreeColorsPalette,
ThreeColorsPalette,
];

export const getLevelPalettes = (): ThreePalettes => {
const palette = getChartPalette();

return [
palette.slice(0, 3),
[palette[0], palette[1], palette[2]],
[palette[3], palette[4], palette[0]],
[palette[1], palette[2], palette[3]],
];
Expand Down
35 changes: 9 additions & 26 deletions webapp/src/features/charts/soil/lib/taxon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,18 @@ import type { LayerMetadata } from "coordo";

import { findCategoricalLabel } from "@shared/lib/utils";

export function getTaxonLabels(
element: string,
metadata: LayerMetadata,
dataType: "tsbf" | "barbA",
): [string, string, string] {
const [taxon1, taxon2, taxon3] = element.split("-");
const taxon1Label =
findCategoricalLabel(metadata, `${dataType}_tax1`, taxon1) || taxon1;
const taxon2Label =
findCategoricalLabel(metadata, `${dataType}_tax2`, taxon2) || taxon2;
const taxon3Label =
findCategoricalLabel(metadata, `${dataType}_tax3`, taxon3) || taxon3;
return [taxon1Label, taxon2Label, taxon3Label];
}

export function formatTaxonLevelLabel(
element: string,
metadata: LayerMetadata,
dataType: "tsbf" | "barbA",
): string {
const [taxon1Label, taxon2Label, taxon3Label] = getTaxonLabels(
element,
metadata,
dataType,
);
const parts = element.split("-");
return parts.length === 1
? taxon1Label
: parts.length === 2
? taxon2Label
: taxon3Label;
// 'taxon1 = element' is for typescript... taxon1 should never be undefined, but typescript doesn't know that
const [taxon1 = element, taxon2, taxon3] = element.split("-");
if (taxon2 === undefined) {
return findCategoricalLabel(metadata, `${dataType}_tax1`, taxon1) || taxon1;
}
if (taxon3 === undefined) {
return findCategoricalLabel(metadata, `${dataType}_tax2`, taxon2) || taxon2;
}
return findCategoricalLabel(metadata, `${dataType}_tax3`, taxon3) || taxon3;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you move ApiContext and apiClient to the layer 'feature' ?

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext } from "react";

import type { ApiClient } from "@shared/api/client";
import type { ApiClient } from "@features/api/client";

export const ApiContext = createContext<ApiClient | undefined>(undefined);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext } from "react";

import { ApiContext } from "@shared/contexts/ApiContext";
import { ApiContext } from "@features/contexts/ApiContext";

export function useApi() {
const context = useContext(ApiContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type ForestInventoryPopupContentProps = RenderPopupProps<ForestInventoryData>;

type TabKind = "biodiversity" | "soil";

const TABS: Record<string, TabKind> = {
const TABS = {
BIODIVERSITY: "biodiversity",
SOIL: "soil",
} as const;
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/features/popup/socio-eco/popup-socio-eco.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type SocioEcoIndicatorProps = RenderPopupProps<SocioEcoData>;

type TabKind = "resources" | "economy";

const TABS: Record<string, TabKind> = {
const TABS = {
ECONOMY: "economy",
RESOURCES: "resources",
} as const;
Expand Down
Loading
Loading