@geo2france/api-dashboard 1.16.0 → 1.18.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.
Files changed (42) hide show
  1. package/README.MD +22 -19
  2. package/dist/components/Charts/ChartEcharts.d.ts +6 -2
  3. package/dist/components/Charts/ChartEcharts.js +1 -2
  4. package/dist/components/Charts/Pie.d.ts +21 -3
  5. package/dist/components/Charts/Pie.js +22 -3
  6. package/dist/components/Charts/Statistics.d.ts +9 -7
  7. package/dist/components/Charts/Statistics.js +12 -10
  8. package/dist/components/Charts/YearSerie.d.ts +1 -2
  9. package/dist/components/Charts/YearSerie.js +1 -0
  10. package/dist/components/Control/Control.d.ts +21 -5
  11. package/dist/components/Control/Control.js +28 -6
  12. package/dist/components/Control/Radio.d.ts +4 -4
  13. package/dist/components/Control/Radio.js +3 -0
  14. package/dist/components/Control/Select.d.ts +5 -3
  15. package/dist/components/Control/Select.js +4 -4
  16. package/dist/components/DashboardPage/Block.js +4 -2
  17. package/dist/components/DashboardPage/Intro.d.ts +12 -0
  18. package/dist/components/DashboardPage/Intro.js +21 -0
  19. package/dist/components/DashboardPage/Page.d.ts +0 -8
  20. package/dist/components/DashboardPage/Page.js +12 -6
  21. package/dist/components/Dataset/Dataset.js +2 -4
  22. package/dist/components/Dataset/Producer.js +2 -2
  23. package/dist/components/Dataset/Transform.d.ts +13 -3
  24. package/dist/components/Dataset/Transform.js +5 -4
  25. package/dist/components/Dataset/hooks.d.ts +5 -1
  26. package/dist/components/Dataset/hooks.js +26 -1
  27. package/dist/components/Debug/Debug.js +1 -1
  28. package/dist/components/Layout/DashboardApp.d.ts +4 -0
  29. package/dist/components/Layout/DashboardApp.js +5 -13
  30. package/dist/components/Layout/Footer.js +42 -4
  31. package/dist/components/Layout/Sider.d.ts +1 -0
  32. package/dist/components/Layout/Sider.js +20 -19
  33. package/dist/components/NextPrevSelect/NextPrevSelect.d.ts +7 -6
  34. package/dist/components/NextPrevSelect/NextPrevSelect.js +16 -12
  35. package/dist/data_providers/file/utils/axios.js +2 -15
  36. package/dist/dsl/index.d.ts +3 -2
  37. package/dist/dsl/index.js +3 -2
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +1 -0
  40. package/dist/utils/aggregator.d.ts +18 -0
  41. package/dist/utils/aggregator.js +49 -0
  42. package/package.json +25 -11
package/README.MD CHANGED
@@ -2,33 +2,35 @@
2
2
 
3
3
  Collection de composants React pour faciliter la création de **tableaux de bords territoriaux**.
4
4
 
