@geo2france/api-dashboard 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.MD +192 -0
  2. package/dist/components/Attributions/Attribution.test.d.ts +1 -0
  3. package/dist/components/Attributions/Attribution.test.js +19 -0
  4. package/dist/components/Attributions/Attributions.d.ts +17 -0
  5. package/dist/components/Attributions/Attributions.js +30 -0
  6. package/dist/components/Charts/ChartEcharts.d.ts +7 -0
  7. package/dist/components/Charts/ChartEcharts.js +35 -0
  8. package/dist/components/Charts/Pie.d.ts +10 -0
  9. package/dist/components/Charts/Pie.js +35 -0
  10. package/dist/components/Charts/YearSerie.d.ts +16 -0
  11. package/dist/components/Charts/YearSerie.js +70 -0
  12. package/dist/components/Control/Control.d.ts +21 -0
  13. package/dist/components/Control/Control.js +84 -0
  14. package/dist/components/Control/Radio.d.ts +19 -0
  15. package/dist/components/Control/Radio.js +27 -0
  16. package/dist/components/Control/Select.d.ts +15 -0
  17. package/dist/components/Control/Select.js +25 -0
  18. package/dist/components/DashboardChart/DashboardChart.d.ts +11 -0
  19. package/dist/components/DashboardChart/DashboardChart.js +42 -0
  20. package/dist/components/DashboardElement/DashboardElement.d.ts +21 -0
  21. package/dist/components/DashboardElement/DashboardElement.js +108 -0
  22. package/dist/components/DashboardElement/hooks.d.ts +28 -0
  23. package/dist/components/DashboardElement/hooks.js +29 -0
  24. package/dist/components/DashboardPage/Block.d.ts +15 -0
  25. package/dist/components/DashboardPage/Block.js +40 -0
  26. package/dist/components/DashboardPage/DashboardPage.test.d.ts +1 -0
  27. package/dist/components/DashboardPage/DashboardPage.test.js +40 -0
  28. package/dist/components/DashboardPage/Page.d.ts +42 -0
  29. package/dist/components/DashboardPage/Page.js +80 -0
  30. package/dist/components/Dataset/DataPreview.d.ts +12 -0
  31. package/dist/components/Dataset/DataPreview.js +21 -0
  32. package/dist/components/Dataset/Dataset.d.ts +17 -0
  33. package/dist/components/Dataset/Dataset.js +94 -0
  34. package/dist/components/Dataset/Filter.d.ts +9 -0
  35. package/dist/components/Dataset/Filter.js +7 -0
  36. package/dist/components/Dataset/Join.d.ts +8 -0
  37. package/dist/components/Dataset/Join.js +7 -0
  38. package/dist/components/Dataset/Producer.d.ts +18 -0
  39. package/dist/components/Dataset/Producer.js +27 -0
  40. package/dist/components/Dataset/Provider.d.ts +21 -0
  41. package/dist/components/Dataset/Provider.js +22 -0
  42. package/dist/components/Dataset/Transform.d.ts +6 -0
  43. package/dist/components/Dataset/Transform.js +7 -0
  44. package/dist/components/Dataset/hooks.d.ts +24 -0
  45. package/dist/components/Dataset/hooks.js +19 -0
  46. package/dist/components/Debug/Debug.d.ts +1 -0
  47. package/dist/components/Debug/Debug.js +24 -0
  48. package/dist/components/FlipCard/FlipCard.d.ts +12 -0
  49. package/dist/components/FlipCard/FlipCard.js +38 -0
  50. package/dist/components/FlipCard/FlipCard.test.d.ts +1 -0
  51. package/dist/components/FlipCard/FlipCard.test.js +36 -0
  52. package/dist/components/KeyFigure/KeyFigure.d.ts +18 -0
  53. package/dist/components/KeyFigure/KeyFigure.js +13 -0
  54. package/dist/components/Layout/DashboardApp.d.ts +18 -0
  55. package/dist/components/Layout/DashboardApp.js +46 -0
  56. package/dist/components/Layout/Error.d.ts +2 -0
  57. package/dist/components/Layout/Error.js +6 -0
  58. package/dist/components/Layout/Footer.d.ts +6 -0
  59. package/dist/components/Layout/Footer.js +47 -0
  60. package/dist/components/Layout/Sider.d.ts +9 -0
  61. package/dist/components/Layout/Sider.js +48 -0
  62. package/dist/components/LoadingContainer/LoadingContainer.d.ts +17 -0
  63. package/dist/components/LoadingContainer/LoadingContainer.js +33 -0
  64. package/dist/components/MapLegend/MapLegend.d.ts +12 -0
  65. package/dist/components/MapLegend/MapLegend.js +19 -0
  66. package/dist/components/NextPrevSelect/NextPrevSelect.d.ts +17 -0
  67. package/dist/components/NextPrevSelect/NextPrevSelect.js +49 -0
  68. package/dist/components/Palette/Palette.d.ts +18 -0
  69. package/dist/components/Palette/Palette.js +29 -0
  70. package/dist/data_providers/datafair/datafair.test.d.ts +1 -0
  71. package/dist/data_providers/datafair/datafair.test.js +39 -0
  72. package/dist/data_providers/datafair/index.d.ts +15 -0
  73. package/dist/data_providers/datafair/index.js +33 -0
  74. package/dist/data_providers/datafair/utils/axios.d.ts +2 -0
  75. package/dist/data_providers/datafair/utils/axios.js +13 -0
  76. package/dist/data_providers/datafair/utils/generateFilter.d.ts +1 -0
  77. package/dist/data_providers/datafair/utils/generateFilter.js +40 -0
  78. package/dist/data_providers/datafair/utils/generateSort.d.ts +1 -0
  79. package/dist/data_providers/datafair/utils/generateSort.js +19 -0
  80. package/dist/data_providers/datafair/utils/index.d.ts +4 -0
  81. package/dist/data_providers/datafair/utils/index.js +4 -0
  82. package/dist/data_providers/datafair/utils/mapOperator.d.ts +1 -0
  83. package/dist/data_providers/datafair/utils/mapOperator.js +13 -0
  84. package/dist/data_providers/file/index.d.ts +6 -0
  85. package/dist/data_providers/file/index.js +25 -0
  86. package/dist/data_providers/file/utils/axios.d.ts +2 -0
  87. package/dist/data_providers/file/utils/axios.js +26 -0
  88. package/dist/data_providers/types.d.ts +52 -0
  89. package/dist/data_providers/types.js +2 -0
  90. package/dist/data_providers/wfs/index.d.ts +3 -0
  91. package/dist/data_providers/wfs/index.js +43 -0
  92. package/dist/data_providers/wfs/utils/axios.d.ts +2 -0
  93. package/dist/data_providers/wfs/utils/axios.js +26 -0
  94. package/dist/data_providers/wfs/utils/generateFilter.d.ts +4 -0
  95. package/dist/data_providers/wfs/utils/generateFilter.js +45 -0
  96. package/dist/data_providers/wfs/utils/generateSort.d.ts +1 -0
  97. package/dist/data_providers/wfs/utils/generateSort.js +20 -0
  98. package/dist/data_providers/wfs/utils/index.d.ts +4 -0
  99. package/dist/data_providers/wfs/utils/index.js +4 -0
  100. package/dist/data_providers/wfs/utils/mapOperator.d.ts +1 -0
  101. package/dist/data_providers/wfs/utils/mapOperator.js +36 -0
  102. package/dist/data_providers/wfs/wfs.test.d.ts +1 -0
  103. package/dist/data_providers/wfs/wfs.test.js +60 -0
  104. package/dist/dsl/index.d.ts +19 -0
  105. package/dist/dsl/index.js +19 -0
  106. package/dist/index.d.ts +29 -0
  107. package/dist/index.js +33 -0
  108. package/dist/types.d.ts +15 -0
  109. package/dist/types.js +1 -0
  110. package/dist/utils/baserecordtogeojsonpoint.d.ts +13 -0
  111. package/dist/utils/baserecordtogeojsonpoint.js +17 -0
  112. package/dist/utils/cardStyles.d.ts +2 -0
  113. package/dist/utils/cardStyles.js +12 -0
  114. package/dist/utils/deepmerge.d.ts +2 -0
  115. package/dist/utils/deepmerge.js +24 -0
  116. package/dist/utils/route_utils.d.ts +6 -0
  117. package/dist/utils/route_utils.js +14 -0
  118. package/dist/utils/useApi.d.ts +12 -0
  119. package/dist/utils/useApi.js +14 -0
  120. package/dist/utils/useMapControl.d.ts +8 -0
  121. package/dist/utils/useMapControl.js +24 -0
  122. package/dist/utils/useSearchParamsState.d.ts +11 -0
  123. package/dist/utils/useSearchParamsState.js +18 -0
  124. package/dist/utils/usechartexports.d.ts +17 -0
  125. package/dist/utils/usechartexports.js +39 -0
  126. package/dist/utils/usecharthightlight.d.ts +25 -0
  127. package/dist/utils/usecharthightlight.js +44 -0
  128. package/package.json +98 -0
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import alasql from "alasql";
3
+ import ReactECharts from 'echarts-for-react';
4
+ import { useRef } from "react";
5
+ import { useChartData, useDashboardElement } from "../DashboardElement/hooks";
6
+ import deepMerge from "../../utils/deepmerge";
7
+ // Les données doivent être de la forme {axeA : axeB : } ?
8
+ const DashboardChart = ({ data, chart_type = 'line', reverse_axies = false, echarts_option = {}, sql }) => {
9
+ const chartRef = useRef();
10
+ useDashboardElement({ chartRef });
11
+ const data_xy = sql ? alasql(sql, [data]) : data;
12
+ useChartData({ data: data_xy });
13
+ const keys = Object.keys(data_xy[0]);
14
+ const axis0 = {
15
+ type: typeof data_xy[0][keys[0]] === 'number' ? 'value' : 'category',
16
+ name: keys[0]
17
+ };
18
+ const axis1 = {
19
+ type: typeof data_xy[0][keys[1]] === 'number' ? 'value' : 'category',
20
+ name: keys[1]
21
+ };
22
+ const options = {
23
+ series: [
24
+ {
25
+ type: chart_type,
26
+ data: data_xy.map((e) => reverse_axies ?
27
+ [e[keys[1]], e[keys[0]]]
28
+ : [e[keys[0]], e[keys[1]]])
29
+ }
30
+ ],
31
+ //@ts-ignore
32
+ xAxis: {
33
+ ...reverse_axies ? axis1 : axis0
34
+ },
35
+ //@ts-ignore
36
+ yAxis: {
37
+ ...reverse_axies ? axis0 : axis1
38
+ }
39
+ };
40
+ return (_jsx(ReactECharts, { option: deepMerge(options, echarts_option), ref: chartRef }));
41
+ };
42
+ export default DashboardChart;
@@ -0,0 +1,21 @@
1
+ import React, { ReactElement, ReactNode } from "react";
2
+ import { SourceMakerProps, SourceProps } from "../Attributions/Attributions";
3
+ import { License } from "../../types";
4
+ export declare const chartContext: React.Context<any>;
5
+ export interface IDashboardElementProps {
6
+ title: string;
7
+ children: ReactNode;
8
+ isFetching?: boolean;
9
+ header?: boolean;
10
+ attributions?: SourceProps[] | SourceMakerProps;
11
+ toolbox?: boolean;
12
+ fullscreen?: boolean;
13
+ exportPNG?: boolean;
14
+ exportData?: boolean;
15
+ description?: ReactElement | string;
16
+ licenses?: License[];
17
+ section?: string;
18
+ virtual?: boolean;
19
+ }
20
+ declare const DashboardElement: React.FC<IDashboardElementProps>;
21
+ export default DashboardElement;
@@ -0,0 +1,108 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { DownloadOutlined, FileImageOutlined, FullscreenOutlined, MoreOutlined, } from "@ant-design/icons";
3
+ import { HiQuestionMarkCircle } from "react-icons/hi2";
4
+ import { Card, theme, Modal, Dropdown, Flex, Button, Popover, Typography } from "antd";
5
+ import React, { createContext, useEffect, useState } from "react";
6
+ import Attribution from "../Attributions/Attributions";
7
+ import { useChartExport } from "../../utils/usechartexports";
8
+ import LoadingContainer from "../LoadingContainer/LoadingContainer";
9
+ import { cardStyles } from "../../utils/cardStyles";
10
+ const { useToken } = theme;
11
+ const { Text } = Typography;
12
+ export const chartContext = createContext({
13
+ setchartRef: () => { },
14
+ setData: () => { },
15
+ setNodata: () => { }
16
+ });
17
+ const DashboardElement = ({ children, title, header = true, attributions, isFetching = false, toolbox = true, fullscreen = true, exportPNG = true, exportData = true, description, licenses = ['CC', 'BY'], virtual = false, }) => {
18
+ const { token } = useToken();
19
+ const [modalIsOpen, setModalIsOpen] = useState(false);
20
+ const [chartRef, setchartRef] = useState(undefined);
21
+ const [data, setData] = useState(undefined);
22
+ const [nodata, setNodata] = useState(false);
23
+ const [requestDlImage, setRequestDlImage] = useState(false);
24
+ const [requestDlData, setrequestDlData] = useState(null);
25
+ const { img64, exportImage } = useChartExport({ chartRef: chartRef });
26
+ const downloadImage = () => {
27
+ exportImage();
28
+ setRequestDlImage(true);
29
+ };
30
+ useEffect(() => {
31
+ if (img64 && requestDlImage) {
32
+ const link = document.createElement("a");
33
+ link.href = img64;
34
+ link.download = `${title}.png`;
35
+ link.style.display = "none";
36
+ document.body.appendChild(link);
37
+ link.click();
38
+ document.body.removeChild(link);
39
+ setRequestDlImage(false);
40
+ }
41
+ }, [img64]);
42
+ const downloadData = (filetype) => {
43
+ setrequestDlData(filetype);
44
+ };
45
+ useEffect(() => {
46
+ if (data && requestDlData) {
47
+ const handleDl = async () => {
48
+ const XLSX = await import("xlsx");
49
+ const worksheet = XLSX.utils.json_to_sheet(data);
50
+ const workbook = XLSX.utils.book_new();
51
+ XLSX.utils.book_append_sheet(workbook, worksheet, "data");
52
+ XLSX.writeFile(workbook, `${title}.${requestDlData}`, {
53
+ compression: true,
54
+ });
55
+ setrequestDlData(null);
56
+ };
57
+ handleDl();
58
+ }
59
+ }, [requestDlData]);
60
+ const fullscreenChildren = React.Children.map(children, (child, index) => {
61
+ if (index === 0 && React.isValidElement(child)) {
62
+ return React.cloneElement(child, {
63
+ ...child.props?.style,
64
+ style: { height: "80vh" },
65
+ });
66
+ }
67
+ return child;
68
+ });
69
+ const dd_items = [
70
+ {
71
+ key: "fullscreen",
72
+ label: (_jsxs("a", { onClick: () => setModalIsOpen(true), children: [_jsx(FullscreenOutlined, {}), " Plein \u00E9cran"] })),
73
+ disabled: !fullscreen,
74
+ },
75
+ {
76
+ key: "export_img",
77
+ label: (_jsxs("a", { onClick: downloadImage, children: [_jsx(FileImageOutlined, {}), " PNG"] })),
78
+ disabled: !chartRef || !exportPNG,
79
+ },
80
+ {
81
+ key: "export_data_csv",
82
+ label: (_jsxs("a", { onClick: () => downloadData("csv"), children: [_jsx(DownloadOutlined, {}), " CSV"] })),
83
+ disabled: !data || !exportData,
84
+ },
85
+ {
86
+ key: "export_data_xls",
87
+ label: (_jsxs("a", { onClick: () => downloadData("xlsx"), children: [_jsx(DownloadOutlined, {}), " XLSX"] })),
88
+ disabled: !data || !exportData,
89
+ },
90
+ {
91
+ key: "export_data_ods",
92
+ label: (_jsxs("a", { onClick: () => downloadData("ods"), children: [_jsx(DownloadOutlined, {}), " ODS"] })),
93
+ disabled: !data || !exportData,
94
+ },
95
+ ];
96
+ const dropdown_toolbox = (_jsx(Dropdown, { menu: { items: dd_items }, children: _jsx("a", { style: { color: token.colorTextBase }, children: _jsx(MoreOutlined, { style: { marginLeft: 10 } }) }) }));
97
+ return (_jsxs(_Fragment, { children: [_jsx(Card, { className: "dashboard-element", styles: cardStyles, style: {
98
+ backgroundColor: virtual ? 'transparent' : undefined,
99
+ boxShadow: virtual ? 'none' : undefined,
100
+ height: "100%",
101
+ display: "flex",
102
+ flexDirection: "column",
103
+ }, variant: virtual ? 'borderless' : 'outlined', title: header && !virtual &&
104
+ _jsxs(Flex, { justify: "space-between", align: "center", children: [_jsx("span", { style: { overflow: "hidden", paddingRight: 15 }, children: title }), _jsx("div", { style: { marginRight: 5, fontSize: 16 }, children: toolbox && dropdown_toolbox })] }), children: _jsxs(Flex, { vertical: true, justify: "space-between", style: { height: "100%" }, children: [_jsx(chartContext.Provider, { value: { chartRef, setchartRef, setData, setNodata }, children: _jsx(LoadingContainer, { isFetching: isFetching, noData: nodata, children: children }) }), _jsxs(Flex, { justify: "flex-end", align: "flex-end", style: { marginRight: 5 }, children: [attributions && (_jsx("div", { style: { marginTop: "auto" }, children: _jsx(Attribution, { licenses: licenses, data: attributions }) })), description && (_jsx(Popover, { content: _jsx("div", { style: { maxWidth: 800 }, children: typeof description === "string" ?
105
+ _jsxs(Text, { italic: true, type: "secondary", children: [" ", description, " "] })
106
+ : _jsx(_Fragment, { children: description }) }), children: _jsx(Button, { type: "link", icon: _jsx(HiQuestionMarkCircle, {}), style: { fontSize: "150%" } }) }))] })] }) }), toolbox && fullscreen && (_jsxs(Modal, { forceRender: true, title: title, open: modalIsOpen, onCancel: () => setModalIsOpen(false), onOk: () => setModalIsOpen(false), footer: null, wrapClassName: "modal-fullscreen", children: [fullscreenChildren, attributions && _jsx(Attribution, { data: attributions })] }))] }));
107
+ };
108
+ export default DashboardElement;
@@ -0,0 +1,28 @@
1
+ import { MutableRefObject } from "react";
2
+ /**
3
+ * Hook permettant de faire remonter la référence du graphique dans le composant DashboardElement
4
+ * Ceci est nécessaire pour les fonctionnalités d'export graphique
5
+ * @example
6
+ * useDashboardElement({chartRef:chartRef})
7
+ */
8
+ export interface useDashboardElementProps {
9
+ chartRef?: MutableRefObject<any>;
10
+ }
11
+ export declare const useDashboardElement: ({ chartRef }: useDashboardElementProps) => void;
12
+ /**
13
+ * Hook permettant de remonter les données au Dashboard Element
14
+ * @param {any} props.data - Les données à proposer au téléchargement.
15
+ * @param {Array<any>} [props.dependencies=[]] - Les dépendances suspeptibles de modifier les données
16
+ */
17
+ export interface useChartDataProps {
18
+ data?: any[];
19
+ dependencies?: any[];
20
+ }
21
+ export declare const useChartData: ({ data, dependencies }: useChartDataProps) => void;
22
+ /**
23
+ * Hook permettant d'indiquer au DashboardElement que aucune données n'est disponible
24
+ * Il est aussi possible d'avoir ce comportement en passant un tableau vide dans useChartData
25
+ * Deprécié (utilisez useChartData avec un tableau vide)
26
+ * @param {boolean} noData - True si aucune donnée n'est disponible.
27
+ */
28
+ export declare const useNoData: (noData: boolean) => void;
@@ -0,0 +1,29 @@
1
+ import { chartContext } from "./DashboardElement";
2
+ import { useContext, useEffect } from "react";
3
+ export const useDashboardElement = ({ chartRef }) => {
4
+ const { setchartRef } = useContext(chartContext); //Gérer les cas où ce contexte n'existe pas
5
+ useEffect(() => {
6
+ if (setchartRef) {
7
+ setchartRef(chartRef);
8
+ }
9
+ }, [chartRef]);
10
+ };
11
+ export const useChartData = ({ data, dependencies = [] }) => {
12
+ const { setData, setNodata } = useContext(chartContext);
13
+ useEffect(() => {
14
+ setData(data);
15
+ data === undefined || data.length < 1 ? setNodata(true) : setNodata(false);
16
+ }, dependencies);
17
+ };
18
+ /**
19
+ * Hook permettant d'indiquer au DashboardElement que aucune données n'est disponible
20
+ * Il est aussi possible d'avoir ce comportement en passant un tableau vide dans useChartData
21
+ * Deprécié (utilisez useChartData avec un tableau vide)
22
+ * @param {boolean} noData - True si aucune donnée n'est disponible.
23
+ */
24
+ export const useNoData = (noData) => {
25
+ const { setNodata } = useContext(chartContext);
26
+ useEffect(() => {
27
+ setNodata(noData);
28
+ }, [noData]); // !
29
+ };
@@ -0,0 +1,15 @@
1
+ import { SimpleRecord } from "../../types";
2
+ export interface ChartBlockConfig {
3
+ title?: string;
4
+ dataExport?: SimpleRecord[];
5
+ }
6
+ type ChartBlockContextType = {
7
+ config: ChartBlockConfig;
8
+ setConfig: (config: ChartBlockConfig) => void;
9
+ };
10
+ export declare const ChartBlockContext: import("react").Context<ChartBlockContextType | undefined>;
11
+ interface IChartBlockProps {
12
+ children: React.ReactElement;
13
+ }
14
+ export declare const DSL_ChartBlock: React.FC<IChartBlockProps>;
15
+ export {};
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, Dropdown, theme } from "antd";
3
+ import { createContext, useId, useState } from "react";
4
+ import { FaFileCsv } from "react-icons/fa";
5
+ import { AiOutlineMore } from "react-icons/ai";
6
+ import { ProducersFooter } from "../Dataset/Producer";
7
+ const { useToken } = theme;
8
+ export const ChartBlockContext = createContext(undefined);
9
+ export const DSL_ChartBlock = ({ children }) => {
10
+ const id = useId();
11
+ const [config, setConfig] = useState({});
12
+ const { token } = useToken();
13
+ const menu_items = [
14
+ {
15
+ key: "export_data_csv",
16
+ label: (_jsxs("a", { onClick: () => handleExportData(), children: [_jsx(FaFileCsv, {}), " CSV"] })),
17
+ disabled: config.dataExport === undefined
18
+ },
19
+ ];
20
+ const has_action = menu_items.some(item => !item.disabled);
21
+ const dropdown_toolbox = (_jsx(Dropdown, { menu: { items: menu_items }, children: _jsx("a", { style: { color: token.colorTextBase }, children: _jsx(AiOutlineMore, {}) }) }));
22
+ const handleExportData = () => {
23
+ console.log('datadl');
24
+ console.log(config.dataExport);
25
+ const DL = async () => {
26
+ if (config.dataExport === undefined) {
27
+ return;
28
+ }
29
+ const XLSX = await import("xlsx");
30
+ const worksheet = XLSX.utils.json_to_sheet(config.dataExport);
31
+ const workbook = XLSX.utils.book_new();
32
+ XLSX.utils.book_append_sheet(workbook, worksheet, "data");
33
+ XLSX.writeFile(workbook, `${config.title || id}.csv`, {
34
+ compression: true,
35
+ });
36
+ };
37
+ DL();
38
+ };
39
+ return (_jsx(ChartBlockContext.Provider, { value: { config: config, setConfig: (e) => setConfig(e) }, children: _jsxs(Card, { style: { height: '100%' }, extra: has_action && dropdown_toolbox, title: config.title, children: [children, _jsx(ProducersFooter, { component: children })] }) }));
40
+ };
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,40 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import DashboardPage from './Page';
5
+ import DashboardElement from '../DashboardElement/DashboardElement';
6
+ import { MemoryRouter } from 'react-router-dom';
7
+ // TODO : test l'affichage des DashboardElement et les filtres radio
8
+ // TOFIX : DashboardElement sans rien a l'intérieur n'affiche rien
9
+ describe("DashboardPage Component", () => {
10
+ beforeAll(() => {
11
+ Object.defineProperty(window, 'matchMedia', {
12
+ writable: true,
13
+ value: jest.fn().mockImplementation((query) => ({
14
+ matches: false,
15
+ media: query,
16
+ onchange: null,
17
+ addListener: jest.fn(), // Deprecated
18
+ removeListener: jest.fn(), // Deprecated
19
+ addEventListener: jest.fn(),
20
+ removeEventListener: jest.fn(),
21
+ dispatchEvent: jest.fn(),
22
+ })),
23
+ });
24
+ });
25
+ test("initial state", async () => {
26
+ // Génération DOM virtuel et sélection des noeuds
27
+ render(_jsx(MemoryRouter, { children: _jsxs(DashboardPage, { children: [_jsx(DashboardElement, { title: "Graphique 1", section: 'Section 1', children: _jsx(_Fragment, {}) }), _jsx(DashboardElement, { title: "Graphique 2", section: 'Section 2', children: _jsx(_Fragment, {}) })] }) }));
28
+ // Assertions
29
+ expect(screen.getByRole('radio', { name: "Section 1" })).toBeInTheDocument();
30
+ expect(screen.queryByRole('radio', { name: "Section 3" })).not.toBeInTheDocument();
31
+ expect(screen.queryByRole('radio', { name: "Autres" })).not.toBeInTheDocument();
32
+ expect(screen.getAllByRole('radio')).toHaveLength(2);
33
+ });
34
+ test("Others section", async () => {
35
+ render(_jsx(MemoryRouter, { children: _jsxs(DashboardPage, { children: [_jsx(DashboardElement, { title: "Graphique 1", section: 'Section 1', children: _jsx(_Fragment, {}) }), _jsx(DashboardElement, { title: "Graphique 2", section: 'Section 2', children: _jsx(_Fragment, {}) }), _jsx(DashboardElement, { title: "Graphique 3", children: _jsx(_Fragment, {}) })] }) }));
36
+ // Assertions
37
+ expect(screen.queryByRole('radio', { name: "Autres" })).toBeInTheDocument();
38
+ expect(screen.getAllByRole('radio')).toHaveLength(3);
39
+ });
40
+ });
@@ -0,0 +1,42 @@
1
+ import { RowProps } from "antd";
2
+ import DashboardElement from "../DashboardElement/DashboardElement";
3
+ import React from "react";
4
+ import { SimpleRecord } from "../../types";
5
+ type Section = {
6
+ key: string;
7
+ libel?: string;
8
+ order?: number;
9
+ hide?: boolean;
10
+ disable?: boolean;
11
+ };
12
+ interface IDashboardPageProps {
13
+ control?: React.ReactElement | React.ReactElement[];
14
+ children: React.ReactElement<typeof DashboardElement>[] | React.ReactElement<typeof DashboardElement>;
15
+ row_gutter?: RowProps['gutter'];
16
+ sections?: string[] | Section[];
17
+ }
18
+ declare const DashboardPage: React.FC<IDashboardPageProps>;
19
+ export default DashboardPage;
20
+ type dataset = {
21
+ id: string;
22
+ resource: string;
23
+ data?: SimpleRecord[];
24
+ isFetching: boolean;
25
+ isError: boolean;
26
+ producers?: any[];
27
+ };
28
+ type ControlContextType = {
29
+ values: Record<string, any>;
30
+ pushValue: (control: {
31
+ name: string;
32
+ value: any;
33
+ }) => void;
34
+ };
35
+ export declare const DatasetContext: React.Context<Record<string, dataset>>;
36
+ export declare const DatasetRegistryContext: React.Context<(dataset: dataset) => void>;
37
+ export declare const ControlContext: React.Context<ControlContextType | undefined>;
38
+ interface IDSLDashboardPageProps {
39
+ children: React.ReactNode;
40
+ name?: string;
41
+ }
42
+ export declare const DSL_DashboardPage: React.FC<IDSLDashboardPageProps>;
@@ -0,0 +1,80 @@
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 } from "antd";
3
+ import React, { isValidElement, useState, createContext, } from "react";
4
+ import { Helmet } from "react-helmet-async";
5
+ import { useSearchParamsState } from "../../utils/useSearchParamsState";
6
+ import Control, { DSL_Control } from "../Control/Control";
7
+ import { Dataset, Debug, Provider } from "../../dsl";
8
+ import { DSL_ChartBlock } from "./Block";
9
+ import { DEFAULT_PALETTE, Palette, PaletteContext } from "../Palette/Palette";
10
+ const { Header } = Layout;
11
+ const getSection = (child) => React.isValidElement(child) ? child.props.section : undefined;
12
+ const DashboardPage = ({ children: children_input, control, row_gutter = [8, 8], sections }) => {
13
+ let sections_std = [];
14
+ const screens = Grid.useBreakpoint();
15
+ const children = React.Children.toArray(children_input).filter((child) => React.isValidElement(child));
16
+ if (sections && typeof (sections[0]) === 'string') {
17
+ sections_std = sections.map((s) => ({ key: s }));
18
+ }
19
+ else if (sections && typeof (sections[0]) === 'object') {
20
+ sections_std = sections;
21
+ }
22
+ else { //Automatic section based on children properties
23
+ sections_std = [...new Set(children.map((child) => getSection(child) ?? 'Autres'))].map((s) => ({ key: s }));
24
+ }
25
+ const [activeTab, setActiveTab] = useSearchParamsState('tab', sections_std[0].key);
26
+ sections_std.sort((a, b) => (a?.order || Infinity) - (b?.order || Infinity));
27
+ return (_jsxs(_Fragment, { children: [_jsx(Control, { children: _jsxs(Flex, { wrap: true, justify: "flex-start", align: "flex-start", gap: "small", children: [_jsx("div", { children: sections_std.length > 1 && !screens.md &&
28
+ _jsx(Dropdown.Button, { menu: { selectedKeys: [activeTab],
29
+ items: sections_std.map((section) => ({
30
+ label: section.libel || section.key,
31
+ key: section.key,
32
+ onClick: () => setActiveTab(section.key)
33
+ })) }, trigger: ['click'], buttonsRender: ([_lb, rb]) => [_jsx(Button, { type: 'primary', children: activeTab }), rb] }) }), sections_std.length > 1 && screens.md &&
34
+ _jsx(Radio.Group, { optionType: "button", buttonStyle: "solid", options: sections_std.map((section) => ({
35
+ label: section.libel || section.key,
36
+ value: section.key
37
+ })), 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)) })] }));
38
+ };
39
+ export default DashboardPage;
40
+ export const DatasetContext = createContext({});
41
+ export const DatasetRegistryContext = createContext(() => { }); // A modifier, utiliser un seul context
42
+ export const ControlContext = createContext(undefined);
43
+ export const DSL_DashboardPage = ({ name = 'Tableau de bord', children }) => {
44
+ const [datasets, setdatasets] = useState({});
45
+ const [palette, setPalette] = useState(DEFAULT_PALETTE);
46
+ //const allDatasetLoaded = Object.values(datasets).every(d => !d.isFetching);
47
+ //const isDatasetError = Object.values(datasets).some(d => d.isError);
48
+ // Ajouter ou mettre à jour un dataset
49
+ const pushDataset = (d) => {
50
+ setdatasets(prev => ({
51
+ ...prev,
52
+ [d.id]: d
53
+ }));
54
+ };
55
+ const childrenArray = React.Children.toArray(children).filter(isValidElement);
56
+ const logicalComponents = [Dataset.name, Provider.name, Palette.name, Debug.name]; //Composant logiques, a ne pas mettre dans la grid
57
+ const getComponentKind = (c) => {
58
+ if (typeof (c.type) != 'string' && logicalComponents.includes(c.type.name)) {
59
+ return "logical";
60
+ }
61
+ else if (typeof (c.type) != 'string' && c.type.name == DSL_Control.name) {
62
+ return "control";
63
+ }
64
+ else {
65
+ return "other";
66
+ }
67
+ };
68
+ const visible_components = childrenArray.filter((c) => c && getComponentKind(c) == 'other');
69
+ const logic_components = childrenArray.filter((c) => getComponentKind(c) == 'logical');
70
+ const control_components = childrenArray.filter((c) => getComponentKind(c) == 'control');
71
+ return (_jsxs(_Fragment, { children: [_jsx(Helmet, { children: _jsx("title", { children: name }) }), _jsx(DatasetRegistryContext.Provider, { value: pushDataset, children: _jsx(DatasetContext.Provider, { value: datasets, children: _jsxs(PaletteContext.Provider, { value: { palette, setPalette }, children: [control_components.length > 0 && _jsx(Header, { style: {
72
+ padding: 12,
73
+ position: "sticky",
74
+ top: 0,
75
+ zIndex: 600, // maplibre top zIndex if 500
76
+ backgroundColor: "#fff",
77
+ height: "auto",
78
+ width: "100%",
79
+ }, children: control_components }), _jsx(Row, { gutter: [8, 8], style: { margin: 16 }, children: visible_components.map((component, idx) => _jsx(Col, { xl: 12, xs: 24, children: _jsx(DSL_ChartBlock, { children: component }) }, idx)) }), logic_components] }) }) })] }));
80
+ };
@@ -0,0 +1,12 @@
1
+ interface DatasetBadgeStatusProps {
2
+ isError?: boolean;
3
+ isFetching?: boolean;
4
+ }
5
+ export declare const DatasetBadgeStatus: React.FC<DatasetBadgeStatusProps>;
6
+ interface DSL_DataPreviewProps {
7
+ dataset?: string;
8
+ pageSize?: number;
9
+ rowKey?: string;
10
+ }
11
+ export declare const DSL_DataPreview: React.FC<DSL_DataPreviewProps>;
12
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Table from "antd/es/table";
3
+ import { Badge } from "antd";
4
+ import { useDataset } from "./hooks";
5
+ export const DatasetBadgeStatus = ({ isError, isFetching }) => {
6
+ return _jsx(Badge, { status: isError ? "error" : isFetching ? 'processing' : 'success' });
7
+ };
8
+ export const DSL_DataPreview = ({ dataset: dataset_id, pageSize, rowKey }) => {
9
+ const dataset = useDataset(dataset_id);
10
+ const data = dataset?.data;
11
+ if (data === undefined) { // TODO : Afficher Empty si les données ont fini de fetcher et data est null ou lenght==0
12
+ return _jsx(_Fragment, {});
13
+ }
14
+ const columns = Object.keys(data[0] || {}).map((key) => ({
15
+ title: key,
16
+ dataIndex: key,
17
+ key,
18
+ ellipsis: true,
19
+ }));
20
+ return (_jsxs(_Fragment, { children: [_jsxs("h3", { children: [_jsx(DatasetBadgeStatus, { isError: dataset?.isError, isFetching: dataset?.isFetching }), " ", dataset?.resource] }), _jsx(Table, { pagination: { pageSize: pageSize }, dataSource: data, columns: columns, rowKey: (row) => rowKey ? row[rowKey] : JSON.stringify(row) })] }));
21
+ };
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from "react";
2
+ import { CrudFilters, DataProvider } from "../../data_providers/types";
3
+ import React from "react";
4
+ import { ProviderType } from "./Provider";
5
+ interface IDatasetProps {
6
+ id: string;
7
+ provider?: DataProvider;
8
+ url?: string;
9
+ type?: ProviderType;
10
+ resource: string;
11
+ filters?: CrudFilters;
12
+ children?: ReactNode;
13
+ pageSize?: number;
14
+ meta?: any;
15
+ }
16
+ export declare const DSL_Dataset: React.FC<IDatasetProps>;
17
+ export {};
@@ -0,0 +1,94 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext, useEffect } from "react";
3
+ import { useApi } from "../..";
4
+ import { ControlContext, DatasetRegistryContext } from "../DashboardPage/Page";
5
+ import { Producer } from "./Producer";
6
+ import React from "react";
7
+ import { Filter, Transform, useAllDatasets } from "../../dsl";
8
+ import alasql from "alasql";
9
+ import { DataProviderContext, getProviderFromType } from "./Provider";
10
+ import { Join } from "./Join";
11
+ import { from } from "arquero";
12
+ export const DSL_Dataset = ({ children, id, provider: provider_input, type: providerType = 'file', url: providerUrl, resource, pageSize, meta }) => {
13
+ const getTransformerFn = (component) => {
14
+ /*
15
+ * Génére une fonction transformer soit
16
+ * - A partir d'un composant Transform (string ou fonction js)
17
+ * - A partir d'un composant Join
18
+ */
19
+ if (typeof component.type != 'string' && component.type == Transform) {
20
+ const children = component.props.children;
21
+ if (typeof children === "string") {
22
+ // Transformation via une requête SQL
23
+ return (data) => data && alasql(children, [data]);
24
+ }
25
+ else {
26
+ return (data) => data && children(data);
27
+ }
28
+ }
29
+ else if (typeof component.type != 'string' && component.type === Join) {
30
+ const props = component.props;
31
+ const funct = (data) => {
32
+ const join_type = props.joinType || 'inner';
33
+ const aq_join_option = {
34
+ left: { left: true, right: false },
35
+ right: { left: false, right: true },
36
+ full: { left: true, right: true },
37
+ inner: { left: false, right: false },
38
+ }[join_type] ?? { left: false, right: false };
39
+ const otherData = allDatasets?.find((d) => d.id === props.dataset)?.data;
40
+ if (!otherData)
41
+ return undefined; // ou [] ou data selon la logique souhaitée
42
+ return from(data).join(from(otherData), props.joinKey, undefined, aq_join_option).objects();
43
+ };
44
+ return funct;
45
+ }
46
+ else {
47
+ throw new Error(`Unknown transformer component: ${component.type}`);
48
+ }
49
+ };
50
+ const datasetRegistryContext = useContext(DatasetRegistryContext);
51
+ const allDatasets = useAllDatasets();
52
+ const someFetching = !!allDatasets?.some(d => d.isFetching);
53
+ const controlContext = useContext(ControlContext);
54
+ const controls = controlContext?.values;
55
+ const providerContext = useContext(DataProviderContext);
56
+ const provider = (providerUrl && getProviderFromType(providerType)(providerUrl)) || providerContext || provider_input;
57
+ if (provider === undefined) {
58
+ throw new Error("Error : No dataProvider, please use one of : <Provider> parent, providerUrl/providerType properties or provider property");
59
+ }
60
+ /* Récupérer les props des filtres depuis les composants enfant Filter */
61
+ const filters = [];
62
+ React.Children.toArray(children)
63
+ .filter((c) => React.isValidElement(c))
64
+ .filter((c) => typeof c.type != 'string' && c.type.name == Filter.name).forEach((c) => {
65
+ const value = String(c.props.children).trim() || controls?.[c.props.control];
66
+ filters.push({
67
+ operator: c.props.operator || 'eq',
68
+ value: value,
69
+ field: c.props.field
70
+ });
71
+ });
72
+ const { data, isFetching, isError } = useApi({ dataProvider: provider, resource: resource, filters: filters, pagination: { pageSize: pageSize }, meta: meta });
73
+ const transformers = [];
74
+ /* Récuperer les fonctions transformers */
75
+ React.Children.toArray(children)
76
+ .filter((c) => React.isValidElement(c))
77
+ .filter((c) => typeof c.type != 'string' && (c.type.name == Transform.name || c.type.name == Join.name)).forEach((c) => {
78
+ transformers.push(getTransformerFn(c));
79
+ });
80
+ const producers = [];
81
+ React.Children.toArray(children)
82
+ .filter((c) => React.isValidElement(c))
83
+ .filter((c) => typeof c.type != 'string' && c.type.name == Producer.name).forEach((c) => {
84
+ producers.push({ nom: c.props.children, url: c.props.url });
85
+ });
86
+ useEffect(() => {
87
+ const finalData = data?.data && transformers.reduce((datat, fn) => fn(datat), data.data);
88
+ if (datasetRegistryContext) {
89
+ datasetRegistryContext({ id: id, resource: resource, data: finalData, isFetching: isFetching, isError: isError, producers: producers });
90
+ //Ajouter une info pour distinguer les erreurs du fourniseurs et celles des transformers ?
91
+ }
92
+ }, [resource, data, isFetching, someFetching, children]);
93
+ return (_jsx(_Fragment, { children: children }));
94
+ };
@@ -0,0 +1,9 @@
1
+ import { CrudOperators } from "../../data_providers/types";
2
+ interface IFilterProps {
3
+ field: string;
4
+ children?: string;
5
+ control?: string;
6
+ operator?: Exclude<CrudOperators, "or" | "and">;
7
+ }
8
+ export declare const DSL_Filter: React.FC<IFilterProps>;
9
+ export {};
@@ -0,0 +1,7 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Les props sont utilisées dans le composant parent <Dataset> pour appliquer un filtre distant
4
+ */
5
+ export const DSL_Filter = () => {
6
+ return _jsx(_Fragment, {});
7
+ };
@@ -0,0 +1,8 @@
1
+ export type joinTypeType = 'left' | 'right' | 'full' | 'inner';
2
+ interface IJoinProps {
3
+ joinKey: string | [string, string];
4
+ dataset: string;
5
+ joinType?: joinTypeType;
6
+ }
7
+ export declare const Join: React.FC<IJoinProps>;
8
+ export {};
@@ -0,0 +1,7 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Les props sont utilisées dans le Dataset parent pour appliquer la jointure
4
+ */
5
+ export const Join = () => {
6
+ return _jsx(_Fragment, {});
7
+ };