@geo2france/api-dashboard 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { EChartsOption } from "echarts";
2
- import EChartsReact from "echarts-for-react";
3
- interface ChartEchartsProps {
4
- option?: EChartsOption;
2
+ import EChartsReact, { EChartsReactProps } from "echarts-for-react";
3
+ interface ChartEchartsProps extends EChartsReactProps {
4
+ option: EChartsOption;
5
5
  }
6
6
  export declare const ChartEcharts: import("react").ForwardRefExoticComponent<ChartEchartsProps & import("react").RefAttributes<EChartsReact>>;
7
7
  export {};
@@ -11,7 +11,7 @@ const { useToken } = theme;
11
11
  * - Utilise le style de texte de l'application
12
12
  * devnote : A partir de React 19, ne plus utiliser forwardRef https://react.dev/reference/react/forwardRef
13
13
  */
14
- export const ChartEcharts = forwardRef(({ option = {} }, ref) => {
14
+ export const ChartEcharts = forwardRef(({ option = {}, ...restProps }, ref) => {
15
15
  const innerRef = useRef(null);
16
16
  useImperativeHandle(ref, () => innerRef.current, []); // Pour exposer le innerref au parent
17
17
  const { token } = useToken();
@@ -31,5 +31,5 @@ export const ChartEcharts = forwardRef(({ option = {} }, ref) => {
31
31
  axisLine: { lineStyle: { color: token.colorTextSecondary } },
32
32
  },
33
33
  };
34
- return (_jsx(EChartsReact, { option: deepMerge({}, default_option, option), ref: innerRef }));
34
+ return (_jsx(EChartsReact, { option: deepMerge({}, default_option, option), ref: innerRef, ...restProps }));
35
35
  });
@@ -1,5 +1,14 @@
1
1
  import { ReactElement } from "react";
2
+ import { SimpleRecord } from "../../types";
2
3
  type comparwithType = "first" | "previous";
4
+ interface annotation_params_type {
5
+ /** Valeur principale */
6
+ value: number;
7
+ /** Jeu de données utilisé */
8
+ data: SimpleRecord[] | undefined;
9
+ /** Valeur de comparaison */
10
+ compareValue: number;
11
+ }
3
12
  interface StatisticsProps {
4
13
  /** Identifiant du jeu de données */
5
14
  dataset: string;
@@ -23,6 +32,8 @@ interface StatisticsProps {
23
32
  help?: string;
24
33
  /** Comparer la valeur avec la précédente ou la première du jeu de données */
25
34
  compareWith?: comparwithType;
35
+ /** Texte d'annotation (remplace evolution si définie) */
36
+ annotation?: React.ReactNode | ((param: annotation_params_type) => React.ReactNode);
26
37
  }
27
38
  /**
28
39
  * Composant `Statistics` affichant une valeur d'un dataset avec son évolution.
@@ -20,15 +20,23 @@ const { Text, Paragraph } = Typography;
20
20
  * @param {StatisticsProps} props - Propriétés du composant
21
21
  * @returns {ReactElement} Carte statistique
22
22
  */
23
- export const Statistics = ({ dataset: dataset_id, dataKey, unit, evolutionSuffix, title, icon: icon_input, color, invertColor = false, help, compareWith, relativeEvolution = false }) => {
23
+ export const Statistics = ({ dataset: dataset_id, dataKey, unit, evolutionSuffix, title, icon: icon_input, color, invertColor = false, help, compareWith, relativeEvolution = false, annotation }) => {
24
24
  const icon = typeof icon_input === "string" ? _jsx(Icon, { icon: icon_input }) : icon_input;
25
25
  const dataset = useDataset(dataset_id);
26
- const value = dataset?.data?.slice(-1)[0][dataKey]; // Dernière valeur du dataset
26
+ const value = dataset?.data?.slice(-1)[0][dataKey]; // Dernière valeur du dataset. Caster en Number ?
27
27
  const compare_value = compareWith === 'previous' ? dataset?.data?.slice(-2)[0][dataKey] : dataset?.data?.slice(0, 1)[0][dataKey]; //Première ou avant dernière
28
28
  const evolution = relativeEvolution ? 100 * ((value - compare_value) / compare_value) : value - compare_value;
29
29
  const evolution_unit = relativeEvolution ? '%' : unit;
30
30
  const evolution_is_good = invertColor ? evolution < 0 : evolution > 0;
31
31
  const tooltip = help && _jsx(Tooltip, { title: help, children: _jsx(QuestionCircleOutlined, {}) });
32
+ const annotation_params = { value: value || NaN, compareValue: compare_value || NaN, data: dataset?.data || [] };
33
+ let subtitle;
34
+ if (annotation !== undefined) {
35
+ subtitle = typeof annotation === 'function' ? annotation(annotation_params) : annotation;
36
+ }
37
+ else if (evolution) {
38
+ subtitle = (_jsxs(Paragraph, { style: { marginBottom: "0.5rem" }, children: [_jsxs(Text, { strong: true, type: evolution_is_good ? "success" : "danger", style: { fontSize: "120%" }, children: [evolution < 0.1 ? '' : '+', evolution.toLocaleString(undefined, { maximumFractionDigits: 1 }), "\u00A0", evolution_unit] }), " ", _jsx(Text, { italic: true, type: "secondary", children: evolutionSuffix })] }));
39
+ }
32
40
  return (_jsx(Card, { title: title, style: {
33
41
  borderLeft: `4px solid ${color}`,
34
42
  height: "100%"
@@ -44,7 +52,10 @@ export const Statistics = ({ dataset: dataset_id, dataKey, unit, evolutionSuffix
44
52
  fontSize: 14,
45
53
  minHeight: 35,
46
54
  },
47
- }, extra: tooltip, children: _jsxs(Flex, { vertical: true, children: [_jsxs(Flex, { justify: "space-between", align: "center", children: [_jsxs(Text, { style: { fontSize: "150%", paddingTop: 8, paddingBottom: 8, paddingLeft: 0 }, children: [value?.toLocaleString(), " ", unit] }), icon && _jsx(Avatar, { size: 32 + 8, icon: icon, style: { backgroundColor: color } })] }), evolution && _jsxs(Paragraph, { style: { marginBottom: "0.5rem" }, children: [_jsxs(Text, { strong: true, type: evolution_is_good ? "success" : "danger", style: { fontSize: "120%" }, children: [evolution < 0.1 ? '' : '+', evolution.toLocaleString(undefined, { maximumFractionDigits: 1 }), "\u00A0", evolution_unit] }), " ", _jsx(Text, { italic: true, type: "secondary", children: evolutionSuffix })] })] }) }));
55
+ }, extra: tooltip, children: _jsxs(Flex, { vertical: true, children: [_jsxs(Flex, { justify: "space-between", align: "center", children: [_jsxs(Text, { style: { fontSize: "150%", paddingTop: 8, paddingBottom: 8, paddingLeft: 0 }, children: [value?.toLocaleString(), " ", unit] }), icon && _jsx(Avatar, { size: 32 + 8, icon: icon, style: { backgroundColor: color } })] }), typeof subtitle == 'string' ?
56
+ _jsx(Text, { italic: true, type: "secondary", children: subtitle })
57
+ :
58
+ _jsx("div", { children: subtitle })] }) }));
48
59
  };
49
60
  /**
50
61
  * `StatisticsCollection` permet de regrouper plusieurs cartes statistiques
@@ -4,6 +4,7 @@ import { createContext, useContext, useEffect, useId, useState } from "react";
4
4
  import { Icon } from "@iconify/react";
5
5
  import { ProducersFooter } from "../Dataset/Producer";
6
6
  import { MoreOutlined } from '@ant-design/icons';
7
+ import { ErrorBoundary } from "../Layout/Error";
7
8
  const { useToken } = theme;
8
9
  export const ChartBlockContext = createContext(undefined);
9
10
  export const DSL_ChartBlock = ({ children }) => {
@@ -36,7 +37,7 @@ export const DSL_ChartBlock = ({ children }) => {
36
37
  };
37
38
  DL();
38
39
  };
39
- return (_jsx(ChartBlockContext.Provider, { value: { config: config, setConfig: (e) => setConfig(e) }, children: _jsxs(Card, { style: { height: '100%' }, extra: has_action && dropdown_toolbox, title: config.title, children: [children, _jsx(ProducersFooter, { component: children })] }) }));
40
+ return (_jsx(ChartBlockContext.Provider, { value: { config: config, setConfig: (e) => setConfig(e) }, children: _jsx(Card, { style: { height: '100%' }, extra: has_action && dropdown_toolbox, title: config.title, children: _jsxs(ErrorBoundary, { children: [children, _jsx(ProducersFooter, { component: children })] }) }) }));
40
41
  };
41
42
  export const useBlockConfig = ({ title, dataExport }) => {
42
43
  const blockContext = useContext(ChartBlockContext);
@@ -1,2 +1,7 @@
1
1
  import React from "react";
2
2
  export declare const ErrorComponent: React.FC;
3
+ interface ErrorBoundaryProps {
4
+ children?: React.ReactNode;
5
+ }
6
+ export declare const ErrorBoundary: React.FC<ErrorBoundaryProps>;
7
+ export {};
@@ -1,6 +1,12 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Result, Typography, Space } from "antd";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Result, Typography, Space, Alert } from "antd";
3
+ import { ErrorBoundary as ErrorBoundaryBase } from "react-error-boundary";
4
+ import { Icon } from "@iconify/react";
3
5
  const { Text } = Typography;
4
6
  export const ErrorComponent = () => {
5
7
  return (_jsx(Result, { status: "404", title: "404", extra: _jsx(Space, { direction: "vertical", size: "large", children: _jsx(Space, { children: _jsx(Text, { children: "La page n'existe pas" }) }) }) }));
6
8
  };
9
+ export const ErrorBoundary = ({ children }) => {
10
+ const fallback = (_jsx(Alert, { message: _jsx(Icon, { icon: "garden:face-very-sad-stroke-12", fontSize: 25 }), description: _jsxs("div", { children: [_jsx("p", { children: " Cette visualisation ne peux malheureusement pas s'afficher." }), _jsx("p", { children: " Contactez l'administrateur\u00B7rice du tableau de bord si le probl\u00E8me persiste." })] }), type: "warning" }));
11
+ return (_jsx(ErrorBoundaryBase, { fallback: fallback, children: children }));
12
+ };
@@ -64,7 +64,7 @@ export const MapLayer = ({ dataset, categoryKey, color = 'red', type = 'circle',
64
64
  : data?.geojson; // Sinon on utilise le geojson (fournisseur wfs)
65
65
  const geom_type = geojson?.features?.[0] && getType(geojson?.features?.[0]);
66
66
  /** Type de données dans categoryKey (string ou number) */
67
- const type_value = categoryKey && typeof (data?.data?.[0][categoryKey]);
67
+ const type_value = categoryKey && typeof (data?.data?.[0]?.[categoryKey]);
68
68
  /** Valeurs distinctes (si type string) */
69
69
  const values = (type_value === 'string') && categoryKey && data?.data && from(data?.data).rollup({ a: op.array_agg_distinct(categoryKey) }).get('a', 0) || undefined;
70
70
  /** Couleurs de la palette */
@@ -105,7 +105,7 @@ export const MapLayer = ({ dataset, categoryKey, color = 'red', type = 'circle',
105
105
  }
106
106
  //devnote : regarder la colonne contenant les valeurs pour proposer une représentation (catégorie ou choroplèthe)
107
107
  useEffect(() => {
108
- if (geojson) {
108
+ if (geojson && geojson.features.length > 0) { // do not fitbound if no features
109
109
  const box = bbox(geojson).slice(0, 4);
110
110
  map?.fitBounds(box, { padding: 20 });
111
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geo2france/api-dashboard",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "private": false,
5
5
  "description": "Build dashboards with JSX/TSX",
6
6
  "main": "dist/index.js",
@@ -56,6 +56,7 @@
56
56
  "echarts": "^6.0.0",
57
57
  "echarts-for-react": "^3.0.4",
58
58
  "query-string": "~7.1.3",
59
+ "react-error-boundary": "^6.0.0",
59
60
  "react-helmet": "^6.1.0",
60
61
  "react-helmet-async": "^2.0.5",
61
62
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"