5
- Le projet permet la mise en place d'un tableau de bord facile à déployer sur une **infrastucture légère**.
6
- Le tableau de bord, une fois compilé, peut-être mis à disposition des utilisateurs via un **simple server web** (HTTP/HTTPS) sans configuration particulière.
7
- Il s'agit d'une application React (Javascript) s'executant dans le navigateur des utilisateurs. Il n'y a pas **backend à installer**,
8
- l'application récupère les données via API **auprès d'un partenaire** (plateforme régionale, portail open-data, etc.) ou sur **votre serveur de données**.
9
- Les données sont ensuites traitées par le client et présentées à l'utilisateur via des graphiques ou cartes.
5
+ Demo :
6
+ - [Odema](https://www.geo2france.fr/public/odema/dashboard/) ([code source](https://github.com/geo2france/odema-dashboard))
7
+ - [Clicnat](https://dashboard.clicnat.fr/)
10
8
 
11
- Le tableau de bord est construit de manière déclarative **JSX** (accronyme de JavaScript XML).
12
- Comme son nom l'indique, il permet de combiner la clarté et l'efficacité du XML, avec la souplesse et la puissance du JavaScript.
9
+ ![capture d'écran](screenshot.png)
13
10
 
14
- Les API suivantes sont actuellement supportées (interrogation, filtre, pagination, etc. ) :
15
- - [WFS](src/data_providers/wfs/) : API proposée par la plupart des serveurs geographiques (QGIS Server, GeoServer, ArcGIS server, etc.)
16
- - [Data Fair](src/data_providers/datafair/) : API de la solution open source Data Fair.
17
- - En développement : OGC API Features, TJS
11
+ ## Points forts
18
12
 
19
- ![diag](architecture_1.png)
13
+ - ✅ Compatible avec de **nombreux fournisseurs de données** (WFS, Datafair, fichiers statiques csv ou json, etc.),
14
+ - ✅ **Traitements** de données possibles (filtres, agréations, etc. via SQL ou JavaScript),
15
+ - ✅ Créez vos dataviz en utilisants [Apache ECharts](https://echarts.apache.org/) ou n'importe quelle bibliothèque web,
16
+ - ✅ Application cliente : peut-être mise à disposition des utilisateurs via un **simple server web** (HTTP/HTTPS) sans configuration particulière,
17
+ - ✅ Ecriture des tableaux de bord en **JSX/TSX** : combiner la clarté du XML avec la souplesse et puissance du JavaScript/TypeScript.
20
18
 
21
- En bref :
22
19
 
23
- - ✅ Déploiement facile et rapide (_client side_)
24
- - ✅ Présentez au même endroit des données tierces, vos données et celles de vos partenaires
25
- - ✅ Possibilité de visualiser des données sensibles
26
- - ✅ Flexibilité
20
+ ## Installation
27
21
 
28
- Les composants sont actuellement utilisés pour le [tableau de bord de l'Odema](https://github.com/geo2france/odema-dashboard).
22
+ ## Installer nvm et Node.js
29
23
 
30
- ## Installation
24
+ - Sur Windows : https://github.com/coreybutler/nvm-windows?tab=readme-ov-file#install-nvm-windows
25
+ - Sur Linux : `apt install nvm`
31
26
 
27
+ Ensuite, depuis un terminal ([Powershell](https://learn.microsoft.com/fr-fr/powershell/scripting/install/install-powershell-on-windows?view=powershell-7.5) ou autre) :
28
+ ```bash
29
+ # Installer la dernière vers LTS de Node.js
30
+ nvm install --lts
31
+ ```
32
+
33
+ ## Créer un tableau de bord
32
34
  Pour créer un nouveau tableau de bord :
33
35
 
34
36
  ```sh
@@ -41,3 +43,4 @@ npm run dev
41
43
 
42
44
  Consulter la [documentation](https://geo2france.github.io/api-dashboard/) du projet.
43
45
 
46
+
@@ -1,7 +1,11 @@
1
1
  import { EChartsOption } from "echarts";
2
2
  import EChartsReact, { EChartsReactProps } from "echarts-for-react";
3
- interface ChartEchartsProps extends EChartsReactProps {
3
+ export interface ChartEchartsProps extends EChartsReactProps {
4
4
  option: EChartsOption;
5
5
  }
6
+ /**
7
+ * Ce composant peut servir de base aux développements d'autres composants ou être utilisé directement dans une page (non conseillé).
8
+ * - Applique la palette utilisateur
9
+ * - Utilise le style de texte de l'application
10
+ */
6
11
  export declare const ChartEcharts: import("react").ForwardRefExoticComponent<ChartEchartsProps & import("react").RefAttributes<EChartsReact>>;
7
- export {};
@@ -5,11 +5,10 @@ import EChartsReact from "echarts-for-react";
5
5
  import { usePalette } from "../Palette/Palette";
6
6
  import deepMerge from "../../utils/deepmerge";
7
7
  const { useToken } = theme;
8
- /*
8
+ /**
9
9
  * Ce composant peut servir de base aux développements d'autres composants ou être utilisé directement dans une page (non conseillé).
10
10
  * - Applique la palette utilisateur
11
11
  * - Utilise le style de texte de l'application
12
- * devnote : A partir de React 19, ne plus utiliser forwardRef https://react.dev/reference/react/forwardRef
13
12
  */
14
13
  export const ChartEcharts = forwardRef(({ option = {}, ...restProps }, ref) => {
15
14
  const innerRef = useRef(null);
@@ -1,11 +1,29 @@
1
- interface IChartPieProps {
1
+ import { EChartsOption, LabelFormatterCallback } from "echarts";
2
+ import type { CallbackDataParams } from 'echarts/types/dist/shared';
3
+ export interface IChartPieProps {
4
+ /** Identifiant du dataset */
2
5
  dataset?: string;
6
+ /** Nom de la colonne qui contient les valeurs numériques */
3
7
  dataKey: string;
8
+ /** Nom de la colonne qui contient les catégories */
4
9
  nameKey: string;
10
+ /** Unité à afficher */
5
11
  unit?: string;
12
+ /** Titre du graphique */
6
13
  title?: string;
14
+ /** Donut ou camembert ? */
7
15
  donut?: boolean;
16
+ /** Merge categorie with less than `other` percent */
8
17
  other?: number | null;
18
+ /** Nombre de décimales après la virgule à afficher (1 par défaut)
19
+ */
20
+ precision?: number;
21
+ /** Personnaliser le formatter des labels de la série
22
+ * cf. https://echarts.apache.org/en/option.html#series-pie.label.formatter */
23
+ labelFormatter?: string | LabelFormatterCallback<CallbackDataParams>;
24
+ /** Options suplémentaires passées à Echarts
25
+ * @see https://echarts.apache.org/en/option.html
26
+ */
27
+ option?: EChartsOption;
9
28
  }
10
- export declare const ChartPie: React.FC<IChartPieProps>;
11
- export {};
29
+ export declare const ChartPie: ({ dataset: dataset_id, nameKey, dataKey, unit, title, donut, other, labelFormatter, precision, option: customOption }: IChartPieProps) => import("react/jsx-runtime").JSX.Element;
@@ -5,7 +5,8 @@ import { from, op } from "arquero";
5
5
  import { ChartEcharts } from "./ChartEcharts";
6
6
  import { merge_others } from "../..";
7
7
  import { useBlockConfig } from "../DashboardPage/Block";
8
- export const ChartPie = ({ dataset: dataset_id, nameKey, dataKey, unit, title, donut = false, other = 5 }) => {
8
+ import deepMerge from "../../utils/deepmerge";
9
+ export const ChartPie = ({ dataset: dataset_id, nameKey, dataKey, unit, title, donut = false, other = 5, labelFormatter, precision = 1, option: customOption = {} }) => {
9
10
  const dataset = useDataset(dataset_id);
10
11
  const data = dataset?.data;
11
12
  useBlockConfig({
@@ -22,6 +23,9 @@ export const ChartPie = ({ dataset: dataset_id, nameKey, dataKey, unit, title, d
22
23
  value: d.value,
23
24
  }))
24
25
  : [];
26
+ //@ts-ignore
27
+ const total = chart_data1.length > 0 && from(chart_data1).rollup({ value: op.sum('value') }).object()?.value;
28
+ console.log('total', total);
25
29
  const chart_data = merge_others({ dataset: chart_data1 || [], min: other || -1 });
26
30
  const colors = usePalette({ nColors: chart_data?.length });
27
31
  const colors_labels = usePaletteLabels();
@@ -36,11 +40,26 @@ export const ChartPie = ({ dataset: dataset_id, nameKey, dataKey, unit, title, d
36
40
  },
37
41
  data: chart_data,
38
42
  radius: donut ? ['40%', '75%'] : [0, '75%'],
43
+ label: {
44
+ formatter: labelFormatter,
45
+ overflow: "break"
46
+ }
39
47
  }],
40
48
  tooltip: {
41
49
  show: true,
42
- valueFormatter: v => `${v?.toLocaleString()} ${unit || ''} `
50
+ valueFormatter: v => `${v?.toLocaleString(undefined, { maximumFractionDigits: precision })} ${unit || ''} `
51
+ },
52
+ graphic: {
53
+ type: 'text',
54
+ left: 'center',
55
+ top: 'center',
56
+ style: {
57
+ text: `${total.toLocaleString(undefined, { maximumFractionDigits: precision })} ${unit}`,
58
+ fontSize: 24,
59
+ fontWeight: 'bold',
60
+ fill: '#333',
61
+ }
43
62
  }
44
63
  };
45
- return _jsx(ChartEcharts, { option: option });
64
+ return _jsx(ChartEcharts, { option: deepMerge({}, option, customOption) });
46
65
  };
@@ -1,6 +1,7 @@
1
1
  import { ReactElement } from "react";
2
2
  import { SimpleRecord } from "../../types";
3
3
  type comparwithType = "first" | "previous";
4
+ type aggregateType = "last" | "first" | "sum" | "lastNotNull" | "min" | "max" | "count" | "mean" | "countDistinct" | "countMissing";
4
5
  interface ICallbackParams {
5
6
  /** Valeur principale */
6
7
  value: number;
@@ -11,7 +12,7 @@ interface ICallbackParams {
11
12
  /** Valeur de comparaison */
12
13
  compareValue: number;
13
14
  }
14
- interface StatisticsProps {
15
+ export interface StatisticsProps {
15
16
  /** Identifiant du jeu de données */
16
17
  dataset: string;
17
18
  /** Nom de la colonne qui contient les valeurs */
@@ -37,7 +38,11 @@ interface StatisticsProps {
37
38
  /** Texte d'annotation (remplace evolution si définie) */
38
39
  annotation?: React.ReactNode | ((param: ICallbackParams) => React.ReactNode);
39
40
  /** Fonction a appliquer avant rendu */
40
- valueFormatter?: ((param: ICallbackParams) => React.ReactNode);
41
+ valueFormatter?: ((param: ICallbackParams) => string);
42
+ /** Méthode d'aggrégation */
43
+ aggregate?: aggregateType;
44
+ /** Afficher une animation (Count-up) */
45
+ animation?: boolean;
41
46
  }
42
47
  /**
43
48
  * Composant `Statistics` affichant une valeur d'un dataset avec son évolution.
@@ -53,7 +58,7 @@ interface StatisticsProps {
53
58
  * @returns {ReactElement} Carte statistique
54
59
  */
55
60
  export declare const Statistics: React.FC<StatisticsProps>;
56
- type StatisticsCollectionProps = {
61
+ export interface StatisticsCollectionProps {
57
62
  /**
58
63
  * Un ou plusieurs composants `<Statistics>`.
59
64
  */
@@ -66,13 +71,10 @@ type StatisticsCollectionProps = {
66
71
  * Titre du bloc.
67
72
  */
68
73
  title?: string;
69
- };
74
+ }
70
75
  /**
71
76
  * `StatisticsCollection` permet de regrouper plusieurs cartes statistiques
72
77
  * dans un bloc
73
- *
74
- * @param {StatisticsProps} props - Propriétés du composant
75
- * @returns {ReactElement} Collection de cartes statistiques
76
78
  * ```
77
79
  */
78
80
  export declare const StatisticsCollection: React.FC<StatisticsCollectionProps>;
@@ -5,6 +5,8 @@ import { Children } from "react";
5
5
  import { useDataset } from "../Dataset/hooks";
6
6
  import { Icon } from "@iconify/react";
7
7
  import { useBlockConfig } from "../DashboardPage/Block";
8
+ import { aggregator } from "../../utils/aggregator";
9
+ import CountUp from "react-countup";
8
10
  const { Text, Paragraph } = Typography;
9
11
  // DEV : modele cf https://bootstrapbrain.com/component/bootstrap-statistics-card-example/
10
12
  /**
@@ -20,17 +22,16 @@ const { Text, Paragraph } = Typography;
20
22
  * @param {StatisticsProps} props - Propriétés du composant
21
23
  * @returns {ReactElement} Carte statistique
22
24
  */
23
- export const Statistics = ({ dataset: dataset_id, dataKey, unit, evolutionSuffix, title, icon: icon_input, color, invertColor = false, help, compareWith, relativeEvolution = false, valueFormatter = (param) => (param.value.toLocaleString()), annotation }) => {
25
+ export const Statistics = ({ dataset: dataset_id, dataKey, unit, evolutionSuffix, title, icon: icon_input, color, invertColor = false, help, compareWith, relativeEvolution = false, valueFormatter = (param) => (param.value.toLocaleString()), annotation, aggregate = "last", animation = false }) => {
24
26
  const icon = typeof icon_input === "string" ? _jsx(Icon, { icon: icon_input }) : icon_input;
25
27
  const dataset = useDataset(dataset_id);
26
- const row = dataset?.data?.slice(-1)[0];
27
- const value = row?.[dataKey]; // Dernière valeur du dataset. Caster en Number ?
28
- const compare_value = compareWith === 'previous' ? dataset?.data?.slice(-2)[0][dataKey] : dataset?.data?.slice(0, 1)[0][dataKey]; //Première ou avant dernière
29
- const evolution = relativeEvolution ? 100 * ((value - compare_value) / compare_value) : value - compare_value;
28
+ const { row, value } = aggregator({ data: dataset?.data, dataKey, aggregate });
29
+ const compare_value = compareWith === 'previous' ? dataset?.data?.slice(-2)?.[0]?.[dataKey] : dataset?.data?.slice(0, 1)?.[0]?.[dataKey]; //Première ou avant dernière
30
+ const evolution = relativeEvolution ? 100 * ((Number(value) - compare_value) / compare_value) : Number(value) - compare_value;
30
31
  const evolution_unit = relativeEvolution ? '%' : unit;
31
32
  const evolution_is_good = invertColor ? evolution < 0 : evolution > 0;
32
33
  const tooltip = help && _jsx(Tooltip, { title: help, children: _jsx(QuestionCircleOutlined, {}) });
33
- const CallbackParams = { value: value || NaN, compareValue: compare_value || NaN, data: dataset?.data || [], row: row };
34
+ const CallbackParams = { value: value ?? NaN, compareValue: compare_value ?? NaN, data: dataset?.data ?? [], row: row };
34
35
  let subtitle;
35
36
  if (annotation !== undefined) {
36
37
  subtitle = typeof annotation === 'function' ? annotation(CallbackParams) : annotation;
@@ -53,17 +54,18 @@ export const Statistics = ({ dataset: dataset_id, dataKey, unit, evolutionSuffix
53
54
  fontSize: 14,
54
55
  minHeight: 35,
55
56
  },
56
- }, 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: [valueFormatter(CallbackParams), " ", unit] }), icon && _jsx(Avatar, { size: 32 + 8, icon: icon, style: { backgroundColor: color } })] }), typeof subtitle == 'string' ?
57
+ }, extra: tooltip, children: _jsxs(Flex, { vertical: true, children: [_jsxs(Flex, { justify: "space-between", align: "center", children: [_jsx(Text, { style: { fontSize: "150%", paddingTop: 8, paddingBottom: 8, paddingLeft: 0 }, children: animation ?
58
+ _jsx(CountUp, { formattingFn: (v) => `${valueFormatter({ ...CallbackParams, value: v })} ${unit}`, duration: 1.5, end: value || NaN })
59
+ :
60
+ _jsxs("span", { children: [valueFormatter(CallbackParams), " ", unit] }) }), icon && _jsx(Avatar, { size: 32 + 8, icon: icon, style: { backgroundColor: color } })] }), typeof subtitle == 'string' ?
57
61
  _jsx(Text, { italic: true, type: "secondary", children: subtitle })
58
62
  :
59
63
  _jsx("div", { children: subtitle })] }) }));
60
64
  };
