From b1783b5a516e79c461148679bb8ac405aa813c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Tue, 26 Nov 2024 09:49:34 +0100 Subject: [PATCH 01/10] feat(client): Enable hover/click interaction on layer features --- client/package.json | 1 + client/src/app/layout.tsx | 7 +-- client/src/app/providers.tsx | 19 +++++++ client/src/components/dataset-card/index.tsx | 13 +++++ .../src/components/map/layer-manager/item.tsx | 17 +++++- .../map/layer-manager/vector-layer.tsx | 40 +++++++++++-- client/src/hooks/use-deckgl-mapbox-overlay.ts | 35 +++++++++++- client/src/hooks/use-layer-config.ts | 35 ++++++++++-- .../src/hooks/use-layer-interaction-state.ts | 56 +++++++++++++++++++ client/src/types/layer.ts | 6 ++ client/src/utils/mapbox-deckgl-bridge.ts | 27 +++++---- client/yarn.lock | 16 ++++++ 12 files changed, 242 insertions(+), 30 deletions(-) create mode 100644 client/src/app/providers.tsx create mode 100644 client/src/hooks/use-layer-interaction-state.ts diff --git a/client/package.json b/client/package.json index 8a0874a..a86aa9f 100644 --- a/client/package.json +++ b/client/package.json @@ -57,6 +57,7 @@ "cmdk": "1.0.0", "date-fns": "4.1.0", "express": "4.21.1", + "jotai": "2.10.3", "lodash-es": "4.17.21", "mapbox-gl": "3.7.0", "next": "14.2.15", diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index 810e742..fbd3208 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -1,12 +1,11 @@ import { Jost, DM_Serif_Text } from "next/font/google"; -import { NuqsAdapter } from "nuqs/adapters/next/app"; -import ReactQueryProvider from "@/app/react-query-provider"; import Head from "@/components/head"; import type { Metadata } from "next"; import "./globals.css"; +import Providers from "@/app/providers"; export const metadata: Metadata = { title: { @@ -38,9 +37,7 @@ export default function RootLayout({ - - {children} - + {children} ); diff --git a/client/src/app/providers.tsx b/client/src/app/providers.tsx new file mode 100644 index 0000000..4edbe8d --- /dev/null +++ b/client/src/app/providers.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { Provider as JotaiProvider } from "jotai"; +import { NuqsAdapter } from "nuqs/adapters/next/app"; +import { PropsWithChildren } from "react"; + +import ReactQueryProvider from "@/app/react-query-provider"; + +const Providers = ({ children }: PropsWithChildren) => { + return ( + + + {children} + + + ); +}; + +export default Providers; diff --git a/client/src/components/dataset-card/index.tsx b/client/src/components/dataset-card/index.tsx index b0722b6..dc0aa61 100644 --- a/client/src/components/dataset-card/index.tsx +++ b/client/src/components/dataset-card/index.tsx @@ -23,6 +23,7 @@ import { import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import YearChart from "@/components/year-chart"; +import useLayerInteractionState from "@/hooks/use-layer-interaction-state"; import useLocation from "@/hooks/use-location"; import { useLocationByCodes } from "@/hooks/use-location-by-codes"; import useMapLayers from "@/hooks/use-map-layers"; @@ -88,6 +89,8 @@ const DatasetCard = ({ // Date that was selected before the animation is played const dateBeforeAnimationRef = useRef(null); + const [, { setHoveredFeature, setSelectedFeature }] = useLayerInteractionState(selectedLayerId); + const selectedLayer = useMemo( () => layers.find(({ id }) => id === selectedLayerId), [layers, selectedLayerId], @@ -144,6 +147,8 @@ const DatasetCard = ({ if (!active) { removeLayer(selectedLayerId); + setHoveredFeature(null); + setSelectedFeature(null); if (isAnimated) { onToggleAnimation(); } @@ -159,6 +164,8 @@ const DatasetCard = ({ selectedDate, isAnimated, onToggleAnimation, + setHoveredFeature, + setSelectedFeature, ], ); @@ -169,6 +176,10 @@ const DatasetCard = ({ const returnPeriod = getDefaultReturnPeriod(id, layers, layersConfiguration); const date = getDefaultDate(id, layers, layersConfiguration); + // We reset the hovered and selected features for the previous layer + setHoveredFeature(null); + setSelectedFeature(null); + setSelectedLayerId(id); setSelectedReturnPeriod(returnPeriod); @@ -188,6 +199,8 @@ const DatasetCard = ({ addLayer, layers, layersConfiguration, + setHoveredFeature, + setSelectedFeature, ], ); diff --git a/client/src/components/map/layer-manager/item.tsx b/client/src/components/map/layer-manager/item.tsx index ed28c54..14d5966 100644 --- a/client/src/components/map/layer-manager/item.tsx +++ b/client/src/components/map/layer-manager/item.tsx @@ -1,4 +1,5 @@ import useLayerConfig from "@/hooks/use-layer-config"; +import useLayerInteractionState from "@/hooks/use-layer-interaction-state"; import { LayerSettings } from "@/types/layer"; import AnimatedLayer from "./animated-layer"; @@ -12,13 +13,15 @@ interface LayerManagerItemProps { } const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) => { - const layerConfig = useLayerConfig(id, settings); + const [interactionState, { setHoveredFeature, setSelectedFeature }] = + useLayerInteractionState(id); + const layerConfig = useLayerConfig(id, settings, interactionState); if (!layerConfig) { return null; } - const { type, config } = layerConfig; + const { type, config, interactive } = layerConfig; if (!config.styles) { return null; @@ -33,7 +36,15 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) => } if (config.source.type === "vector") { - return ; + return ( + + ); } console.warn(`Unsupported layer type (${config.source.type})`); diff --git a/client/src/components/map/layer-manager/vector-layer.tsx b/client/src/components/map/layer-manager/vector-layer.tsx index 8934f3a..63b47a1 100644 --- a/client/src/components/map/layer-manager/vector-layer.tsx +++ b/client/src/components/map/layer-manager/vector-layer.tsx @@ -1,5 +1,6 @@ +import { GetPickingInfoParams } from "@deck.gl/core"; import { MaskExtension } from "@deck.gl/extensions"; -import { MVTLayer, MVTLayerProps } from "@deck.gl/geo-layers"; +import { MVTLayer, MVTLayerPickingInfo, MVTLayerProps } from "@deck.gl/geo-layers"; import { ScatterplotLayer } from "@deck.gl/layers"; import { BinaryFeatureCollection } from "@loaders.gl/schema"; import { useContext, useEffect } from "react"; @@ -7,7 +8,7 @@ import { VectorSourceRaw as IVectorTileSource } from "react-map-gl"; import { env } from "@/env"; import useMapZoom from "@/hooks/use-map-zoom"; -import { LayerConfig } from "@/types/layer"; +import { LayerConfig, LayerInteractionState } from "@/types/layer"; import { convertBinaryToPointGeoJSON, resolveDeckglProperties } from "@/utils/mapbox-deckgl-bridge"; import { DeckGLMapboxOverlayContext } from "../deckgl-mapbox-provider"; @@ -15,9 +16,12 @@ import { DeckGLMapboxOverlayContext } from "../deckgl-mapbox-provider"; interface VectorLayerProps { config: LayerConfig; beforeId: string; + interactive: boolean; + onHover: (properties: LayerInteractionState["hoveredFeature"]) => void; + onClick: (properties: LayerInteractionState["selectedFeature"]) => void; } -const VectorLayer = ({ config, beforeId }: VectorLayerProps) => { +const VectorLayer = ({ config, beforeId, interactive, onHover, onClick }: VectorLayerProps) => { const { addLayer, removeLayer } = useContext(DeckGLMapboxOverlayContext); const zoom = useMapZoom(); @@ -51,6 +55,21 @@ const VectorLayer = ({ config, beforeId }: VectorLayerProps) => { ...resolveDeckglProperties(style, zoom), extensions: [new MaskExtension()], maskId: "mask", + pickable: interactive, + onHover: ({ picked, object }) => { + if (picked) { + onHover(object.properties); + } else { + onHover(null); + } + }, + onClick: ({ picked, object }) => { + if (picked) { + onClick(object.properties); + } else { + onClick(null); + } + }, }; // Here's an edge case: when the vector layer contains polygons and lines, Mapbox allows @@ -77,7 +96,20 @@ const VectorLayer = ({ config, beforeId }: VectorLayerProps) => { }; } - layers.push(new MVTLayer(layerProps)); + // We extend the MVTLayer class to make sure that when interacting with the layer, we pick + // the correct feature information, which is decoded binary data (i.e. GeoJSON properties) for + // the circle layers + class MVTJSONLayer extends MVTLayer { + getPickingInfo(params: GetPickingInfoParams) { + if (style.type === "circle") { + return params.info as MVTLayerPickingInfo; + } + + return super.getPickingInfo(params); + } + } + + layers.push(new MVTJSONLayer(layerProps)); }); layers.map((layer) => { diff --git a/client/src/hooks/use-deckgl-mapbox-overlay.ts b/client/src/hooks/use-deckgl-mapbox-overlay.ts index af04a97..efc5d0b 100644 --- a/client/src/hooks/use-deckgl-mapbox-overlay.ts +++ b/client/src/hooks/use-deckgl-mapbox-overlay.ts @@ -1,17 +1,46 @@ +import { PickingInfo } from "@deck.gl/core"; import { MapboxOverlay, MapboxOverlayProps } from "@deck.gl/mapbox"; -import { useEffect, useMemo } from "react"; +import { useSetAtom } from "jotai"; +import { useCallback, useEffect, useMemo, useRef } from "react"; import { useMap } from "react-map-gl"; +import { selectedFeatureByLayerAtom } from "@/hooks/use-layer-interaction-state"; + export default function useDeckGLMapboxOverlay(props: MapboxOverlayProps = { interleaved: true }) { const { current: map } = useMap(); + const cursorRef = useRef("grab"); + const setSelectedFeatureByLayer = useSetAtom(selectedFeatureByLayerAtom); + + const onClick = useCallback( + ({ picked }: PickingInfo) => { + if (!picked) { + // If the user clicks on the map and no geometry was below the cursor, then we make sure to + // reset all the layers' selected features + setSelectedFeatureByLayer({}); + } + }, + [setSelectedFeatureByLayer], + ); + + const onHover = useCallback( + ({ picked }: PickingInfo) => { + cursorRef.current = picked ? "pointer" : "grab"; + if (map) { + map.getCanvas().style.cursor = cursorRef.current; + } + }, + [map], + ); const mapboxOverlay = useMemo( () => new MapboxOverlay({ ...props, - getCursor: () => map?.getCanvas().style.cursor || "", + getCursor: () => cursorRef.current, + onClick, + onHover, }), - [props, map], + [props, onClick, onHover], ); useEffect(() => { diff --git a/client/src/hooks/use-layer-config.ts b/client/src/hooks/use-layer-config.ts index 0796090..86d95f1 100644 --- a/client/src/hooks/use-layer-config.ts +++ b/client/src/hooks/use-layer-config.ts @@ -6,6 +6,7 @@ import { useGetLayersId } from "@/types/generated/layer"; import { LayerType } from "@/types/generated/strapi.schemas"; import { LayerConfig, + LayerInteractionState, LayerParamsConfig, LayerResolvedParamsConfig, LayerSettings, @@ -14,8 +15,9 @@ import { const resolveLayerParamsConfig = ( paramsConfig: LayerParamsConfig, settings: LayerSettings, + interaction: LayerInteractionState, ): LayerResolvedParamsConfig => { - return paramsConfig.reduce((res, param) => { + const config: LayerResolvedParamsConfig = paramsConfig.reduce((res, param) => { const hasSettings = (key: string): key is keyof LayerSettings => { return key in settings; }; @@ -31,6 +33,17 @@ const resolveLayerParamsConfig = ( [param.key]: settings[param.key] ?? param.default, }; }, {}); + + const interactive = config["interactive"] as boolean | undefined | null; + const featureId = config["feature-id"] as string | undefined | null; + + // If the layer is interactive, we compute the properties related to the interaction + if (interactive === true && featureId !== undefined && featureId !== null) { + config["hovered-feature-id"] = interaction.hoveredFeature?.[featureId] ?? ""; + config["selected-feature-id"] = interaction.selectedFeature?.[featureId] ?? ""; + } + + return config; }; const resolveLayerConfig = ( @@ -73,7 +86,11 @@ const resolveLayerConfig = ( return converter.convertJson(config); }; -export default function useLayerConfig(layerId: number, settings: LayerSettings) { +export default function useLayerConfig( + layerId: number, + settings: LayerSettings, + interaction: LayerInteractionState, +) { const { data, isLoading } = useGetLayersId(layerId, { query: { select: (data) => { @@ -97,8 +114,8 @@ export default function useLayerConfig(layerId: number, settings: LayerSettings) return undefined; } - return resolveLayerParamsConfig(data.paramsConfig, settings); - }, [data, isLoading, settings]); + return resolveLayerParamsConfig(data.paramsConfig, settings, interaction); + }, [data, isLoading, settings, interaction]); const resolvedConfig = useMemo(() => { if (isLoading || !data || !resolvedParamsConfig) { @@ -116,9 +133,17 @@ export default function useLayerConfig(layerId: number, settings: LayerSettings) return data.type; }, [data, isLoading]); + const interactive = useMemo(() => { + if (!resolvedParamsConfig) { + return false; + } + + return "interactive" in resolvedParamsConfig && resolvedParamsConfig.interactive === true; + }, [resolvedParamsConfig]); + if (!type || !resolvedConfig) { return undefined; } - return { type, config: resolvedConfig }; + return { type, config: resolvedConfig, interactive }; } diff --git a/client/src/hooks/use-layer-interaction-state.ts b/client/src/hooks/use-layer-interaction-state.ts new file mode 100644 index 0000000..e38ea41 --- /dev/null +++ b/client/src/hooks/use-layer-interaction-state.ts @@ -0,0 +1,56 @@ +"use client"; + +import { atom, useAtom } from "jotai"; +import { useCallback, useMemo } from "react"; + +import { LayerInteractionState } from "@/types/layer"; + +// NOTE: prefer using the hook below instead of the atom directly, unless you need to access all the +// hovered features at once +export const hoveredFeatureByLayerAtom = atom< + Record +>({}); + +// NOTE: prefer using the hook below instead of the atom directly, unless you need to access all the +// selected features at once +export const selectedFeatureByLayerAtom = atom< + Record +>({}); + +export default function useLayerInteractionState(layerId: number | undefined) { + const [hoveredFeatureByLayer, setHoveredFeatureByLayer] = useAtom(hoveredFeatureByLayerAtom); + const [selectedFeatureByLayer, setSelectedFeatureByLayer] = useAtom(selectedFeatureByLayerAtom); + + const hoveredFeature = useMemo( + () => (layerId === undefined ? null : (hoveredFeatureByLayer[layerId] ?? null)), + [layerId, hoveredFeatureByLayer], + ); + + const selectedFeature = useMemo( + () => (layerId === undefined ? null : (selectedFeatureByLayer[layerId] ?? null)), + [layerId, selectedFeatureByLayer], + ); + + const setHoveredFeature = useCallback( + (value: LayerInteractionState["hoveredFeature"]) => { + if (layerId !== undefined) { + setHoveredFeatureByLayer((prev) => ({ ...prev, [layerId]: value })); + } + }, + [layerId, setHoveredFeatureByLayer], + ); + + const setSelectedFeature = useCallback( + (value: LayerInteractionState["selectedFeature"]) => { + if (layerId !== undefined) { + setSelectedFeatureByLayer((prev) => ({ ...prev, [layerId]: value })); + } + }, + [layerId, setSelectedFeatureByLayer], + ); + + return [ + { hoveredFeature, selectedFeature } as LayerInteractionState, + { setHoveredFeature, setSelectedFeature }, + ] as const; +} diff --git a/client/src/types/layer.ts b/client/src/types/layer.ts index 54d3556..52c0b39 100644 --- a/client/src/types/layer.ts +++ b/client/src/types/layer.ts @@ -1,3 +1,4 @@ +import { GeoJsonProperties } from "geojson"; import { CircleLayerSpecification, FillLayerSpecification, @@ -14,6 +15,11 @@ export interface LayerSettings { date?: string; } +export interface LayerInteractionState { + hoveredFeature: GeoJsonProperties | null; + selectedFeature: GeoJsonProperties | null; +} + export interface LayerConfig { source: AnySource; styles: ( diff --git a/client/src/utils/mapbox-deckgl-bridge.ts b/client/src/utils/mapbox-deckgl-bridge.ts index 2436642..9776a97 100644 --- a/client/src/utils/mapbox-deckgl-bridge.ts +++ b/client/src/utils/mapbox-deckgl-bridge.ts @@ -266,16 +266,23 @@ export const resolveDeckglProperties = (style: LayerConfig["styles"][0], zoom: n resolvedProperties.stroked = false; } - return Object.entries(resolvedProperties).reduce((res, [key, value]) => { - if (value === undefined) { - return res; - } - - return { - ...res, - [key]: value, - }; - }, {}) as Partial; + return Object.entries(resolvedProperties).reduce( + (res, [key, value]) => { + if (value === undefined) { + return res; + } + + return { + ...res, + [key]: value, + updateTriggers: { + ...res["updateTriggers"], + [key]: [style], + }, + }; + }, + { updateTriggers: {} }, + ) as Partial; }; export const convertBinaryToPointGeoJSON = (data: BinaryFeatureCollection) => { diff --git a/client/yarn.lock b/client/yarn.lock index 233e0db..9e89960 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -5950,6 +5950,7 @@ __metadata: express: "npm:4.21.1" husky: "npm:9.1.6" jiti: "npm:1.21.6" + jotai: "npm:2.10.3" lodash-es: "npm:4.17.21" mapbox-gl: "npm:3.7.0" next: "npm:14.2.15" @@ -8876,6 +8877,21 @@ __metadata: languageName: node linkType: hard +"jotai@npm:2.10.3": + version: 2.10.3 + resolution: "jotai@npm:2.10.3" + peerDependencies: + "@types/react": ">=17.0.0" + react: ">=17.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 10c0/64f6536aaa91f77dacd8d9714fb846f254bfed6e5354b3005375433d72844f3ae1d6d893ee4dd423d5bddd109d393dd338e562da914605b31190989a3d47db35 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" From a596f7a721dafb937e8d202aa4027c0eacff250f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Tue, 26 Nov 2024 11:01:01 +0100 Subject: [PATCH 02/10] feat(cms): Configuration for charts displayed on interaction --- client/src/app/layout.tsx | 2 +- .../map/layer-manager/vector-layer.tsx | 2 +- client/src/types/generated/strapi.schemas.ts | 23 ++++++-- .../sync/admin-role.strapi-super-admin.json | 21 +++++--- ...ent_types##api##chart-data.chart-data.json | 30 ++++++++--- ...n_content_types##api##dataset.dataset.json | 25 ++++----- ...ation_content_types##api##layer.layer.json | 52 ++++++++++++++++--- ...ntent_types##api##sub-topic.sub-topic.json | 12 ++--- .../content-types/chart-data/schema.json | 7 ++- .../api/layer/content-types/layer/schema.json | 7 +++ .../1.0.0/full_documentation.json | 51 ++++++++++++++++-- cms/types/generated/components.d.ts | 40 +++++++------- cms/types/generated/contentTypes.d.ts | 6 ++- 13 files changed, 207 insertions(+), 71 deletions(-) diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index fbd3208..74736c7 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -1,11 +1,11 @@ import { Jost, DM_Serif_Text } from "next/font/google"; +import Providers from "@/app/providers"; import Head from "@/components/head"; import type { Metadata } from "next"; import "./globals.css"; -import Providers from "@/app/providers"; export const metadata: Metadata = { title: { diff --git a/client/src/components/map/layer-manager/vector-layer.tsx b/client/src/components/map/layer-manager/vector-layer.tsx index 63b47a1..ad052a5 100644 --- a/client/src/components/map/layer-manager/vector-layer.tsx +++ b/client/src/components/map/layer-manager/vector-layer.tsx @@ -121,7 +121,7 @@ const VectorLayer = ({ config, beforeId, interactive, onHover, onClick }: Vector removeLayer(layer.id); }); }; - }, [config, beforeId, addLayer, removeLayer, zoom]); + }, [config, beforeId, addLayer, removeLayer, zoom, interactive, onHover, onClick]); return null; }; diff --git a/client/src/types/generated/strapi.schemas.ts b/client/src/types/generated/strapi.schemas.ts index 520e021..19253a9 100644 --- a/client/src/types/generated/strapi.schemas.ts +++ b/client/src/types/generated/strapi.schemas.ts @@ -1145,6 +1145,7 @@ export const LayerType = { export interface Layer { chart_data?: LayerChartData; + chart_sentence?: string; chart_unit?: string; createdAt?: string; createdBy?: LayerCreatedBy; @@ -1154,6 +1155,7 @@ export interface Layer { mapbox_config: unknown; name: string; params_config: unknown; + show_chart_on_interaction?: boolean; type: LayerType; updatedAt?: string; updatedBy?: LayerUpdatedBy; @@ -1318,6 +1320,7 @@ export const LayerDatasetDataAttributesLayersDataItemAttributesType = { export type LayerDatasetDataAttributesLayersDataItemAttributes = { chart_data?: LayerDatasetDataAttributesLayersDataItemAttributesChartData; + chart_sentence?: string; chart_unit?: string; createdAt?: string; createdBy?: LayerDatasetDataAttributesLayersDataItemAttributesCreatedBy; @@ -1327,6 +1330,7 @@ export type LayerDatasetDataAttributesLayersDataItemAttributes = { mapbox_config?: unknown; name?: string; params_config?: unknown; + show_chart_on_interaction?: boolean; type?: LayerDatasetDataAttributesLayersDataItemAttributesType; updatedAt?: string; updatedBy?: LayerDatasetDataAttributesLayersDataItemAttributesUpdatedBy; @@ -1386,6 +1390,7 @@ export type LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemA createdBy?: LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemAttributesCreatedBy; layer?: LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemAttributesLayer; location_code?: string; + unique_identifier?: string; updatedAt?: string; updatedBy?: LayerDatasetDataAttributesLayersDataItemAttributesChartDataDataItemAttributesUpdatedBy; x_values?: unknown; @@ -1667,6 +1672,7 @@ export type LayerRequestDataChartDataItem = number | string; export type LayerRequestData = { chart_data?: LayerRequestDataChartDataItem[]; + chart_sentence?: string; chart_unit?: string; dataset?: LayerRequestDataDataset; download_link?: string; @@ -1674,6 +1680,7 @@ export type LayerRequestData = { mapbox_config: unknown; name: string; params_config: unknown; + show_chart_on_interaction?: boolean; type: LayerRequestDataType; }; @@ -1776,6 +1783,7 @@ export const DatasetLayersDataItemAttributesType = { export type DatasetLayersDataItemAttributes = { chart_data?: DatasetLayersDataItemAttributesChartData; + chart_sentence?: string; chart_unit?: string; createdAt?: string; createdBy?: DatasetLayersDataItemAttributesCreatedBy; @@ -1785,6 +1793,7 @@ export type DatasetLayersDataItemAttributes = { mapbox_config?: unknown; name?: string; params_config?: unknown; + show_chart_on_interaction?: boolean; type?: DatasetLayersDataItemAttributesType; updatedAt?: string; updatedBy?: DatasetLayersDataItemAttributesUpdatedBy; @@ -2176,6 +2185,7 @@ export type DatasetLayersDataItemAttributesChartDataDataItemAttributes = { createdBy?: DatasetLayersDataItemAttributesChartDataDataItemAttributesCreatedBy; layer?: DatasetLayersDataItemAttributesChartDataDataItemAttributesLayer; location_code?: string; + unique_identifier?: string; updatedAt?: string; updatedBy?: DatasetLayersDataItemAttributesChartDataDataItemAttributesUpdatedBy; x_values?: unknown; @@ -2289,12 +2299,13 @@ export interface ChartData { createdAt?: string; createdBy?: ChartDataCreatedBy; layer?: ChartDataLayer; - location_code: string; + location_code?: string; + unique_identifier?: string; updatedAt?: string; updatedBy?: ChartDataUpdatedBy; x_values: unknown; y_values: unknown; - year: number; + year?: number; } export type ChartDataLayerDataAttributesUpdatedByDataAttributes = { [key: string]: unknown }; @@ -2310,6 +2321,7 @@ export type ChartDataLayerDataAttributesUpdatedBy = { export type ChartDataLayerDataAttributes = { chart_data?: ChartDataLayerDataAttributesChartData; + chart_sentence?: string; chart_unit?: string; createdAt?: string; createdBy?: ChartDataLayerDataAttributesCreatedBy; @@ -2319,6 +2331,7 @@ export type ChartDataLayerDataAttributes = { mapbox_config?: unknown; name?: string; params_config?: unknown; + show_chart_on_interaction?: boolean; type?: ChartDataLayerDataAttributesType; updatedAt?: string; updatedBy?: ChartDataLayerDataAttributesUpdatedBy; @@ -2692,6 +2705,7 @@ export type ChartDataLayerDataAttributesChartDataDataItemAttributes = { createdBy?: ChartDataLayerDataAttributesChartDataDataItemAttributesCreatedBy; layer?: ChartDataLayerDataAttributesChartDataDataItemAttributesLayer; location_code?: string; + unique_identifier?: string; updatedAt?: string; updatedBy?: ChartDataLayerDataAttributesChartDataDataItemAttributesUpdatedBy; x_values?: unknown; @@ -2779,10 +2793,11 @@ export type ChartDataRequestDataLayer = number | string; export type ChartDataRequestData = { layer?: ChartDataRequestDataLayer; - location_code: string; + location_code?: string; + unique_identifier?: string; x_values: unknown; y_values: unknown; - year: number; + year?: number; }; export interface ChartDataRequest { diff --git a/cms/config/sync/admin-role.strapi-super-admin.json b/cms/config/sync/admin-role.strapi-super-admin.json index 0cff370..7ec113e 100644 --- a/cms/config/sync/admin-role.strapi-super-admin.json +++ b/cms/config/sync/admin-role.strapi-super-admin.json @@ -13,7 +13,8 @@ "year", "x_values", "y_values", - "layer" + "layer", + "unique_identifier" ] }, "conditions": [] @@ -35,7 +36,8 @@ "year", "x_values", "y_values", - "layer" + "layer", + "unique_identifier" ] }, "conditions": [] @@ -50,7 +52,8 @@ "year", "x_values", "y_values", - "layer" + "layer", + "unique_identifier" ] }, "conditions": [] @@ -160,7 +163,9 @@ "dataset", "download_link", "chart_data", - "chart_unit" + "chart_unit", + "show_chart_on_interaction", + "chart_sentence" ] }, "conditions": [] @@ -192,7 +197,9 @@ "dataset", "download_link", "chart_data", - "chart_unit" + "chart_unit", + "show_chart_on_interaction", + "chart_sentence" ] }, "conditions": [] @@ -217,7 +224,9 @@ "dataset", "download_link", "chart_data", - "chart_unit" + "chart_unit", + "show_chart_on_interaction", + "chart_sentence" ] }, "conditions": [] diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json index cd14bb8..d6aed27 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##chart-data.chart-data.json @@ -22,7 +22,7 @@ "location_code": { "edit": { "label": "location_code", - "description": "", + "description": "Required if the chart's visibility doesn't depend on interaction with the layer. This is the case when the layer's show_chart_on_interaction attribute is false.", "placeholder": "", "visible": true, "editable": true @@ -36,7 +36,7 @@ "year": { "edit": { "label": "year", - "description": "", + "description": "Required if the chart's visibility doesn't depend on interaction with the layer. This is the case when the layer's show_chart_on_interaction attribute is false.", "placeholder": "", "visible": true, "editable": true @@ -90,6 +90,20 @@ "sortable": true } }, + "unique_identifier": { + "edit": { + "label": "unique_identifier", + "description": "Required if the chart's visibility depends on interaction with the layer. This is the case when the layer's show_chart_on_interaction attribute is true.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "unique_identifier", + "searchable": true, + "sortable": true + } + }, "createdAt": { "edit": { "label": "createdAt", @@ -155,16 +169,20 @@ { "name": "layer", "size": 6 - }, - { - "name": "location_code", - "size": 6 } ], [ + { + "name": "location_code", + "size": 4 + }, { "name": "year", "size": 4 + }, + { + "name": "unique_identifier", + "size": 4 } ], [ diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json index 8d3e28e..b9831d0 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##dataset.dataset.json @@ -195,15 +195,17 @@ } }, "layouts": { + "list": [ + "id", + "topic", + "sub_topic", + "name" + ], "edit": [ [ { "name": "name", "size": 6 - }, - { - "name": "order", - "size": 6 } ], [ @@ -237,14 +239,13 @@ "name": "metadata", "size": 12 } + ], + [ + { + "name": "order", + "size": 4 + } ] - ], - "list": [ - "id", - "topic", - "sub_topic", - "name", - "order" ] }, "uid": "api::dataset.dataset" @@ -252,4 +253,4 @@ "type": "object", "environment": null, "tag": null -} \ No newline at end of file +} diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json index 9116df2..470517b 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##layer.layer.json @@ -147,6 +147,34 @@ "sortable": true } }, + "show_chart_on_interaction": { + "edit": { + "label": "show_chart_on_interaction", + "description": "Whether the chart should only be shown once the user has interacted with the layer", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "show_chart_on_interaction", + "searchable": true, + "sortable": true + } + }, + "chart_sentence": { + "edit": { + "label": "chart_sentence", + "description": "Sentence displayed below the chart. If show_chart_on_interaction is true, the selected feature's attributes can be accessed as follows: {name_of_attribute}.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "chart_sentence", + "searchable": true, + "sortable": true + } + }, "createdAt": { "edit": { "label": "createdAt", @@ -207,12 +235,6 @@ } }, "layouts": { - "list": [ - "id", - "dataset", - "type", - "name" - ], "edit": [ [ { @@ -263,7 +285,25 @@ "name": "chart_unit", "size": 6 } + ], + [ + { + "name": "show_chart_on_interaction", + "size": 6 + } + ], + [ + { + "name": "chart_sentence", + "size": 12 + } ] + ], + "list": [ + "id", + "dataset", + "type", + "name" ] }, "uid": "api::layer.layer" diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json index ad87547..b136cfb 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##sub-topic.sub-topic.json @@ -107,6 +107,11 @@ } }, "layouts": { + "list": [ + "id", + "name", + "order" + ], "edit": [ [ { @@ -118,11 +123,6 @@ "size": 4 } ] - ], - "list": [ - "id", - "name", - "order" ] }, "uid": "api::sub-topic.sub-topic" @@ -130,4 +130,4 @@ "type": "object", "environment": null, "tag": null -} \ No newline at end of file +} diff --git a/cms/src/api/chart-data/content-types/chart-data/schema.json b/cms/src/api/chart-data/content-types/chart-data/schema.json index 5d224e4..5536283 100644 --- a/cms/src/api/chart-data/content-types/chart-data/schema.json +++ b/cms/src/api/chart-data/content-types/chart-data/schema.json @@ -14,12 +14,12 @@ "attributes": { "location_code": { "type": "string", - "required": true + "required": false }, "year": { "type": "integer", "min": 0, - "required": true + "required": false }, "x_values": { "type": "json", @@ -34,6 +34,9 @@ "relation": "manyToOne", "target": "api::layer.layer", "inversedBy": "chart_data" + }, + "unique_identifier": { + "type": "string" } } } diff --git a/cms/src/api/layer/content-types/layer/schema.json b/cms/src/api/layer/content-types/layer/schema.json index 2a22619..2b705e9 100644 --- a/cms/src/api/layer/content-types/layer/schema.json +++ b/cms/src/api/layer/content-types/layer/schema.json @@ -55,6 +55,13 @@ }, "chart_unit": { "type": "string" + }, + "show_chart_on_interaction": { + "type": "boolean", + "default": false + }, + "chart_sentence": { + "type": "text" } } } diff --git a/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json b/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json index 3785c14..1196f28 100644 --- a/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json +++ b/cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json @@ -14,7 +14,7 @@ "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - "x-generation-date": "2024-11-22T14:01:53.948Z" + "x-generation-date": "2024-11-26T13:48:27.187Z" }, "x-strapi-config": { "path": "/documentation", @@ -94,8 +94,6 @@ "properties": { "data": { "required": [ - "location_code", - "year", "x_values", "y_values" ], @@ -119,6 +117,9 @@ } ], "example": "string or id" + }, + "unique_identifier": { + "type": "string" } } } @@ -173,8 +174,6 @@ "ChartData": { "type": "object", "required": [ - "location_code", - "year", "x_values", "y_values" ], @@ -824,6 +823,9 @@ } } }, + "unique_identifier": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" @@ -876,6 +878,12 @@ "chart_unit": { "type": "string" }, + "show_chart_on_interaction": { + "type": "boolean" + }, + "chart_sentence": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" @@ -924,6 +932,9 @@ } } }, + "unique_identifier": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" @@ -1759,6 +1770,9 @@ } } }, + "unique_identifier": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" @@ -1811,6 +1825,12 @@ "chart_unit": { "type": "string" }, + "show_chart_on_interaction": { + "type": "boolean" + }, + "chart_sentence": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" @@ -2082,6 +2102,12 @@ }, "chart_unit": { "type": "string" + }, + "show_chart_on_interaction": { + "type": "boolean" + }, + "chart_sentence": { + "type": "string" } } } @@ -2304,6 +2330,9 @@ } } }, + "unique_identifier": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" @@ -2608,6 +2637,12 @@ "chart_unit": { "type": "string" }, + "show_chart_on_interaction": { + "type": "boolean" + }, + "chart_sentence": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" @@ -2921,6 +2956,12 @@ "chart_unit": { "type": "string" }, + "show_chart_on_interaction": { + "type": "boolean" + }, + "chart_sentence": { + "type": "string" + }, "createdAt": { "type": "string", "format": "date-time" diff --git a/cms/types/generated/components.d.ts b/cms/types/generated/components.d.ts index 0fcf675..ff7c4e4 100644 --- a/cms/types/generated/components.d.ts +++ b/cms/types/generated/components.d.ts @@ -1,5 +1,24 @@ import type { Schema, Attribute } from '@strapi/strapi'; +export interface MetadataItem extends Schema.Component { + collectionName: 'components_metadata_item'; + info: { + displayName: 'Item'; + description: ''; + }; + attributes: { + source: Attribute.String; + website: Attribute.String; + description: Attribute.Text; + main_applications: Attribute.RichText; + temporal_coverage: Attribute.RichText; + spatial_resolution: Attribute.String; + units: Attribute.String; + full_name: Attribute.String & Attribute.Required; + temporal_resolution: Attribute.RichText; + }; +} + export interface LegendLegendConfig extends Schema.Component { collectionName: 'components_legend_legend_configs'; info: { @@ -35,31 +54,12 @@ export interface LegendItems extends Schema.Component { }; } -export interface MetadataItem extends Schema.Component { - collectionName: 'components_metadata_item'; - info: { - displayName: 'Item'; - description: ''; - }; - attributes: { - source: Attribute.String; - website: Attribute.String; - description: Attribute.Text; - main_applications: Attribute.RichText; - temporal_coverage: Attribute.RichText; - spatial_resolution: Attribute.String; - units: Attribute.String; - full_name: Attribute.String & Attribute.Required; - temporal_resolution: Attribute.RichText; - }; -} - declare module '@strapi/types' { export module Shared { export interface Components { + 'metadata.item': MetadataItem; 'legend.legend-config': LegendLegendConfig; 'legend.items': LegendItems; - 'metadata.item': MetadataItem; } } } diff --git a/cms/types/generated/contentTypes.d.ts b/cms/types/generated/contentTypes.d.ts index 72c5e1c..c552e91 100644 --- a/cms/types/generated/contentTypes.d.ts +++ b/cms/types/generated/contentTypes.d.ts @@ -800,9 +800,8 @@ export interface ApiChartDataChartData extends Schema.CollectionType { draftAndPublish: false; }; attributes: { - location_code: Attribute.String & Attribute.Required; + location_code: Attribute.String; year: Attribute.Integer & - Attribute.Required & Attribute.SetMinMax< { min: 0; @@ -816,6 +815,7 @@ export interface ApiChartDataChartData extends Schema.CollectionType { 'manyToOne', 'api::layer.layer' >; + unique_identifier: Attribute.String; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation< @@ -919,6 +919,8 @@ export interface ApiLayerLayer extends Schema.CollectionType { 'api::chart-data.chart-data' >; chart_unit: Attribute.String; + show_chart_on_interaction: Attribute.Boolean & Attribute.DefaultTo; + chart_sentence: Attribute.Text; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation< From 950b9a79cea7539bfbf2e96f291f23cb2f706619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Tue, 26 Nov 2024 14:02:38 +0100 Subject: [PATCH 03/10] refactor(client): Change when layer name is displayed in legend The layer's name is now only displayed if the dataset has multiple layers. --- client/src/components/map/legend/item/index.tsx | 8 +++----- client/src/hooks/use-layer-legend.ts | 7 ++++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/src/components/map/legend/item/index.tsx b/client/src/components/map/legend/item/index.tsx index 33833cb..0efff08 100644 --- a/client/src/components/map/legend/item/index.tsx +++ b/client/src/components/map/legend/item/index.tsx @@ -131,11 +131,9 @@ const LegendItem = ({ id, settings, sortableAttributes, sortableListeners }: Leg )} - {!isLoading && - data.topicSlug !== "contextual" && - data.topicSlug !== "hydrometeorological" && ( -
{data.name}
- )} + {!isLoading && data.topicSlug !== "contextual" && data.datasetLayersCount > 1 && ( +
{data.name}
+ )}
{isLoading && ( <> diff --git a/client/src/hooks/use-layer-legend.ts b/client/src/hooks/use-layer-legend.ts index 1740dae..c3d9caf 100644 --- a/client/src/hooks/use-layer-legend.ts +++ b/client/src/hooks/use-layer-legend.ts @@ -7,6 +7,7 @@ export default function useLayerLegend(id: number) { return useGetLayers<{ name: string; dataset: string; + datasetLayersCount: number; topicSlug: string; type: LegendLegendConfigComponent["type"]; unit: string; @@ -23,6 +24,9 @@ export default function useLayerLegend(id: number) { dataset: { fields: ["name"], populate: { + layers: { + fields: ["id"], + }, topic: { fields: ["slug"], }, @@ -44,13 +48,14 @@ export default function useLayerLegend(id: number) { } const { name, dataset, legend_config } = data.data[0].attributes!; - const { name: datasetName, topic } = dataset!.data!.attributes!; + const { name: datasetName, topic, layers } = dataset!.data!.attributes!; const { slug: topicSlug } = topic!.data!.attributes!; const { type, unit, items } = legend_config!; return { name, dataset: datasetName, + datasetLayersCount: layers!.data!.length, topicSlug, type, unit, From f19362129601e19a818beb4fd89a11d23f77485c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Tue, 26 Nov 2024 14:05:08 +0100 Subject: [PATCH 04/10] feat(client): Indicate when a layer has interaction --- client/src/components/dataset-card/index.tsx | 7 +++++++ client/src/components/panels/drought/index.tsx | 2 +- client/src/components/panels/flood/index.tsx | 2 +- client/src/components/panels/hydrometeorological/index.tsx | 2 +- client/src/svgs/cursor-arrow-rays.svg | 4 ++++ client/tailwind.config.ts | 1 + 6 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 client/src/svgs/cursor-arrow-rays.svg diff --git a/client/src/components/dataset-card/index.tsx b/client/src/components/dataset-card/index.tsx index dc0aa61..00c3503 100644 --- a/client/src/components/dataset-card/index.tsx +++ b/client/src/components/dataset-card/index.tsx @@ -31,6 +31,7 @@ import useYearChartData from "@/hooks/use-year-chart-data"; import { cn } from "@/lib/utils"; import CalendarDaysIcon from "@/svgs/calendar-days.svg"; import ChevronDownIcon from "@/svgs/chevron-down.svg"; +import CursorArrowRaysIcon from "@/svgs/cursor-arrow-rays.svg"; import DownloadIcon from "@/svgs/download.svg"; import GraphIcon from "@/svgs/graph.svg"; import PauseIcon from "@/svgs/pause.svg"; @@ -423,6 +424,12 @@ const DatasetCard = ({ )} + {selectedLayer !== undefined && !!selectedLayer.attributes!.show_chart_on_interaction && ( +
+ + Select a point on the map for details. +
+ )} {selectedDate !== undefined && selectedLayerId !== undefined && (
{ const { data, isLoading } = useDatasetsBySubTopic( "drought", - ["name", "params_config", "download_link"], + ["name", "params_config", "download_link", "show_chart_on_interaction"], true, ); diff --git a/client/src/components/panels/flood/index.tsx b/client/src/components/panels/flood/index.tsx index 0b8db01..12cc2ba 100644 --- a/client/src/components/panels/flood/index.tsx +++ b/client/src/components/panels/flood/index.tsx @@ -8,7 +8,7 @@ import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic"; const FloodPanel = () => { const { data, isLoading } = useDatasetsBySubTopic( "flood", - ["name", "params_config", "download_link"], + ["name", "params_config", "download_link", "show_chart_on_interaction"], true, ); diff --git a/client/src/components/panels/hydrometeorological/index.tsx b/client/src/components/panels/hydrometeorological/index.tsx index 6828639..981cf86 100644 --- a/client/src/components/panels/hydrometeorological/index.tsx +++ b/client/src/components/panels/hydrometeorological/index.tsx @@ -9,7 +9,7 @@ import HydrometeorologicalImage from "../../../../public/assets/images/hydromete const HydrometeorologicalPanel = () => { const { data, isLoading } = useDatasetsBySubTopic( "hydrometeorological", - ["name", "params_config", "download_link"], + ["name", "params_config", "download_link", "show_chart_on_interaction"], true, ); diff --git a/client/src/svgs/cursor-arrow-rays.svg b/client/src/svgs/cursor-arrow-rays.svg new file mode 100644 index 0000000..0c306eb --- /dev/null +++ b/client/src/svgs/cursor-arrow-rays.svg @@ -0,0 +1,4 @@ + + + diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts index 829ca64..5df3589 100644 --- a/client/tailwind.config.ts +++ b/client/tailwind.config.ts @@ -25,6 +25,7 @@ const config: Config = { "300": "#b6cbda", "400": "#a4bdd0", "500": "#7999b8", + "800": "#4E5F7F", "950": "#2b3340", }, "rhino-blue": { From 1c7b278d389d4082390f3c21a9228c6952eb651c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 27 Nov 2024 14:50:09 +0100 Subject: [PATCH 05/10] feat(client): Render interaction chart --- client/src/components/dataset-card/index.tsx | 55 +++-- .../components/interaction-chart/index.tsx | 207 ++++++++++++++++++ .../src/hooks/use-interaction-chart-data.ts | 97 ++++++++ client/tailwind.config.ts | 1 + 4 files changed, 345 insertions(+), 15 deletions(-) create mode 100644 client/src/components/interaction-chart/index.tsx create mode 100644 client/src/hooks/use-interaction-chart-data.ts diff --git a/client/src/components/dataset-card/index.tsx b/client/src/components/dataset-card/index.tsx index 00c3503..d9d530a 100644 --- a/client/src/components/dataset-card/index.tsx +++ b/client/src/components/dataset-card/index.tsx @@ -8,6 +8,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import * as React from "react"; import DatasetMetadata from "@/components/dataset-metadata"; +import InteractionChart from "@/components/interaction-chart"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; @@ -23,6 +24,7 @@ import { import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import YearChart from "@/components/year-chart"; +import useInteractionChartData from "@/hooks/use-interaction-chart-data"; import useLayerInteractionState from "@/hooks/use-layer-interaction-state"; import useLocation from "@/hooks/use-location"; import { useLocationByCodes } from "@/hooks/use-location-by-codes"; @@ -90,7 +92,8 @@ const DatasetCard = ({ // Date that was selected before the animation is played const dateBeforeAnimationRef = useRef(null); - const [, { setHoveredFeature, setSelectedFeature }] = useLayerInteractionState(selectedLayerId); + const [{ selectedFeature }, { setHoveredFeature, setSelectedFeature }] = + useLayerInteractionState(selectedLayerId); const selectedLayer = useMemo( () => layers.find(({ id }) => id === selectedLayerId), @@ -119,11 +122,14 @@ const DatasetCard = ({ [layers, selectedLayerId], ); - const { data: chartData, isLoading: chartIsLoading } = useYearChartData( + const { data: yearChartData, isLoading: yearChartIsLoading } = useYearChartData( selectedLayerId, selectedDate, ); + const { data: interactionChartData, isLoading: interacionChartIsLoading } = + useInteractionChartData(selectedLayer, selectedFeature); + const { data: locationData, isLoading: locationIsLoading } = useLocationByCodes( location.code.slice(-1), ); @@ -246,8 +252,8 @@ const DatasetCard = ({ const onClickSaveChartData = useCallback(() => { if ( - chartIsLoading || - !chartData || + yearChartIsLoading || + !yearChartData || locationIsLoading || !locationData?.length || !selectedDate @@ -269,7 +275,7 @@ const DatasetCard = ({ }, {}), year: getYear(selectedDate), location: locationData[0].name, - ...chartData, + ...yearChartData, }; const blob = new Blob([JSON.stringify(data)], { type: "application/json" }); @@ -279,7 +285,15 @@ const DatasetCard = ({ link.href = URL.createObjectURL(blob); link.click(); link.remove(); - }, [chartIsLoading, chartData, locationIsLoading, locationData, selectedDate, name, metadata]); + }, [ + yearChartIsLoading, + yearChartData, + locationIsLoading, + locationData, + selectedDate, + name, + metadata, + ]); // When the layer is animated, show each month of the year in a loop useEffect(() => { @@ -319,7 +333,9 @@ const DatasetCard = ({ variant="ghost" size="icon-sm" className="group/chart" - disabled={chartIsLoading || !chartData || locationIsLoading || !locationData} + disabled={ + yearChartIsLoading || !yearChartData || locationIsLoading || !locationData + } onClick={onClickSaveChartData} > Save chart data @@ -424,22 +440,31 @@ const DatasetCard = ({ )} - {selectedLayer !== undefined && !!selectedLayer.attributes!.show_chart_on_interaction && ( -
- - Select a point on the map for details. -
- )} + {isDatasetActive && + selectedLayer !== undefined && + !!selectedLayer.attributes!.show_chart_on_interaction && ( +
+ + Select a point on the map for details. +
+ )} {selectedDate !== undefined && selectedLayerId !== undefined && (
)} + {selectedLayer !== undefined && + !!selectedLayer.attributes!.show_chart_on_interaction && + !!selectedFeature && ( +
+ +
+ )} {selectedDate !== undefined && dateRange !== undefined && isDatasetActive && (
+
+ + + + + + + + + +
+
+ ); +}; + +export default DateControls; diff --git a/client/src/components/dataset-card/index.tsx b/client/src/components/dataset-card/index.tsx index 390a1f5..f5b1b72 100644 --- a/client/src/components/dataset-card/index.tsx +++ b/client/src/components/dataset-card/index.tsx @@ -1,19 +1,16 @@ "use client"; -import { getMonth, getYear } from "date-fns"; -import { format } from "date-fns/format"; +import { getYear } from "date-fns"; import { camelCase } from "lodash-es"; import Link from "next/link"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import * as React from "react"; +import { useCallback, useMemo, useState } from "react"; import DatasetMetadata from "@/components/dataset-metadata"; import InteractionChart from "@/components/interaction-chart"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; -import MonthPicker from "@/components/ui/month-picker"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, @@ -30,23 +27,18 @@ import useLocation from "@/hooks/use-location"; import { useLocationByCodes } from "@/hooks/use-location-by-codes"; import useMapLayers from "@/hooks/use-map-layers"; import useYearChartData from "@/hooks/use-year-chart-data"; -import { cn } from "@/lib/utils"; -import CalendarDaysIcon from "@/svgs/calendar-days.svg"; -import ChevronDownIcon from "@/svgs/chevron-down.svg"; import CursorArrowRaysIcon from "@/svgs/cursor-arrow-rays.svg"; import DownloadIcon from "@/svgs/download.svg"; import GraphIcon from "@/svgs/graph.svg"; -import PauseIcon from "@/svgs/pause.svg"; -import PlayIcon from "@/svgs/play.svg"; import QuestionMarkIcon from "@/svgs/question-mark.svg"; import { DatasetLayersDataItem, MetadataItemComponent } from "@/types/generated/strapi.schemas"; -import { LayerParamsConfig } from "@/types/layer"; +import DateControls from "./date-controls"; import { + getDefaultDate, getDefaultReturnPeriod, getDefaultSelectedLayerId, getReturnPeriods, - getDefaultDate, } from "./utils"; interface DatasetCardProps { @@ -87,10 +79,6 @@ const DatasetCard = ({ const [selectedLayerId, setSelectedLayerId] = useState(defaultSelectedLayerId); const [selectedReturnPeriod, setSelectedReturnPeriod] = useState(defaultSelectedReturnPeriod); const [selectedDate, setSelectedDate] = useState(defaultSelectedDate); - const [isAnimated, setIsAnimated] = useState(false); - const animationIntervalRef = useRef(null); - // Date that was selected before the animation is played - const dateBeforeAnimationRef = useRef(null); const [{ selectedFeature }, { setHoveredFeature, setSelectedFeature }] = useLayerInteractionState(selectedLayerId); @@ -105,15 +93,6 @@ const DatasetCard = ({ [selectedLayer], ); - const dateRange = useMemo(() => { - if (!selectedLayer) { - return undefined; - } - - const paramsConfig = selectedLayer.attributes!.params_config! as LayerParamsConfig; - return paramsConfig.find(({ key }) => key === "date-range")?.default as [string, string]; - }, [selectedLayer]); - const isDatasetActive = useMemo(() => { if (selectedLayerId === undefined) { return false; @@ -139,18 +118,6 @@ const DatasetCard = ({ location.code.slice(-1), ); - const onToggleAnimation = useCallback(() => { - const newIsAnimated = !isAnimated; - - if (newIsAnimated) { - dateBeforeAnimationRef.current = selectedDate !== undefined ? selectedDate : null; - } else { - dateBeforeAnimationRef.current = null; - } - - setIsAnimated(newIsAnimated); - }, [selectedDate, isAnimated, setIsAnimated]); - const onToggleDataset = useCallback( (active: boolean) => { if (selectedLayerId === undefined) { @@ -161,9 +128,6 @@ const DatasetCard = ({ removeLayer(selectedLayerId); setHoveredFeature(null); setSelectedFeature(null); - if (isAnimated) { - onToggleAnimation(); - } } else { addLayer(selectedLayerId, { ["return-period"]: selectedReturnPeriod, date: selectedDate }); } @@ -174,8 +138,6 @@ const DatasetCard = ({ removeLayer, selectedReturnPeriod, selectedDate, - isAnimated, - onToggleAnimation, setHoveredFeature, setSelectedFeature, ], @@ -240,21 +202,6 @@ const DatasetCard = ({ ], ); - const onChangeSelectedDate = useCallback( - (date: string) => { - const returnPeriod = getDefaultReturnPeriod(selectedLayerId, layers, layersConfiguration); - - setSelectedDate(date); - - if (isDatasetActive && selectedLayerId !== undefined) { - updateLayer(selectedLayerId, { date }); - } else if (selectedLayerId !== undefined) { - addLayer(selectedLayerId, { ["return-period"]: returnPeriod, date }); - } - }, - [selectedLayerId, isDatasetActive, addLayer, updateLayer, layers, layersConfiguration], - ); - const onClickSaveChartData = useCallback(() => { const canDownload = (showChartOnInteraction && @@ -321,28 +268,15 @@ const DatasetCard = ({ metadata, ]); - // When the layer is animated, show each month of the year in a loop - useEffect(() => { - if (isAnimated && selectedDate !== undefined && selectedLayerId !== undefined) { - animationIntervalRef.current = setInterval(() => { - const date = format( - new Date(selectedDate).setMonth((getMonth(selectedDate) + 1) % 12), - "yyyy-MM-dd", - ); - - setSelectedDate(date); + const onChangeDate = useCallback( + (date: string) => { + setSelectedDate(date); + if (selectedLayerId !== undefined) { updateLayer(selectedLayerId, { date }); - }, 500); - } else if (animationIntervalRef.current !== null) { - clearInterval(animationIntervalRef.current); - } - - return () => { - if (animationIntervalRef.current !== null) { - clearInterval(animationIntervalRef.current); } - }; - }, [selectedLayerId, selectedDate, isAnimated, setSelectedDate, updateLayer]); + }, + [updateLayer, selectedLayerId, setSelectedDate], + ); return (
@@ -496,61 +430,8 @@ const DatasetCard = ({
)} - {selectedDate !== undefined && dateRange !== undefined && isDatasetActive && ( -
- -
- - - - - - - - - -
-
+ {isDatasetActive && selectedLayer !== undefined && selectedDate !== undefined && ( + )}
From 679183beac35c639a1a0493aabfa18db410c9e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 27 Nov 2024 16:56:58 +0100 Subject: [PATCH 09/10] refactor(client): Isolate dataset card buttons --- .../components/dataset-card/date-controls.tsx | 1 - .../dataset-card/download-chart-button.tsx | 48 ++++ .../dataset-card/download-layer-button.tsx | 33 +++ client/src/components/dataset-card/index.tsx | 258 +++++++----------- .../dataset-card/metadata-button.tsx | 39 +++ 5 files changed, 222 insertions(+), 157 deletions(-) create mode 100644 client/src/components/dataset-card/download-chart-button.tsx create mode 100644 client/src/components/dataset-card/download-layer-button.tsx create mode 100644 client/src/components/dataset-card/metadata-button.tsx diff --git a/client/src/components/dataset-card/date-controls.tsx b/client/src/components/dataset-card/date-controls.tsx index 0d85ac1..07d22da 100644 --- a/client/src/components/dataset-card/date-controls.tsx +++ b/client/src/components/dataset-card/date-controls.tsx @@ -1,6 +1,5 @@ import { getMonth } from "date-fns"; import { format } from "date-fns/format"; -import * as React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; diff --git a/client/src/components/dataset-card/download-chart-button.tsx b/client/src/components/dataset-card/download-chart-button.tsx new file mode 100644 index 0000000..4bfeaab --- /dev/null +++ b/client/src/components/dataset-card/download-chart-button.tsx @@ -0,0 +1,48 @@ +import { useCallback } from "react"; + +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import GraphIcon from "@/svgs/graph.svg"; + +interface DownloadChartButtonProps { + data: unknown; + fileName: string; + disabled: boolean; +} + +const DownloadChartButton = ({ data, fileName, disabled }: DownloadChartButtonProps) => { + const onClickSaveChartData = useCallback(() => { + const blob = new Blob([JSON.stringify(data)], { type: "application/json" }); + + const link = document.createElement("a"); + link.download = fileName; + link.href = URL.createObjectURL(blob); + link.click(); + link.remove(); + }, [data, fileName]); + + return ( + + + + + + Save chart data + + + ); +}; + +export default DownloadChartButton; diff --git a/client/src/components/dataset-card/download-layer-button.tsx b/client/src/components/dataset-card/download-layer-button.tsx new file mode 100644 index 0000000..6e3bc03 --- /dev/null +++ b/client/src/components/dataset-card/download-layer-button.tsx @@ -0,0 +1,33 @@ +import Link from "next/link"; + +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import DownloadIcon from "@/svgs/download.svg"; + +interface DownloadLayerButtonProps { + link: string; + fileName: string; +} + +const DownloadLayerButton = ({ link, fileName }: DownloadLayerButtonProps) => { + return ( + + + + + + Download dataset + + + ); +}; + +export default DownloadLayerButton; diff --git a/client/src/components/dataset-card/index.tsx b/client/src/components/dataset-card/index.tsx index f5b1b72..488e8de 100644 --- a/client/src/components/dataset-card/index.tsx +++ b/client/src/components/dataset-card/index.tsx @@ -2,14 +2,10 @@ import { getYear } from "date-fns"; import { camelCase } from "lodash-es"; -import Link from "next/link"; import * as React from "react"; import { useCallback, useMemo, useState } from "react"; -import DatasetMetadata from "@/components/dataset-metadata"; import InteractionChart from "@/components/interaction-chart"; -import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Select, @@ -19,7 +15,6 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import YearChart from "@/components/year-chart"; import useInteractionChartData from "@/hooks/use-interaction-chart-data"; import useLayerInteractionState from "@/hooks/use-layer-interaction-state"; @@ -28,12 +23,12 @@ import { useLocationByCodes } from "@/hooks/use-location-by-codes"; import useMapLayers from "@/hooks/use-map-layers"; import useYearChartData from "@/hooks/use-year-chart-data"; import CursorArrowRaysIcon from "@/svgs/cursor-arrow-rays.svg"; -import DownloadIcon from "@/svgs/download.svg"; -import GraphIcon from "@/svgs/graph.svg"; -import QuestionMarkIcon from "@/svgs/question-mark.svg"; import { DatasetLayersDataItem, MetadataItemComponent } from "@/types/generated/strapi.schemas"; import DateControls from "./date-controls"; +import DownloadChartButton from "./download-chart-button"; +import DownloadLayerButton from "./download-layer-button"; +import MetadataButton from "./metadata-button"; import { getDefaultDate, getDefaultReturnPeriod, @@ -118,6 +113,87 @@ const DatasetCard = ({ location.code.slice(-1), ); + const isChartDownloadVisible = useMemo(() => { + return ( + (showChartOnInteraction && selectedFeature) || + (!showChartOnInteraction && selectedDate !== undefined && selectedLayerId !== undefined) + ); + }, [selectedDate, selectedFeature, selectedLayerId, showChartOnInteraction]); + + const isChartDownloadDisabled = useMemo(() => { + const isInteractionChartDownloadDisabled = + showChartOnInteraction && (interactionChartIsLoading || !interactionChartData); + + const isYearChartDownloadDisabled = + !showChartOnInteraction && + (yearChartIsLoading || + !yearChartData || + locationIsLoading || + !locationData || + locationData.length === 0); + + return isInteractionChartDownloadDisabled || isYearChartDownloadDisabled; + }, [ + interactionChartData, + interactionChartIsLoading, + locationData, + locationIsLoading, + showChartOnInteraction, + yearChartData, + yearChartIsLoading, + ]); + + const downloadableChartData = useMemo(() => { + if (isChartDownloadDisabled) { + return; + } + + return { + dataset: name, + datasetMetadata: Object.entries(metadata ?? {}).reduce((res, [key, value]) => { + if (key === "id") { + return res; + } + + return { + ...res, + [camelCase(key)]: value, + }; + }, {}), + ...(!showChartOnInteraction + ? { + year: getYear(selectedDate!), + location: locationData![0].name, + ...yearChartData, + } + : {}), + ...(showChartOnInteraction + ? { + feature: selectedFeature, + ...interactionChartData, + } + : {}), + }; + }, [ + interactionChartData, + isChartDownloadDisabled, + locationData, + metadata, + name, + selectedDate, + selectedFeature, + showChartOnInteraction, + yearChartData, + ]); + + const downloadableChartDataFileName = useMemo(() => { + if (isChartDownloadDisabled) { + return ""; + } + + return `${name}${!showChartOnInteraction ? ` - ${locationData![0].name}` : ""}.json`; + }, [isChartDownloadDisabled, locationData, name, showChartOnInteraction]); + const onToggleDataset = useCallback( (active: boolean) => { if (selectedLayerId === undefined) { @@ -202,72 +278,6 @@ const DatasetCard = ({ ], ); - const onClickSaveChartData = useCallback(() => { - const canDownload = - (showChartOnInteraction && - !interactionChartIsLoading && - !!interactionChartData && - !!selectedFeature) || - (!showChartOnInteraction && - !yearChartIsLoading && - !!yearChartData && - !locationIsLoading && - !!locationData && - locationData.length > 0 && - !!selectedDate); - - if (!canDownload) { - return; - } - - const data = { - dataset: name, - datasetMetadata: Object.entries(metadata ?? {}).reduce((res, [key, value]) => { - if (key === "id") { - return res; - } - - return { - ...res, - [camelCase(key)]: value, - }; - }, {}), - ...(!showChartOnInteraction - ? { - year: getYear(selectedDate!), - location: locationData![0].name, - ...yearChartData, - } - : {}), - ...(showChartOnInteraction - ? { - feature: selectedFeature, - ...interactionChartData, - } - : {}), - }; - - const blob = new Blob([JSON.stringify(data)], { type: "application/json" }); - - const link = document.createElement("a"); - link.download = `${name}${!showChartOnInteraction ? ` - ${locationData![0].name}` : ""}.json`; - link.href = URL.createObjectURL(blob); - link.click(); - link.remove(); - }, [ - yearChartIsLoading, - yearChartData, - locationIsLoading, - locationData, - selectedDate, - interactionChartIsLoading, - interactionChartData, - showChartOnInteraction, - selectedFeature, - name, - metadata, - ]); - const onChangeDate = useCallback( (date: string) => { setSelectedDate(date); @@ -285,84 +295,20 @@ const DatasetCard = ({ {name}
- {((showChartOnInteraction && selectedFeature) || - (!showChartOnInteraction && - selectedDate !== undefined && - selectedLayerId !== undefined)) && ( - - - - - - Save chart data - - - )} - {!!selectedLayer?.attributes!.download_link && ( - - - - - - Download dataset - - + {isChartDownloadVisible && ( + )} - {!!metadata && ( - - - - - - - - - More info - - - - - - + {selectedLayer !== undefined && !!selectedLayer.attributes!.download_link && ( + )} + {!!metadata && } {(!!selectedLayer?.attributes!.download_link || !!metadata) && (
)} @@ -409,12 +355,17 @@ const DatasetCard = ({ )} - {isDatasetActive && selectedLayer !== undefined && !!showChartOnInteraction && ( + {isDatasetActive && showChartOnInteraction && selectedLayer !== undefined && (
Select a point on the map for details.
)} + {showChartOnInteraction && selectedLayer !== undefined && !!selectedFeature && ( +
+ +
+ )} {selectedDate !== undefined && selectedLayerId !== undefined && (
)} - {selectedLayer !== undefined && !!showChartOnInteraction && !!selectedFeature && ( -
- -
- )} {isDatasetActive && selectedLayer !== undefined && selectedDate !== undefined && ( )} diff --git a/client/src/components/dataset-card/metadata-button.tsx b/client/src/components/dataset-card/metadata-button.tsx new file mode 100644 index 0000000..f49558a --- /dev/null +++ b/client/src/components/dataset-card/metadata-button.tsx @@ -0,0 +1,39 @@ +import DatasetMetadata from "@/components/dataset-metadata"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import QuestionMarkIcon from "@/svgs/question-mark.svg"; +import { MetadataItemComponent } from "@/types/generated/strapi.schemas"; + +interface MetadataButtonProps { + datasetName: string; + metadata: MetadataItemComponent; +} + +const MetadataButton = ({ datasetName, metadata }: MetadataButtonProps) => { + return ( + + + + + + + + + More info + + + + + + + ); +}; + +export default MetadataButton; From 512cab238af616dd7d99d73c8c49e67e366c7956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Wed, 27 Nov 2024 17:26:06 +0100 Subject: [PATCH 10/10] feat(client): Add chart sentence --- .../dataset-card/chart-sentence.tsx | 30 +++++++++++++++++++ client/src/components/dataset-card/index.tsx | 28 +++++++++++++++-- .../src/components/panels/drought/index.tsx | 2 +- client/src/components/panels/flood/index.tsx | 2 +- .../panels/hydrometeorological/index.tsx | 2 +- 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 client/src/components/dataset-card/chart-sentence.tsx diff --git a/client/src/components/dataset-card/chart-sentence.tsx b/client/src/components/dataset-card/chart-sentence.tsx new file mode 100644 index 0000000..ed8d3ad --- /dev/null +++ b/client/src/components/dataset-card/chart-sentence.tsx @@ -0,0 +1,30 @@ +import { GeoJsonProperties } from "geojson"; +import { useMemo } from "react"; + +interface ChartSentenceprops { + sentence: string; + feature?: GeoJsonProperties; +} + +const ChartSentence = ({ sentence, feature }: ChartSentenceprops) => { + const resolvedSentence = useMemo(() => { + let res = `${sentence}`; // Creating a copy + + if (!!feature) { + Object.entries(feature).forEach(([key, value]) => { + res = res.replace(`{${key}}`, value); + }); + } + + return res; + }, [sentence, feature]); + + return ( +
+ {!!feature &&
Selected point
} +
{resolvedSentence}
+
+ ); +}; + +export default ChartSentence; diff --git a/client/src/components/dataset-card/index.tsx b/client/src/components/dataset-card/index.tsx index 488e8de..d9b23e8 100644 --- a/client/src/components/dataset-card/index.tsx +++ b/client/src/components/dataset-card/index.tsx @@ -25,6 +25,7 @@ import useYearChartData from "@/hooks/use-year-chart-data"; import CursorArrowRaysIcon from "@/svgs/cursor-arrow-rays.svg"; import { DatasetLayersDataItem, MetadataItemComponent } from "@/types/generated/strapi.schemas"; +import ChartSentence from "./chart-sentence"; import DateControls from "./date-controls"; import DownloadChartButton from "./download-chart-button"; import DownloadLayerButton from "./download-layer-button"; @@ -194,6 +195,16 @@ const DatasetCard = ({ return `${name}${!showChartOnInteraction ? ` - ${locationData![0].name}` : ""}.json`; }, [isChartDownloadDisabled, locationData, name, showChartOnInteraction]); + const isInteractionChartVisible = useMemo( + () => showChartOnInteraction && selectedLayer !== undefined && !!selectedFeature, + [selectedFeature, selectedLayer, showChartOnInteraction], + ); + + const isYearChartVisible = useMemo( + () => selectedDate !== undefined && selectedLayerId !== undefined, + [selectedDate, selectedLayerId], + ); + const onToggleDataset = useCallback( (active: boolean) => { if (selectedLayerId === undefined) { @@ -361,21 +372,32 @@ const DatasetCard = ({ Select a point on the map for details.
)} - {showChartOnInteraction && selectedLayer !== undefined && !!selectedFeature && ( + {isInteractionChartVisible && (
)} - {selectedDate !== undefined && selectedLayerId !== undefined && ( + {isYearChartVisible && (
)} + {isDatasetActive && + (isInteractionChartVisible || isYearChartVisible) && + selectedLayer !== undefined && + !!selectedLayer.attributes!.chart_sentence && ( +
+ +
+ )} {isDatasetActive && selectedLayer !== undefined && selectedDate !== undefined && ( )} diff --git a/client/src/components/panels/drought/index.tsx b/client/src/components/panels/drought/index.tsx index 538165e..dc098e9 100644 --- a/client/src/components/panels/drought/index.tsx +++ b/client/src/components/panels/drought/index.tsx @@ -8,7 +8,7 @@ import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic"; const DroughtPanel = () => { const { data, isLoading } = useDatasetsBySubTopic( "drought", - ["name", "params_config", "download_link", "show_chart_on_interaction"], + ["name", "params_config", "download_link", "show_chart_on_interaction", "chart_sentence"], true, ); diff --git a/client/src/components/panels/flood/index.tsx b/client/src/components/panels/flood/index.tsx index 12cc2ba..1a8c1b5 100644 --- a/client/src/components/panels/flood/index.tsx +++ b/client/src/components/panels/flood/index.tsx @@ -8,7 +8,7 @@ import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic"; const FloodPanel = () => { const { data, isLoading } = useDatasetsBySubTopic( "flood", - ["name", "params_config", "download_link", "show_chart_on_interaction"], + ["name", "params_config", "download_link", "show_chart_on_interaction", "chart_sentence"], true, ); diff --git a/client/src/components/panels/hydrometeorological/index.tsx b/client/src/components/panels/hydrometeorological/index.tsx index 981cf86..9d9cbc4 100644 --- a/client/src/components/panels/hydrometeorological/index.tsx +++ b/client/src/components/panels/hydrometeorological/index.tsx @@ -9,7 +9,7 @@ import HydrometeorologicalImage from "../../../../public/assets/images/hydromete const HydrometeorologicalPanel = () => { const { data, isLoading } = useDatasetsBySubTopic( "hydrometeorological", - ["name", "params_config", "download_link", "show_chart_on_interaction"], + ["name", "params_config", "download_link", "show_chart_on_interaction", "chart_sentence"], true, );