@geo2france/api-dashboard 1.20.0 → 1.22.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/dist/components/Charts/ChartEvolution.d.ts +30 -0
- package/dist/components/Charts/ChartEvolution.js +71 -0
- package/dist/components/Charts/Statistics.d.ts +1 -1
- package/dist/components/Control/Control.d.ts +5 -4
- package/dist/components/Control/Control.js +2 -5
- package/dist/components/Control/Select.d.ts +3 -5
- package/dist/components/DashboardPage/Page.d.ts +9 -2
- package/dist/components/DashboardPage/Page.js +9 -4
- package/dist/components/DashboardPage/Section.d.ts +9 -4
- package/dist/components/DashboardPage/Section.js +3 -4
- package/dist/components/Layout/DashboardApp.d.ts +23 -2
- package/dist/components/Layout/DashboardApp.js +47 -5
- package/dist/dsl/index.d.ts +2 -1
- package/dist/dsl/index.js +2 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/utils/icon.d.ts +3 -0
- package/dist/utils/icon.js +6 -0
- package/dist/utils/route_utils.d.ts +6 -0
- package/dist/utils/route_utils.js +68 -12
- package/package.json +10 -8
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EChartsOption } from "echarts";
|
|
2
|
+
import { datasetInput } from "../Dataset/hooks";
|
|
3
|
+
export interface ChartEvolutionProps {
|
|
4
|
+
/** Identifiant du dataset */
|
|
5
|
+
dataset?: datasetInput;
|
|
6
|
+
/** Nom de la colonne qui contient les valeurs numériques */
|
|
7
|
+
valueKey: string;
|
|
8
|
+
/** Nom de la colonne qui contient les catégories */
|
|
9
|
+
nameKey?: string;
|
|
10
|
+
/** Colonne qui contient la date ou année */
|
|
11
|
+
timeKey: string;
|
|
12
|
+
/** Type de graphique */
|
|
13
|
+
chartType?: 'bar' | 'line' | 'area';
|
|
14
|
+
/** Unité à afficher */
|
|
15
|
+
unit?: string;
|
|
16
|
+
/** Titre du graphique */
|
|
17
|
+
title?: string;
|
|
18
|
+
/** Empiler les valeurs ? */
|
|
19
|
+
stack?: boolean;
|
|
20
|
+
/** Afficher un marquer sur la ligne de temps */
|
|
21
|
+
timeMarker?: number | string;
|
|
22
|
+
/** Options suplémentaires passées à Echarts
|
|
23
|
+
* https://echarts.apache.org/en/option.html
|
|
24
|
+
*/
|
|
25
|
+
option?: EChartsOption;
|
|
26
|
+
}
|
|
27
|
+
/** Composant de visualisation permettant de montrer l'évolution d'une valeur au cours du temps.
|
|
28
|
+
* La valeur peut-être décomposée en plusieurs catégories
|
|
29
|
+
*/
|
|
30
|
+
export declare const ChartEvolution: React.FC<ChartEvolutionProps>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { from, op } from "arquero";
|
|
3
|
+
import deepMerge from "../../utils/deepmerge";
|
|
4
|
+
import { useBlockConfig } from "../DashboardPage/Block";
|
|
5
|
+
import { ChartEcharts, useDataset } from "../../dsl";
|
|
6
|
+
/** Composant de visualisation permettant de montrer l'évolution d'une valeur au cours du temps.
|
|
7
|
+
* La valeur peut-être décomposée en plusieurs catégories
|
|
8
|
+
*/
|
|
9
|
+
export const ChartEvolution = ({ dataset: dataset_id, title, nameKey, timeKey, valueKey, chartType = 'bar', stack: stack_in, timeMarker, unit, option: custom_option = {} }) => {
|
|
10
|
+
const dataset = useDataset(dataset_id);
|
|
11
|
+
const data = dataset?.data;
|
|
12
|
+
useBlockConfig({
|
|
13
|
+
title: title,
|
|
14
|
+
dataExport: data
|
|
15
|
+
});
|
|
16
|
+
// Devnote : ajouter une propriété "timePrecision" ? (year, month, day, etc..)
|
|
17
|
+
// Ceci pourrait permettre aussi d'aggréger les données selon la précision souhaité
|
|
18
|
+
const first_time = data?.[0]?.[timeKey];
|
|
19
|
+
const yearMode = (typeof first_time === 'number' || isNaN(Number(first_time)) == false);
|
|
20
|
+
const stack = stack_in ?? (chartType !== 'line');
|
|
21
|
+
const chart_data1 = data && data.length > 0
|
|
22
|
+
? from(data.filter(e => e[valueKey]))
|
|
23
|
+
.groupby(nameKey ? [nameKey, timeKey] : timeKey)
|
|
24
|
+
.rollup({ value: op.sum(valueKey) })
|
|
25
|
+
.orderby(timeKey)
|
|
26
|
+
.objects()
|
|
27
|
+
.map((d) => ([
|
|
28
|
+
String(d[timeKey]),
|
|
29
|
+
d.value,
|
|
30
|
+
nameKey ? d[nameKey] : valueKey // Si pas de dimension "cat", on utilise le nom la colonne de valeur
|
|
31
|
+
]))
|
|
32
|
+
: [];
|
|
33
|
+
const cat = nameKey ?
|
|
34
|
+
data?.length ? from(data).select(nameKey).dedupe().array(nameKey) : undefined
|
|
35
|
+
: [valueKey]; // Nom de la série unique
|
|
36
|
+
const series = cat?.map((c, idx) => ({
|
|
37
|
+
type: chartType === "area" ? 'line' : chartType,
|
|
38
|
+
name: c,
|
|
39
|
+
data: chart_data1.filter(row => row[2] == c),
|
|
40
|
+
stack: stack ? 'total' : undefined,
|
|
41
|
+
connectNulls: true,
|
|
42
|
+
areaStyle: chartType === 'area' ? {} : undefined,
|
|
43
|
+
symbolSize: chartType === 'area' ? 2 : chartType === 'line' ? 4 : undefined,
|
|
44
|
+
markLine: idx === 0 && timeMarker ? {
|
|
45
|
+
symbol: 'none',
|
|
46
|
+
animation: false,
|
|
47
|
+
silent: true,
|
|
48
|
+
data: [
|
|
49
|
+
{ xAxis: String(timeMarker) }
|
|
50
|
+
]
|
|
51
|
+
} : undefined,
|
|
52
|
+
})) || [];
|
|
53
|
+
const option = {
|
|
54
|
+
legend: {
|
|
55
|
+
show: true,
|
|
56
|
+
},
|
|
57
|
+
tooltip: {
|
|
58
|
+
show: true,
|
|
59
|
+
trigger: 'axis',
|
|
60
|
+
axisPointer: {
|
|
61
|
+
// N'afficher que l'année (au lieu de la date du 1er janvier)
|
|
62
|
+
label: { formatter: yearMode ? (p) => String(new Date(p.value).getUTCFullYear()) ?? `` : undefined }
|
|
63
|
+
},
|
|
64
|
+
valueFormatter: (v) => `${v?.toLocaleString(undefined, { maximumFractionDigits: 0 })} ${unit ?? ''}`
|
|
65
|
+
},
|
|
66
|
+
yAxis: { show: true, type: 'value' },
|
|
67
|
+
xAxis: { show: true, type: 'time' },
|
|
68
|
+
series: series,
|
|
69
|
+
};
|
|
70
|
+
return _jsx(ChartEcharts, { notMerge: true, option: deepMerge(option, custom_option) });
|
|
71
|
+
};
|
|
@@ -31,7 +31,7 @@ export interface StatisticsProps {
|
|
|
31
31
|
/** Inverser les couleurs (rouge/vert) de l'évolution */
|
|
32
32
|
invertColor?: boolean;
|
|
33
33
|
/** Icône. Composant ou nom de l'icône sur https://icon-sets.iconify.design/ */
|
|
34
|
-
icon?:
|
|
34
|
+
icon?: React.ReactNode | string;
|
|
35
35
|
/** Texte à afficher dans le tooltip d'aide */
|
|
36
36
|
help?: string;
|
|
37
37
|
/** Comparer la valeur avec la précédente ou la première du jeu de données */
|
|
@@ -7,13 +7,14 @@ 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[] | {
|
|
10
|
+
export declare const list_to_options: (input?: string[] | number[] | {
|
|
11
11
|
label: string;
|
|
12
|
-
value: string;
|
|
13
|
-
}[]) => {
|
|
12
|
+
value: string | number;
|
|
13
|
+
}[]) => ({
|
|
14
14
|
label: string;
|
|
15
15
|
value: string;
|
|
16
|
-
}[];
|
|
16
|
+
}[]);
|
|
17
|
+
export type ListToOptionsInput = Parameters<typeof list_to_options>[0];
|
|
17
18
|
interface IControlProps {
|
|
18
19
|
children: ReactElement | ReactElement[];
|
|
19
20
|
}
|
|
@@ -44,14 +44,11 @@ export const useAllControls = () => {
|
|
|
44
44
|
};
|
|
45
45
|
/* Convenient function to return Options from list or Options */
|
|
46
46
|
export const list_to_options = (input = []) => {
|
|
47
|
-
if (input === undefined) {
|
|
48
|
-
return [];
|
|
49
|
-
}
|
|
50
47
|
return input.map((o) => {
|
|
51
|
-
if (typeof o == "string") {
|
|
48
|
+
if (typeof o == "string" || typeof o == "number") {
|
|
52
49
|
return { label: String(o), value: String(o) };
|
|
53
50
|
}
|
|
54
|
-
return o;
|
|
51
|
+
return { label: String(o.label), value: String(o.value) };
|
|
55
52
|
});
|
|
56
53
|
};
|
|
57
54
|
export const DSL_Control = ({ children }) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SelectProps } from 'antd';
|
|
2
|
+
import { ListToOptionsInput } from './Control';
|
|
2
3
|
type ExtendedSelectProps = Omit<SelectProps<any>, 'options'> & {
|
|
3
4
|
/** Nom technique du controle */
|
|
4
5
|
name?: string;
|
|
@@ -6,11 +7,8 @@ type ExtendedSelectProps = Omit<SelectProps<any>, 'options'> & {
|
|
|
6
7
|
label?: string;
|
|
7
8
|
/** Jeu de données contenant les choix (à la place de options) */
|
|
8
9
|
dataset?: string;
|
|
9
|
-
/** Choix
|
|
10
|
-
options?:
|
|
11
|
-
label: string;
|
|
12
|
-
value: string;
|
|
13
|
-
}[] | string[];
|
|
10
|
+
/** Choix possibles */
|
|
11
|
+
options?: ListToOptionsInput;
|
|
14
12
|
/** Valeur intiale sélectionnée */
|
|
15
13
|
initial_value?: string;
|
|
16
14
|
/** Colonne à utiliser pour le libellé (si dataset utilisé) */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RowProps } from "antd";
|
|
2
2
|
import DashboardElement from "../DashboardElement/DashboardElement";
|
|
3
|
-
import React from "react";
|
|
3
|
+
import React, { ReactNode } from "react";
|
|
4
4
|
import { Section } from "./Section";
|
|
5
5
|
type Section = {
|
|
6
6
|
key: string;
|
|
@@ -18,9 +18,16 @@ interface IDashboardPageProps {
|
|
|
18
18
|
declare const DashboardPage: React.FC<IDashboardPageProps>;
|
|
19
19
|
export default DashboardPage;
|
|
20
20
|
interface IDSLDashboardPageProps {
|
|
21
|
-
|
|
21
|
+
/** Composants visibles (dataviz, carto) ou logiques (dataset, palette) */
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
/** Nom de la page (titre). */
|
|
22
24
|
name?: string;
|
|
25
|
+
/** Nombre de colonnes. */
|
|
23
26
|
columns?: number;
|
|
27
|
+
/** Mode debug. Affiche un bouton permettant d'accéder aux informations développeur. */
|
|
24
28
|
debug?: boolean;
|
|
25
29
|
}
|
|
30
|
+
/** Une page de tableau de bord.
|
|
31
|
+
* Cette page peut contenir plusieurs `Section` ou directement le contenu.
|
|
32
|
+
*/
|
|
26
33
|
export declare const DSL_DashboardPage: React.FC<IDSLDashboardPageProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Button, Col, Dropdown, Flex, Grid, Layout, Radio, Row, Tabs, theme } from "antd";
|
|
2
|
+
import { Button, Col, Dropdown, Empty, Flex, Grid, Layout, Radio, Row, Tabs, theme } from "antd";
|
|
3
3
|
import React, { isValidElement, useState, useEffect, useContext } from "react";
|
|
4
4
|
import { Helmet } from "react-helmet-async";
|
|
5
5
|
import { useSearchParamsState } from "../../utils/useSearchParamsState";
|
|
@@ -41,6 +41,9 @@ const DashboardPage = ({ children: children_input, control, row_gutter = [8, 8],
|
|
|
41
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)) })] }));
|
|
42
42
|
};
|
|
43
43
|
export default DashboardPage;
|
|
44
|
+
/** Une page de tableau de bord.
|
|
45
|
+
* Cette page peut contenir plusieurs `Section` ou directement le contenu.
|
|
46
|
+
*/
|
|
44
47
|
export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, children, debug = false }) => {
|
|
45
48
|
const { token } = useToken();
|
|
46
49
|
const [palette, setPalette] = useState(DEFAULT_PALETTE);
|
|
@@ -91,7 +94,7 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
|
|
|
91
94
|
items.push({
|
|
92
95
|
key: "99 - Autres",
|
|
93
96
|
label: '-',
|
|
94
|
-
children: _jsx(Section, { title: 'Autres', children: visible_components })
|
|
97
|
+
children: _jsx(Section, { title: 'Autres', columns: columns, children: visible_components })
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
return (_jsxs(_Fragment, { children: [_jsx(Helmet, { children: _jsx("title", { children: name }) }), _jsxs(PaletteContext.Provider, { value: { palette, setPalette }, children: [control_components.length > 0 && _jsx(Header, { style: {
|
|
@@ -108,6 +111,8 @@ export const DSL_DashboardPage = ({ name = 'Tableau de bord', columns = 2, child
|
|
|
108
111
|
background: token.colorBgContainer,
|
|
109
112
|
borderRadius: token.borderRadiusLG }, style: { margin: 4 } })
|
|
110
113
|
:
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
items.length == 1 ?
|
|
115
|
+
_jsxs("div", { style: { margin: 4 }, children: [" ", items?.[0].children, " "] }) //Show content without tabs if only one
|
|
116
|
+
: //no visible content
|
|
117
|
+
_jsx(Flex, { align: "center", justify: "center", style: { height: "100%" }, children: _jsx(Empty, { description: "Ce tableau de bord est vide pour le moment.", image: _jsx(Icon, { icon: "solar:sleeping-circle-bold", height: 55, color: token.colorIcon }) }) }), logic_components, intro_component] })] }));
|
|
113
118
|
};
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FC, ReactNode } from "react";
|
|
2
2
|
export interface SectionProps {
|
|
3
|
+
/** Titre de la section, doit être unique */
|
|
3
4
|
title: string;
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
/** Composants visibles (dataviz, carto) ou logiques (dataset, palette) */
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
/** Icone de la section. Composant ou nom (iconify) de l'icon */
|
|
8
|
+
icon?: ReactNode | string;
|
|
9
|
+
/** Nombre de colonnes. */
|
|
10
|
+
columns?: number;
|
|
6
11
|
}
|
|
7
|
-
export declare const Section:
|
|
12
|
+
export declare const Section: FC<SectionProps>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Col, Row } from "antd";
|
|
3
|
-
import
|
|
3
|
+
import { Children, isValidElement } from "react";
|
|
4
4
|
import { DSL_ChartBlock } from "./Block";
|
|
5
|
-
export const Section = ({ children }) => {
|
|
6
|
-
const
|
|
7
|
-
const childrenArray = React.Children.toArray(children).filter(isValidElement);
|
|
5
|
+
export const Section = ({ children, columns = 2 }) => {
|
|
6
|
+
const childrenArray = Children.toArray(children).filter(isValidElement);
|
|
8
7
|
return (_jsx(Row, { gutter: [8, 8], style: { margin: 0 }, children: childrenArray.map((component, idx) => (_jsx(Col, { xl: 24 / columns, xs: 24, children: _jsx(DSL_ChartBlock, { children: component }) }, idx))) }));
|
|
9
8
|
};
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { ThemeConfig } from "antd";
|
|
2
2
|
import { Partner, RouteConfig } from "../../types";
|
|
3
|
+
import { ReactElement, ReactNode } from "react";
|
|
4
|
+
export declare const default_theme: ThemeConfig;
|
|
3
5
|
interface AppContextProps {
|
|
4
6
|
title?: string;
|
|
5
7
|
subtitle?: string;
|
|
6
8
|
logo?: string;
|
|
7
9
|
}
|
|
10
|
+
export interface PageProps {
|
|
11
|
+
title?: string;
|
|
12
|
+
/** Icône de la page.
|
|
13
|
+
* Composant ou nom (iconify) de l'icone */
|
|
14
|
+
icon?: ReactElement | string;
|
|
15
|
+
hidden?: boolean;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
8
18
|
export declare const AppContext: import("react").Context<AppContextProps>;
|
|
9
19
|
export interface DashboardConfig {
|
|
20
|
+
/** Pages de dashboard. La première page sera également l'index (homepage) */
|
|
21
|
+
children?: ReactElement<PageProps> | ReactElement<PageProps>[];
|
|
10
22
|
/**
|
|
11
23
|
* Titre principal du tableau de bord (affiché dans le header ou le titre de page).
|
|
12
24
|
*/
|
|
@@ -17,16 +29,18 @@ export interface DashboardConfig {
|
|
|
17
29
|
subtitle?: string;
|
|
18
30
|
/**
|
|
19
31
|
* Liste des routes de l'application (chaque route correspond à une page du tableau de bord).
|
|
32
|
+
* @deprecated since 1.22. Use DashboardApp childrens.
|
|
20
33
|
*/
|
|
21
|
-
routes
|
|
34
|
+
routes?: RouteConfig[];
|
|
22
35
|
/**
|
|
23
36
|
* Configuration du thème Ant Design (permet de personnaliser les couleurs, la typographie, etc.).
|
|
37
|
+
* Voir : https://ant.design/docs/react/customize-theme#theme
|
|
24
38
|
*/
|
|
25
39
|
theme?: ThemeConfig;
|
|
26
40
|
/**
|
|
27
41
|
* URL ou chemin du logo à afficher dans le tableau de bord.
|
|
28
42
|
*/
|
|
29
|
-
logo
|
|
43
|
+
logo?: string;
|
|
30
44
|
/**
|
|
31
45
|
* Liste optionnelle de partenaires ou marques à afficher dans le footer ou ailleurs.
|
|
32
46
|
*/
|
|
@@ -40,5 +54,12 @@ export interface DashboardConfig {
|
|
|
40
54
|
*/
|
|
41
55
|
disablePoweredBy?: boolean;
|
|
42
56
|
}
|
|
57
|
+
/** Composant principal de l'application.
|
|
58
|
+
*
|
|
59
|
+
* Les enfants de l'application sont les différentes pages de tableau de bord.
|
|
60
|
+
* La configuration globale de l'application (nom, style, etc.) se fait via les propriétés.
|
|
61
|
+
*/
|
|
43
62
|
declare const DashboardApp: React.FC<DashboardConfig>;
|
|
44
63
|
export default DashboardApp;
|
|
64
|
+
/** Regrouper des pages dans le menu */
|
|
65
|
+
export declare const PagesGroup: React.FC<PageProps>;
|
|
@@ -2,19 +2,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
3
|
import { ConfigProvider, Layout } from "antd";
|
|
4
4
|
import { HashRouter, Outlet, Route, Routes } from "react-router-dom";
|
|
5
|
-
import { generateRoutes } from "../../utils/route_utils";
|
|
5
|
+
//import { generateRoutes } from "../../utils/route_utils";
|
|
6
6
|
import DashboardSider from "./Sider";
|
|
7
7
|
import { Content } from "antd/es/layout/layout";
|
|
8
8
|
import { ErrorComponent } from "./Error";
|
|
9
9
|
import { DasbhoardFooter } from "./Footer";
|
|
10
|
-
import { createContext } from "react";
|
|
10
|
+
import { Children, createContext, isValidElement } from "react";
|
|
11
11
|
import { HelmetProvider } from "react-helmet-async";
|
|
12
12
|
import { createDatasetRegistry } from "../Dataset/hooks";
|
|
13
13
|
import { DatasetRegistryContext } from "../Dataset/context";
|
|
14
14
|
import { ControlContext, CreateControlesRegistry } from "../Control/Control";
|
|
15
|
+
import slug from 'slug';
|
|
16
|
+
import { generateRoutes, getFirstValidElement } from "../../utils/route_utils";
|
|
17
|
+
import renderIcon from "../../utils/icon";
|
|
15
18
|
//import '../../index.css' //TODO a intégrer en jsx
|
|
16
19
|
const queryClient = new QueryClient();
|
|
17
|
-
const default_theme = {
|
|
20
|
+
export const default_theme = {
|
|
18
21
|
token: {
|
|
19
22
|
colorPrimary: "#95c11f",
|
|
20
23
|
linkHoverDecoration: 'underline',
|
|
@@ -33,8 +36,47 @@ const default_theme = {
|
|
|
33
36
|
}
|
|
34
37
|
};
|
|
35
38
|
export const AppContext = createContext({});
|
|
36
|
-
|
|
39
|
+
/** Composant principal de l'application.
|
|
40
|
+
*
|
|
41
|
+
* Les enfants de l'application sont les différentes pages de tableau de bord.
|
|
42
|
+
* La configuration globale de l'application (nom, style, etc.) se fait via les propriétés.
|
|
43
|
+
*/
|
|
44
|
+
const DashboardApp = ({ children, theme, routes: routes_legacy, logo, brands, footerSlider, title, subtitle, disablePoweredBy = false }) => {
|
|
37
45
|
const context_values = { title, subtitle, logo };
|
|
38
|
-
|
|
46
|
+
const pages = Children.toArray(children)
|
|
47
|
+
.filter(isValidElement);
|
|
48
|
+
const routes = pages.length >= 1 ? pages.map((page, idx) => {
|
|
49
|
+
if (typeof (page.type) != 'string' && page.type.name == PagesGroup.name) { // Groupe
|
|
50
|
+
return ({
|
|
51
|
+
label: page.props.title ?? String(idx),
|
|
52
|
+
path: slug(page.props.title ?? String(idx)),
|
|
53
|
+
element: undefined, // Pas de route pour les groupes
|
|
54
|
+
hidden: page.props.hidden ?? false,
|
|
55
|
+
icon: renderIcon(page.props.icon),
|
|
56
|
+
children: Children.toArray(page.props.children)?.map((c, idx) => ({
|
|
57
|
+
label: c.props.title, // A factoriser avec les pages hors groupes
|
|
58
|
+
path: slug(c.props.title ?? idx),
|
|
59
|
+
element: c,
|
|
60
|
+
hidden: c.props.hidden ?? false,
|
|
61
|
+
icon: renderIcon(c.props.icon)
|
|
62
|
+
}))
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else { //Pages directes (sans groupe)
|
|
66
|
+
return ({
|
|
67
|
+
label: page.props.title ?? String(idx),
|
|
68
|
+
path: slug(page.props.title ?? String(idx)),
|
|
69
|
+
element: page,
|
|
70
|
+
hidden: page.props.hidden ?? false,
|
|
71
|
+
icon: renderIcon(page.props.icon)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}) : routes_legacy ?? []; // Pour rétro-compatibiltié
|
|
75
|
+
const route_tree = generateRoutes(routes);
|
|
76
|
+
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: [_jsx(Route, { index: true, element: getFirstValidElement(route_tree) }), route_tree, _jsx(Route, { path: "*", element: _jsx(ErrorComponent, {}) })] }) }) }) }) }) }) }) }) }));
|
|
39
77
|
};
|
|
40
78
|
export default DashboardApp;
|
|
79
|
+
/** Regrouper des pages dans le menu */
|
|
80
|
+
export const PagesGroup = ({ children }) => {
|
|
81
|
+
return children;
|
|
82
|
+
};
|
package/dist/dsl/index.d.ts
CHANGED
|
@@ -24,4 +24,5 @@ import { LegendControl } from "../components/MapLegend/MapLegend";
|
|
|
24
24
|
import { Intro } from "../components/DashboardPage/Intro";
|
|
25
25
|
import { ChartComparison } from "../components/Charts/ChartComparison";
|
|
26
26
|
import { DataTable } from "../components/Charts/Datatable";
|
|
27
|
-
|
|
27
|
+
import { ChartEvolution } from "../components/Charts/ChartEvolution";
|
|
28
|
+
export { Dashboard, Dataset, Provider, Transform, Join, Filter, Section, Intro, DataPreview, ChartEcharts, ChartPie, ChartYearSerie, ChartComparison, ChartEvolution, DataTable, 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
|
@@ -24,4 +24,5 @@ import { LegendControl } from "../components/MapLegend/MapLegend";
|
|
|
24
24
|
import { Intro } from "../components/DashboardPage/Intro";
|
|
25
25
|
import { ChartComparison } from "../components/Charts/ChartComparison";
|
|
26
26
|
import { DataTable } from "../components/Charts/Datatable";
|
|
27
|
-
|
|
27
|
+
import { ChartEvolution } from "../components/Charts/ChartEvolution";
|
|
28
|
+
export { Dashboard, Dataset, Provider, Transform, Join, Filter, Section, Intro, DataPreview, ChartEcharts, ChartPie, ChartYearSerie, ChartComparison, ChartEvolution, DataTable, 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
|
@@ -16,11 +16,11 @@ import NextPrevSelect from "./components/NextPrevSelect/NextPrevSelect";
|
|
|
16
16
|
import Control from "./components/Control/Control";
|
|
17
17
|
import DashboardChart from "./components/DashboardChart/DashboardChart";
|
|
18
18
|
import MapLegend from "./components/MapLegend/MapLegend";
|
|
19
|
-
import DashboardApp from "./components/Layout/DashboardApp";
|
|
19
|
+
import DashboardApp, { PagesGroup } from "./components/Layout/DashboardApp";
|
|
20
20
|
import DashboardSider from "./components/Layout/Sider";
|
|
21
21
|
import DashboardPage from "./components/DashboardPage/Page";
|
|
22
22
|
import DashboardElement from "./components/DashboardElement/DashboardElement";
|
|
23
|
-
export { KeyFigure, DashboardElement, LoadingContainer, FlipCard, Attribution, NextPrevSelect, Control, DashboardChart, DashboardPage, MapLegend, DashboardSider, DashboardApp, };
|
|
23
|
+
export { KeyFigure, DashboardElement, LoadingContainer, FlipCard, Attribution, NextPrevSelect, Control, DashboardChart, DashboardPage, MapLegend, DashboardSider, DashboardApp, PagesGroup, };
|
|
24
24
|
import { dataProvider as WfsProvider } from "./data_providers/wfs";
|
|
25
25
|
import { dataProvider as DatafairProvider } from "./data_providers/datafair";
|
|
26
26
|
import { dataProvider as FileProvider } from "./data_providers/file";
|
|
@@ -29,5 +29,6 @@ export type { SimpleRecord, Partner, RouteConfig } from "./types";
|
|
|
29
29
|
export type { LegendItem } from "./components/MapLegend/MapLegend";
|
|
30
30
|
export type { DashboardConfig } from "./components/Layout/DashboardApp";
|
|
31
31
|
export type { datasetInput } from "./components/Dataset/hooks";
|
|
32
|
+
export type { PageProps } from "./components/Layout/DashboardApp";
|
|
32
33
|
import * as DSL from './dsl';
|
|
33
34
|
export { DSL };
|
package/dist/index.js
CHANGED
|
@@ -20,11 +20,11 @@ import Control from "./components/Control/Control";
|
|
|
20
20
|
import DashboardChart from "./components/DashboardChart/DashboardChart";
|
|
21
21
|
import MapLegend from "./components/MapLegend/MapLegend";
|
|
22
22
|
// Layout
|
|
23
|
-
import DashboardApp from "./components/Layout/DashboardApp";
|
|
23
|
+
import DashboardApp, { PagesGroup } from "./components/Layout/DashboardApp";
|
|
24
24
|
import DashboardSider from "./components/Layout/Sider";
|
|
25
25
|
import DashboardPage from "./components/DashboardPage/Page";
|
|
26
26
|
import DashboardElement from "./components/DashboardElement/DashboardElement";
|
|
27
|
-
export { KeyFigure, DashboardElement, LoadingContainer, FlipCard, Attribution, NextPrevSelect, Control, DashboardChart, DashboardPage, MapLegend, DashboardSider, DashboardApp, };
|
|
27
|
+
export { KeyFigure, DashboardElement, LoadingContainer, FlipCard, Attribution, NextPrevSelect, Control, DashboardChart, DashboardPage, MapLegend, DashboardSider, DashboardApp, PagesGroup, };
|
|
28
28
|
// DataProviders
|
|
29
29
|
import { dataProvider as WfsProvider } from "./data_providers/wfs";
|
|
30
30
|
import { dataProvider as DatafairProvider } from "./data_providers/datafair";
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { RouteConfig } from "../types";
|
|
2
2
|
import type { MenuProps } from 'antd';
|
|
3
|
+
import React from "react";
|
|
3
4
|
type MenuItem = Required<MenuProps>['items'][number];
|
|
4
5
|
export declare const generateRoutes: (routes: RouteConfig[]) => import("react/jsx-runtime").JSX.Element[];
|
|
5
6
|
export declare const generateMenuItems: (routes: RouteConfig[], parentPath?: string) => MenuItem[];
|
|
7
|
+
/** AI Generated,
|
|
8
|
+
* Get first "viewable" route (aka "not a group")
|
|
9
|
+
* Used for index route
|
|
10
|
+
*/
|
|
11
|
+
export declare function getFirstValidElement(routes: React.ReactElement[]): React.ReactElement | null;
|
|
6
12
|
export {};
|
|
@@ -1,14 +1,70 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
//Retourne les routes et le menu
|
|
3
|
-
import { NavLink, Route } from "react-router-dom";
|
|
4
|
-
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
3
|
+
import { NavLink, Outlet, Route } from "react-router-dom";
|
|
4
|
+
import React from "react";
|
|
5
|
+
export const generateRoutes = (routes) => routes.map((route) => (_jsx(Route, { path: route.path, element: route.children ? _jsx(Outlet, {}) : route.element, children: route.children && generateRoutes(route.children) }, route.path)));
|
|
6
|
+
export const generateMenuItems = (routes, parentPath = "") => {
|
|
7
|
+
const out = routes.filter((route) => route.hidden != true).map((route) => {
|
|
8
|
+
const fullPath = `${parentPath}/${route.path}`;
|
|
9
|
+
const menuItem = {
|
|
10
|
+
key: fullPath,
|
|
11
|
+
label: route.children ? _jsxs(_Fragment, { children: [" ", route.label || route.path] }) : _jsx(NavLink, { to: fullPath, children: route.label || route.path }),
|
|
12
|
+
icon: route.icon,
|
|
13
|
+
...(route.children && { children: generateMenuItems(route.children, fullPath) }), // Ajout conditionnel des enfants. Legacy
|
|
14
|
+
};
|
|
15
|
+
return menuItem;
|
|
16
|
+
});
|
|
17
|
+
return buildMenuTree(out);
|
|
18
|
+
};
|
|
19
|
+
/** AI generated function */
|
|
20
|
+
function buildMenuTree(items) {
|
|
21
|
+
// Trier par profondeur de path
|
|
22
|
+
const sorted = [...items].sort((a, b) =>
|
|
23
|
+
//@ts-ignore
|
|
24
|
+
a.key.split("/").filter(Boolean).length - b.key.split("/").filter(Boolean).length);
|
|
25
|
+
const menuMap = new Map();
|
|
26
|
+
const roots = [];
|
|
27
|
+
for (const item of sorted) {
|
|
28
|
+
//@ts-ignore
|
|
29
|
+
const normalizedKey = item.key.replace(/\/$/, "") || "/";
|
|
30
|
+
const segments = normalizedKey.split("/").filter(Boolean);
|
|
31
|
+
const parentKey = segments.length > 0
|
|
32
|
+
? "/" + segments.slice(0, -1).join("/")
|
|
33
|
+
: null;
|
|
34
|
+
const parent = parentKey ? menuMap.get(parentKey) : null;
|
|
35
|
+
// On clone l'item pour ne pas muter l'original
|
|
36
|
+
const menuItem = { ...item, key: normalizedKey };
|
|
37
|
+
menuMap.set(normalizedKey, menuItem);
|
|
38
|
+
if (parent) {
|
|
39
|
+
parent.children = parent.children ?? [];
|
|
40
|
+
parent.children.push(menuItem);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
roots.push(menuItem);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return roots;
|
|
47
|
+
}
|
|
48
|
+
/** AI Generated,
|
|
49
|
+
* Get first "viewable" route (aka "not a group")
|
|
50
|
+
* Used for index route
|
|
51
|
+
*/
|
|
52
|
+
export function getFirstValidElement(routes) {
|
|
53
|
+
function isEmptyOutlet(el) {
|
|
54
|
+
return React.isValidElement(el) && el.type === Outlet;
|
|
55
|
+
}
|
|
56
|
+
for (const route of routes) {
|
|
57
|
+
const el = route.props.element;
|
|
58
|
+
if (el && !isEmptyOutlet(el)) { // Page
|
|
59
|
+
return el;
|
|
60
|
+
}
|
|
61
|
+
//Group
|
|
62
|
+
const children = React.Children.toArray(route.props.children).filter(React.isValidElement);
|
|
63
|
+
if (children.length > 0) {
|
|
64
|
+
const firstChild = getFirstValidElement(children);
|
|
65
|
+
if (firstChild)
|
|
66
|
+
return firstChild;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geo2france/api-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build dashboards with JSX/TSX",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -64,17 +64,18 @@
|
|
|
64
64
|
"react-helmet": "^6.1.0",
|
|
65
65
|
"react-helmet-async": "^2.0.5",
|
|
66
66
|
"slick-carousel": "^1.8.1",
|
|
67
|
+
"slug": "^11.0.1",
|
|
67
68
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@ant-design/icons": "^6.0.2",
|
|
71
|
-
"@chromatic-com/storybook": "^5.0.
|
|
72
|
+
"@chromatic-com/storybook": "^5.0.1",
|
|
72
73
|
"@iconify/json": "^2.2.382",
|
|
73
|
-
"@storybook/addon-a11y": "^10.2.
|
|
74
|
-
"@storybook/addon-docs": "^10.2.
|
|
75
|
-
"@storybook/addon-onboarding": "^10.2.
|
|
76
|
-
"@storybook/addon-vitest": "^10.2.
|
|
77
|
-
"@storybook/react-vite": "^10.2.
|
|
74
|
+
"@storybook/addon-a11y": "^10.2.13",
|
|
75
|
+
"@storybook/addon-docs": "^10.2.13",
|
|
76
|
+
"@storybook/addon-onboarding": "^10.2.13",
|
|
77
|
+
"@storybook/addon-vitest": "^10.2.13",
|
|
78
|
+
"@storybook/react-vite": "^10.2.13",
|
|
78
79
|
"@tanstack/react-query": "^5.51.11",
|
|
79
80
|
"@testing-library/dom": "^10.4.0",
|
|
80
81
|
"@testing-library/jest-dom": "^6.5.0",
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
"@types/react-dom": "^18.3.1",
|
|
89
90
|
"@types/react-helmet-async": "^1.0.1",
|
|
90
91
|
"@types/react-icons": "^2.2.7",
|
|
92
|
+
"@types/slug": "^5.0.9",
|
|
91
93
|
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
|
92
94
|
"@typescript-eslint/parser": "^7.16.1",
|
|
93
95
|
"@vitejs/plugin-react": "^4.3.1",
|
|
@@ -101,7 +103,7 @@
|
|
|
101
103
|
"react": "^18.3.1",
|
|
102
104
|
"react-map-gl": "^7.1.9",
|
|
103
105
|
"react-router-dom": "^7.13.0",
|
|
104
|
-
"storybook": "^10.2.
|
|
106
|
+
"storybook": "^10.2.13",
|
|
105
107
|
"ts-jest": "^29.2.5",
|
|
106
108
|
"ts-node": "^10.9.2",
|
|
107
109
|
"tsup": "^8.5.0",
|