@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.
- package/README.MD +22 -19
- package/dist/components/Charts/ChartEcharts.d.ts +6 -2
- package/dist/components/Charts/ChartEcharts.js +1 -2
- package/dist/components/Charts/Pie.d.ts +21 -3
- package/dist/components/Charts/Pie.js +22 -3
- package/dist/components/Charts/Statistics.d.ts +9 -7
- package/dist/components/Charts/Statistics.js +12 -10
- package/dist/components/Charts/YearSerie.d.ts +1 -2
- package/dist/components/Charts/YearSerie.js +1 -0
- package/dist/components/Control/Control.d.ts +21 -5
- package/dist/components/Control/Control.js +28 -6
- package/dist/components/Control/Radio.d.ts +4 -4
- package/dist/components/Control/Radio.js +3 -0
- package/dist/components/Control/Select.d.ts +5 -3
- package/dist/components/Control/Select.js +4 -4
- package/dist/components/DashboardPage/Block.js +4 -2
- package/dist/components/DashboardPage/Intro.d.ts +12 -0
- package/dist/components/DashboardPage/Intro.js +21 -0
- package/dist/components/DashboardPage/Page.d.ts +0 -8
- package/dist/components/DashboardPage/Page.js +12 -6
- package/dist/components/Dataset/Dataset.js +2 -4
- package/dist/components/Dataset/Producer.js +2 -2
- package/dist/components/Dataset/Transform.d.ts +13 -3
- package/dist/components/Dataset/Transform.js +5 -4
- package/dist/components/Dataset/hooks.d.ts +5 -1
- package/dist/components/Dataset/hooks.js +26 -1
- package/dist/components/Debug/Debug.js +1 -1
- package/dist/components/Layout/DashboardApp.d.ts +4 -0
- package/dist/components/Layout/DashboardApp.js +5 -13
- package/dist/components/Layout/Footer.js +42 -4
- package/dist/components/Layout/Sider.d.ts +1 -0
- package/dist/components/Layout/Sider.js +20 -19
- package/dist/components/NextPrevSelect/NextPrevSelect.d.ts +7 -6
- package/dist/components/NextPrevSelect/NextPrevSelect.js +16 -12
- package/dist/data_providers/file/utils/axios.js +2 -15
- package/dist/dsl/index.d.ts +3 -2
- package/dist/dsl/index.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/aggregator.d.ts +18 -0
- package/dist/utils/aggregator.js +49 -0
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
+

|
|
13
10
|
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
## Installer nvm et Node.js
|
|
29
23
|
|
|
30
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
27
|
-
const
|
|
28
|
-
const
|
|
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
|
|
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: [
|
|
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[] |
|
|
11
|
-
label: string
|
|
12
|
-
value: string
|
|
10
|
+
export declare const list_to_options: (input?: string[] | {
|
|
11
|
+
label: string;
|
|
12
|
+
value: string;
|
|
13
13
|
}[]) => {
|
|
14
|
-
label: string
|
|
15
|
-
value: string
|
|
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
|
|
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"
|
|
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,
|
|
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
|
|
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
|
|
13
|
-
}[] | string[]
|
|
12
|
+
value: string;
|
|
13
|
+
}[] | string[];
|
|
14
14
|
labelField?: string;
|
|
15
15
|
valueField?: string;
|
|
16
|
-
initalValue?: string
|
|
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
|
|
7
|
-
}[] | string[]
|
|
8
|
-
initial_value?: string
|
|
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"
|
|
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:
|
|
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,
|
|
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 {
|
|
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 =
|
|
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
|
};
|