@geo2france/api-dashboard 1.17.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 +10 -8
- package/dist/components/Charts/YearSerie.d.ts +1 -2
- 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.js +6 -1
- package/dist/components/Dataset/hooks.js +21 -0
- package/dist/components/Debug/Debug.js +1 -1
- package/dist/components/Layout/DashboardApp.d.ts +4 -0
- package/dist/components/Layout/DashboardApp.js +2 -2
- package/dist/components/Layout/Sider.d.ts +1 -0
- package/dist/components/Layout/Sider.js +20 -19
- package/dist/data_providers/file/utils/axios.js +2 -15
- package/dist/dsl/index.d.ts +2 -1
- package/dist/dsl/index.js +2 -1
- 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,13 +22,12 @@ 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 value = row?.[dataKey]; // Dernière valeur du dataset. Caster en Number ?
|
|
28
|
+
const { row, value } = aggregator({ data: dataset?.data, dataKey, aggregate });
|
|
28
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
|
|
29
|
-
const evolution = relativeEvolution ? 100 * ((value - compare_value) / compare_value) : value - compare_value;
|
|
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, {}) });
|
|
@@ -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 {};
|
|
@@ -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
|
+
};
|
|
@@ -9,6 +9,7 @@ import { DEFAULT_PALETTE, Palette, PaletteContext } from "../Palette/Palette";
|
|
|
9
9
|
import { Section } from "./Section";
|
|
10
10
|
import { Icon } from "@iconify/react";
|
|
11
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;
|
|
@@ -59,6 +60,9 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
|
|
|
59
60
|
if (typeof (c.type) != 'string' && logicalComponents.includes(c.type.name)) {
|
|
60
61
|
return "logical";
|
|
61
62
|
}
|
|
63
|
+
else if (typeof (c.type) != 'string' && c.type.name == Intro.name) {
|
|
64
|
+
return "intro";
|
|
65
|
+
}
|
|
62
66
|
else if (typeof (c.type) != 'string' && c.type.name == DSL_Control.name) {
|
|
63
67
|
return "control";
|
|
64
68
|
}
|
|
@@ -73,6 +77,7 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
|
|
|
73
77
|
const logic_components = childrenArray.filter((c) => getComponentKind(c) == 'logical');
|
|
74
78
|
const control_components = childrenArray.filter((c) => getComponentKind(c) == 'control');
|
|
75
79
|
const section_components = childrenArray.filter((c) => getComponentKind(c) == 'section');
|
|
80
|
+
const intro_component = childrenArray.find((c) => getComponentKind(c) == 'intro');
|
|
76
81
|
if (debug && !logic_components.some((c) => typeof c.type !== "string" && c.type.name === Debug.name)) {
|
|
77
82
|
logic_components.push(_jsx(Debug, {}, "debug_property"));
|
|
78
83
|
}
|
|
@@ -104,5 +109,5 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
|
|
|
104
109
|
borderRadius: token.borderRadiusLG }, style: { margin: 4 } })
|
|
105
110
|
:
|
|
106
111
|
_jsxs("div", { style: { margin: 4 }, children: [" ", items?.[0].children, " "] }) //Show content without tabs if only one
|
|
107
|
-
, logic_components] })] }));
|
|
112
|
+
, logic_components, intro_component] })] }));
|
|
108
113
|
};
|
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import { useCallback, useContext, useState } from "react";
|
|
2
2
|
import { DatasetRegistryContext } from "./context";
|
|
3
|
+
const demo_dataset = {
|
|
4
|
+
id: 'demo_dataset',
|
|
5
|
+
isError: false,
|
|
6
|
+
isFetching: false,
|
|
7
|
+
resource: 'demo_dataset',
|
|
8
|
+
data: [
|
|
9
|
+
{ name: 'Mercure', type: 'Planète', satellites: 0, diameter_km: 4879 },
|
|
10
|
+
{ name: 'Vénus', type: 'Planète', satellites: 0, diameter_km: 12104 },
|
|
11
|
+
{ name: 'Terre', type: 'Planète', satellites: 1, diameter_km: 12742 },
|
|
12
|
+
{ name: 'Mars', type: 'Planète', satellites: 2, diameter_km: 6779 },
|
|
13
|
+
{ name: 'Jupiter', type: 'Planète', satellites: 79, diameter_km: 139820 },
|
|
14
|
+
{ name: 'Saturne', type: 'Planète', satellites: 83, diameter_km: 116460 },
|
|
15
|
+
{ name: 'Uranus', type: 'Planète', satellites: 27, diameter_km: 50724 },
|
|
16
|
+
{ name: 'Neptune', type: 'Planète', satellites: 14, diameter_km: 49244 },
|
|
17
|
+
{ name: 'Pluton', type: 'Planète naine', satellites: 5, diameter_km: 2370 },
|
|
18
|
+
{ name: 'Cérès', type: 'Astéroïde', satellites: 0, diameter_km: 946 },
|
|
19
|
+
]
|
|
20
|
+
};
|
|
3
21
|
// 🔹 Hook pour récupérer un dataset unique
|
|
4
22
|
export const useDataset = (dataset_id) => {
|
|
23
|
+
if (dataset_id == 'demo_dataset') {
|
|
24
|
+
return demo_dataset;
|
|
25
|
+
}
|
|
5
26
|
const datasetRegistry = useContext(DatasetRegistryContext);
|
|
6
27
|
if (dataset_id) {
|
|
7
28
|
return datasetRegistry.get(dataset_id);
|
|
@@ -20,5 +20,5 @@ export const Debug = () => {
|
|
|
20
20
|
label: _jsxs("span", { children: [_jsx(DatasetBadgeStatus, { isError: dataset?.isError, isFetching: dataset?.isFetching }), " ", dataset.id, " ", " ", _jsxs(Text, { type: "secondary", children: [" ", dataset?.resource, " "] }), " ", _jsx(Badge, { color: token.colorInfo, overflowCount: 9999, count: dataset?.data?.length })] }),
|
|
21
21
|
children: _jsx(DataPreview, { dataset: dataset.id, pageSize: 3 })
|
|
22
22
|
}));
|
|
23
|
-
return (_jsxs(_Fragment, { children: [_jsx(FloatButton, { icon: _jsx(BugOutlined, {}), type: "primary", onClick: () => setIsModalOpen(true), style: {
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx(FloatButton, { icon: _jsx(BugOutlined, {}), type: "primary", onClick: () => setIsModalOpen(true), style: { bottom: 8, left: 8 }, className: "debugFloatButton" }), _jsxs(Modal, { title: "Information concepteur", width: "90%", centered: true, styles: { body: { 'width': "100%", padding: 36 } }, closable: { 'aria-label': 'Custom Close Button' }, open: isModalOpen, onCancel: () => setIsModalOpen(false), footer: null, children: [_jsx(Title, { level: 5, children: "Jeux de donn\u00E9es " }), _jsx(Collapse, { accordion: true, items: items }), _jsx(Divider, {}), _jsx(Title, { level: 5, children: "Contr\u00F4les utilisateur " }), _jsx(ControlPreview, {}), _jsx(Divider, {}), _jsx(Title, { level: 5, children: "Palette " }), _jsx(PalettePreview, {})] })] }));
|
|
24
24
|
};
|
|
@@ -35,6 +35,10 @@ export interface DashboardConfig {
|
|
|
35
35
|
* Active ou désactive le mode “slider” dans le pied de page (faire défiler les logos de partenaires).
|
|
36
36
|
*/
|
|
37
37
|
footerSlider?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Désactiver la mention à Géo2France
|
|
40
|
+
*/
|
|
41
|
+
disablePoweredBy?: boolean;
|
|
38
42
|
}
|
|
39
43
|
declare const DashboardApp: React.FC<DashboardConfig>;
|
|
40
44
|
export default DashboardApp;
|
|
@@ -33,8 +33,8 @@ const default_theme = {
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
export const AppContext = createContext({});
|
|
36
|
-
const DashboardApp = ({ routes, theme, logo, brands, footerSlider, title, subtitle }) => {
|
|
36
|
+
const DashboardApp = ({ routes, theme, logo, brands, footerSlider, title, subtitle, disablePoweredBy = false }) => {
|
|
37
37
|
const context_values = { title, subtitle, logo };
|
|
38
|
-
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ConfigProvider, { theme: theme || default_theme /* Merger plutôt ?*/, children: _jsx(HelmetProvider, { children: _jsx(AppContext.Provider, { value: context_values, children: _jsx(DatasetRegistryContext.Provider, { value: createDatasetRegistry(), children: _jsx(ControlContext.Provider, { value: CreateControlesRegistry(), children: _jsx(HashRouter, { children: _jsx(Routes, { children: _jsxs(Route, { element: _jsxs(Layout, { hasSider: true, style: { minHeight: '100vh' }, children: [_jsx(DashboardSider, { route_config: routes }), _jsxs(Layout, { children: [_jsx(Content, { style: { width: "100%" }, children: _jsx(Outlet, {}) }), _jsx(DasbhoardFooter, { brands: brands, slider: footerSlider })] })] }), children: [generateRoutes(routes), _jsx(Route, { path: "*", element: _jsx(ErrorComponent, {}) })] }) }) }) }) }) }) }) }) }));
|
|
38
|
+
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ConfigProvider, { theme: theme || default_theme /* Merger plutôt ?*/, children: _jsx(HelmetProvider, { children: _jsx(AppContext.Provider, { value: context_values, children: _jsx(DatasetRegistryContext.Provider, { value: createDatasetRegistry(), children: _jsx(ControlContext.Provider, { value: CreateControlesRegistry(), children: _jsx(HashRouter, { children: _jsx(Routes, { children: _jsxs(Route, { element: _jsxs(Layout, { hasSider: true, style: { minHeight: '100vh' }, children: [_jsx(DashboardSider, { route_config: routes, poweredBy: !disablePoweredBy }), _jsxs(Layout, { children: [_jsx(Content, { style: { width: "100%" }, children: _jsx(Outlet, {}) }), _jsx(DasbhoardFooter, { brands: brands, slider: footerSlider })] })] }), children: [generateRoutes(routes), _jsx(Route, { path: "*", element: _jsx(ErrorComponent, {}) })] }) }) }) }) }) }) }) }) }));
|
|
39
39
|
};
|
|
40
40
|
export default DashboardApp;
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useContext, useState } from "react";
|
|
3
|
-
import { Layout, Menu, theme, Row, Col, Button, Divider } from "antd";
|
|
4
|
-
import { NavLink, useLocation } from "react-router-dom";
|
|
3
|
+
import { Layout, Menu, theme, Row, Col, Button, Divider, Typography } from "antd";
|
|
4
|
+
import { Link, NavLink, useLocation } from "react-router-dom";
|
|
5
5
|
import { generateMenuItems } from "../../utils/route_utils";
|
|
6
6
|
import { AppContext } from "./DashboardApp";
|
|
7
7
|
import { Icon } from "@iconify/react";
|
|
8
|
+
const { Text } = Typography;
|
|
8
9
|
const style_img = {
|
|
9
10
|
height: 52,
|
|
10
11
|
maxWidth: "100%",
|
|
11
12
|
objectFit: "contain"
|
|
12
13
|
};
|
|
13
|
-
const DashboardSider = ({ style, logo, route_config }) => {
|
|
14
|
+
const DashboardSider = ({ style, logo, route_config, poweredBy = true }) => {
|
|
14
15
|
const { logo: appLogo, title } = useContext(AppContext);
|
|
15
16
|
const { token } = theme.useToken();
|
|
16
17
|
const { pathname: selectedKey } = useLocation();
|
|
@@ -32,21 +33,21 @@ const DashboardSider = ({ style, logo, route_config }) => {
|
|
|
32
33
|
borderRight: "1px solid #ccc",
|
|
33
34
|
...style
|
|
34
35
|
};
|
|
35
|
-
return (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
return (_jsxs(Layout.Sider, { theme: "light", collapsible: true, collapsedWidth: isMobile ? 40 : 80, collapsed: collapsed, onCollapse: toggleCollapsed, style: siderStyle, width: isMobile ? '80%' : 220, trigger: null, children: [_jsxs(Row, { justify: "center", children: [_jsx(Col, { span: 24, children: _jsxs("div", { style: {
|
|
37
|
+
margin: 4,
|
|
38
|
+
display: "flex",
|
|
39
|
+
justifyContent: "center",
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
backgroundColor: token.colorBgElevated,
|
|
42
|
+
}, children: [_jsx(NavLink, { to: "", style: {
|
|
43
|
+
display: collapsed ? 'none' : undefined,
|
|
44
|
+
marginTop: 8, marginLeft: 8
|
|
45
|
+
}, children: _jsx("img", { style: style_img, src: appLogo, alt: title }) }), _jsx(Divider, { style: { display: collapsed ? 'none' : undefined }, type: "vertical" }), _jsx(Button, { type: "text", onClick: () => setCollapsed(!collapsed), icon: collapsed ? _jsx(Icon, { icon: "material-symbols:keyboard-double-arrow-right-rounded" }) : _jsx(Icon, { icon: "material-symbols:keyboard-double-arrow-left-rounded" }), style: {
|
|
46
|
+
fontSize: '28px',
|
|
47
|
+
width: 32,
|
|
48
|
+
height: 32,
|
|
49
|
+
//backgroundColor: token.colorFillSecondary,
|
|
50
|
+
marginTop: 8
|
|
51
|
+
} })] }) }), _jsx(Col, { span: 24, children: _jsx(Menu, { items: route_config && generateMenuItems(route_config), selectedKeys: [selectedKey], mode: "inline", style: { marginTop: "20px", width: "100%" } }) })] }), (poweredBy && !collapsed) && _jsxs(Text, { type: "secondary", style: { position: "absolute", bottom: 0, left: 0, margin: 4 }, children: ["Propuls\u00E9 et fait avec \u2764\uFE0F par", " ", _jsx(Link, { to: "https://github.com/geo2france/api-dashboard", children: "G\u00E9o2France" })] })] }));
|
|
51
52
|
};
|
|
52
53
|
export default DashboardSider;
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
const axiosInstance = axios.create();
|
|
3
|
-
axiosInstance.interceptors.response.use(
|
|
4
|
-
|
|
5
|
-
// Une réponse est présumée valide si elle renvoie un objet json valide. (Sinon c'est un xml)
|
|
6
|
-
// TODO parser le XML retourné en cas d'erreur
|
|
7
|
-
(response) => {
|
|
8
|
-
if (typeof response.data === "object") {
|
|
9
|
-
return response;
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
const customError = {
|
|
13
|
-
message: response.data,
|
|
14
|
-
statusCode: response.status,
|
|
15
|
-
};
|
|
16
|
-
return Promise.reject(customError);
|
|
17
|
-
}
|
|
3
|
+
axiosInstance.interceptors.response.use((response) => {
|
|
4
|
+
return response;
|
|
18
5
|
}, (error) => {
|
|
19
6
|
const customError = {
|
|
20
7
|
...error,
|
package/dist/dsl/index.d.ts
CHANGED
|
@@ -21,4 +21,5 @@ import { Statistics, StatisticsCollection } from "../components/Charts/Statistic
|
|
|
21
21
|
import { MapLayer, Map } from "../components/Map/Map";
|
|
22
22
|
import { Section } from "../components/DashboardPage/Section";
|
|
23
23
|
import { LegendControl } from "../components/MapLegend/MapLegend";
|
|
24
|
-
|
|
24
|
+
import { Intro } from "../components/DashboardPage/Intro";
|
|
25
|
+
export { Dashboard, Dataset, Provider, Transform, Join, Filter, Section, Intro, DataPreview, ChartEcharts, ChartPie, ChartYearSerie, Statistics, StatisticsCollection, useDataset, useDatasets, useAllDatasets, useBlockConfig, Producer, Control, useControl, useAllControls, Radio, Select, Input, Palette, usePalette, usePaletteLabels, PalettePreview, Debug, Map, MapLayer, LegendControl };
|
package/dist/dsl/index.js
CHANGED
|
@@ -21,4 +21,5 @@ import { Statistics, StatisticsCollection } from "../components/Charts/Statistic
|
|
|
21
21
|
import { MapLayer, Map } from "../components/Map/Map";
|
|
22
22
|
import { Section } from "../components/DashboardPage/Section";
|
|
23
23
|
import { LegendControl } from "../components/MapLegend/MapLegend";
|
|
24
|
-
|
|
24
|
+
import { Intro } from "../components/DashboardPage/Intro";
|
|
25
|
+
export { Dashboard, Dataset, Provider, Transform, Join, Filter, Section, Intro, DataPreview, ChartEcharts, ChartPie, ChartYearSerie, Statistics, StatisticsCollection, useDataset, useDatasets, useAllDatasets, useBlockConfig, Producer, Control, useControl, useAllControls, Radio, Select, Input, Palette, usePalette, usePaletteLabels, PalettePreview, Debug, Map, MapLayer, LegendControl };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { useMapControl } from "./utils/useMapControl";
|
|
|
7
7
|
export { BaseRecordToGeojsonPoint } from "./utils/baserecordtogeojsonpoint";
|
|
8
8
|
export { cardStyles } from "./utils/cardStyles";
|
|
9
9
|
export { merge_others } from "./utils/merge_others";
|
|
10
|
+
export { aggregator } from "./utils/aggregator";
|
|
10
11
|
import KeyFigure from "./components/KeyFigure/KeyFigure";
|
|
11
12
|
import LoadingContainer from "./components/LoadingContainer/LoadingContainer";
|
|
12
13
|
import FlipCard from "./components/FlipCard/FlipCard";
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { useMapControl } from "./utils/useMapControl";
|
|
|
9
9
|
export { BaseRecordToGeojsonPoint } from "./utils/baserecordtogeojsonpoint";
|
|
10
10
|
export { cardStyles } from "./utils/cardStyles";
|
|
11
11
|
export { merge_others } from "./utils/merge_others";
|
|
12
|
+
export { aggregator } from "./utils/aggregator";
|
|
12
13
|
// Components
|
|
13
14
|
import KeyFigure from "./components/KeyFigure/KeyFigure";
|
|
14
15
|
import LoadingContainer from "./components/LoadingContainer/LoadingContainer";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SimpleRecord } from "../types";
|
|
2
|
+
interface AggregatorParams {
|
|
3
|
+
/** Tableau de données */
|
|
4
|
+
data?: SimpleRecord[];
|
|
5
|
+
/** Colonne à aggréger */
|
|
6
|
+
dataKey?: string;
|
|
7
|
+
/** Agregat */
|
|
8
|
+
aggregate: "last" | "first" | "sum" | "lastNotNull" | "min" | "max" | "count" | "mean" | "countDistinct" | "countMissing";
|
|
9
|
+
}
|
|
10
|
+
interface AggregatorResult {
|
|
11
|
+
/** Ligne retenue (pour "last", "first", "lastNotNull") */
|
|
12
|
+
row?: SimpleRecord;
|
|
13
|
+
/** Valeur agrégée */
|
|
14
|
+
value?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Fonction permettant d'agréger une colonne d'un dataset */
|
|
17
|
+
export declare const aggregator: ({ data, dataKey, aggregate }: AggregatorParams) => AggregatorResult;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { from, op } from "arquero";
|
|
2
|
+
/** Fonction permettant d'agréger une colonne d'un dataset */
|
|
3
|
+
export const aggregator = ({ data, dataKey, aggregate }) => {
|
|
4
|
+
if (data == undefined || dataKey == undefined || data.length < 1) {
|
|
5
|
+
return { row: undefined, value: undefined };
|
|
6
|
+
}
|
|
7
|
+
switch (aggregate) {
|
|
8
|
+
case "last": {
|
|
9
|
+
const row = data.slice(-1)[0];
|
|
10
|
+
return { row, value: Number(row[dataKey]) };
|
|
11
|
+
}
|
|
12
|
+
case "first": {
|
|
13
|
+
const row = data[0];
|
|
14
|
+
return { row, value: Number(row[dataKey]) };
|
|
15
|
+
}
|
|
16
|
+
case "lastNotNull": {
|
|
17
|
+
const row = data.filter(r => r[dataKey] != null).slice(-1)?.[0];
|
|
18
|
+
return { row, value: Number(row?.[dataKey]) };
|
|
19
|
+
}
|
|
20
|
+
case "sum": {
|
|
21
|
+
const value = from(data).rollup({ value: op.sum(dataKey) }).object().value;
|
|
22
|
+
return { row: undefined, value };
|
|
23
|
+
}
|
|
24
|
+
case "min": {
|
|
25
|
+
const value = from(data).rollup({ value: op.min(dataKey) }).object().value;
|
|
26
|
+
return { row: undefined, value };
|
|
27
|
+
}
|
|
28
|
+
case "max": {
|
|
29
|
+
const value = from(data).rollup({ value: op.max(dataKey) }).object().value;
|
|
30
|
+
return { row: undefined, value };
|
|
31
|
+
}
|
|
32
|
+
case "count": {
|
|
33
|
+
const value = from(data).rollup({ value: op.valid(dataKey) }).object().value;
|
|
34
|
+
return { row: undefined, value };
|
|
35
|
+
}
|
|
36
|
+
case "mean": {
|
|
37
|
+
const value = from(data).rollup({ value: op.average(dataKey) }).object().value;
|
|
38
|
+
return { row: undefined, value };
|
|
39
|
+
}
|
|
40
|
+
case "countDistinct": {
|
|
41
|
+
const value = from(data).rollup({ value: op.distinct(dataKey) }).object().value;
|
|
42
|
+
return { row: undefined, value };
|
|
43
|
+
}
|
|
44
|
+
case "countMissing": {
|
|
45
|
+
const value = from(data).rollup({ value: op.invalid(dataKey) }).object().value;
|
|
46
|
+
return { row: undefined, value };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geo2france/api-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build dashboards with JSX/TSX",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"tsc": "tsc",
|
|
45
|
-
"test": "jest --watchAll"
|
|
45
|
+
"test": "jest --watchAll",
|
|
46
|
+
"storybook": "storybook dev -p 6006",
|
|
47
|
+
"build-storybook": "storybook build"
|
|
46
48
|
},
|
|
47
49
|
"dependencies": {
|
|
48
50
|
"@ant-design/react-slick": "^1.1.2",
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
"echarts": "^6.0.0",
|
|
58
60
|
"echarts-for-react": "^3.0.4",
|
|
59
61
|
"query-string": "~7.1.3",
|
|
62
|
+
"react-countup": "^6.5.3",
|
|
60
63
|
"react-error-boundary": "^6.0.0",
|
|
61
64
|
"react-helmet": "^6.1.0",
|
|
62
65
|
"react-helmet-async": "^2.0.5",
|
|
@@ -65,7 +68,13 @@
|
|
|
65
68
|
},
|
|
66
69
|
"devDependencies": {
|
|
67
70
|
"@ant-design/icons": "^6.0.2",
|
|
71
|
+
"@chromatic-com/storybook": "^5.0.0",
|
|
68
72
|
"@iconify/json": "^2.2.382",
|
|
73
|
+
"@storybook/addon-a11y": "^10.2.1",
|
|
74
|
+
"@storybook/addon-docs": "^10.2.1",
|
|
75
|
+
"@storybook/addon-onboarding": "^10.2.1",
|
|
76
|
+
"@storybook/addon-vitest": "^10.2.1",
|
|
77
|
+
"@storybook/react-vite": "^10.2.1",
|
|
69
78
|
"@tanstack/react-query": "^5.51.11",
|
|
70
79
|
"@testing-library/dom": "^10.4.0",
|
|
71
80
|
"@testing-library/jest-dom": "^6.5.0",
|
|
@@ -74,36 +83,41 @@
|
|
|
74
83
|
"@types/chroma-js": "^3.1.1",
|
|
75
84
|
"@types/geojson": "^7946.0.14",
|
|
76
85
|
"@types/jest": "^29.5.13",
|
|
77
|
-
"@types/node": "^
|
|
78
|
-
"@types/react": "^18.3.
|
|
79
|
-
"@types/react-dom": "^18.3.
|
|
86
|
+
"@types/node": "^25.1.0",
|
|
87
|
+
"@types/react": "^18.3.1",
|
|
88
|
+
"@types/react-dom": "^18.3.1",
|
|
80
89
|
"@types/react-helmet-async": "^1.0.1",
|
|
81
90
|
"@types/react-icons": "^2.2.7",
|
|
82
91
|
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
|
83
92
|
"@typescript-eslint/parser": "^7.16.1",
|
|
84
93
|
"@vitejs/plugin-react": "^4.3.1",
|
|
85
|
-
"
|
|
94
|
+
"@vitest/browser-playwright": "^4.0.18",
|
|
95
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
96
|
+
"antd": "^6.2.2",
|
|
86
97
|
"jest": "^29.7.0",
|
|
87
98
|
"jest-environment-jsdom": "^29.7.0",
|
|
88
99
|
"maplibre-gl": "^4.7.1",
|
|
100
|
+
"playwright": "^1.58.0",
|
|
89
101
|
"react": "^18.3.1",
|
|
90
102
|
"react-map-gl": "^7.1.9",
|
|
91
|
-
"react-router-dom": "^
|
|
103
|
+
"react-router-dom": "^7.13.0",
|
|
104
|
+
"storybook": "^10.2.1",
|
|
92
105
|
"ts-jest": "^29.2.5",
|
|
93
106
|
"ts-node": "^10.9.2",
|
|
94
107
|
"tsup": "^8.5.0",
|
|
95
108
|
"typescript": "^5.5.2",
|
|
96
|
-
"vite": "^
|
|
97
|
-
"vite-plugin-svgr": "^4.2.0"
|
|
109
|
+
"vite": "^7.3.1",
|
|
110
|
+
"vite-plugin-svgr": "^4.2.0",
|
|
111
|
+
"vitest": "^4.0.18"
|
|
98
112
|
},
|
|
99
113
|
"peerDependencies": {
|
|
100
114
|
"@ant-design/icons": "^6.0.2",
|
|
101
115
|
"@tanstack/react-query": "^5.51.11",
|
|
102
|
-
"antd": "^
|
|
116
|
+
"antd": "^6.2.2",
|
|
103
117
|
"maplibre-gl": "^4.7.1",
|
|
104
118
|
"react": "^18.3.1",
|
|
105
119
|
"react-dom": "^18.3.1",
|
|
106
120
|
"react-map-gl": "^7.1.9",
|
|
107
|
-
"react-router-dom": "^
|
|
121
|
+
"react-router-dom": "^7.13.0"
|
|
108
122
|
}
|
|
109
123
|
}
|