@datagouv/components-next 1.0.2-dev.9 → 1.0.2-dev.90

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 (96) hide show
  1. package/assets/main.css +4 -0
  2. package/dist/{Control-DuZJdKV_.js → Control-ZFh5ta_U.js} +1 -1
  3. package/dist/Datafair.client-CyZRNADr.js +30 -0
  4. package/dist/{Event--kp8kMdJ.js → Event-DSQcW7OF.js} +24 -24
  5. package/dist/{Image-34hvypZI.js → Image-BijNEG0p.js} +6 -6
  6. package/dist/JsonPreview.client-C9iaPSmQ.js +40 -0
  7. package/dist/{Map-BjUnLyj8.js → Map-BUtPf5GN.js} +756 -756
  8. package/dist/MapContainer.client-BuoZ69XO.js +101 -0
  9. package/dist/{OSM-s40W6sQ2.js → OSM-D4MTdBtk.js} +2 -2
  10. package/dist/{PdfPreview.client-BVjPxlPu.js → PdfPreview.client-MI0bDghc.js} +822 -865
  11. package/dist/{Pmtiles.client-CRJ56yX2.js → Pmtiles.client-CaKEYQBc.js} +574 -579
  12. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BKqb6TMw.js +61 -0
  13. package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-hJQIqcZm.js} +2 -2
  14. package/dist/{Tile-DbNFNPfU.js → Tile-Dcl7oIVu.js} +35 -35
  15. package/dist/{TileImage-BsXBxMtq.js → TileImage-BJeHipMX.js} +4 -4
  16. package/dist/{View-BR92hTWP.js → View-xp_P_OHw.js} +412 -401
  17. package/dist/XmlPreview.client-BVAeNK4n.js +34 -0
  18. package/dist/{common-PJfpC179.js → common-BjQlan3k.js} +36 -36
  19. package/dist/components-next.css +6 -6
  20. package/dist/components-next.js +166 -148
  21. package/dist/components.css +1 -1
  22. package/dist/{index-BZsAZ7iw.js → index-BBdS8QKx.js} +32886 -27183
  23. package/dist/{main-qc4CO9Kn.js → main-Dk_66g-3.js} +91331 -75844
  24. package/dist/{proj-DsetBcW7.js → proj-CsNo9yH1.js} +532 -512
  25. package/dist/{tilecoord-Db24Px13.js → tilecoord-A0fLnBZr.js} +28 -28
  26. package/dist/{vue3-xml-viewer.common-CCOV_ohP.js → vue3-xml-viewer.common-B8dNNkOU.js} +1 -1
  27. package/package.json +18 -11
  28. package/src/components/ActivityList/ActivityList.vue +0 -2
  29. package/src/components/Chart/ChartViewer.vue +226 -0
  30. package/src/components/Chart/ChartViewerWrapper.vue +170 -0
  31. package/src/components/Form/Listbox.vue +101 -0
  32. package/src/components/Form/SearchableSelect.vue +2 -1
  33. package/src/components/InfiniteLoader.vue +53 -0
  34. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  35. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  36. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  37. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  38. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  39. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  40. package/src/components/OpenApiViewer/openapi.ts +150 -0
  41. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  42. package/src/components/Pagination.vue +8 -5
  43. package/src/components/ReadMore.vue +1 -1
  44. package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
  45. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
  46. package/src/components/ResourceAccordion/MapContainer.client.vue +5 -14
  47. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  48. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -103
  49. package/src/components/ResourceAccordion/Pmtiles.client.vue +5 -10
  50. package/src/components/ResourceAccordion/Preview.vue +16 -21
  51. package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
  52. package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
  53. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  54. package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -7
  55. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
  56. package/src/components/ResourceExplorer/ResourceExplorer.vue +81 -13
  57. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  58. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +30 -11
  59. package/src/components/Search/GlobalSearch.vue +191 -110
  60. package/src/components/Search/SearchInput.vue +5 -4
  61. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  62. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  63. package/src/components/TabularExplorer/TabularExplorer.vue +870 -0
  64. package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
  65. package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
  66. package/src/components/TabularExplorer/types.ts +83 -0
  67. package/src/composables/useHasTabularData.ts +6 -0
  68. package/src/composables/useResourceCapabilities.ts +1 -1
  69. package/src/composables/useSearchFilter.ts +118 -0
  70. package/src/composables/useStableQueryParams.ts +31 -3
  71. package/src/config.ts +3 -0
  72. package/src/functions/api.ts +34 -33
  73. package/src/functions/api.types.ts +1 -0
  74. package/src/functions/charts.ts +68 -0
  75. package/src/functions/datasets.ts +0 -17
  76. package/src/functions/resources.ts +56 -1
  77. package/src/functions/tabular.ts +60 -0
  78. package/src/functions/tabularApi.ts +138 -11
  79. package/src/main.ts +55 -7
  80. package/src/types/dataservices.ts +2 -0
  81. package/src/types/pages.ts +0 -5
  82. package/src/types/posts.ts +2 -2
  83. package/src/types/reports.ts +5 -1
  84. package/src/types/search.ts +52 -1
  85. package/src/types/site.ts +5 -3
  86. package/src/types/users.ts +2 -1
  87. package/src/types/visualizations.ts +89 -0
  88. package/assets/swagger-themes/newspaper.css +0 -1670
  89. package/dist/Datafair.client-0UYUu5yf.js +0 -35
  90. package/dist/JsonPreview.client-BrTMBWHZ.js +0 -87
  91. package/dist/MapContainer.client-CUmKyByc.js +0 -107
  92. package/dist/Swagger.client-2Yn7iF0A.js +0 -4
  93. package/dist/XmlPreview.client-DxqlVnKu.js +0 -79
  94. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  95. package/src/functions/pagination.ts +0 -9
  96. /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