65
+ ;
61
66
  /**
62
67
  * `StatisticsCollection` permet de regrouper plusieurs cartes statistiques
63
68
  * dans un bloc
64
- *
65
- * @param {StatisticsProps} props - Propriétés du composant
66
- * @returns {ReactElement} Collection de cartes statistiques
67
69
  * ```
68
70
  */
69
71
  export const StatisticsCollection = ({ children, columns = 3, title }) => {
@@ -2,7 +2,7 @@
2
2
  * Graphique standard pour afficher des données annuelles
3
3
  */
4
4
  import { EChartsOption, SeriesOption } from "echarts";
5
- interface IYearSerieProps {
5
+ export interface IYearSerieProps {
6
6
  dataset: string;
7
7
  title?: string;
8
8
  yearKey: string;
@@ -25,4 +25,3 @@ interface IYearSerieProps {
25
25
  options?: Partial<EChartsOption>;
26
26
  }
27
27
  export declare const ChartYearSerie: React.FC<IYearSerieProps>;
28
- export {};
@@ -40,6 +40,7 @@ export const ChartYearSerie = ({ dataset: dataset_id, categoryKey, valueKey, sec
40
40
  .derive({ part: d => 100 * d.value / op.sum(d.value) }) // Data for normalized view
41
41
  .rename({ value: valueKey, part: `${valueKey}_pct`, secondaryValue: secondaryValueKey || '' }) // Rename to original var name
42
42
  ).objects();
43
+ chart_data.sort((a, b) => a[yearKey] - b[yearKey]);
43
44
  }
44
45
  const COLORS = usePalette({ nColors: distinct_cat?.length }) || [];
45
46
  const colors_labels = usePaletteLabels();
@@ -7,15 +7,31 @@ declare const Control: React.FC<IControlProps>;
7
7
  export default Control;
8
8
  export declare const useControl: (name: string) => string | undefined;
9
9
  export declare const useAllControls: () => Record<string, any>;
10
- export declare const list_to_options: (input?: string[] | number[] | {
11
- label: string | number;
12
- value: string | number;
10
+ export declare const list_to_options: (input?: string[] | {
11
+ label: string;
12
+ value: string;
13
13
  }[]) => {
14
- label: string | number;
15
- value: string | number;
14
+ label: string;
15
+ value: string;
16
16
  }[];
17
17
  interface IControlProps {
18
18
  children: ReactElement | ReactElement[];
19
19
  }
20
20
  export declare const DSL_Control: React.FC<IControlProps>;
21
21
  export declare const ControlPreview: React.FC;
22
+ type ControlContextType = {
23
+ values: Record<string, any>;
24
+ register: (control: {
25
+ name: string;
26
+ value: any;
27
+ }) => void;
28
+ clear: () => void;
29
+ getAll: () => Record<string, any>;
30
+ };
31
+ export declare const ControlContext: React.Context<ControlContextType | undefined>;
32
+ export declare const CreateControlesRegistry: () => {
33
+ register: (c: Record<string, any>) => void;
34
+ clear: () => void;
35
+ getAll: () => Record<string, any>;
36
+ values: Record<string, any>;
37
+ };
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Descriptions, Form, Layout } from "antd";
3
- import React, { useContext, useEffect } from "react";
4
- import { ControlContext } from "../DashboardPage/Page";
3
+ import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
5
4
  const { Header } = Layout;
6
5
  /*
7
6
  * Composant destiné à recevoir un Form avec les contrôles de la page
@@ -28,7 +27,7 @@ export const useControl = (name) => {
28
27
  if (!context_controls) {
29
28
  throw new Error("useControl must be used within a ControlProvider");
30
29
  }
31
- const { values } = context_controls;
30
+ const values = context_controls.getAll();
32
31
  const value = values[name];
33
32
  return value;
34
33
  };
@@ -49,8 +48,8 @@ export const list_to_options = (input = []) => {
49
48
  return [];
50
49
  }
51
50
  return input.map((o) => {
52
- if (typeof o == "string" || typeof o == "number") {
53
- return { label: String(o), value: o };
51
+ if (typeof o == "string") {
52
+ return { label: String(o), value: String(o) };
54
53
  }
55
54
  return o;
56
55
  });
@@ -64,7 +63,7 @@ export const DSL_Control = ({ children }) => {
64
63
  if (!context_controls) { //Le contexte peut être nul ?
65
64
  throw new Error("useControl must be used within a ControlProvider");
66
65
  }
67
- const { values: _control, pushValue: pushControl } = context_controls;
66
+ const { values: _control, register: pushControl } = context_controls;
68
67
  const childrenArray = React.Children.toArray(children).filter((child) => React.isValidElement(child));
69
68
  //Ajout des nouvelles valeurs de controles dans le contexte de la page
70
69
  const handleChange = (changed_value) => {
@@ -83,3 +82,26 @@ export const ControlPreview = ({}) => {
83
82
  }));
84
83
  return (_jsx(Descriptions, { items: items }));
85
84
  };
85
+ export const ControlContext = createContext(undefined);
86
+ export const CreateControlesRegistry = () => {
87
+ /* CONTROLS */
88
+ const [controls, setControles] = useState({});
89
+ const pushControl = useCallback((c) => {
90
+ setControles(prev => ({
91
+ ...prev,
92
+ ...c
93
+ }));
94
+ }, []);
95
+ const clear = useCallback(() => {
96
+ setControles({});
97
+ }, []);
98
+ const getAll = useCallback(() => {
99
+ return controls;
100
+ }, [controls]);
101
+ return ({
102
+ register: pushControl,
103
+ clear,
104
+ getAll,
105
+ values: controls
106
+ });
107
+ };
@@ -2,18 +2,18 @@ import type { RadioGroupProps } from 'antd';
2
2
  import { SimpleRecord } from '../../types';
3
3
  export declare const buildOptionsFromData: (data: SimpleRecord[], labelField?: string, valueField?: string) => {
4
4
  label: string;
5
- value: string | number;
5
+ value: string;
6
6
  }[];
7
7
  type ExtendedRadioGroupProps = RadioGroupProps & {
8
8
  name?: string;
9
9
  dataset?: string;
10
10
  options?: {
11
11
  label: string;
12
- value: string | number;
13
- }[] | string[] | number[];
12
+ value: string;
13
+ }[] | string[];
14
14
  labelField?: string;
15
15
  valueField?: string;
16
- initalValue?: string | number;
16
+ initalValue?: string;
17
17
  };
18
18
  export declare const Radio: React.FC<ExtendedRadioGroupProps>;
19
19
  export {};
@@ -6,6 +6,9 @@ import { list_to_options } from './Control';
6
6
  // On construit les options depuis le tableau de données, utiliser pour Radio et Select
7
7
  export const buildOptionsFromData = (data, labelField = 'label', valueField = 'value') => {
8
8
  const t = from(data);
9
+ if (data.length <= 0) { //Avoir arquero error on empty data
10
+ return [];
11
+ }
9
12
  return (t.select(labelField, valueField)
10
13
  .dedupe(valueField)
11
14
  .objects()
@@ -3,13 +3,15 @@ type ExtendedSelectProps = Omit<SelectProps<any>, 'options'> & {
3
3
  dataset?: string;
4
4
  options?: {
5
5
  label: string;
6
- value: string | number;
7
- }[] | string[] | number[];
8
- initial_value?: string | number;
6
+ value: string;
7
+ }[] | string[];
8
+ initial_value?: string;
9
9
  labelField?: string;
10
10
  valueField?: string;
11
11
  name?: string;
12
+ label?: string;
12
13
  arrows?: boolean;
14
+ reverse?: boolean;
13
15
  };
14
16
  export declare const Select: React.FC<ExtendedSelectProps>;
15
17
  export {};
@@ -5,13 +5,13 @@ import NextPrevSelect from '../NextPrevSelect/NextPrevSelect';
5
5
  import { list_to_options } from './Control';
6
6
  // TODO : a fusionner avec NextPrevSelect pour n'avoir qu'un seul composant
7
7
  // Actuellement, Select apporte seulement le fait de choisir les valeurs depuis un
8
- export const Select = ({ name, dataset: datasetSource, options: input_options = [], labelField = 'label', valueField = 'value', arrows = false, initial_value: initial_value_in, ...rest }) => {
8
+ export const Select = ({ name, dataset: datasetSource, options: input_options = [], labelField = 'label', valueField = 'value', arrows = false, reverse = false, label, initial_value: initial_value_in, ...rest }) => {
9
9
  const options = list_to_options(input_options);
10
10
  const data = useDataset(datasetSource)?.data;
11
11
  const data_options = datasetSource ? (data && buildOptionsFromData(data, labelField, valueField)) : options;
12
12
  const myOptions = data_options && data_options.map((o) => {
13
- if (typeof o == "string" || typeof o == "number") {
14
- return { label: o, value: o };
13
+ if (typeof o == "string") {
14
+ return { label: o, value: String(o) };
15
15
  }
16
16
  return o;
17
17
  });
@@ -22,5 +22,5 @@ export const Select = ({ name, dataset: datasetSource, options: input_options =
22
22
  const value = initial_value == null || initial_value == false
23
23
  ? undefined
24
24
  : initial_value;
25
- return (_jsx(NextPrevSelect, { name: name, options: data_options, defaultValue: value, value: value, arrows: arrows, optionFilterProp: "label", ...rest }));
25
+ return (_jsx(NextPrevSelect, { name: name, label: label ?? name, options: data_options, defaultValue: value, value: value, arrows: arrows, reverse: reverse, optionFilterProp: "label", ...rest }));
26
26
  };
@@ -1,17 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Card, Dropdown, theme } from "antd";
2
+ import { Card, Dropdown, Spin, theme } from "antd";
3
3
  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
7
  import { ErrorBoundary } from "../Layout/Error";
8
8
  import { cardStyles } from "../../utils/cardStyles";
9
+ import { useDataset } from "../../dsl";
9
10
  const { useToken } = theme;
10
11
  export const ChartBlockContext = createContext(undefined);
11
12
  export const DSL_ChartBlock = ({ children }) => {
12
13
  const id = useId();
13
14
  const [config, setConfig] = useState({});
14
15
  const { token } = useToken();
16
+ const dataset = useDataset(children.props.dataset);
15
17
  const menu_items = [
16
18
  {
17
19
  key: "export_data_csv",
@@ -38,7 +40,7 @@ export const DSL_ChartBlock = ({ children }) => {
38
40
  };
39
41
  DL();
40
42
  };
41
- return (_jsx(ChartBlockContext.Provider, { value: { config: config, setConfig: (e) => setConfig(e) }, children: _jsx(Card, { className: "dashboard-element", style: { height: '100%' }, styles: cardStyles, extra: has_action && dropdown_toolbox, title: config.title, children: _jsxs(ErrorBoundary, { children: [children, _jsx(ProducersFooter, { component: children })] }) }) }));
43
+ return (_jsx(ChartBlockContext.Provider, { value: { config: config, setConfig: (e) => setConfig(e) }, children: _jsx(Card, { className: "dashboard-element", style: { height: '100%' }, styles: cardStyles, extra: has_action && dropdown_toolbox, title: config.title, children: _jsx(ErrorBoundary, { children: _jsxs(Spin, { spinning: dataset?.isFetching || false, size: "large", delay: 250, children: [children, _jsx(ProducersFooter, { component: children })] }) }) }) }));
42
44
  };
43
45
  export const useBlockConfig = ({ title, dataExport }) => {
44
46
  const blockContext = useContext(ChartBlockContext);
@@ -0,0 +1,12 @@
1
+ import { ReactElement } from "react";
2
+ interface IntroProps {
3
+ children: ReactElement;
4
+ /** Titre de la modal */
5
+ title?: string;
6
+ }
7
+ /**
8
+ * Texte introductif optionnel
9
+ * Modal accessible via un bouton flottant dans le coin supérieur droit
10
+ */
11
+ export declare const Intro: React.FC<IntroProps>;
12
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Icon } from "@iconify/react";
3
+ import { FloatButton, Modal } from "antd";
4
+ import { useState } from "react";
5
+ /**
6
+ * Texte introductif optionnel
7
+ * Modal accessible via un bouton flottant dans le coin supérieur droit
8
+ */
9
+ export const Intro = ({ children, title }) => {
10
+ const [isModalOpen, setIsModalOpen] = useState(false);
11
+ const showModal = () => {
12
+ setIsModalOpen(true);
13
+ };
14
+ const handleOk = () => {
15
+ setIsModalOpen(false);
16
+ };
17
+ const handleCancel = () => {
18
+ setIsModalOpen(false);
19
+ };
20
+ return (_jsxs(_Fragment, { children: [_jsx(FloatButton, { type: "primary", onClick: showModal, className: "IntroButton", style: { top: 5, height: 25 }, icon: _jsx(Icon, { icon: "fontisto:info" }), shape: "square" }), _jsx(Modal, { title: title, open: isModalOpen, onOk: handleOk, onCancel: handleCancel, footer: null, width: { xs: '100%', xl: '80%', xxl: '80%' }, children: children })] }));
21
+ };
@@ -17,14 +17,6 @@ interface IDashboardPageProps {
17
17
  }
18
18
  declare const DashboardPage: React.FC<IDashboardPageProps>;
19
19
  export default DashboardPage;
20
- type ControlContextType = {
21
- values: Record<string, any>;
22
- pushValue: (control: {
23
- name: string;
24
- value: any;
25
- }) => void;
26
- };
27
- export declare const ControlContext: React.Context<ControlContextType | undefined>;
28
20
  interface IDSLDashboardPageProps {
29
21
  children: React.ReactNode;
30
22
  name?: string;
@@ -1,14 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Button, Col, Dropdown, Flex, Grid, Layout, Radio, Row, Tabs, theme } from "antd";
3
- import React, { isValidElement, useState, createContext, useEffect } from "react";
3
+ import React, { isValidElement, useState, useEffect, useContext } from "react";
4
4
  import { Helmet } from "react-helmet-async";
5
5
  import { useSearchParamsState } from "../../utils/useSearchParamsState";
6
- import Control, { DSL_Control } from "../Control/Control";
6
+ import Control, { ControlContext, DSL_Control } from "../Control/Control";
7
7
  import { Dataset, Debug, Provider } from "../../dsl";
8
8
  import { DEFAULT_PALETTE, Palette, PaletteContext } from "../Palette/Palette";
9
9
  import { Section } from "./Section";
10
10
  import { Icon } from "@iconify/react";
11
- import { useDatasetRegistry } from "../Dataset/hooks";
11
+ import { DatasetRegistryContext } from "../Dataset/context";
12
+ import { Intro } from "./Intro";
12
13
  const { Header } = Layout;
13
14
  const { useToken } = theme;
14
15
  const getSection = (child) => React.isValidElement(child) ? child.props.section : undefined;
@@ -40,14 +41,15 @@ const DashboardPage = ({ children: children_input, control, row_gutter = [8, 8],
40
41
  })), value: activeTab, onChange: (e) => setActiveTab(e.target.value) }), control] }) }), _jsx(Row, { gutter: row_gutter, style: { margin: 16 }, children: children.map((child, idx) => ({ child, idx })).filter(({ child }) => (getSection(child) ?? 'Autres') == activeTab).map(({ child, idx }) => _jsx(Col, { xl: 12, xs: 24, children: child }, idx)) })] }));
