@datagouv/components-next 1.0.2-dev.4 → 1.0.2-dev.40
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/Datafair.client-CYO9vwx6.js +30 -0
- package/dist/JsonPreview.client-B6aU3vl4.js +78 -0
- package/dist/{MapContainer.client-DjjvdKBp.js → MapContainer.client-BZsKgRUh.js} +35 -38
- package/dist/{PdfPreview.client-CsvKU0Aq.js → PdfPreview.client-ClkseuKU.js} +694 -700
- package/dist/{Pmtiles.client-uqg1fwOl.js → Pmtiles.client-CUaeaV-O.js} +574 -579
- package/dist/Swagger.client-FpYXdDuX.js +4 -0
- package/dist/XmlPreview.client-BNGHvVnU.js +70 -0
- package/dist/components-next.css +3 -3
- package/dist/components-next.js +83 -86
- package/dist/components.css +1 -1
- package/dist/{index-PMeuFwWj.js → index-B0fPq7-b.js} +1 -1
- package/dist/{main-ByqZlhiZ.js → main-ifX24DGW.js} +31224 -30474
- package/dist/{vue3-xml-viewer.common-DFrGHXJC.js → vue3-xml-viewer.common-Bkgr-tAS.js} +1 -1
- package/package.json +10 -8
- package/src/components/ActivityList/ActivityList.vue +0 -2
- package/src/components/Form/SearchableSelect.vue +2 -1
- package/src/components/Pagination.vue +8 -5
- package/src/components/ReadMore.vue +1 -1
- package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
- package/src/components/ResourceAccordion/JsonPreview.client.vue +34 -47
- package/src/components/ResourceAccordion/MapContainer.client.vue +7 -11
- package/src/components/ResourceAccordion/Metadata.vue +1 -2
- package/src/components/ResourceAccordion/PdfPreview.client.vue +28 -32
- package/src/components/ResourceAccordion/Pmtiles.client.vue +5 -10
- package/src/components/ResourceAccordion/Preview.vue +6 -11
- package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
- package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +1 -2
- package/src/components/ResourceAccordion/XmlPreview.client.vue +34 -47
- package/src/components/ResourceExplorer/ResourceExplorer.vue +21 -10
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +24 -3
- package/src/components/Search/GlobalSearch.vue +29 -4
- package/src/composables/useResourceCapabilities.ts +1 -1
- package/src/config.ts +2 -0
- package/src/functions/datasets.ts +0 -17
- package/src/functions/resources.ts +56 -1
- package/src/functions/tabularApi.ts +7 -84
- package/src/main.ts +2 -22
- package/src/types/dataservices.ts +2 -0
- package/src/types/organizations.ts +1 -1
- package/src/types/reports.ts +3 -0
- package/src/types/search.ts +26 -1
- package/src/types/users.ts +0 -1
- package/dist/Datafair.client-c1cUKkQR.js +0 -35
- package/dist/JsonPreview.client-CAs9XTCX.js +0 -87
- package/dist/Swagger.client-BGrkka3l.js +0 -4
- package/dist/XmlPreview.client-BWbKzLte.js +0 -79
- package/src/components/Chart/ChartViewer.vue +0 -152
- package/src/components/Chart/ChartViewerWrapper.vue +0 -194
- package/src/functions/pagination.ts +0 -9
- package/src/types/visualizations.ts +0 -84
- /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
package/src/types/search.ts
CHANGED
|
@@ -325,7 +325,16 @@ export type OrganizationSearchConfig = {
|
|
|
325
325
|
sortOptions?: SortOption<OrganizationSearchSort>[]
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
export type
|
|
328
|
+
export type TopicSearchConfig = {
|
|
329
|
+
class: 'topics'
|
|
330
|
+
name?: string
|
|
331
|
+
hiddenFilters?: HiddenFilter<TopicSearchFilters>[]
|
|
332
|
+
basicFilters?: (keyof TopicSearchFilters)[]
|
|
333
|
+
advancedFilters?: (keyof TopicSearchFilters)[]
|
|
334
|
+
sortOptions?: SortOption<TopicSearchSort>[]
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export type SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig | OrganizationSearchConfig | TopicSearchConfig
|
|
329
338
|
|
|
330
339
|
export type SearchType = SearchTypeConfig['class']
|
|
331
340
|
|
|
@@ -397,11 +406,27 @@ export function getDefaultOrganizationConfig(overrides?: Partial<Omit<Organizati
|
|
|
397
406
|
}
|
|
398
407
|
}
|
|
399
408
|
|
|
409
|
+
export const defaultTopicSortOptions: SortOption<TopicSearchSort>[] = [
|
|
410
|
+
{ value: '-created', label: 'Date de création' },
|
|
411
|
+
{ value: '-last_modified', label: 'Dernière mise à jour' },
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
export function getDefaultTopicConfig(overrides?: Partial<Omit<TopicSearchConfig, 'class'>>): TopicSearchConfig {
|
|
415
|
+
return {
|
|
416
|
+
class: 'topics',
|
|
417
|
+
basicFilters: ['last_update_range', 'producer_type'],
|
|
418
|
+
advancedFilters: ['organization', 'tag'],
|
|
419
|
+
sortOptions: defaultTopicSortOptions,
|
|
420
|
+
...overrides,
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
400
424
|
export function getDefaultGlobalSearchConfig(): GlobalSearchConfig {
|
|
401
425
|
return [
|
|
402
426
|
getDefaultDatasetConfig(),
|
|
403
427
|
getDefaultDataserviceConfig(),
|
|
404
428
|
getDefaultReuseConfig(),
|
|
405
429
|
getDefaultOrganizationConfig(),
|
|
430
|
+
getDefaultTopicConfig(),
|
|
406
431
|
]
|
|
407
432
|
}
|
package/src/types/users.ts
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { defineComponent as f, computed as a, createElementBlock as o, openBlock as t, createBlock as p, createElementVNode as i, withCtx as m, createVNode as _, unref as c, toDisplayString as x } from "vue";
|
|
2
|
-
import { a as h, _ as v, F as g } from "./main-ByqZlhiZ.js";
|
|
3
|
-
const k = { class: "fr-text--xs" }, b = { key: 0 }, y = ["src"], B = /* @__PURE__ */ f({
|
|
4
|
-
__name: "Datafair.client",
|
|
5
|
-
props: {
|
|
6
|
-
resource: {},
|
|
7
|
-
dataset: {}
|
|
8
|
-
},
|
|
9
|
-
setup(d) {
|
|
10
|
-
const e = d, { t: l } = h(), r = a(() => e.resource.extras.datafairOrigin || e.dataset.extras.datafairOrigin), s = a(() => e.resource.extras.datafairDatasetId || e.dataset.extras.datafairDatasetId), u = a(() => e.resource.extras.datafairEmbed), n = a(() => !r.value || !s.value ? null : `${r.value}/embed/dataset/${s.value}/${u.value}`);
|
|
11
|
-
return (D, E) => (t(), o("div", k, [
|
|
12
|
-
n.value ? (t(), o("div", b, [
|
|
13
|
-
i("iframe", {
|
|
14
|
-
src: n.value,
|
|
15
|
-
width: "100%",
|
|
16
|
-
height: "500px",
|
|
17
|
-
style: { "background-color": "transparent", border: "none" }
|
|
18
|
-
}, null, 8, y)
|
|
19
|
-
])) : (t(), p(v, {
|
|
20
|
-
key: 1,
|
|
21
|
-
type: "warning",
|
|
22
|
-
class: "flex items-center space-x-2"
|
|
23
|
-
}, {
|
|
24
|
-
default: m(() => [
|
|
25
|
-
_(c(g), { class: "shrink-0 size-6" }),
|
|
26
|
-
i("span", null, x(c(l)("Erreur lors de l'affichage de l'aperçu.")), 1)
|
|
27
|
-
]),
|
|
28
|
-
_: 1
|
|
29
|
-
}))
|
|
30
|
-
]));
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
export {
|
|
34
|
-
B as default
|
|
35
|
-
};
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { defineComponent as S, defineAsyncComponent as b, ref as o, computed as w, onMounted as N, createElementBlock as p, openBlock as r, createBlock as f, createCommentVNode as T, createVNode as u, unref as t, toDisplayString as i, withCtx as d, createElementVNode as h } from "vue";
|
|
2
|
-
import { u as P, a as E, g as q, _ as m, F as v } from "./main-ByqZlhiZ.js";
|
|
3
|
-
const B = { class: "fr-text--xs" }, O = { key: 0 }, V = {
|
|
4
|
-
key: 1,
|
|
5
|
-
class: "text-gray-medium"
|
|
6
|
-
}, L = /* @__PURE__ */ S({
|
|
7
|
-
__name: "JsonPreview.client",
|
|
8
|
-
props: {
|
|
9
|
-
resource: {}
|
|
10
|
-
},
|
|
11
|
-
setup(k) {
|
|
12
|
-
const z = b(
|
|
13
|
-
() => import("./vue3-json-viewer-BXwup7nO.js").then((e) => (Promise.resolve({ }), e.JsonViewer))
|
|
14
|
-
), g = k, x = P(), { t: s } = E(), l = o(null), c = o(!1), n = o(null), _ = o(!1), y = w(() => q(g.resource)), J = w(() => {
|
|
15
|
-
const e = y.value;
|
|
16
|
-
if (!e || !x.maxJsonPreviewCharSize)
|
|
17
|
-
return !1;
|
|
18
|
-
const a = x.maxJsonPreviewCharSize;
|
|
19
|
-
return e <= a;
|
|
20
|
-
}), C = async () => {
|
|
21
|
-
if (!J.value) {
|
|
22
|
-
_.value = !0;
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
c.value = !0, n.value = null;
|
|
26
|
-
try {
|
|
27
|
-
const e = await fetch(g.resource.url);
|
|
28
|
-
if (!e.ok)
|
|
29
|
-
throw new Error(`HTTP error! status: ${e.status}`);
|
|
30
|
-
const a = await e.json();
|
|
31
|
-
l.value = a;
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error("Error loading JSON:", e), e instanceof TypeError ? n.value = "network" : n.value = "generic", l.value = null;
|
|
34
|
-
} finally {
|
|
35
|
-
c.value = !1;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
return N(() => {
|
|
39
|
-
C();
|
|
40
|
-
}), (e, a) => (r(), p("div", B, [
|
|
41
|
-
l.value ? (r(), p("div", O, [
|
|
42
|
-
u(t(z), {
|
|
43
|
-
value: l.value,
|
|
44
|
-
boxed: "",
|
|
45
|
-
sort: "",
|
|
46
|
-
theme: "light",
|
|
47
|
-
"max-depth": 3,
|
|
48
|
-
"expand-depth": 2,
|
|
49
|
-
"indent-width": 2
|
|
50
|
-
}, null, 8, ["value"])
|
|
51
|
-
])) : c.value ? (r(), p("div", V, i(t(s)("Chargement de l'aperçu JSON...")), 1)) : _.value ? (r(), f(m, {
|
|
52
|
-
key: 2,
|
|
53
|
-
type: "warning",
|
|
54
|
-
class: "flex items-center space-x-2"
|
|
55
|
-
}, {
|
|
56
|
-
default: d(() => [
|
|
57
|
-
u(t(v), { class: "shrink-0 size-6" }),
|
|
58
|
-
h("span", null, i(y.value ? t(s)("Fichier JSON trop volumineux pour l'aperçu. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.") : t(s)("L'aperçu n'est pas disponible car la taille du fichier est inconnue. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")), 1)
|
|
59
|
-
]),
|
|
60
|
-
_: 1
|
|
61
|
-
})) : n.value === "network" ? (r(), f(m, {
|
|
62
|
-
key: 3,
|
|
63
|
-
type: "warning",
|
|
64
|
-
class: "flex items-center space-x-2"
|
|
65
|
-
}, {
|
|
66
|
-
default: d(() => [
|
|
67
|
-
u(t(v), { class: "shrink-0 size-6" }),
|
|
68
|
-
h("span", null, i(t(s)("Ce fichier JSON ne peut pas être prévisualisé, peut-être parce qu'il est hébergé sur un autre site qui ne l'autorise pas. Pour le consulter, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")), 1)
|
|
69
|
-
]),
|
|
70
|
-
_: 1
|
|
71
|
-
})) : n.value ? (r(), f(m, {
|
|
72
|
-
key: 4,
|
|
73
|
-
type: "warning",
|
|
74
|
-
class: "flex items-center space-x-2"
|
|
75
|
-
}, {
|
|
76
|
-
default: d(() => [
|
|
77
|
-
u(t(v), { class: "shrink-0 size-6" }),
|
|
78
|
-
h("span", null, i(t(s)("Erreur lors du chargement de l'aperçu JSON.")), 1)
|
|
79
|
-
]),
|
|
80
|
-
_: 1
|
|
81
|
-
})) : T("", !0)
|
|
82
|
-
]));
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
export {
|
|
86
|
-
L as default
|
|
87
|
-
};
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { defineComponent as b, defineAsyncComponent as L, ref as o, computed as w, onMounted as T, createElementBlock as p, openBlock as r, createBlock as m, createCommentVNode as P, createVNode as u, unref as t, toDisplayString as i, withCtx as f, createElementVNode as d } from "vue";
|
|
2
|
-
import { u as E, a as M, g as q, _ as h, F as v } from "./main-ByqZlhiZ.js";
|
|
3
|
-
const B = { class: "fr-text--xs" }, S = { key: 0 }, V = {
|
|
4
|
-
key: 1,
|
|
5
|
-
class: "text-gray-medium"
|
|
6
|
-
}, N = /* @__PURE__ */ b({
|
|
7
|
-
__name: "XmlPreview.client",
|
|
8
|
-
props: {
|
|
9
|
-
resource: {}
|
|
10
|
-
},
|
|
11
|
-
setup(k) {
|
|
12
|
-
const z = L(
|
|
13
|
-
() => import("./vue3-xml-viewer.common-DFrGHXJC.js").then((e) => e.v).then((e) => e.default || e.XmlViewer)
|
|
14
|
-
), g = k, x = E(), { t: l } = M(), n = o(null), c = o(!1), s = o(null), _ = o(!1), y = w(() => q(g.resource)), X = w(() => {
|
|
15
|
-
const e = y.value;
|
|
16
|
-
if (!e || !x.maxXmlPreviewCharSize)
|
|
17
|
-
return !1;
|
|
18
|
-
const a = x.maxXmlPreviewCharSize;
|
|
19
|
-
return e <= a;
|
|
20
|
-
}), C = async () => {
|
|
21
|
-
if (!X.value) {
|
|
22
|
-
_.value = !0;
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
c.value = !0, s.value = null;
|
|
26
|
-
try {
|
|
27
|
-
const e = await fetch(g.resource.url);
|
|
28
|
-
if (!e.ok)
|
|
29
|
-
throw new Error(`HTTP error! status: ${e.status}`);
|
|
30
|
-
const a = await e.text();
|
|
31
|
-
n.value = a;
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error("Error loading XML:", e), e instanceof TypeError ? s.value = "network" : s.value = "generic", n.value = null;
|
|
34
|
-
} finally {
|
|
35
|
-
c.value = !1;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
return T(() => {
|
|
39
|
-
C();
|
|
40
|
-
}), (e, a) => (r(), p("div", B, [
|
|
41
|
-
n.value ? (r(), p("div", S, [
|
|
42
|
-
u(t(z), { xml: n.value }, null, 8, ["xml"])
|
|
43
|
-
])) : c.value ? (r(), p("div", V, i(t(l)("Chargement de l'aperçu XML...")), 1)) : _.value ? (r(), m(h, {
|
|
44
|
-
key: 2,
|
|
45
|
-
type: "warning",
|
|
46
|
-
class: "flex items-center space-x-2"
|
|
47
|
-
}, {
|
|
48
|
-
default: f(() => [
|
|
49
|
-
u(t(v), { class: "shrink-0 size-6" }),
|
|
50
|
-
d("span", null, i(y.value ? t(l)("Fichier XML trop volumineux pour l'aperçu. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.") : t(l)("L'aperçu n'est pas disponible car la taille du fichier est inconnue. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")), 1)
|
|
51
|
-
]),
|
|
52
|
-
_: 1
|
|
53
|
-
})) : s.value === "network" ? (r(), m(h, {
|
|
54
|
-
key: 3,
|
|
55
|
-
type: "warning",
|
|
56
|
-
class: "flex items-center space-x-2"
|
|
57
|
-
}, {
|
|
58
|
-
default: f(() => [
|
|
59
|
-
u(t(v), { class: "shrink-0 size-6" }),
|
|
60
|
-
d("span", null, i(t(l)("Ce fichier XML ne peut pas être prévisualisé, peut-être parce qu'il est hébergé sur un autre site qui ne l'autorise pas. Pour le consulter, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")), 1)
|
|
61
|
-
]),
|
|
62
|
-
_: 1
|
|
63
|
-
})) : s.value ? (r(), m(h, {
|
|
64
|
-
key: 4,
|
|
65
|
-
type: "warning",
|
|
66
|
-
class: "flex items-center space-x-2"
|
|
67
|
-
}, {
|
|
68
|
-
default: f(() => [
|
|
69
|
-
u(t(v), { class: "shrink-0 size-6" }),
|
|
70
|
-
d("span", null, i(t(l)("Erreur lors du chargement de l'aperçu XML.")), 1)
|
|
71
|
-
]),
|
|
72
|
-
_: 1
|
|
73
|
-
})) : P("", !0)
|
|
74
|
-
]));
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
export {
|
|
78
|
-
N as default
|
|
79
|
-
};
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<VChart
|
|
3
|
-
class="w-full min-h-96"
|
|
4
|
-
:option="echartsOption"
|
|
5
|
-
autoresize
|
|
6
|
-
/>
|
|
7
|
-
</template>
|
|
8
|
-
|
|
9
|
-
<script setup lang="ts">
|
|
10
|
-
import { format, use, type ComposeOption } from 'echarts/core'
|
|
11
|
-
import { CanvasRenderer } from 'echarts/renderers'
|
|
12
|
-
import { LineChart, BarChart, type BarSeriesOption, type LineSeriesOption } from 'echarts/charts'
|
|
13
|
-
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent } from 'echarts/components'
|
|
14
|
-
import VChart from 'vue-echarts'
|
|
15
|
-
import { computed } from 'vue'
|
|
16
|
-
import { summarize } from '../../functions/helpers'
|
|
17
|
-
import type { Chart, DataSeries, XAxis, YAxis, ChartForm, XAxisForm } from '../../types/visualizations'
|
|
18
|
-
|
|
19
|
-
use([CanvasRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent])
|
|
20
|
-
|
|
21
|
-
const props = defineProps<{
|
|
22
|
-
chart: Chart | ChartForm
|
|
23
|
-
series: {
|
|
24
|
-
data: Record<string, Array<Record<string, unknown>>>
|
|
25
|
-
columns: Record<string, Array<string>>
|
|
26
|
-
}
|
|
27
|
-
}>()
|
|
28
|
-
|
|
29
|
-
function mapSeriesType(type: DataSeries['type']): 'line' | 'bar' {
|
|
30
|
-
return (type ?? 'line') === 'histogram' ? 'bar' : 'line'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function mapXAxisType(xAxis: XAxis | XAxisForm): 'category' | 'value' {
|
|
34
|
-
if (!xAxis) return 'category'
|
|
35
|
-
return (xAxis.type ?? 'discrete') === 'continuous' ? 'value' : 'category'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefined {
|
|
39
|
-
return (value: number) => {
|
|
40
|
-
const v = summarize(value)
|
|
41
|
-
if (!yAxis.unit) return v
|
|
42
|
-
if (yAxis.unit_position === 'prefix') return `${yAxis.unit} ${v}`
|
|
43
|
-
return `${v} ${yAxis.unit}`
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const echartsOption = computed(() => {
|
|
48
|
-
const seriesCount = props.chart.series.length
|
|
49
|
-
if (!props.chart.series || seriesCount === 0) return
|
|
50
|
-
|
|
51
|
-
// Create series configuration with data mapping
|
|
52
|
-
const seriesData = props.chart.series.map((s) => {
|
|
53
|
-
const xColumn = s.column_x_name_override ?? props.chart.x_axis.column_x
|
|
54
|
-
const yColumn = s.aggregate_y ? `${s.column_y}__${s.aggregate_y}` : s.column_y
|
|
55
|
-
const resourceId = s.resource_id
|
|
56
|
-
const seriesType = s.type
|
|
57
|
-
|
|
58
|
-
if (!xColumn || !yColumn || !resourceId || !seriesType || !props.series.data[resourceId] || !props.series.columns[resourceId]) {
|
|
59
|
-
return null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Sort data before passing to ECharts to avoid transform issues
|
|
63
|
-
const sortedData = [...props.series.data[resourceId]]
|
|
64
|
-
const sortBy = props.chart.x_axis.sort_x_by
|
|
65
|
-
const sortDirection = props.chart.x_axis.sort_x_direction ?? 'asc'
|
|
66
|
-
|
|
67
|
-
if (sortBy && sortDirection && props.chart.x_axis.column_x) {
|
|
68
|
-
const sortKey = sortBy === 'axis_x' ? xColumn : yColumn
|
|
69
|
-
sortedData.sort((a, b) => {
|
|
70
|
-
const valA = a[sortKey] as number
|
|
71
|
-
const valB = b[sortKey] as number
|
|
72
|
-
if (valA < valB) return sortDirection === 'asc' ? -1 : 1
|
|
73
|
-
if (valA > valB) return sortDirection === 'asc' ? 1 : -1
|
|
74
|
-
return 0
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
series: {
|
|
80
|
-
type: mapSeriesType(seriesType),
|
|
81
|
-
dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
|
|
82
|
-
name: s.column_y,
|
|
83
|
-
encode: {
|
|
84
|
-
x: xColumn,
|
|
85
|
-
y: yColumn,
|
|
86
|
-
},
|
|
87
|
-
} as LineSeriesOption | BarSeriesOption,
|
|
88
|
-
data: {
|
|
89
|
-
source: sortedData,
|
|
90
|
-
dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
}).filter(Boolean).reduce((acc: { series: Array<LineSeriesOption | BarSeriesOption>, data: Array<Record<string, unknown>> }, curr) => {
|
|
94
|
-
if (curr) {
|
|
95
|
-
acc.series.push(curr.series)
|
|
96
|
-
acc.data.push(curr.data)
|
|
97
|
-
}
|
|
98
|
-
return acc
|
|
99
|
-
}, {
|
|
100
|
-
series: [],
|
|
101
|
-
data: [],
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
dataset: [...seriesData.data],
|
|
106
|
-
title: {
|
|
107
|
-
text: props.chart.title,
|
|
108
|
-
left: 'center',
|
|
109
|
-
},
|
|
110
|
-
tooltip: {
|
|
111
|
-
trigger: 'axis' as const,
|
|
112
|
-
formatter: (params: Array<{ value: Record<string, unknown>, axisValueLabel: string, seriesName: string }>) => {
|
|
113
|
-
let tooltip = ''
|
|
114
|
-
for (const param of params) {
|
|
115
|
-
const keys = Object.keys(param.value)
|
|
116
|
-
const col = keys.find(key => key.startsWith(param.seriesName))!
|
|
117
|
-
const formatter = new Intl.NumberFormat('fr-FR')
|
|
118
|
-
tooltip += `${format.encodeHTML(param.axisValueLabel)}: <strong>${formatter.format(Number(param.value[col]))}</strong><br>`
|
|
119
|
-
}
|
|
120
|
-
return tooltip
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
legend: {
|
|
124
|
-
bottom: 0,
|
|
125
|
-
},
|
|
126
|
-
grid: {
|
|
127
|
-
top: 60,
|
|
128
|
-
bottom: 40,
|
|
129
|
-
left: 20,
|
|
130
|
-
right: 20,
|
|
131
|
-
containLabel: true,
|
|
132
|
-
},
|
|
133
|
-
xAxis: {
|
|
134
|
-
type: mapXAxisType(props.chart.x_axis),
|
|
135
|
-
name: (props.chart.x_axis as XAxis).column_x,
|
|
136
|
-
},
|
|
137
|
-
yAxis: {
|
|
138
|
-
type: 'value' as const,
|
|
139
|
-
name: props.chart.y_axis.label ?? undefined,
|
|
140
|
-
min: props.chart.y_axis.min ?? undefined,
|
|
141
|
-
max: props.chart.y_axis.max ?? undefined,
|
|
142
|
-
axisLabel: {
|
|
143
|
-
formatter: buildYAxisFormatter(props.chart.y_axis),
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
series: seriesData.series,
|
|
147
|
-
} satisfies ComposeOption<
|
|
148
|
-
| BarSeriesOption
|
|
149
|
-
| LineSeriesOption
|
|
150
|
-
>
|
|
151
|
-
})
|
|
152
|
-
</script>
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<LoadingBlock
|
|
3
|
-
:status="status"
|
|
4
|
-
:data="series"
|
|
5
|
-
>
|
|
6
|
-
<template #default="{ data }">
|
|
7
|
-
<ChartViewer
|
|
8
|
-
:chart="chart"
|
|
9
|
-
:series="data"
|
|
10
|
-
/>
|
|
11
|
-
</template>
|
|
12
|
-
<template #error>
|
|
13
|
-
<div class="text-center py-8 text-gray-500">
|
|
14
|
-
Une erreur est survenue lors du chargement des données du graphique.
|
|
15
|
-
</div>
|
|
16
|
-
</template>
|
|
17
|
-
</LoadingBlock>
|
|
18
|
-
</template>
|
|
19
|
-
|
|
20
|
-
<script setup lang="ts">
|
|
21
|
-
import { reactive, ref, watch } from 'vue'
|
|
22
|
-
import ChartViewer from './ChartViewer.vue'
|
|
23
|
-
import LoadingBlock from '../../components/LoadingBlock.vue'
|
|
24
|
-
import { useComponentsConfig } from '../../config'
|
|
25
|
-
import { fetchTabularData, useGetProfile, type TabularDataResponse, type TabularProfileResponse } from '../../functions/tabularApi'
|
|
26
|
-
import type { Chart, ChartForm } from '../../types/visualizations'
|
|
27
|
-
|
|
28
|
-
const chart = defineModel<Chart | ChartForm>({ required: true })
|
|
29
|
-
|
|
30
|
-
const emit = defineEmits<{
|
|
31
|
-
columns: [columns: Record<string, Array<string>>]
|
|
32
|
-
}>()
|
|
33
|
-
|
|
34
|
-
const config = useComponentsConfig()
|
|
35
|
-
const getProfile = useGetProfile()
|
|
36
|
-
|
|
37
|
-
// Loading and error states
|
|
38
|
-
const status = ref<'idle' | 'pending' | 'success' | 'error'>('idle')
|
|
39
|
-
const error = ref<Error | null>(null)
|
|
40
|
-
|
|
41
|
-
// Dataset source for the chart
|
|
42
|
-
const series = reactive<{
|
|
43
|
-
data: Record<string, Array<Record<string, unknown>>>
|
|
44
|
-
columns: Record<string, Array<string>>
|
|
45
|
-
}>({
|
|
46
|
-
data: {},
|
|
47
|
-
columns: {},
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
async function fetchSeriesProfile() {
|
|
51
|
-
status.value = 'pending'
|
|
52
|
-
error.value = null
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
if (chart.value.series.length === 0) {
|
|
56
|
-
status.value = 'success'
|
|
57
|
-
series.data = {}
|
|
58
|
-
series.columns = {}
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Fetch data for all series in parallel
|
|
63
|
-
const fetchPromises = chart.value.series.map(async (serie) => {
|
|
64
|
-
if (!serie.resource_id) return
|
|
65
|
-
return {
|
|
66
|
-
id: serie.resource_id,
|
|
67
|
-
profile: await getProfile(serie.resource_id),
|
|
68
|
-
}
|
|
69
|
-
}).filter(Boolean) as Array<Promise<{
|
|
70
|
-
id: string
|
|
71
|
-
profile: TabularProfileResponse
|
|
72
|
-
}>>
|
|
73
|
-
|
|
74
|
-
const results = (await Promise.allSettled(fetchPromises))
|
|
75
|
-
.filter(r => r.status === 'fulfilled')
|
|
76
|
-
.map(r => r.value)
|
|
77
|
-
series.columns = Object.fromEntries(results.map(result => [
|
|
78
|
-
result.id,
|
|
79
|
-
result.profile.profile.header,
|
|
80
|
-
]))
|
|
81
|
-
if (results.length > 0) {
|
|
82
|
-
const columns = results[0]?.profile.profile.header ?? []
|
|
83
|
-
const firstColumn = columns.filter(c => c !== '__id')[0]
|
|
84
|
-
if (!chart.value.x_axis.column_x && firstColumn) {
|
|
85
|
-
chart.value.x_axis.column_x = firstColumn
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
status.value = 'success'
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
error.value = err instanceof Error ? err : new Error('Failed to fetch series profile')
|
|
92
|
-
status.value = 'error'
|
|
93
|
-
console.log(err)
|
|
94
|
-
series.columns = {}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function fetchSeriesData() {
|
|
99
|
-
status.value = 'pending'
|
|
100
|
-
error.value = null
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
if (chart.value.series.length === 0 || !chart.value.x_axis.column_x) {
|
|
104
|
-
status.value = 'success'
|
|
105
|
-
series.data = {}
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Fetch data for all series in parallel
|
|
110
|
-
const fetchPromises = chart.value.series.map(async (serie) => {
|
|
111
|
-
const xColumn = serie.column_x_name_override ?? chart.value.x_axis.column_x
|
|
112
|
-
if (!xColumn || !serie.resource_id || !serie.column_y) return
|
|
113
|
-
return {
|
|
114
|
-
id: serie.resource_id,
|
|
115
|
-
profile: await getProfile(serie.resource_id),
|
|
116
|
-
data: await fetchTabularData(config, {
|
|
117
|
-
columns: serie.aggregate_y ? undefined : [xColumn, serie.column_y],
|
|
118
|
-
resourceId: serie.resource_id,
|
|
119
|
-
page: 1,
|
|
120
|
-
pageSize: 100,
|
|
121
|
-
groupBy: xColumn,
|
|
122
|
-
aggregation: serie.column_y && serie.aggregate_y
|
|
123
|
-
? {
|
|
124
|
-
column: serie.column_y,
|
|
125
|
-
type: serie.aggregate_y,
|
|
126
|
-
}
|
|
127
|
-
: undefined,
|
|
128
|
-
}),
|
|
129
|
-
}
|
|
130
|
-
}).filter(Boolean) as Array<Promise<{
|
|
131
|
-
id: string
|
|
132
|
-
data: TabularDataResponse
|
|
133
|
-
}>>
|
|
134
|
-
|
|
135
|
-
const results = (await Promise.allSettled(fetchPromises))
|
|
136
|
-
.filter(r => r.status === 'fulfilled')
|
|
137
|
-
.map(r => r.value)
|
|
138
|
-
// Transform data into echarts format
|
|
139
|
-
series.data = Object.fromEntries(results.map(result => [
|
|
140
|
-
result.id,
|
|
141
|
-
result.data.data,
|
|
142
|
-
]))
|
|
143
|
-
status.value = 'success'
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
error.value = err instanceof Error ? err : new Error('Failed to fetch series data')
|
|
147
|
-
status.value = 'error'
|
|
148
|
-
series.data = {}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Watch for changes in the chart or its series
|
|
153
|
-
watch(() => chart.value.series, async () => {
|
|
154
|
-
await fetchSeriesProfile()
|
|
155
|
-
}, { immediate: true, deep: true })
|
|
156
|
-
|
|
157
|
-
// Watch for changes in the chart or its series
|
|
158
|
-
watch([() => chart.value.series, () => chart.value.x_axis.column_x], async () => {
|
|
159
|
-
await fetchSeriesData()
|
|
160
|
-
}, { immediate: true, deep: true })
|
|
161
|
-
|
|
162
|
-
watch(() => series.columns, () => {
|
|
163
|
-
emit('columns', series.columns)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Combines data from multiple series into a single echarts dataset source
|
|
168
|
-
*/
|
|
169
|
-
// function combineSeriesData(seriesData: Array<TabularDataResponse | null>): Array<Array<unknown>> {
|
|
170
|
-
// if (!seriesData.length || seriesData.every(data => !data || !data.data || data.data.length === 0)) {
|
|
171
|
-
// return []
|
|
172
|
-
// }
|
|
173
|
-
|
|
174
|
-
// // Get all unique columns from all series
|
|
175
|
-
// const allColumns = Array.from(new Set(seriesData.flatMap(data =>
|
|
176
|
-
// data?.data?.flatMap(Object.keys) || []
|
|
177
|
-
// )))
|
|
178
|
-
|
|
179
|
-
// // Build the source array with headers
|
|
180
|
-
// const source: Array<Array<unknown>> = [allColumns]
|
|
181
|
-
|
|
182
|
-
// // Add data rows - for simplicity, we'll use data from the first series
|
|
183
|
-
// // In a real implementation, you'd want to merge/combine the data appropriately
|
|
184
|
-
// const firstData = seriesData.find(data => data?.data?.length)?.data
|
|
185
|
-
// if (firstData) {
|
|
186
|
-
// firstData.forEach(row => {
|
|
187
|
-
// const rowValues = allColumns.map(column => row[column])
|
|
188
|
-
// source.push(rowValues)
|
|
189
|
-
// })
|
|
190
|
-
// }
|
|
191
|
-
|
|
192
|
-
// return source
|
|
193
|
-
// }
|
|
194
|
-
</script>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { useRoute } from 'vue-router'
|
|
2
|
-
|
|
3
|
-
export function getLink(page: number): string {
|
|
4
|
-
const route = useRoute()
|
|
5
|
-
const routePath = route.path
|
|
6
|
-
const search = new URLSearchParams(route.query as Record<string, string>)
|
|
7
|
-
search.set('page', page.toFixed(0))
|
|
8
|
-
return `${routePath}?${search.toString()}`
|
|
9
|
-
}
|