@@ -1,6 +1,6 @@
1
- import { T as _, W as nt, n as xt, c as H, b as et } from "./Image-34hvypZI.js";
2
- import { z as Tt, g as pt } from "./common-PJfpC179.js";
3
- import { aC as It, E as ft, al as Xt, U as dt, g as at, X as Wt, w as Mt, F as b, n as _t, L, an as gt, aS as vt, x as Yt, r as Lt, aT as Ft, ak as wt, aI as yt, az as Dt, aJ as Ot, aU as st, ae as ot, i as Ct, W as z, a1 as $, d as V, l as Pt, u as rt, a7 as ht, I as At, a as Rt } from "./proj-DsetBcW7.js";
1
+ import { T as _, W as nt, n as xt, c as H, b as et } from "./Image-BijNEG0p.js";
2
+ import { z as Tt, g as pt } from "./common-BjQlan3k.js";
3
+ import { aD as It, E as ft, al as Xt, U as dt, g as at, X as Wt, w as Mt, F as b, n as _t, L, an as gt, aU as vt, x as Yt, r as Lt, aV as Ft, ak as wt, aJ as yt, aA as Dt, aK as Ot, aW as st, ae as ot, i as Ct, W as z, a1 as $, d as V, l as Pt, u as rt, a7 as ht, I as At, a as Rt } from "./proj-CsNo9yH1.js";
4
4
  class mt extends It {
5
5
  /**
6
6
  * @param {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
@@ -339,7 +339,7 @@ function ut(a, t, e, s) {
339
339
  }
340
340
  return i;
341
341
  }
342
- function Nt(a, t, e, s) {
342
+ function Kt(a, t, e, s) {
343
343
  const n = Yt(e);
344
344
  let i = ut(
345
345
  a,
@@ -356,7 +356,7 @@ function Nt(a, t, e, s) {
356
356
  ), isFinite(i) && i > 0;
357
357
  }), i;
358
358
  }
359
- function St(a, t, e, s, n, i, c, m, g, T, X, x, F, w) {
359
+ function Nt(a, t, e, s, n, i, c, m, g, T, X, x, F, w) {
360
360
  const r = H(
361
361
  Math.round(e * a),
362
362
  Math.round(e * t),
@@ -409,7 +409,7 @@ function St(a, t, e, s, n, i, c, m, g, T, X, x, F, w) {
409
409
  return m.getTriangles().forEach(function(o, y, C) {
410
410
  const p = o.source, I = o.target;
411
411
  let v = p[0][0], Y = p[0][1], W = p[1][0], M = p[1][1], D = p[2][0], A = p[2][1];
412
- const G = h((I[0][0] - u[0]) / i), N = h(
412
+ const G = h((I[0][0] - u[0]) / i), K = h(
413
413
  -(I[0][1] - u[1]) / i
414
414
  ), R = h((I[1][0] - u[0]) / i), k = h(
415
415
  -(I[1][1] - u[1]) / i
@@ -420,32 +420,32 @@ function St(a, t, e, s, n, i, c, m, g, T, X, x, F, w) {
420
420
  const Et = [
421
421
  [W, M, 0, 0, R - G],
422
422
  [D, A, 0, 0, U - G],
423
- [0, 0, W, M, k - N],
424
- [0, 0, D, A, Q - N]
425
- ], S = vt(Et);
426
- if (!S)
423
+ [0, 0, W, M, k - K],
424
+ [0, 0, D, A, Q - K]
425
+ ], N = vt(Et);
426
+ if (!N)
427
427
  return;
428
428
  if (r.save(), r.beginPath(), Gt() || !x) {
429
429
  r.moveTo(R, k);
430
- const O = 4, K = G - R, it = N - k;
430
+ const O = 4, S = G - R, it = K - k;
431
431
  for (let P = 0; P < O; P++)
432
432
  r.lineTo(
433
- R + h((P + 1) * K / O),
433
+ R + h((P + 1) * S / O),
434
434
  k + h(P * it / (O - 1))
435
435
  ), P != O - 1 && r.lineTo(
436
- R + h((P + 1) * K / O),
436
+ R + h((P + 1) * S / O),
437
437
  k + h((P + 1) * it / (O - 1))
438
438
  );
439
439
  r.lineTo(U, Q);
440
440
  } else
441
- r.moveTo(R, k), r.lineTo(G, N), r.lineTo(U, Q);
441
+ r.moveTo(R, k), r.lineTo(G, K), r.lineTo(U, Q);
442
442
  r.clip(), r.transform(
443
- S[0],
444
- S[2],
445
- S[1],
446
- S[3],
443
+ N[0],
444
+ N[2],
445
+ N[1],
446
+ N[3],
447
447
  G,
448
- N
448
+ K
449
449
  ), r.translate(
450
450
  E[0] - j,
451
451
  E[3] - q
@@ -454,10 +454,10 @@ function St(a, t, e, s, n, i, c, m, g, T, X, x, F, w) {
454
454
  if (l)
455
455
  Z = l.canvas, r.scale(d, -d);
456
456
  else {
457
- const O = g[0], K = O.extent;
457
+ const O = g[0], S = O.extent;
458
458
  Z = O.image, r.scale(
459
- L(K) / Z.width,
460
- -b(K) / Z.height
459
+ L(S) / Z.width,
460
+ -b(S) / Z.height
461
461
  );
462
462
  }
463
463
  r.drawImage(Z, 0, 0), r.restore();
@@ -467,7 +467,7 @@ function St(a, t, e, s, n, i, c, m, g, T, X, x, F, w) {
467
467
  }), r.restore()), r.canvas;
468
468
  }
469
469
  const Zt = 10, lt = 0.25;
470
- class Kt {
470
+ class St {
471
471
  /**
472
472
  * @param {import("../proj/Projection.js").default} sourceProj Source projection.
473
473
  * @param {import("../proj/Projection.js").default} targetProj Target projection.
@@ -715,7 +715,7 @@ class Vt extends mt {
715
715
  l && (h ? h = V(h, l) : h = l);
716
716
  const f = n.getResolution(
717
717
  this.wrappedTileCoord_[0]
718
- ), d = Nt(
718
+ ), d = Kt(
719
719
  t,
720
720
  s,
721
721
  E,
@@ -726,7 +726,7 @@ class Vt extends mt {
726
726
  return;
727
727
  }
728
728
  const u = X !== void 0 ? X : bt;
729
- if (this.triangulation_ = new Kt(
729
+ if (this.triangulation_ = new St(
730
730
  t,
731
731
  s,
732
732
  E,
@@ -806,7 +806,7 @@ class Vt extends mt {
806
806
  ), g = this.targetTileGrid_.getTileCoordExtent(
807
807
  this.wrappedTileCoord_
808
808
  );
809
- this.canvas_ = St(
809
+ this.canvas_ = Nt(
810
810
  n,
811
811
  i,
812
812
  this.pixelRatio_,
@@ -890,13 +890,13 @@ export {
890
890
  zt as I,
891
891
  Vt as R,
892
892
  mt as T,
893
- Kt as a,
893
+ St as a,
894
894
  Bt as b,
895
895
  ut as c,
896
896
  Jt as d,
897
897
  $t as e,
898
898
  ts as g,
899
899
  ss as h,
900
- St as r,
900
+ Nt as r,
901
901
  es as w
902
902
  };
@@ -1,4 +1,4 @@
1
- import { b as Ke } from "./main-qc4CO9Kn.js";
1
+ import { c as Ke } from "./main-Dk_66g-3.js";
2
2
  import We from "vue";
3
3
  function Fe(I, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.2-dev.9",
3
+ "version": "1.0.2-dev.90",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -10,22 +10,24 @@
10
10
  "yarn": "use-pnpm-instead"
11
11
  },
12
12
  "main": "./src/main.ts",
13
+ "sideEffects": false,
13
14
  "files": [
14
15
  "assets",
15
16
  "dist",
16
17
  "src"
17
18
  ],
18
19
  "dependencies": {
19
- "@floating-ui/vue": "^1.1.8",
20
- "@types/leaflet": "^1.9.17",
20
+ "@floating-ui/vue": "^1.1.11",
21
21
  "@headlessui/vue": "^1.7.23",
22
22
  "@remixicon/vue": "^4.5.0",
23
23
  "@types/hast": "^3.0.4",
24
- "@vueuse/core": "^13.1.0",
25
- "@vueuse/router": "^13.1.0",
24
+ "@types/leaflet": "^1.9.17",
25
+ "@vueuse/core": "^14.2.1",
26
+ "@vueuse/router": "^14.2.1",
26
27
  "chart.js": "^4.4.8",
27
28
  "dompurify": "^3.2.5",
28
- "geopf-extensions-openlayers": "^1.0.0-beta.5",
29
+ "echarts": "^6.0.0",
30
+ "geopf-extensions-openlayers": "^1.0.0-beta.10",
29
31
  "leaflet": "^1.9.4",
30
32
  "maplibre-gl": "^5.6.2",
31
33
  "ofetch": "^1.4.1",
@@ -45,16 +47,14 @@
45
47
  "remark-rehype": "^11.1.2",
46
48
  "strip-markdown": "^6.0.0",
47
49
  "stylefire": "^7.0.3",
48
- "swagger-ui-dist": "^5.27.1",
49
50
  "unified": "^11.0.5",
50
51
  "unist-util-visit": "^5.0.0",
51
- "vue": "^3.5.13",
52
52
  "vue-content-loader": "^2.0.1",
53
- "vue-router": "^4.5.0",
54
53
  "vue-sonner": "^2.0.9",
55
54
  "vue3-json-viewer": "^2.4.1",
56
55
  "vue3-text-clamp": "^0.1.2",
57
- "vue3-xml-viewer": "^0.0.14"
56
+ "vue3-xml-viewer": "^0.0.14",
57
+ "yaml": "^2.8.2"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@intlify/cli": "^0.13.1",
@@ -71,13 +71,20 @@
71
71
  "eslint-plugin-vue": "^10.0",
72
72
  "jiti": "^2.4.2",
73
73
  "npm-run-all2": "^8.0",
74
+ "openapi-types": "^12.1.3",
74
75
  "prettier": "^3.0.0",
75
76
  "tailwindcss": "^4.0.8",
76
- "typescript": "^5.7.3",
77
+ "typescript": "^5.9.3",
77
78
  "vite": "^7.0",
78
79
  "vite-plugin-vue-devtools": "^8.0",
80
+ "vue": "^3.5.33",
81
+ "vue-router": "^5.0.4",
79
82
  "vue-tsc": "^3.0"
80
83
  },
84
+ "peerDependencies": {
85
+ "vue": "^3.5.13",
86
+ "vue-router": "^4.5.0 || ^5.0.0"
87
+ },
81
88
  "scarfSettings": {
82
89
  "enabled": false
83
90
  },
@@ -90,7 +90,6 @@
90
90
  :total-results="activities.total"
91
91
  :page-size="activities.page_size"
92
92
  :page="activities.page"
93
- :link="getLink"
94
93
  @change="(newPage: number) => page = newPage"
95
94
  />
96
95
  </template>
@@ -117,7 +116,6 @@ import { useTranslation } from '../../composables/useTranslation'
117
116
  import { getActivityTranslation } from '../../functions/activities'
118
117
  import { useFetch } from '../../functions/api'
119
118
  import { useFormatDate } from '../../functions/dates'
120
- import { getLink } from '../../functions/pagination'
121
119
  import type { PaginatedArray } from '../../types/api'
122
120
  import type { Activity } from '../../types/activity'
123
121
  import Avatar from '../Avatar.vue'
@@ -0,0 +1,226 @@
1
+ <template>
2
+ <div
3
+ ref="chartContainer"
4
+ class="w-full min-h-96"
5
+ />
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { format, use, type ComposeOption } from 'echarts/core'
10
+ import { CanvasRenderer } from 'echarts/renderers'
11
+ import { LineChart, BarChart, type BarSeriesOption, type LineSeriesOption } from 'echarts/charts'
12
+ import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent } from 'echarts/components'
13
+ import { init, type ECharts as EChartsType } from 'echarts'
14
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
15
+ import { summarize } from '../../functions/helpers'
16
+ import type { Chart, XAxis, YAxis, XAxisForm, ChartForApi } from '../../types/visualizations'
17
+ import { useTranslation } from '../../composables/useTranslation'
18
+
19
+ use([CanvasRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent])
20
+
21
+ const props = defineProps<{
22
+ chart: Chart | ChartForApi
23
+ series: {
24
+ data: Record<string, Array<Record<string, unknown>>>
25
+ columns: Record<string, Array<string>>
26
+ }
27
+ }>()
28
+
29
+ const { locale } = useTranslation()
30
+ const chartContainer = ref<HTMLElement | null>(null)
31
+ let echartsInstance: EChartsType | null = null
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
+ const seriesArray = props.chart.series.map((s) => {
52
+ const xColumn = s.column_x_name_override ?? props.chart.x_axis.column_x
53
+ const yColumn = s.aggregate_y ? `${s.column_y}__${s.aggregate_y}` : s.column_y
54
+ const resourceId = s.resource_id
55
+
56
+ if (!xColumn || !yColumn || !resourceId || !s.type || !props.series.data[resourceId] || !props.series.columns[resourceId]) {
57
+ return null
58
+ }
59
+
60
+ const sortedData = [...props.series.data[resourceId]]
61
+ const sortBy = props.chart.x_axis.sort_x_by
62
+ const sortDirection = props.chart.x_axis.sort_x_direction ?? 'asc'
63
+
64
+ if (sortBy && sortDirection && props.chart.x_axis.column_x) {
65
+ const sortKey = sortBy === 'axis_x' ? xColumn : yColumn
66
+ sortedData.sort((a, b) => {
67
+ const valA = a[sortKey]
68
+ const valB = b[sortKey]
69
+
70
+ const aNullish = valA === null || valA === undefined
71
+ const bNullish = valB === null || valB === undefined
72
+ if (aNullish && bNullish) return 0
73
+ if (aNullish) return sortDirection === 'asc' ? -1 : 1
74
+ if (bNullish) return sortDirection === 'asc' ? 1 : -1
75
+
76
+ if (valA instanceof Date && valB instanceof Date) {
77
+ const timeA = valA.getTime()
78
+ const timeB = valB.getTime()
79
+ if (timeA < timeB) return sortDirection === 'asc' ? -1 : 1
80
+ if (timeA > timeB) return sortDirection === 'asc' ? 1 : -1
81
+ return 0
82
+ }
83
+
84
+ if (typeof valA === 'boolean' && typeof valB === 'boolean') {
85
+ if (valA === valB) return 0
86
+ // false comes before true in ascending order
87
+ if (valB) return sortDirection === 'asc' ? -1 : 1
88
+ return sortDirection === 'asc' ? 1 : -1
89
+ }
90
+
91
+ const numA = Number(valA)
92
+ const numB = Number(valB)
93
+ const bothNumeric = !isNaN(numA) && !isNaN(numB)
94
+
95
+ if (bothNumeric) {
96
+ if (numA < numB) return sortDirection === 'asc' ? -1 : 1
97
+ if (numA > numB) return sortDirection === 'asc' ? 1 : -1
98
+ return 0
99
+ }
100
+
101
+ const strA = String(valA)
102
+ const strB = String(valB)
103
+ return strA.localeCompare(strB, locale, {
104
+ sensitivity: 'base',
105
+ numeric: true,
106
+ }) * (sortDirection === 'asc' ? 1 : -1)
107
+ })
108
+ }
109
+
110
+ return {
111
+ series: {
112
+ type: s.type === 'histogram' ? 'bar' : 'line',
113
+ dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
114
+ name: yColumn,
115
+ encode: {
116
+ x: xColumn,
117
+ y: yColumn,
118
+ },
119
+ } as LineSeriesOption | BarSeriesOption,
120
+ data: {
121
+ source: sortedData,
122
+ dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
123
+ },
124
+ }
125
+ })
126
+
127
+ const seriesData = {
128
+ series: [] as Array<LineSeriesOption | BarSeriesOption>,
129
+ data: [] as Array<Record<string, unknown>>,
130
+ }
131
+
132
+ for (const curr of seriesArray) {
133
+ if (!curr) continue
134
+ seriesData.series.push(curr.series)
135
+ seriesData.data.push(curr.data)
136
+ }
137
+
138
+ return {
139
+ dataset: [...seriesData.data],
140
+ title: {
141
+ text: props.chart.title,
142
+ left: 'center',
143
+ },
144
+ tooltip: {
145
+ trigger: 'axis' as const,
146
+ formatter: (params: Array<{ value: Record<string, unknown>, axisValueLabel: string, seriesName: string }>) => {
147
+ let tooltip = ''
148
+ const formatter = new Intl.NumberFormat('fr-FR')
149
+ for (const param of params) {
150
+ const seriesName = param.seriesName
151
+ tooltip += `${format.encodeHTML(param.axisValueLabel)}: <strong>${formatter.format(Number(param.value[seriesName]))}</strong><br>`
152
+ }
153
+ return tooltip
154
+ },
155
+ },
156
+ legend: {
157
+ show: seriesData.series.length > 1,
158
+ bottom: 0,
159
+ },
160
+ grid: {
161
+ top: 60,
162
+ bottom: 40,
163
+ left: 20,
164
+ right: 20,
165
+ containLabel: true,
166
+ },
167
+ xAxis: {
168
+ type: mapXAxisType(props.chart.x_axis),
169
+ name: (props.chart.x_axis as XAxis).column_x,
170
+ },
171
+ yAxis: {
172
+ type: 'value' as const,
173
+ name: props.chart.y_axis.label ?? undefined,
174
+ min: props.chart.y_axis.min ?? undefined,
175
+ max: props.chart.y_axis.max ?? undefined,
176
+ axisLabel: {
177
+ formatter: buildYAxisFormatter(props.chart.y_axis),
178
+ },
179
+ },
180
+ series: seriesData.series,
181
+ } satisfies ComposeOption<
182
+ | BarSeriesOption
183
+ | LineSeriesOption
184
+ >
185
+ })
186
+
187
+ onMounted(() => {
188
+ if (chartContainer.value) {
189
+ echartsInstance = init(chartContainer.value)
190
+ updateChart()
191
+ }
192
+ })
193
+
194
+ onUnmounted(() => {
195
+ if (echartsInstance) {
196
+ echartsInstance.dispose()
197
+ echartsInstance = null
198
+ }
199
+ })
200
+
201
+ watch(() => props.chart, updateChart, { deep: true })
202
+
203
+ watch(() => props.series, updateChart, { deep: true })
204
+
205
+ function updateChart() {
206
+ if (!echartsInstance) return
207
+ const option = echartsOption.value
208
+ if (option) {
209
+ echartsInstance.setOption(option, { notMerge: true })
210
+ }
211
+ }
212
+
213
+ const handleResize = () => {
214
+ if (echartsInstance) {
215
+ echartsInstance.resize()
216
+ }
217
+ }
218
+
219
+ onMounted(() => {
220
+ window.addEventListener('resize', handleResize)
221
+ })
222
+
223
+ onUnmounted(() => {
224
+ window.removeEventListener('resize', handleResize)
225
+ })
226
+ </script>
@@ -0,0 +1,170 @@
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
+ {{ t('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 } from '../../functions/tabularApi'
26
+ import type { Chart, ChartForApi } from '../../types/visualizations'
27
+ import { useTranslation } from '../../composables/useTranslation'
28
+
29
+ const props = defineProps<{
30
+ chart: Chart | ChartForApi
31
+ }>()
32
+
33
+ const emit = defineEmits<{
34
+ columns: [columns: Record<string, Array<string>>]
35
+ }>()
36
+
37
+ const { t } = useTranslation()
38
+ const config = useComponentsConfig()
39
+ const getProfile = useGetProfile()
40
+
41
+ const status = ref<'idle' | 'pending' | 'success' | 'error'>('idle')
42
+ const error = ref<Error | null>(null)
43
+
44
+ const pendingOperations = ref(0)
45
+
46
+ const series = reactive<{
47
+ data: Record<string, Array<Record<string, unknown>>>
48
+ columns: Record<string, Array<string>>
49
+ }>({
50
+ data: {},
51
+ columns: {},
52
+ })
53
+
54
+ async function fetchSeriesProfile() {
55
+ pendingOperations.value++
56
+ status.value = 'pending'
57
+ error.value = null
58
+
59
+ try {
60
+ if (props.chart.series.length === 0) {
61
+ status.value = 'success'
62
+ series.data = {}
63
+ series.columns = {}
64
+ return
65
+ }
66
+
67
+ const fetchPromises = props.chart.series
68
+ .filter(serie => serie.resource_id)
69
+ .map(async (serie) => {
70
+ return {
71
+ id: serie.resource_id,
72
+ profile: await getProfile(serie.resource_id),
73
+ }
74
+ })
75
+
76
+ const results = (await Promise.allSettled(fetchPromises))
77
+ .filter(r => r.status === 'fulfilled')
78
+ .map(r => r.value)
79
+ series.columns = Object.fromEntries(results.map(result => [
80
+ result.id,
81
+ result.profile.profile.header,
82
+ ]))
83
+ }
84
+ catch (err) {
85
+ error.value = err instanceof Error ? err : new Error('Failed to fetch series profile')
86
+ status.value = 'error'
87
+ console.error(err)
88
+ series.columns = {}
89
+ }
90
+ finally {
91
+ pendingOperations.value--
92
+ }
93
+ }
94
+
95
+ async function fetchSeriesData() {
96
+ pendingOperations.value++
97
+ status.value = 'pending'
98
+ error.value = null
99
+
100
+ try {
101
+ if (props.chart.series.length === 0 || !props.chart.x_axis.column_x) {
102
+ status.value = 'success'
103
+ series.data = {}
104
+ return
105
+ }
106
+
107
+ const fetchPromises = props.chart.series
108
+ .filter((serie) => {
109
+ const xColumn = serie.column_x_name_override ?? props.chart.x_axis.column_x
110
+ return xColumn && serie.resource_id && serie.column_y
111
+ })
112
+ .map(async (serie) => {
113
+ const xColumn = serie.column_x_name_override ?? props.chart.x_axis.column_x
114
+ return {
115
+ id: 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
+ filters: serie.filters ?? undefined,
129
+ }),
130
+ }
131
+ })
132
+
133
+ const results = (await Promise.allSettled(fetchPromises))
134
+ .filter(r => r.status === 'fulfilled')
135
+ .map(r => r.value)
136
+ series.data = Object.fromEntries(results.map(result => [
137
+ result.id,
138
+ result.data.data,
139
+ ]))
140
+ }
141
+ catch (err) {
142
+ error.value = err instanceof Error ? err : new Error('Failed to fetch series data')
143
+ status.value = 'error'
144
+ series.data = {}
145
+ }
146
+ finally {
147
+ pendingOperations.value--
148
+ }
149
+ }
150
+
151
+ watch(() => props.chart.series, async () => {
152
+ await fetchSeriesProfile()
153
+ }, { immediate: true, deep: true })
154
+
155
+ watch([() => props.chart.series, () => props.chart.x_axis.column_x], async () => {
156
+ await fetchSeriesData()
157
+ }, { immediate: true, deep: true })
158
+
159
+ watch(() => series.columns, () => {
160
+ emit('columns', series.columns)
161
+ })
162
+
163
+ watch(pendingOperations, (count) => {
164
+ if (count === 0) {
165
+ if (error.value === null && status.value === 'pending') {
166
+ status.value = 'success'
167
+ }
168
+ }
169
+ })
170
+ </script>