@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.
- package/README.MD +192 -0
- package/dist/components/Attributions/Attribution.test.d.ts +1 -0
- package/dist/components/Attributions/Attribution.test.js +19 -0
- package/dist/components/Attributions/Attributions.d.ts +17 -0
- package/dist/components/Attributions/Attributions.js +30 -0
- package/dist/components/Charts/ChartEcharts.d.ts +7 -0
- package/dist/components/Charts/ChartEcharts.js +35 -0
- package/dist/components/Charts/Pie.d.ts +10 -0
- package/dist/components/Charts/Pie.js +35 -0
- package/dist/components/Charts/YearSerie.d.ts +16 -0
- package/dist/components/Charts/YearSerie.js +70 -0
- package/dist/components/Control/Control.d.ts +21 -0
- package/dist/components/Control/Control.js +84 -0
- package/dist/components/Control/Radio.d.ts +19 -0
- package/dist/components/Control/Radio.js +27 -0
- package/dist/components/Control/Select.d.ts +15 -0
- package/dist/components/Control/Select.js +25 -0
- package/dist/components/DashboardChart/DashboardChart.d.ts +11 -0
- package/dist/components/DashboardChart/DashboardChart.js +42 -0
- package/dist/components/DashboardElement/DashboardElement.d.ts +21 -0
- package/dist/components/DashboardElement/DashboardElement.js +108 -0
- package/dist/components/DashboardElement/hooks.d.ts +28 -0
- package/dist/components/DashboardElement/hooks.js +29 -0
- package/dist/components/DashboardPage/Block.d.ts +15 -0
- package/dist/components/DashboardPage/Block.js +40 -0
- package/dist/components/DashboardPage/DashboardPage.test.d.ts +1 -0
- package/dist/components/DashboardPage/DashboardPage.test.js +40 -0
- package/dist/components/DashboardPage/Page.d.ts +42 -0
- package/dist/components/DashboardPage/Page.js +80 -0
- package/dist/components/Dataset/DataPreview.d.ts +12 -0
- package/dist/components/Dataset/DataPreview.js +21 -0
- package/dist/components/Dataset/Dataset.d.ts +17 -0
- package/dist/components/Dataset/Dataset.js +94 -0
- package/dist/components/Dataset/Filter.d.ts +9 -0
- package/dist/components/Dataset/Filter.js +7 -0
- package/dist/components/Dataset/Join.d.ts +8 -0
- package/dist/components/Dataset/Join.js +7 -0
- package/dist/components/Dataset/Producer.d.ts +18 -0
- package/dist/components/Dataset/Producer.js +27 -0
- package/dist/components/Dataset/Provider.d.ts +21 -0
- package/dist/components/Dataset/Provider.js +22 -0
- package/dist/components/Dataset/Transform.d.ts +6 -0
- package/dist/components/Dataset/Transform.js +7 -0
- package/dist/components/Dataset/hooks.d.ts +24 -0
- package/dist/components/Dataset/hooks.js +19 -0
- package/dist/components/Debug/Debug.d.ts +1 -0
- package/dist/components/Debug/Debug.js +24 -0
- package/dist/components/FlipCard/FlipCard.d.ts +12 -0
- package/dist/components/FlipCard/FlipCard.js +38 -0
- package/dist/components/FlipCard/FlipCard.test.d.ts +1 -0
- package/dist/components/FlipCard/FlipCard.test.js +36 -0
- package/dist/components/KeyFigure/KeyFigure.d.ts +18 -0
- package/dist/components/KeyFigure/KeyFigure.js +13 -0
- package/dist/components/Layout/DashboardApp.d.ts +18 -0
- package/dist/components/Layout/DashboardApp.js +46 -0
- package/dist/components/Layout/Error.d.ts +2 -0
- package/dist/components/Layout/Error.js +6 -0
- package/dist/components/Layout/Footer.d.ts +6 -0
- package/dist/components/Layout/Footer.js +47 -0
- package/dist/components/Layout/Sider.d.ts +9 -0
- package/dist/components/Layout/Sider.js +48 -0
- package/dist/components/LoadingContainer/LoadingContainer.d.ts +17 -0
- package/dist/components/LoadingContainer/LoadingContainer.js +33 -0
- package/dist/components/MapLegend/MapLegend.d.ts +12 -0
- package/dist/components/MapLegend/MapLegend.js +19 -0
- package/dist/components/NextPrevSelect/NextPrevSelect.d.ts +17 -0
- package/dist/components/NextPrevSelect/NextPrevSelect.js +49 -0
- package/dist/components/Palette/Palette.d.ts +18 -0
- package/dist/components/Palette/Palette.js +29 -0
- package/dist/data_providers/datafair/datafair.test.d.ts +1 -0
- package/dist/data_providers/datafair/datafair.test.js +39 -0
- package/dist/data_providers/datafair/index.d.ts +15 -0
- package/dist/data_providers/datafair/index.js +33 -0
- package/dist/data_providers/datafair/utils/axios.d.ts +2 -0
- package/dist/data_providers/datafair/utils/axios.js +13 -0
- package/dist/data_providers/datafair/utils/generateFilter.d.ts +1 -0
- package/dist/data_providers/datafair/utils/generateFilter.js +40 -0
- package/dist/data_providers/datafair/utils/generateSort.d.ts +1 -0
- package/dist/data_providers/datafair/utils/generateSort.js +19 -0
- package/dist/data_providers/datafair/utils/index.d.ts +4 -0
- package/dist/data_providers/datafair/utils/index.js +4 -0
- package/dist/data_providers/datafair/utils/mapOperator.d.ts +1 -0
- package/dist/data_providers/datafair/utils/mapOperator.js +13 -0
- package/dist/data_providers/file/index.d.ts +6 -0
- package/dist/data_providers/file/index.js +25 -0
- package/dist/data_providers/file/utils/axios.d.ts +2 -0
- package/dist/data_providers/file/utils/axios.js +26 -0
- package/dist/data_providers/types.d.ts +52 -0
- package/dist/data_providers/types.js +2 -0
- package/dist/data_providers/wfs/index.d.ts +3 -0
- package/dist/data_providers/wfs/index.js +43 -0
- package/dist/data_providers/wfs/utils/axios.d.ts +2 -0
- package/dist/data_providers/wfs/utils/axios.js +26 -0
- package/dist/data_providers/wfs/utils/generateFilter.d.ts +4 -0
- package/dist/data_providers/wfs/utils/generateFilter.js +45 -0
- package/dist/data_providers/wfs/utils/generateSort.d.ts +1 -0
- package/dist/data_providers/wfs/utils/generateSort.js +20 -0
- package/dist/data_providers/wfs/utils/index.d.ts +4 -0
- package/dist/data_providers/wfs/utils/index.js +4 -0
- package/dist/data_providers/wfs/utils/mapOperator.d.ts +1 -0
- package/dist/data_providers/wfs/utils/mapOperator.js +36 -0
- package/dist/data_providers/wfs/wfs.test.d.ts +1 -0
- package/dist/data_providers/wfs/wfs.test.js +60 -0
- package/dist/dsl/index.d.ts +19 -0
- package/dist/dsl/index.js +19 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +33 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.js +1 -0
- package/dist/utils/baserecordtogeojsonpoint.d.ts +13 -0
- package/dist/utils/baserecordtogeojsonpoint.js +17 -0
- package/dist/utils/cardStyles.d.ts +2 -0
- package/dist/utils/cardStyles.js +12 -0
- package/dist/utils/deepmerge.d.ts +2 -0
- package/dist/utils/deepmerge.js +24 -0
- package/dist/utils/route_utils.d.ts +6 -0
- package/dist/utils/route_utils.js +14 -0
- package/dist/utils/useApi.d.ts +12 -0
- package/dist/utils/useApi.js +14 -0
- package/dist/utils/useMapControl.d.ts +8 -0
- package/dist/utils/useMapControl.js +24 -0
- package/dist/utils/useSearchParamsState.d.ts +11 -0
- package/dist/utils/useSearchParamsState.js +18 -0
- package/dist/utils/usechartexports.d.ts +17 -0
- package/dist/utils/usechartexports.js +39 -0
- package/dist/utils/usecharthightlight.d.ts +25 -0
- package/dist/utils/usecharthightlight.js +44 -0
- package/package.json +98 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CaretLeftOutlined, CaretRightOutlined } from "@ant-design/icons";
|
|
3
|
+
import { Button, ConfigProvider, Flex, Form, Select } from "antd";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { list_to_options } from '../Control/Control';
|
|
6
|
+
// Update field and trigger form OnValueChange, thanks to : https://github.com/ant-design/ant-design/issues/23782#issuecomment-2114700558
|
|
7
|
+
const updateFieldValue = (form, name, value) => {
|
|
8
|
+
form.getInternalHooks('RC_FORM_INTERNAL_HOOKS').dispatch({
|
|
9
|
+
type: 'updateValue',
|
|
10
|
+
namePath: [name],
|
|
11
|
+
value: value
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const style_button_left = {
|
|
15
|
+
borderRadius: 0,
|
|
16
|
+
borderTopLeftRadius: 4,
|
|
17
|
+
borderBottomLeftRadius: 4,
|
|
18
|
+
borderRight: 0,
|
|
19
|
+
};
|
|
20
|
+
const style_button_right = {
|
|
21
|
+
borderRadius: 0,
|
|
22
|
+
borderTopRightRadius: 4,
|
|
23
|
+
borderBottomRightRadius: 4,
|
|
24
|
+
borderLeft: 0,
|
|
25
|
+
};
|
|
26
|
+
const NextPrevSelect = ({ name, options: input_options = [], style, value, defaultValue, onChange, reverse = false, arrows = true, ...rest }) => {
|
|
27
|
+
const [current_value, setCurrent_value] = useState(value);
|
|
28
|
+
const form = Form.useFormInstance();
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setCurrent_value(value);
|
|
31
|
+
}, [value]);
|
|
32
|
+
const options = list_to_options(input_options);
|
|
33
|
+
const current_index = options?.findIndex((o) => o.value == form?.getFieldValue(name) || o.value == current_value);
|
|
34
|
+
const next = () => reverse
|
|
35
|
+
? options[current_index - 1].value
|
|
36
|
+
: options[current_index + 1].value;
|
|
37
|
+
const previous = () => reverse
|
|
38
|
+
? options[current_index + 1].value
|
|
39
|
+
: options[current_index - 1].value;
|
|
40
|
+
const isFirst = () => reverse ? current_index == options.length - 1 : current_index == 0;
|
|
41
|
+
const isLast = () => reverse ? current_index == 0 : current_index == options.length - 1;
|
|
42
|
+
const handleChange = (v) => {
|
|
43
|
+
setCurrent_value(v);
|
|
44
|
+
name && form && updateFieldValue(form, name, v);
|
|
45
|
+
onChange && onChange(v);
|
|
46
|
+
};
|
|
47
|
+
return (_jsxs(Flex, { style: style, name: name, children: [arrows && _jsx(Button, { style: style_button_left, onClick: () => handleChange(previous()), disabled: isFirst(), children: _jsx(CaretLeftOutlined, {}) }), _jsxs(ConfigProvider, { theme: arrows ? { components: { Select: { borderRadius: 0, }, }, } : undefined, children: [" ", _jsx(Form.Item, { name: name, label: name, noStyle: arrows, initialValue: defaultValue, shouldUpdate: true, children: _jsx(Select, { className: "nextPrevSelect", options: options, style: { ...style }, value: current_value, defaultValue: defaultValue, onChange: handleChange, ...rest }) })] }), arrows && _jsx(Button, { style: style_button_right, onClick: () => handleChange(next()), disabled: isLast(), children: _jsx(CaretRightOutlined, {}) })] }));
|
|
48
|
+
};
|
|
49
|
+
export default NextPrevSelect;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const DEFAULT_PALETTE: PaletteType;
|
|
2
|
+
type PaletteModeType = 'rgb' | 'hsl' | 'lab' | 'lrgb' | 'lch';
|
|
3
|
+
export interface PaletteType {
|
|
4
|
+
steps?: string[];
|
|
5
|
+
mode?: PaletteModeType;
|
|
6
|
+
}
|
|
7
|
+
type PaletteContextType = {
|
|
8
|
+
palette: PaletteType;
|
|
9
|
+
setPalette: (p: PaletteType) => void;
|
|
10
|
+
};
|
|
11
|
+
export declare const PaletteContext: import("react").Context<PaletteContextType | undefined>;
|
|
12
|
+
interface UsePaletteProps {
|
|
13
|
+
nColors?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const usePalette: ({ nColors }: UsePaletteProps) => string[] | undefined;
|
|
16
|
+
export declare const Palette: React.FC<PaletteType>;
|
|
17
|
+
export declare const PalettePreview: React.FC;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Tag } from "antd";
|
|
3
|
+
import chroma from "chroma-js";
|
|
4
|
+
import { createContext, useContext, useEffect } from "react";
|
|
5
|
+
export const DEFAULT_PALETTE = { steps: ['red', 'green', 'blue'], mode: 'hsl' };
|
|
6
|
+
export const PaletteContext = createContext({ palette: DEFAULT_PALETTE, setPalette: () => { } });
|
|
7
|
+
export const usePalette = ({ nColors }) => {
|
|
8
|
+
const palette_context = useContext(PaletteContext);
|
|
9
|
+
const palette_info = palette_context?.palette;
|
|
10
|
+
if (nColors === undefined) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return palette_info?.steps && chroma.scale(palette_info.steps).mode(palette_info.mode || 'hsl').colors(nColors);
|
|
14
|
+
};
|
|
15
|
+
/*
|
|
16
|
+
* Composant permettant à l'utilisateur de définir une palette pour sa page
|
|
17
|
+
*/
|
|
18
|
+
export const Palette = ({ steps, mode }) => {
|
|
19
|
+
const palette = { steps, mode };
|
|
20
|
+
const palette_context = useContext(PaletteContext);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
palette_context?.setPalette(palette);
|
|
23
|
+
}, [steps]);
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
export const PalettePreview = ({}) => {
|
|
27
|
+
const colors = usePalette({ nColors: 10 }) || [];
|
|
28
|
+
return (_jsx(_Fragment, { children: colors.map((color) => (_jsx(Tag, { color: color, children: color }, color))) }));
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { DatafairProvider } from '../..';
|
|
4
|
+
const test_provider_url = "https://data.ademe.fr/data-fair/api/v1/datasets";
|
|
5
|
+
const test_page_size = 3;
|
|
6
|
+
const test_filter_field = "L_TYP_REG_SERVICE";
|
|
7
|
+
const test_filter_value = "Valorisation organique";
|
|
8
|
+
const resource = "sinoe-(r)-destination-des-dma-collectes-par-type-de-traitement/lines";
|
|
9
|
+
const filters = [
|
|
10
|
+
{
|
|
11
|
+
field: test_filter_field,
|
|
12
|
+
operator: "eq",
|
|
13
|
+
value: test_filter_value,
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const pagination = {
|
|
17
|
+
mode: "server",
|
|
18
|
+
pageSize: test_page_size,
|
|
19
|
+
};
|
|
20
|
+
const sorters = undefined;
|
|
21
|
+
const queryClient = new QueryClient();
|
|
22
|
+
describe("DataFair Provider", () => {
|
|
23
|
+
test("basic filter & pagination", async () => {
|
|
24
|
+
const provider = DatafairProvider(test_provider_url);
|
|
25
|
+
const queryKey = [provider.getApiUrl, resource, filters, pagination, sorters, undefined];
|
|
26
|
+
const result = await queryClient.fetchQuery({
|
|
27
|
+
queryKey: queryKey,
|
|
28
|
+
queryFn: () => provider.getList({
|
|
29
|
+
resource: resource,
|
|
30
|
+
filters: filters,
|
|
31
|
+
pagination: pagination,
|
|
32
|
+
sorters: sorters
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
expect(result).toHaveProperty('data');
|
|
36
|
+
expect(result.data).toHaveLength(test_page_size);
|
|
37
|
+
expect(result.data[0][test_filter_field]).toEqual(test_filter_value);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AxiosInstance } from "axios";
|
|
2
|
+
export interface GetListParams {
|
|
3
|
+
resource: string;
|
|
4
|
+
pagination?: any;
|
|
5
|
+
filters?: any[];
|
|
6
|
+
sorters?: any[];
|
|
7
|
+
meta?: any;
|
|
8
|
+
}
|
|
9
|
+
export declare const dataProvider: (apiUrl: string, httpClient?: AxiosInstance) => {
|
|
10
|
+
getList: ({ resource, pagination, filters, sorters, meta }: GetListParams) => Promise<{
|
|
11
|
+
data: any;
|
|
12
|
+
total: any;
|
|
13
|
+
}>;
|
|
14
|
+
getApiUrl: () => string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { axiosInstance, generateSort, generateFilter } from "./utils";
|
|
2
|
+
import queryString from "query-string";
|
|
3
|
+
export const dataProvider = (apiUrl, httpClient = axiosInstance) => ({
|
|
4
|
+
getList: async ({ resource, pagination, filters, sorters, meta }) => {
|
|
5
|
+
const url = `${apiUrl}/${resource}`; //Ajouter /line par défaut ? (si aucun autre /machin)
|
|
6
|
+
const { current = 1, pageSize = 10, mode = "server" } = pagination ?? {};
|
|
7
|
+
const { headers: headersFromMeta, method } = meta ?? {};
|
|
8
|
+
const requestMethod = method ?? "get";
|
|
9
|
+
const queryFilters = generateFilter(filters);
|
|
10
|
+
const query = {};
|
|
11
|
+
if (mode === "server") {
|
|
12
|
+
query.page = current;
|
|
13
|
+
query.size = pageSize;
|
|
14
|
+
}
|
|
15
|
+
const generatedSort = generateSort(sorters);
|
|
16
|
+
if (generatedSort) {
|
|
17
|
+
query.sort = generatedSort;
|
|
18
|
+
}
|
|
19
|
+
if (queryFilters) {
|
|
20
|
+
query.qs = queryFilters;
|
|
21
|
+
}
|
|
22
|
+
const { data } = await httpClient[requestMethod](`${url}?${queryString.stringify(query)}`, {
|
|
23
|
+
headers: headersFromMeta,
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
data: data.results,
|
|
27
|
+
total: data.total,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
getApiUrl: () => {
|
|
31
|
+
return apiUrl;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
const axiosInstance = axios.create();
|
|
3
|
+
axiosInstance.interceptors.response.use((response) => {
|
|
4
|
+
return response;
|
|
5
|
+
}, (error) => {
|
|
6
|
+
const customError = {
|
|
7
|
+
...error,
|
|
8
|
+
message: error.response?.data?.message,
|
|
9
|
+
statusCode: error.response?.status,
|
|
10
|
+
};
|
|
11
|
+
return Promise.reject(customError);
|
|
12
|
+
});
|
|
13
|
+
export { axiosInstance };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateFilter: (filters?: any[]) => string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const generateFilter = (filters) => {
|
|
2
|
+
const array_filter = [];
|
|
3
|
+
if (filters) {
|
|
4
|
+
filters.map((filter) => {
|
|
5
|
+
if (filter.operator !== "or" && filter.operator !== "and" && "field" in filter) { // LogicalFilter
|
|
6
|
+
const value = (() => {
|
|
7
|
+
switch (filter.operator) {
|
|
8
|
+
case "contains":
|
|
9
|
+
return filter.value;
|
|
10
|
+
case "eq":
|
|
11
|
+
case "ne":
|
|
12
|
+
return `"${filter.value}"`;
|
|
13
|
+
case "startswith":
|
|
14
|
+
return `${filter.value}*`;
|
|
15
|
+
case "endswith":
|
|
16
|
+
return `*${filter.value}`;
|
|
17
|
+
case "in":
|
|
18
|
+
return filter.value.join(' ');
|
|
19
|
+
}
|
|
20
|
+
})();
|
|
21
|
+
const not = (() => {
|
|
22
|
+
switch (filter.operator) {
|
|
23
|
+
case "ne":
|
|
24
|
+
case "nstartswith":
|
|
25
|
+
case "nendswith":
|
|
26
|
+
case "nin":
|
|
27
|
+
return 'NOT ';
|
|
28
|
+
default:
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
array_filter.push(`(${not}${filter.field}:${value})`);
|
|
33
|
+
}
|
|
34
|
+
else { //Conditionnal filter
|
|
35
|
+
throw new Error(`[datafair-data-provider]: Condtionnal filter 'OR' not implemented yet `);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return array_filter.join(' AND ');
|
|
40
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateSort: (sorters?: any[]) => string | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const generateSort = (sorters) => {
|
|
2
|
+
if (sorters && sorters.length > 0) {
|
|
3
|
+
const _sort = [];
|
|
4
|
+
sorters.map((item) => {
|
|
5
|
+
const order = (() => {
|
|
6
|
+
switch (item.order) {
|
|
7
|
+
case 'desc':
|
|
8
|
+
return '-';
|
|
9
|
+
case 'asc':
|
|
10
|
+
default:
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
14
|
+
_sort.push(`${order}${item.field}`);
|
|
15
|
+
});
|
|
16
|
+
return _sort.join(',');
|
|
17
|
+
}
|
|
18
|
+
return;
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const mapOperator: (operator: any) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { axiosInstance } from "./utils/axios";
|
|
2
|
+
// Ajouter la possibilite de passer au provider une fonction pour parser le résultat (ex : (e) => e.result)
|
|
3
|
+
export const dataProvider = (apiUrl, options) => {
|
|
4
|
+
const { httpClient = axiosInstance, processData } = options || {};
|
|
5
|
+
return {
|
|
6
|
+
getList: async ({ resource, pagination, filters, sorters, meta }) => {
|
|
7
|
+
const url = `${apiUrl}/${resource}`; // (ressource = url + nom du fichier + extension)
|
|
8
|
+
const { headers: headersFromMeta, method } = meta ?? {};
|
|
9
|
+
const requestMethod = method ?? "get";
|
|
10
|
+
const { data, headers: _headers } = await httpClient[requestMethod](`${url}`, {
|
|
11
|
+
headers: headersFromMeta,
|
|
12
|
+
});
|
|
13
|
+
const out_data = processData ? processData(data) : data;
|
|
14
|
+
//const features = //papaparse data
|
|
15
|
+
return {
|
|
16
|
+
data: out_data, //Parser ici avec la fonction utilisateur
|
|
17
|
+
total: out_data.length,
|
|
18
|
+
// Ajouter le fichier brut ?
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
getApiUrl: () => {
|
|
22
|
+
return apiUrl;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
const axiosInstance = axios.create();
|
|
3
|
+
axiosInstance.interceptors.response.use(
|
|
4
|
+
// API WFS retourne toujours un code http 200, y compris en cas d'erreur.
|
|
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
|
+
}
|
|
18
|
+
}, (error) => {
|
|
19
|
+
const customError = {
|
|
20
|
+
...error,
|
|
21
|
+
message: error.response?.data?.message,
|
|
22
|
+
statusCode: error.response?.status,
|
|
23
|
+
};
|
|
24
|
+
return Promise.reject(customError);
|
|
25
|
+
});
|
|
26
|
+
export { axiosInstance };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface Pagination {
|
|
2
|
+
/**
|
|
3
|
+
* Initial page index
|
|
4
|
+
* @default 1
|
|
5
|
+
*/
|
|
6
|
+
current?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Initial number of items per page
|
|
9
|
+
* @default 10
|
|
10
|
+
*/
|
|
11
|
+
pageSize?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Whether to use server side pagination or not.
|
|
14
|
+
* @default "server"
|
|
15
|
+
*/
|
|
16
|
+
mode?: "client" | "server" | "off";
|
|
17
|
+
}
|
|
18
|
+
export type CrudOperators = "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in" | "nin" | "ina" | "nina" | "contains" | "ncontains" | "containss" | "ncontainss" | "between" | "nbetween" | "null" | "nnull" | "startswith" | "nstartswith" | "startswiths" | "nstartswiths" | "endswith" | "nendswith" | "endswiths" | "nendswiths" | "or" | "and";
|
|
19
|
+
export type SortOrder = "desc" | "asc" | null;
|
|
20
|
+
export type LogicalFilter = {
|
|
21
|
+
field: string;
|
|
22
|
+
operator: Exclude<CrudOperators, "or" | "and">;
|
|
23
|
+
value: any;
|
|
24
|
+
};
|
|
25
|
+
export type ConditionalFilter = {
|
|
26
|
+
key?: string;
|
|
27
|
+
operator: Extract<CrudOperators, "or" | "and">;
|
|
28
|
+
value: (LogicalFilter | ConditionalFilter)[];
|
|
29
|
+
};
|
|
30
|
+
export type CrudFilter = LogicalFilter | ConditionalFilter;
|
|
31
|
+
export type CrudSort = {
|
|
32
|
+
field: string;
|
|
33
|
+
order: "asc" | "desc";
|
|
34
|
+
};
|
|
35
|
+
export type CrudFilters = CrudFilter[];
|
|
36
|
+
export type CrudSorting = CrudSort[];
|
|
37
|
+
export interface GetListResponse {
|
|
38
|
+
data: any[];
|
|
39
|
+
total: number;
|
|
40
|
+
geojson?: any;
|
|
41
|
+
[key: string]: any;
|
|
42
|
+
}
|
|
43
|
+
export interface DataProvider {
|
|
44
|
+
getApiUrl: () => string;
|
|
45
|
+
getList: (params: {
|
|
46
|
+
resource: string;
|
|
47
|
+
filters?: CrudFilters;
|
|
48
|
+
pagination?: Pagination;
|
|
49
|
+
sorters?: CrudSorting;
|
|
50
|
+
meta?: any;
|
|
51
|
+
}) => Promise<GetListResponse>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { axiosInstance, generateSort, generateFilter } from "./utils";
|
|
2
|
+
import queryString from "query-string";
|
|
3
|
+
export const dataProvider = (apiUrl, httpClient = axiosInstance) => ({
|
|
4
|
+
getList: async ({ resource, pagination, filters, sorters, meta }) => {
|
|
5
|
+
const url = `${apiUrl}/`;
|
|
6
|
+
const { current = 1, pageSize = 10, mode = "off" } = pagination ?? {};
|
|
7
|
+
const { headers: headersFromMeta, method } = meta ?? {};
|
|
8
|
+
const requestMethod = method ?? "get";
|
|
9
|
+
const { cql_filter: queryFilters, bbox } = generateFilter(filters);
|
|
10
|
+
const generatedSort = generateSort(sorters);
|
|
11
|
+
const query = { service: 'WFS', request: 'GetFeature', sortby: '', version: '2.0.0', outputformat: 'application/json', typenames: resource,
|
|
12
|
+
srsname: meta?.srsname, propertyname: meta?.properties?.join(',') };
|
|
13
|
+
if (mode === "server") {
|
|
14
|
+
query.startindex = (current - 1) * pageSize;
|
|
15
|
+
query.count = pageSize;
|
|
16
|
+
}
|
|
17
|
+
if (generatedSort) {
|
|
18
|
+
query.sortby = generatedSort;
|
|
19
|
+
}
|
|
20
|
+
if (queryFilters) {
|
|
21
|
+
query.cql_filter = queryFilters;
|
|
22
|
+
}
|
|
23
|
+
if (bbox !== '') {
|
|
24
|
+
query.bbox = bbox;
|
|
25
|
+
}
|
|
26
|
+
const { data, headers: _headers } = await httpClient[requestMethod](`${url}?${queryString.stringify({ ...query, sortby: undefined })}&sortby=${query.sortby}&`, //"le + de sortby ne doit pas être urlencode"
|
|
27
|
+
{
|
|
28
|
+
headers: headersFromMeta,
|
|
29
|
+
});
|
|
30
|
+
const features = data.features.map((feature) => {
|
|
31
|
+
const { properties, type, ...rest } = feature; //Remonter d'un niveau les properties, supprimer root.type
|
|
32
|
+
return { ...rest, ...properties };
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
data: features,
|
|
36
|
+
geojson: data,
|
|
37
|
+
total: data.numberMatched,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
getApiUrl: () => {
|
|
41
|
+
return apiUrl;
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
const axiosInstance = axios.create();
|
|
3
|
+
axiosInstance.interceptors.response.use(
|
|
4
|
+
// API WFS retourne toujours un code http 200, y compris en cas d'erreur.
|
|
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
|
+
}
|
|
18
|
+
}, (error) => {
|
|
19
|
+
const customError = {
|
|
20
|
+
...error,
|
|
21
|
+
message: error.response?.data?.message,
|
|
22
|
+
statusCode: error.response?.status,
|
|
23
|
+
};
|
|
24
|
+
return Promise.reject(customError);
|
|
25
|
+
});
|
|
26
|
+
export { axiosInstance };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mapOperator } from "./mapOperator";
|
|
2
|
+
export const generateFilter = (filters) => {
|
|
3
|
+
const array_filter = [];
|
|
4
|
+
let bbox = '';
|
|
5
|
+
if (filters) {
|
|
6
|
+
filters.map((filter) => {
|
|
7
|
+
if (filter.operator !== "or" && filter.operator !== "and" && "field" in filter) { // LogicalFilter
|
|
8
|
+
const mappedOperator = mapOperator(filter.operator);
|
|
9
|
+
if (filter.field === "geometry") {
|
|
10
|
+
bbox = filter.value;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
const value = (() => {
|
|
14
|
+
switch (filter.operator) {
|
|
15
|
+
case "contains":
|
|
16
|
+
case "containss":
|
|
17
|
+
case "ncontains":
|
|
18
|
+
case "ncontainss":
|
|
19
|
+
return `'%${filter.value}%'`;
|
|
20
|
+
case "startswith":
|
|
21
|
+
case "startswiths":
|
|
22
|
+
case "nstartswith":
|
|
23
|
+
case "nstartswiths":
|
|
24
|
+
return `'${filter.value}%'`;
|
|
25
|
+
case "endswith":
|
|
26
|
+
case "endswiths":
|
|
27
|
+
case "nendswith":
|
|
28
|
+
case "nendswiths":
|
|
29
|
+
return `'%${filter.value}'`;
|
|
30
|
+
case "in":
|
|
31
|
+
return `(${filter.value.map((i) => `'${i}'`).join(',')})`;
|
|
32
|
+
default:
|
|
33
|
+
return `'${filter.value}'`;
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
array_filter.push(`${filter.field} ${mappedOperator} ${value}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else { //Conditionnal filter
|
|
40
|
+
throw new Error(`[wfs-data-provider]: Condtionnal filter 'OR' not implemented yet `);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return { cql_filter: array_filter.join(' and '), bbox: bbox };
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateSort: (sorters?: any[]) => string | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const generateSort = (sorters) => {
|
|
2
|
+
if (sorters && sorters.length > 0) {
|
|
3
|
+
const _sort = [];
|
|
4
|
+
sorters.map((item) => {
|
|
5
|
+
const order = (() => {
|
|
6
|
+
switch (item.order) {
|
|
7
|
+
case 'asc':
|
|
8
|
+
return 'A';
|
|
9
|
+
case 'desc':
|
|
10
|
+
return 'D';
|
|
11
|
+
default:
|
|
12
|
+
return 'A';
|
|
13
|
+
}
|
|
14
|
+
})();
|
|
15
|
+
_sort.push(`${item.field}+${order}`);
|
|
16
|
+
});
|
|
17
|
+
return _sort.join(',');
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const mapOperator: (operator: any) => string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const mapOperator = (operator) => {
|
|
2
|
+
switch (operator) {
|
|
3
|
+
case "ne":
|
|
4
|
+
return '<>';
|
|
5
|
+
case "gte":
|
|
6
|
+
return '>=';
|
|
7
|
+
case "gt":
|
|
8
|
+
return '>';
|
|
9
|
+
case "lte":
|
|
10
|
+
return `<=`;
|
|
11
|
+
case "lt":
|
|
12
|
+
return `<`;
|
|
13
|
+
case "eq":
|
|
14
|
+
return "=";
|
|
15
|
+
case "contains":
|
|
16
|
+
case "startswith":
|
|
17
|
+
case "endswith":
|
|
18
|
+
return "ilike";
|
|
19
|
+
case "containss":
|
|
20
|
+
case "startswiths":
|
|
21
|
+
case "endswiths":
|
|
22
|
+
return "like";
|
|
23
|
+
case "ncontains":
|
|
24
|
+
case "nstartswith":
|
|
25
|
+
case "nendswith":
|
|
26
|
+
return "not ilike";
|
|
27
|
+
case "ncontainss":
|
|
28
|
+
case "nstartswiths":
|
|
29
|
+
case "nendswiths":
|
|
30
|
+
return "not like";
|
|
31
|
+
case "in":
|
|
32
|
+
return operator;
|
|
33
|
+
default:
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|