41
42
  };
42
43
  export default DashboardPage;
43
- export const ControlContext = createContext(undefined);
44
44
  export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, children, debug = false }) => {
45
45
  const { token } = useToken();
46
46
  const [palette, setPalette] = useState(DEFAULT_PALETTE);
47
- const datasetRegistry = useDatasetRegistry();
47
+ const datasetRegistry = useContext(DatasetRegistryContext);
48
+ const controlesRegistry = useContext(ControlContext);
48
49
  useEffect(() => {
49
50
  return () => {
50
51
  datasetRegistry.clear();
52
+ controlesRegistry?.clear();
51
53
  };
52
54
  }, []);
53
55
  //const allDatasetLoaded = Object.values(datasets).every(d => !d.isFetching);
@@ -58,6 +60,9 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
58
60
  if (typeof (c.type) != 'string' && logicalComponents.includes(c.type.name)) {
59
61
  return "logical";
60
62
  }
63
+ else if (typeof (c.type) != 'string' && c.type.name == Intro.name) {
64
+ return "intro";
65
+ }
61
66
  else if (typeof (c.type) != 'string' && c.type.name == DSL_Control.name) {
62
67
  return "control";
63
68
  }
@@ -72,6 +77,7 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
72
77
  const logic_components = childrenArray.filter((c) => getComponentKind(c) == 'logical');
73
78
  const control_components = childrenArray.filter((c) => getComponentKind(c) == 'control');
74
79
  const section_components = childrenArray.filter((c) => getComponentKind(c) == 'section');
80
+ const intro_component = childrenArray.find((c) => getComponentKind(c) == 'intro');
75
81
  if (debug && !logic_components.some((c) => typeof c.type !== "string" && c.type.name === Debug.name)) {
76
82
  logic_components.push(_jsx(Debug, {}, "debug_property"));
77
83
  }
@@ -103,5 +109,5 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
103
109
  borderRadius: token.borderRadiusLG }, style: { margin: 4 } })
104
110
  :
105
111
  _jsxs("div", { style: { margin: 4 }, children: [" ", items?.[0].children, " "] }) //Show content without tabs if only one
106
- , logic_components] })] }));
112
+ , logic_components, intro_component] })] }));
107
113
  };