@datagouv/components-next 1.0.2-dev.52 → 1.0.2-dev.53

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.
@@ -1,4 +1,4 @@
1
- import { c as Ke } from "./main-DaWGX8hL.js";
1
+ import { c as Ke } from "./main-7DRSPyNj.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.52",
3
+ "version": "1.0.2-dev.53",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -25,7 +25,6 @@
25
25
  "@vueuse/router": "^14.2.1",
26
26
  "chart.js": "^4.4.8",
27
27
  "dompurify": "^3.2.5",
28
- "echarts": "^6.0.0",
29
28
  "geopf-extensions-openlayers": "^1.0.0-beta.5",
30
29
  "leaflet": "^1.9.4",
31
30
  "maplibre-gl": "^5.6.2",
@@ -49,7 +48,6 @@
49
48
  "unified": "^11.0.5",
50
49
  "unist-util-visit": "^5.0.0",
51
50
  "vue-content-loader": "^2.0.1",
52
- "vue-echarts": "^8.0.1",
53
51
  "vue-sonner": "^2.0.9",
54
52
  "vue3-json-viewer": "^2.4.1",
55
53
  "vue3-text-clamp": "^0.1.2",
@@ -142,7 +142,7 @@ async function getTableInfos(page: number, sortConfig?: SortConfig) {
142
142
  try {
143
143
  // Check that this function return wanted data
144
144
  const response = await getData(config, props.resource.id, page, sortConfig)
145
- if ('data' in response && response.data && 0 in response.data) {
145
+ if ('data' in response && response.data && response.data.length > 0) {
146
146
  // Update existing rows
147
147
  rows.value = response.data
148
148
  columns.value = Object.keys(response.data[0]).filter(item => item !== '__id')
@@ -1,16 +1,10 @@
1
1
  import { useComponentsConfig } from '../config'
2
2
  import type { Resource } from '../types/resources'
3
3
 
4
- /**
5
- * Composable to determine if a resource has tabular data.
6
- * This is used to show the "Données" tab for tabular files AND the "Structure des données" tab (for tabular data structure).
7
- *
8
- * @returns A function to check if a resource has tabular data.
9
- */
10
4
  export const useHasTabularData = () => {
11
5
  const config = useComponentsConfig()
12
6
 
13
- const hasTabularData = (resource: Resource) => {
7
+ return (resource: Resource) => {
14
8
  return (
15
9
  config.tabularApiUrl
16
10
  && resource.extras['analysis:parsing:parsing_table']
@@ -18,6 +12,4 @@ export const useHasTabularData = () => {
18
12
  && (config.tabularAllowRemote || resource.filetype === 'file')
19
13
  )
20
14
  }
21
-
22
- return hasTabularData
23
15
  }
@@ -1,146 +1,20 @@
1
1
  import { ofetch } from 'ofetch'
2
2
  import { useComponentsConfig, type PluginConfig } from '../config'
3
- import type { GenericFilter } from '../types/visualizations'
4
3
 
5
4
  export type SortConfig = {
6
5
  column: string
7
6
  type: string
8
7
  } | null
9
8
 
10
- export type TabularDataResponse = {
11
- data: Array<Record<string, unknown>>
12
- links: {
13
- profile: string
14
- swagger: string
15
- next: string
16
- }
17
- meta: { total: number }
18
- }
19
-
20
- export type TabularAggregateType = 'avg' | 'sum' | 'count' | 'min' | 'max'
21
-
22
- export type FetchTabularDataOptions = {
23
- resourceId: string
24
- page?: number
25
- pageSize?: number
26
- columns?: Array<string> | undefined
27
- sort?: SortConfig
28
- groupBy?: string | undefined
29
- aggregation?: {
30
- column: string
31
- type: TabularAggregateType
32
- } | undefined
33
- filters?: GenericFilter | undefined
34
- }
35
-
36
- export type TabularProfileResponse = {
37
- profile: {
38
- header: Array<string>
39
- columns: Record<string, {
40
- score: number
41
- format: string
42
- python_type: string
43
- }>
44
- formats: Record<string, Array<string>>
45
- profile: Record<string, {
46
- tops: Array<{ count: number, value: string }>
47
- nb_distinct: number
48
- nb_missing_values: number
49
- min?: number
50
- max?: number
51
- std?: number
52
- mean?: number
53
- }>
54
- encoding: string
55
- separator: string
56
- categorical: Array<string>
57
- total_lines: number
58
- nb_duplicates: number
59
- columns_fields: Record<string, {
60
- score: number
61
- format: string
62
- python_type: string
63
- }>
64
- columns_labels: Record<string, {
65
- score: number
66
- format: string
67
- python_type: string
68
- }>
69
- header_row_idx: number
70
- heading_columns: number
71
- trailing_columns: number
72
- }
73
- deleted_at: string | null
74
- dataset_id: string
75
- indexes: null
76
- }
77
-
78
- /**
79
- * Call Tabular-api to get table content with options object
80
- */
81
- export async function fetchTabularData(config: PluginConfig, options: FetchTabularDataOptions): Promise<TabularDataResponse> {
82
- const page = options.page ?? 1
83
- const pageSize = options.pageSize ?? config.tabularApiPageSize ?? 15
84
- console.log(options)
85
- let url = `${config.tabularApiUrl}/api/resources/${options.resourceId}/data/?page=${page}&page_size=${pageSize}`
86
- if (options.columns) {
87
- url += `&columns=${options.columns.join(',')}`
88
- }
89
- if (options.sort) {
90
- url += `&${options.sort.column}__sort=${options.sort.type}`
91
- }
92
- if (options.groupBy && options.aggregation?.type) {
93
- url += `&${options.groupBy}__groupby&${options.aggregation.column}__${options.aggregation.type}`
94
- }
95
- if (options.filters) {
96
- const filterQuery = buildFilterQuery(options.filters)
97
- if (filterQuery) {
98
- url += `&${filterQuery}`
99
- }
100
- }
101
- return await ofetch<TabularDataResponse>(url)
102
- }
103
-
104
- function buildFilterQuery(filters: GenericFilter): string {
105
- const params: Array<string> = []
106
- if ('filters' in filters) {
107
- for (const filter of filters.filters) {
108
- if ('filters' in filter) {
109
- params.push(buildFilterQuery(filter))
110
- }
111
- else {
112
- if (filter.condition === 'is_null') {
113
- params.push(`${filter.column}__isnull`)
114
- }
115
- else if (filter.condition === 'is_not_null') {
116
- params.push(`${filter.column}__isnotnull`)
117
- }
118
- else if (filter.value !== null && filter.value !== undefined && filter.value !== '') {
119
- params.push(`${filter.column}__${filter.condition}=${encodeURIComponent(filter.value)}`)
120
- }
121
- }
122
- }
123
- }
124
- else {
125
- const filter = filters
126
- if (filter.condition === 'is_null') {
127
- params.push(`${filter.column}__isnull`)
128
- }
129
- else if (filter.condition === 'is_not_null') {
130
- params.push(`${filter.column}__isnotnull`)
131
- }
132
- else if (filter.value !== null && filter.value !== undefined && filter.value !== '') {
133
- params.push(`${filter.column}__${filter.condition}=${encodeURIComponent(filter.value)}`)
134
- }
135
- }
136
- return params.join('&')
137
- }
138
-
139
9
  /**
140
10
  * Call Tabular-api to get table content
141
11
  */
142
- export function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig) {
143
- return fetchTabularData(config, { resourceId: id, page, sort: sortConfig })
12
+ export async function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig) {
13
+ let url = `${config.tabularApiUrl}/api/resources/${id}/data/?page=${page}&page_size=${config.tabularApiPageSize || 15}`
14
+ if (sortConfig) {
15
+ url = url + `&${sortConfig.column}__sort=${sortConfig.type}`
16
+ }
17
+ return await ofetch(url)
144
18
  }
145
19
 
146
20
  /**
@@ -148,5 +22,5 @@ export function getData(config: PluginConfig, id: string, page: number, sortConf
148
22
  */
149
23
  export function useGetProfile() {
150
24
  const config = useComponentsConfig()
151
- return (id: string) => ofetch<TabularProfileResponse>(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
25
+ return (id: string) => ofetch(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
152
26
  }
package/src/main.ts CHANGED
@@ -23,7 +23,6 @@ import type { Site } from './types/site'
23
23
  import type { Weight, WellType } from './types/ui'
24
24
  import type { User, UserReference } from './types/users'
25
25
  import type { Report, ReportSubject, ReportReason } from './types/reports'
26
- import type { Chart, ChartForm, ChartForApi, FilterCondition, Filter, AndFilters, GenericFilter, XAxisType, XAxisSortBy, SortDirection, XAxis, XAxisForm, UnitPosition, YAxis, DataSeriesType, DataSeries, DataSeriesForm } from './types/visualizations'
27
26
  import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
28
27
  import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultTopicConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
29
28
 
@@ -81,8 +80,6 @@ import ReuseDetails from './components/ReuseDetails.vue'
81
80
  import SchemaCard from './components/SchemaCard.vue'
82
81
  import SimpleBanner from './components/SimpleBanner.vue'
83
82
  import SmallChart from './components/SmallChart.vue'
84
- import ChartViewer from './components/Chart/ChartViewer.vue'
85
- import ChartViewerWrapper from './components/Chart/ChartViewerWrapper.vue'
86
83
  import StatBox from './components/StatBox.vue'
87
84
  import Tab from './components/Tabs/Tab.vue'
88
85
  import TabGroup from './components/Tabs/TabGroup.vue'
@@ -97,14 +94,12 @@ import GlobalSearch from './components/Search/GlobalSearch.vue'
97
94
  import SearchInput from './components/Search/SearchInput.vue'
98
95
  import SearchableSelect from './components/Form/SearchableSelect.vue'
99
96
  import SelectGroup from './components/Form/SelectGroup.vue'
100
- import Listbox from './components/Form/Listbox.vue'
101
97
  import type { UseFetchFunction } from './functions/api.types'
102
98
  import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
103
99
 
104
100
  export { Toaster, toast } from 'vue-sonner'
105
101
 
106
102
  export * from './composables/useActiveDescendant'
107
- export * from './composables/useDebouncedRef'
108
103
  export * from './composables/useMetrics'
109
104
  export * from './composables/useReuseType'
110
105
  export * from './composables/useTranslation'
@@ -125,8 +120,6 @@ export * from './functions/resources'
125
120
  export * from './functions/reuses'
126
121
  export * from './functions/schemas'
127
122
  export * from './functions/users'
128
- export * from './functions/tabularApi'
129
- export * from './functions/charts'
130
123
  export * from './types/access_types'
131
124
 
132
125
  export type {
@@ -223,23 +216,6 @@ export type {
223
216
  ValidataError,
224
217
  Weight,
225
218
  WellType,
226
- Chart,
227
- ChartForm,
228
- ChartForApi,
229
- FilterCondition,
230
- Filter,
231
- AndFilters,
232
- GenericFilter,
233
- XAxisType,
234
- XAxisSortBy,
235
- SortDirection,
236
- XAxis,
237
- XAxisForm,
238
- UnitPosition,
239
- YAxis,
240
- DataSeriesType,
241
- DataSeries,
242
- DataSeriesForm,
243
219
  }
244
220
 
245
221
  export {
@@ -325,8 +301,6 @@ export {
325
301
  SchemaCard,
326
302
  SimpleBanner,
327
303
  SmallChart,
328
- ChartViewer,
329
- ChartViewerWrapper,
330
304
  StatBox,
331
305
  OpenApiViewer,
332
306
  Tab,
@@ -343,5 +317,4 @@ export {
343
317
  SearchInput,
344
318
  SearchableSelect,
345
319
  SelectGroup,
346
- Listbox,
347
320
  }
@@ -1,147 +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, XAxis, YAxis, XAxisForm, ChartForApi } from '../../types/visualizations'
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
- function mapXAxisType(xAxis: XAxis | XAxisForm): 'category' | 'value' {
30
- if (!xAxis) return 'category'
31
- return (xAxis.type ?? 'discrete') === 'continuous' ? 'value' : 'category'
32
- }
33
-
34
- function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefined {
35
- return (value: number) => {
36
- const v = summarize(value)
37
- if (!yAxis.unit) return v
38
- if (yAxis.unit_position === 'prefix') return `${yAxis.unit} ${v}`
39
- return `${v} ${yAxis.unit}`
40
- }
41
- }
42
-
43
- const echartsOption = computed(() => {
44
- const seriesCount = props.chart.series.length
45
- if (!props.chart.series || seriesCount === 0) return
46
-
47
- const seriesData = props.chart.series.map((s) => {
48
- const xColumn = s.column_x_name_override ?? props.chart.x_axis.column_x
49
- const yColumn = s.aggregate_y ? `${s.column_y}__${s.aggregate_y}` : s.column_y
50
- const resourceId = s.resource_id
51
-
52
- if (!xColumn || !yColumn || !resourceId || !s.type || !props.series.data[resourceId] || !props.series.columns[resourceId]) {
53
- return null
54
- }
55
-
56
- const sortedData = [...props.series.data[resourceId]]
57
- let sortBy: string | null = null
58
- let sortDirection: 'asc' | 'desc' = 'asc'
59
- sortBy = props.chart.x_axis.sort_x_by
60
- sortDirection = props.chart.x_axis.sort_x_direction ?? 'asc'
61
-
62
- if (sortBy && sortDirection && props.chart.x_axis.column_x) {
63
- const sortKey = sortBy === 'axis_x' ? xColumn : yColumn
64
- sortedData.sort((a, b) => {
65
- const valA = a[sortKey] as number
66
- const valB = b[sortKey] as number
67
- if (valA < valB) return sortDirection === 'asc' ? -1 : 1
68
- if (valA > valB) return sortDirection === 'asc' ? 1 : -1
69
- return 0
70
- })
71
- }
72
-
73
- return {
74
- series: {
75
- type: s.type === 'histogram' ? 'bar' : 'line',
76
- dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
77
- name: s.column_y,
78
- encode: {
79
- x: xColumn,
80
- y: yColumn,
81
- },
82
- } as LineSeriesOption | BarSeriesOption,
83
- data: {
84
- source: sortedData,
85
- dimensions: s.aggregate_y ? [xColumn, yColumn] : props.series.columns[resourceId],
86
- },
87
- }
88
- }).filter(Boolean).reduce((acc: { series: Array<LineSeriesOption | BarSeriesOption>, data: Array<Record<string, unknown>> }, curr) => {
89
- if (curr) {
90
- acc.series.push(curr.series)
91
- acc.data.push(curr.data)
92
- }
93
- return acc
94
- }, {
95
- series: [],
96
- data: [],
97
- })
98
-
99
- return {
100
- dataset: [...seriesData.data],
101
- title: {
102
- text: props.chart.title,
103
- left: 'center',
104
- },
105
- tooltip: {
106
- trigger: 'axis' as const,
107
- formatter: (params: Array<{ value: Record<string, unknown>, axisValueLabel: string, seriesName: string }>) => {
108
- let tooltip = ''
109
- for (const param of params) {
110
- const keys = Object.keys(param.value)
111
- const col = keys.find(key => key.startsWith(param.seriesName))!
112
- const formatter = new Intl.NumberFormat('fr-FR')
113
- tooltip += `${format.encodeHTML(param.axisValueLabel)}: <strong>${formatter.format(Number(param.value[col]))}</strong><br>`
114
- }
115
- return tooltip
116
- },
117
- },
118
- legend: {
119
- bottom: 0,
120
- },
121
- grid: {
122
- top: 60,
123
- bottom: 40,
124
- left: 20,
125
- right: 20,
126
- containLabel: true,
127
- },
128
- xAxis: {
129
- type: mapXAxisType(props.chart.x_axis),
130
- name: (props.chart.x_axis as XAxis).column_x,
131
- },
132
- yAxis: {
133
- type: 'value' as const,
134
- name: props.chart.y_axis.label ?? undefined,
135
- min: props.chart.y_axis.min ?? undefined,
136
- max: props.chart.y_axis.max ?? undefined,
137
- axisLabel: {
138
- formatter: buildYAxisFormatter(props.chart.y_axis),
139
- },
140
- },
141
- series: seriesData.series,
142
- } satisfies ComposeOption<
143
- | BarSeriesOption
144
- | LineSeriesOption
145
- >
146
- })
147
- </script>
@@ -1,224 +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, ChartForApi } from '../../types/visualizations'
27
-
28
- const props = defineProps<{
29
- chart: Chart | ChartForApi
30
- loadAllPages?: boolean
31
- }>()
32
-
33
- const emit = defineEmits<{
34
- columns: [columns: Record<string, Array<string>>]
35
- }>()
36
-
37
- const config = useComponentsConfig()
38
- const getProfile = useGetProfile()
39
-
40
- // Loading and error states
41
- const status = ref<'idle' | 'pending' | 'success' | 'error'>('idle')
42
- const error = ref<Error | null>(null)
43
-
44
- // Dataset source for the chart
45
- const series = reactive<{
46
- data: Record<string, Array<Record<string, unknown>>>
47
- columns: Record<string, Array<string>>
48
- page: Record<string, number>
49
- hasNextPage: Record<string, boolean>
50
- }>({
51
- data: {},
52
- columns: {},
53
- page: {},
54
- hasNextPage: {},
55
- })
56
-
57
- async function fetchSeriesProfile() {
58
- status.value = 'pending'
59
- error.value = null
60
-
61
- try {
62
- if (props.chart.series.length === 0) {
63
- status.value = 'success'
64
- series.data = {}
65
- series.columns = {}
66
- return
67
- }
68
-
69
- // Fetch data for all series in parallel
70
- const fetchPromises = props.chart.series.map(async (serie) => {
71
- if (!serie.resource_id) return
72
- return {
73
- id: serie.resource_id,
74
- profile: await getProfile(serie.resource_id),
75
- }
76
- }).filter(Boolean) as Array<Promise<{
77
- id: string
78
- profile: TabularProfileResponse
79
- }>>
80
-
81
- const results = (await Promise.allSettled(fetchPromises))
82
- .filter(r => r.status === 'fulfilled')
83
- .map(r => r.value)
84
- series.columns = Object.fromEntries(results.map(result => [
85
- result.id,
86
- result.profile.profile.header,
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 loadMorePages() {
99
- for (const serie of props.chart.series) {
100
- const xColumn = serie.column_x_name_override ?? props.chart.x_axis.column_x
101
- const resourceId = serie.resource_id
102
- if (!xColumn || !resourceId || !serie.column_y) continue
103
-
104
- // Check if there's more data to load for this series
105
- if (!series.hasNextPage[resourceId]) {
106
- continue
107
- }
108
-
109
- // Calculate the next page to fetch
110
- const nextPage = (series.page[resourceId] || 0) + 1
111
-
112
- const response = await fetchTabularData(config, {
113
- columns: serie.aggregate_y ? undefined : [xColumn, serie.column_y],
114
- resourceId,
115
- page: nextPage,
116
- pageSize: 100,
117
- groupBy: xColumn,
118
- aggregation: serie.column_y && serie.aggregate_y
119
- ? {
120
- column: serie.column_y,
121
- type: serie.aggregate_y,
122
- }
123
- : undefined,
124
- filters: serie.filters ?? undefined,
125
- })
126
-
127
- // Update the page tracker
128
- series.page[resourceId] = nextPage
129
-
130
- if (!series.data[resourceId]) {
131
- series.data[resourceId] = []
132
- }
133
- series.data[resourceId] = [...series.data[resourceId], ...response.data]
134
-
135
- // Update hasNextPage based on the API response
136
- series.hasNextPage[resourceId] = !!response.links.next
137
- }
138
- }
139
-
140
- async function fetchSeriesData() {
141
- status.value = 'pending'
142
- error.value = null
143
-
144
- try {
145
- if (props.chart.series.length === 0 || !props.chart.x_axis.column_x) {
146
- status.value = 'success'
147
- series.data = {}
148
- return
149
- }
150
-
151
- const fetchPromises = props.chart.series.map(async (serie) => {
152
- const xColumn = serie.column_x_name_override ?? props.chart.x_axis.column_x
153
-
154
- if (!xColumn || !serie.resource_id || !serie.column_y) return
155
- return {
156
- id: serie.resource_id,
157
- data: await fetchTabularData(config, {
158
- columns: serie.aggregate_y ? undefined : [xColumn, serie.column_y],
159
- resourceId: serie.resource_id,
160
- page: 1,
161
- pageSize: 100,
162
- groupBy: xColumn,
163
- aggregation: serie.column_y && serie.aggregate_y
164
- ? {
165
- column: serie.column_y,
166
- type: serie.aggregate_y,
167
- }
168
- : undefined,
169
- filters: serie.filters ?? undefined,
170
- }),
171
- }
172
- }).filter(Boolean) as Array<Promise<{
173
- id: string
174
- data: TabularDataResponse
175
- }>>
176
-
177
- const results = (await Promise.allSettled(fetchPromises))
178
- .filter(r => r.status === 'fulfilled')
179
- .map(r => r.value)
180
- // Transform data into echarts format
181
- series.data = Object.fromEntries(results.map(result => [
182
- result.id,
183
- result.data.data,
184
- ]))
185
-
186
- // Reset page tracking for each series to 0 (page 1 has been loaded)
187
- // Also initialize hasNextPage based on the response
188
- for (const result of results) {
189
- const serie = props.chart.series.find(s => s.resource_id === result.id)
190
- if (serie) {
191
- series.page[result.id] = 0
192
- series.hasNextPage[result.id] = !!result.data.links.next
193
- }
194
- }
195
-
196
- // If loadAllPages is true, fetch the next page (loadMorePages can be called again for more)
197
- // This allows progressive loading - first page loads quickly, then more can be loaded on demand
198
- if (props.loadAllPages) {
199
- await loadMorePages()
200
- }
201
-
202
- status.value = 'success'
203
- }
204
- catch (err) {
205
- error.value = err instanceof Error ? err : new Error('Failed to fetch series data')
206
- status.value = 'error'
207
- series.data = {}
208
- }
209
- }
210
-
211
- // Watch for changes in the chart or its series
212
- watch(() => props.chart.series, async () => {
213
- await fetchSeriesProfile()
214
- }, { immediate: true, deep: true })
215
-
216
- // Watch for changes in the chart or its series
217
- watch([() => props.chart.series, () => props.chart.x_axis.column_x], async () => {
218
- await fetchSeriesData()
219
- }, { immediate: true, deep: true })
220
-
221
- watch(() => series.columns, () => {
222
- emit('columns', series.columns)
223
- })
224
- </script>