@adminforth/dashboard 1.4.0 → 1.4.2

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 (86) hide show
  1. package/README.md +23 -4
  2. package/custom/api/dashboardApi.ts +6 -9
  3. package/custom/model/dashboard.types.ts +60 -275
  4. package/custom/model/dashboardTopics.ts +5 -0
  5. package/custom/package.json +1 -0
  6. package/custom/runtime/DashboardGroup.vue +2 -2
  7. package/custom/runtime/DashboardPage.vue +17 -7
  8. package/custom/runtime/DashboardRuntime.vue +20 -8
  9. package/custom/runtime/WidgetRenderer.vue +1 -2
  10. package/custom/runtime/WidgetShell.vue +3 -3
  11. package/custom/skills/adminforth-dashboard/SKILL.md +2 -2
  12. package/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  13. package/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  14. package/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  15. package/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  16. package/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  17. package/custom/widgets/chart/ChartWidget.vue +4 -15
  18. package/{dist/custom/widgets/chart/funnel → custom/widgets/chart}/FunnelChart.vue +80 -78
  19. package/{dist/custom/widgets/chart/line → custom/widgets/chart}/LineChart.vue +2 -2
  20. package/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  21. package/{dist/custom/widgets/chart/stacked-bar → custom/widgets/chart}/StackedBarChart.vue +97 -95
  22. package/custom/widgets/chart/chart.types.ts +0 -28
  23. package/dist/custom/api/dashboardApi.d.ts +4 -8
  24. package/dist/custom/api/dashboardApi.js +2 -6
  25. package/dist/custom/api/dashboardApi.ts +6 -9
  26. package/dist/custom/composables/useElementSize.js +7 -10
  27. package/dist/custom/model/dashboard.types.d.ts +38 -32
  28. package/dist/custom/model/dashboard.types.js +4 -161
  29. package/dist/custom/model/dashboard.types.ts +60 -275
  30. package/dist/custom/model/dashboardTopics.d.ts +2 -0
  31. package/dist/custom/model/dashboardTopics.js +4 -0
  32. package/dist/custom/model/dashboardTopics.ts +5 -0
  33. package/dist/custom/package.json +1 -0
  34. package/dist/custom/queries/useDashboardConfig.d.ts +96 -96
  35. package/dist/custom/queries/useDashboardConfig.js +9 -12
  36. package/dist/custom/queries/useWidgetData.d.ts +96 -96
  37. package/dist/custom/queries/useWidgetData.js +9 -12
  38. package/dist/custom/runtime/DashboardGroup.vue +2 -2
  39. package/dist/custom/runtime/DashboardPage.vue +17 -7
  40. package/dist/custom/runtime/DashboardRuntime.vue +20 -8
  41. package/dist/custom/runtime/WidgetRenderer.vue +1 -2
  42. package/dist/custom/runtime/WidgetShell.vue +3 -3
  43. package/dist/custom/skills/adminforth-dashboard/SKILL.md +2 -2
  44. package/dist/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  45. package/dist/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  46. package/dist/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  47. package/dist/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  48. package/dist/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  49. package/dist/custom/widgets/chart/ChartWidget.vue +4 -15
  50. package/{custom/widgets/chart/funnel → dist/custom/widgets/chart}/FunnelChart.vue +80 -78
  51. package/{custom/widgets/chart/line → dist/custom/widgets/chart}/LineChart.vue +2 -2
  52. package/dist/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  53. package/{custom/widgets/chart/stacked-bar → dist/custom/widgets/chart}/StackedBarChart.vue +97 -95
  54. package/dist/custom/widgets/chart/chart.types.d.ts +0 -2
  55. package/dist/custom/widgets/chart/chart.types.js +1 -25
  56. package/dist/custom/widgets/chart/chart.types.ts +0 -28
  57. package/dist/custom/widgets/chart/chart.utils.js +6 -14
  58. package/dist/custom/widgets/registry.js +14 -22
  59. package/dist/endpoint/dashboard.d.ts +2 -3
  60. package/dist/endpoint/dashboard.js +12 -32
  61. package/dist/endpoint/groups.d.ts +2 -21
  62. package/dist/endpoint/groups.js +18 -16
  63. package/dist/endpoint/widgets.d.ts +0 -3
  64. package/dist/endpoint/widgets.js +27 -74
  65. package/dist/index.js +1 -3
  66. package/dist/schema/api.d.ts +2090 -511
  67. package/dist/schema/api.js +18 -15
  68. package/dist/schema/widget.d.ts +1003 -250
  69. package/dist/schema/widget.js +102 -46
  70. package/dist/services/dashboardConfigService.d.ts +0 -10
  71. package/dist/services/dashboardConfigService.js +6 -21
  72. package/dist/services/widgetDataService.js +226 -196
  73. package/endpoint/dashboard.ts +13 -46
  74. package/endpoint/groups.ts +25 -42
  75. package/endpoint/widgets.ts +36 -95
  76. package/index.ts +0 -3
  77. package/package.json +3 -3
  78. package/schema/api.ts +19 -15
  79. package/schema/widget.ts +113 -52
  80. package/services/dashboardConfigService.ts +6 -25
  81. package/services/widgetDataService.ts +304 -229
  82. package/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  83. package/dist/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  84. package/dist/services/widgetConfigValidator.d.ts +0 -8
  85. package/dist/services/widgetConfigValidator.js +0 -27
  86. package/services/widgetConfigValidator.ts +0 -61
@@ -136,10 +136,10 @@ const widgetLayoutVars = computed<CSSProperties>(() => {
136
136
 
137
137
  return {
138
138
  '--widget-basis': clampToContainerWidth(fixedWidth ?? basis),
139
- '--widget-min-width': clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.minWidth) ?? basis),
140
- '--widget-max-width': props.layout?.maxWidth === null
139
+ '--widget-min-width': clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.min_width) ?? basis),
140
+ '--widget-max-width': props.layout?.max_width === null
141
141
  ? '100%'
142
- : clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.maxWidth) ?? '100%'),
142
+ : clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.max_width) ?? '100%'),
143
143
  height: formatWidth(props.layout?.height ?? DEFAULT_WIDGET_HEIGHT),
144
144
  }
145
145
  })
@@ -139,8 +139,8 @@ Use the current schema keys exactly:
139
139
 
140
140
  - Use `target`, not `type`.
141
141
  - Use `label`, not `title`.
142
- - Use `query`, not `data_source`.
143
- - Use `resource`, not `resource_id`.
142
+ - Use `query`, not `dataSource`.
143
+ - Use `resource`, not `resourceId`.
144
144
  - Use `group_by`, not `groupBy`.
145
145
  - Use `order_by`, not `orderBy`.
146
146
  - Use `page_size`, not `pageSize`.
@@ -1,8 +1,66 @@
1
+ <template>
2
+ <div class="mt-3 rounded-lg border border-lightListBorder bg-lightTableBackground p-4 dark:border-darkListBorder dark:bg-darkTableBackground">
3
+ <div
4
+ v-if="isLoading"
5
+ class="text-sm text-lightListTableText dark:text-darkListTableText"
6
+ >
7
+ Loading...
8
+ </div>
9
+
10
+ <div
11
+ v-else-if="error"
12
+ class="text-sm text-lightInputErrorColor"
13
+ >
14
+ Failed to load gauge data
15
+ </div>
16
+
17
+ <div
18
+ v-else
19
+ class="flex flex-col items-center gap-2"
20
+ >
21
+ <svg
22
+ width="180"
23
+ height="104"
24
+ viewBox="0 0 180 104"
25
+ role="img"
26
+ :aria-label="valueField"
27
+ >
28
+ <path
29
+ d="M18 90a72 72 0 0 1 144 0"
30
+ class="text-lightListBorder dark:text-darkListBorder"
31
+ fill="none"
32
+ stroke="currentColor"
33
+ stroke-linecap="round"
34
+ stroke-width="18"
35
+ />
36
+ <path
37
+ d="M18 90a72 72 0 0 1 144 0"
38
+ fill="none"
39
+ :stroke="gaugeColor"
40
+ stroke-linecap="round"
41
+ stroke-width="18"
42
+ :stroke-dasharray="circumference"
43
+ :stroke-dashoffset="strokeDashoffset"
44
+ />
45
+ </svg>
46
+
47
+ <div class="text-3xl font-bold text-lightNavbarText dark:text-darkNavbarText">
48
+ {{ gaugeConfig?.value.prefix ?? '' }}{{ formattedValue }}{{ gaugeConfig?.value.suffix ?? '' }}
49
+ </div>
50
+ <div class="text-sm text-lightListTableText dark:text-darkListTableText">
51
+ {{ formattedMinValue }} - {{ formattedMaxValue }}
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </template>
56
+
57
+
58
+
1
59
  <script setup lang="ts">
2
60
  import { computed, watch } from 'vue'
3
- import { useWidgetData } from '../../queries/useWidgetData.js'
4
- import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
5
- import { CHART_COLORS, formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
61
+ import { useWidgetData } from '../queries/useWidgetData.js'
62
+ import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../model/dashboard.types.js'
63
+ import { CHART_COLORS, formatChartValue, toFiniteNumber } from './chart/chart.utils.js'
6
64
 
7
65
  const props = defineProps<{
8
66
  dashboardSlug: string
@@ -57,13 +115,13 @@ const widgetData = computed(() => data.value?.data as DashboardWidgetTableData |
57
115
  const columns = computed(() => widgetData.value?.columns ?? [])
58
116
  const firstRow = computed(() => widgetData.value?.rows[0] ?? {})
59
117
  const valueField = computed(() => gaugeConfig.value?.value.field || columns.value[0])
60
- const targetField = computed(() => gaugeConfig.value?.target?.field ?? gaugeConfig.value?.progress?.targetField)
118
+ const targetField = computed(() => gaugeConfig.value?.target?.field ?? gaugeConfig.value?.progress?.target_field)
61
119
  const minValue = computed(() => {
62
120
  return 0
63
121
  })
64
122
  const maxValue = computed(() => {
65
123
  const dynamicMax = targetField.value ? parseOptionalNumber(firstRow.value[targetField.value]) : undefined
66
- return dynamicMax ?? parseOptionalNumber(gaugeConfig.value?.target?.value ?? gaugeConfig.value?.progress?.targetValue) ?? 100
124
+ return dynamicMax ?? parseOptionalNumber(gaugeConfig.value?.target?.value ?? gaugeConfig.value?.progress?.target_value) ?? 100
67
125
  })
68
126
  const value = computed(() => toFiniteNumber(firstRow.value[valueField.value]))
69
127
  const fractionDigits = computed(() => Math.min([
@@ -95,59 +153,3 @@ const circumference = Math.PI * radius
95
153
  const strokeDashoffset = computed(() => circumference * (1 - progress.value))
96
154
  const gaugeColor = computed(() => gaugeConfig.value?.color || CHART_COLORS[0])
97
155
  </script>
98
-
99
- <template>
100
- <div class="mt-3 rounded-lg border border-lightListBorder bg-lightTableBackground p-4 dark:border-darkListBorder dark:bg-darkTableBackground">
101
- <div
102
- v-if="isLoading"
103
- class="text-sm text-lightListTableText dark:text-darkListTableText"
104
- >
105
- Loading...
106
- </div>
107
-
108
- <div
109
- v-else-if="error"
110
- class="text-sm text-lightInputErrorColor"
111
- >
112
- Failed to load gauge data
113
- </div>
114
-
115
- <div
116
- v-else
117
- class="flex flex-col items-center gap-2"
118
- >
119
- <svg
120
- width="180"
121
- height="104"
122
- viewBox="0 0 180 104"
123
- role="img"
124
- :aria-label="valueField"
125
- >
126
- <path
127
- d="M18 90a72 72 0 0 1 144 0"
128
- class="text-lightListBorder dark:text-darkListBorder"
129
- fill="none"
130
- stroke="currentColor"
131
- stroke-linecap="round"
132
- stroke-width="18"
133
- />
134
- <path
135
- d="M18 90a72 72 0 0 1 144 0"
136
- fill="none"
137
- :stroke="gaugeColor"
138
- stroke-linecap="round"
139
- stroke-width="18"
140
- :stroke-dasharray="circumference"
141
- :stroke-dashoffset="strokeDashoffset"
142
- />
143
- </svg>
144
-
145
- <div class="text-3xl font-bold text-lightNavbarText dark:text-darkNavbarText">
146
- {{ gaugeConfig?.value.prefix ?? '' }}{{ formattedValue }}{{ gaugeConfig?.value.suffix ?? '' }}
147
- </div>
148
- <div class="text-sm text-lightListTableText dark:text-darkListTableText">
149
- {{ formattedMinValue }} - {{ formattedMaxValue }}
150
- </div>
151
- </div>
152
- </div>
153
- </template>
@@ -1,8 +1,40 @@
1
+ <template>
2
+ <div class="mt-3 rounded-lg border border-lightListBorder bg-lightTableBackground p-4 dark:border-darkListBorder dark:bg-darkTableBackground">
3
+ <div
4
+ v-if="isLoading"
5
+ class="text-sm text-lightListTableText dark:text-darkListTableText"
6
+ >
7
+ Loading...
8
+ </div>
9
+
10
+ <div
11
+ v-else-if="error"
12
+ class="text-sm text-lightInputErrorColor"
13
+ >
14
+ Failed to load KPI data
15
+ </div>
16
+
17
+ <div
18
+ v-else
19
+ class="grid gap-1"
20
+ >
21
+ <div class="text-3xl font-bold text-lightNavbarText dark:text-darkNavbarText">
22
+ {{ formattedValue }}
23
+ </div>
24
+ <div class="text-sm text-lightListTableText dark:text-darkListTableText">
25
+ {{ label }}
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+
32
+
1
33
  <script setup lang="ts">
2
34
  import { computed, watch } from 'vue'
3
- import { useWidgetData } from '../../queries/useWidgetData.js'
4
- import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
5
- import { formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
35
+ import { useWidgetData } from '../queries/useWidgetData.js'
36
+ import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../model/dashboard.types.js'
37
+ import { formatChartValue, toFiniteNumber } from './chart/chart.utils.js'
6
38
 
7
39
  const props = defineProps<{
8
40
  dashboardSlug: string
@@ -37,33 +69,3 @@ const label = computed(() => kpiConfig.value?.subtitle?.field
37
69
  : kpiConfig.value?.subtitle?.text ?? kpiConfig.value?.title ?? props.widget.label)
38
70
  const formattedValue = computed(() => `${kpiConfig.value?.value.prefix ?? ''}${formatChartValue(value.value)}${kpiConfig.value?.value.suffix ?? ''}`)
39
71
  </script>
40
-
41
- <template>
42
- <div class="mt-3 rounded-lg border border-lightListBorder bg-lightTableBackground p-4 dark:border-darkListBorder dark:bg-darkTableBackground">
43
- <div
44
- v-if="isLoading"
45
- class="text-sm text-lightListTableText dark:text-darkListTableText"
46
- >
47
- Loading...
48
- </div>
49
-
50
- <div
51
- v-else-if="error"
52
- class="text-sm text-lightInputErrorColor"
53
- >
54
- Failed to load KPI data
55
- </div>
56
-
57
- <div
58
- v-else
59
- class="grid gap-1"
60
- >
61
- <div class="text-3xl font-bold text-lightNavbarText dark:text-darkNavbarText">
62
- {{ formattedValue }}
63
- </div>
64
- <div class="text-sm text-lightListTableText dark:text-darkListTableText">
65
- {{ label }}
66
- </div>
67
- </div>
68
- </div>
69
- </template>
@@ -1,11 +1,78 @@
1
+ <template>
2
+ <div class="mt-3 flex h-full min-h-0 flex-col overflow-hidden rounded-lg border border-lightListBorder bg-lightTableBackground dark:border-darkListBorder dark:bg-darkTableBackground">
3
+ <div
4
+ v-if="isLoading"
5
+ class="p-4 text-sm text-lightListTableText dark:text-darkListTableText"
6
+ >
7
+ Loading...
8
+ </div>
9
+
10
+ <div
11
+ v-else-if="error"
12
+ class="p-4 text-sm text-lightInputErrorColor"
13
+ >
14
+ Failed to load pivot data
15
+ </div>
16
+
17
+ <div
18
+ v-else-if="!pivotRows.length"
19
+ class="p-4 text-sm text-lightListTableText dark:text-darkListTableText"
20
+ >
21
+ No data available
22
+ </div>
23
+
24
+ <div
25
+ v-else
26
+ class="min-h-0 flex-1 overflow-auto"
27
+ >
28
+ <table class="min-w-max w-full border-collapse text-left text-sm">
29
+ <thead class="bg-lightTableHeadingBackground text-xs uppercase text-lightTableHeadingText dark:bg-darkTableHeadingBackground dark:text-darkTableHeadingText">
30
+ <tr>
31
+ <th class="px-3 py-2 font-semibold">
32
+ {{ rowField }}
33
+ </th>
34
+ <th
35
+ v-for="column in pivotColumnLabels"
36
+ :key="column"
37
+ class="px-3 py-2 text-right font-semibold"
38
+ >
39
+ {{ column }}
40
+ </th>
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ <tr
45
+ v-for="row in pivotRows"
46
+ :key="String(row.label)"
47
+ class="border-t border-lightListBorder odd:bg-lightTableOddBackground even:bg-lightTableEvenBackground dark:border-darkListBorder odd:dark:bg-darkTableOddBackground even:dark:bg-darkTableEvenBackground"
48
+ >
49
+ <td class="px-3 py-2 font-medium text-lightNavbarText dark:text-darkNavbarText">
50
+ {{ row.label }}
51
+ </td>
52
+ <td
53
+ v-for="column in pivotColumnLabels"
54
+ :key="column"
55
+ class="px-3 py-2 text-right text-lightListTableText dark:text-darkListTableText"
56
+ >
57
+ {{ formatChartValue(typeof row[column] === 'number' ? row[column] : 0) }}
58
+ </td>
59
+ </tr>
60
+ </tbody>
61
+ </table>
62
+ </div>
63
+ </div>
64
+ </template>
65
+
66
+
67
+
1
68
  <script setup lang="ts">
2
69
  import { computed, watch } from 'vue'
3
- import { useWidgetData } from '../../queries/useWidgetData.js'
70
+ import { useWidgetData } from '../queries/useWidgetData.js'
4
71
  import {
5
72
  getFieldRefField,
6
- } from '../../model/dashboard.types.js'
7
- import type { DashboardWidgetConfig, DashboardWidgetData } from '../../model/dashboard.types.js'
8
- import { formatChartLabel, formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
73
+ } from '../model/dashboard.types.js'
74
+ import type { DashboardWidgetConfig, DashboardWidgetData } from '../model/dashboard.types.js'
75
+ import { formatChartLabel, formatChartValue, toFiniteNumber } from './chart/chart.utils.js'
9
76
 
10
77
  const props = defineProps<{
11
78
  dashboardSlug: string
@@ -77,67 +144,3 @@ const pivotRows = computed(() => {
77
144
  })
78
145
  </script>
79
146
 
80
- <template>
81
- <div class="mt-3 flex h-full min-h-0 flex-col overflow-hidden rounded-lg border border-lightListBorder bg-lightTableBackground dark:border-darkListBorder dark:bg-darkTableBackground">
82
- <div
83
- v-if="isLoading"
84
- class="p-4 text-sm text-lightListTableText dark:text-darkListTableText"
85
- >
86
- Loading...
87
- </div>
88
-
89
- <div
90
- v-else-if="error"
91
- class="p-4 text-sm text-lightInputErrorColor"
92
- >
93
- Failed to load pivot data
94
- </div>
95
-
96
- <div
97
- v-else-if="!pivotRows.length"
98
- class="p-4 text-sm text-lightListTableText dark:text-darkListTableText"
99
- >
100
- No data available
101
- </div>
102
-
103
- <div
104
- v-else
105
- class="min-h-0 flex-1 overflow-auto"
106
- >
107
- <table class="min-w-max w-full border-collapse text-left text-sm">
108
- <thead class="bg-lightTableHeadingBackground text-xs uppercase text-lightTableHeadingText dark:bg-darkTableHeadingBackground dark:text-darkTableHeadingText">
109
- <tr>
110
- <th class="px-3 py-2 font-semibold">
111
- {{ rowField }}
112
- </th>
113
- <th
114
- v-for="column in pivotColumnLabels"
115
- :key="column"
116
- class="px-3 py-2 text-right font-semibold"
117
- >
118
- {{ column }}
119
- </th>
120
- </tr>
121
- </thead>
122
- <tbody>
123
- <tr
124
- v-for="row in pivotRows"
125
- :key="String(row.label)"
126
- class="border-t border-lightListBorder odd:bg-lightTableOddBackground even:bg-lightTableEvenBackground dark:border-darkListBorder odd:dark:bg-darkTableOddBackground even:dark:bg-darkTableEvenBackground"
127
- >
128
- <td class="px-3 py-2 font-medium text-lightNavbarText dark:text-darkNavbarText">
129
- {{ row.label }}
130
- </td>
131
- <td
132
- v-for="column in pivotColumnLabels"
133
- :key="column"
134
- class="px-3 py-2 text-right text-lightListTableText dark:text-darkListTableText"
135
- >
136
- {{ formatChartValue(typeof row[column] === 'number' ? row[column] : 0) }}
137
- </td>
138
- </tr>
139
- </tbody>
140
- </table>
141
- </div>
142
- </div>
143
- </template>
@@ -112,14 +112,14 @@
112
112
 
113
113
  <script setup lang="ts">
114
114
  import { computed, ref, watch } from 'vue'
115
- import { useWidgetData } from '../../queries/useWidgetData.js'
116
- import { getFieldRefField } from '../../model/dashboard.types.js'
117
- import type { DashboardWidgetConfig, DashboardWidgetTableData, FieldRef } from '../../model/dashboard.types.js'
115
+ import { useWidgetData } from '../queries/useWidgetData.js'
116
+ import { getFieldRefField } from '../model/dashboard.types.js'
117
+ import type { DashboardWidgetConfig, DashboardWidgetTableData, FieldRef } from '../model/dashboard.types.js'
118
118
 
119
119
  type TableWidgetConfig = {
120
120
  columns?: FieldRef[]
121
121
  pagination?: boolean
122
- pageSize?: number
122
+ page_size?: number
123
123
  }
124
124
 
125
125
  const DEFAULT_PAGE_SIZE = 10
@@ -133,7 +133,7 @@ const currentPage = ref(1)
133
133
  const currentPageInput = ref(1)
134
134
  const tableConfig = computed(() => props.widget.table as TableWidgetConfig | undefined)
135
135
  const isPaginationEnabled = computed(() => tableConfig.value?.pagination !== false)
136
- const pageSize = computed(() => tableConfig.value?.pageSize ?? DEFAULT_PAGE_SIZE)
136
+ const pageSize = computed(() => tableConfig.value?.page_size ?? DEFAULT_PAGE_SIZE)
137
137
  const dashboardSlugRef = computed(() => props.dashboardSlug)
138
138
  const widgetIdRef = computed(() => props.widget.id)
139
139
  const widgetDataRequest = computed(() => (
@@ -82,7 +82,7 @@
82
82
 
83
83
  <script setup lang="ts">
84
84
  import { computed } from 'vue'
85
- import { useElementSize } from '../../../composables/useElementSize.js'
85
+ import { useElementSize } from '../../composables/useElementSize.js'
86
86
  import {
87
87
  CHART_COLORS,
88
88
  formatChartAxisLabel,
@@ -90,7 +90,7 @@ import {
90
90
  formatChartValue,
91
91
  getChartYAxisWidth,
92
92
  toFiniteNumber,
93
- } from '../chart.utils.js'
93
+ } from './chart.utils.js'
94
94
 
95
95
  const props = withDefaults(defineProps<{
96
96
  rows: Record<string, unknown>[]
@@ -41,16 +41,7 @@
41
41
  />
42
42
 
43
43
  <BarChart
44
- v-else-if="chartConfig?.type === 'bar'"
45
- :rows="barRows"
46
- :label-field="barLabelField"
47
- :value-field="barValueField"
48
- :color="chartConfig.color"
49
- :height="chartHeight"
50
- />
51
-
52
- <HistogramChart
53
- v-else-if="chartConfig?.type === 'histogram'"
44
+ v-else-if="chartConfig?.type === 'bar' || chartConfig?.type === 'histogram'"
54
45
  :rows="barRows"
55
46
  :label-field="barLabelField"
56
47
  :value-field="barValueField"
@@ -91,11 +82,9 @@
91
82
  <script setup lang="ts">
92
83
  import { computed, watch } from 'vue'
93
84
  import { useWidgetData } from '../../queries/useWidgetData.js'
94
- import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
95
- import { normalizeChartWidgetConfig } from './chart.types.js'
85
+ import type { ChartDashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
96
86
  import BarChart from './bar/BarChart.vue'
97
87
  import FunnelChart from './funnel/FunnelChart.vue'
98
- import HistogramChart from './histogram/HistogramChart.vue'
99
88
  import LineChart from './line/LineChart.vue'
100
89
  import PieChart from './pie/PieChart.vue'
101
90
  import StackedBarChart from './stacked-bar/StackedBarChart.vue'
@@ -105,7 +94,7 @@ const DEFAULT_WIDGET_HEIGHT = 500
105
94
 
106
95
  const props = defineProps<{
107
96
  dashboardSlug: string
108
- widget: DashboardWidgetConfig
97
+ widget: ChartDashboardWidgetConfig
109
98
  }>()
110
99
 
111
100
  const dashboardSlugRef = computed(() => props.dashboardSlug)
@@ -128,7 +117,7 @@ watch(
128
117
  const chartData = computed(() => data.value?.data as DashboardWidgetTableData | null)
129
118
  const rows = computed(() => chartData.value?.rows ?? [])
130
119
  const columns = computed(() => chartData.value?.columns ?? [])
131
- const chartConfig = computed(() => normalizeChartWidgetConfig(props.widget.chart))
120
+ const chartConfig = computed(() => props.widget.chart)
132
121
 
133
122
  function resolveChartDimensionField(field: string | undefined, fallbackField: string | undefined) {
134
123
  const resolvedField = field ?? fallbackField
@@ -1,13 +1,91 @@
1
+ <template>
2
+ <div
3
+ ref="rootEl"
4
+ class="grid h-full min-h-0 w-full gap-4 overflow-hidden"
5
+ :class="isCompact ? 'grid-rows-[minmax(0,1fr)_auto]' : 'grid-cols-[minmax(0,1fr)_200px]'"
6
+ >
7
+ <div
8
+ ref="svgEl"
9
+ class="min-h-0 w-full overflow-hidden"
10
+ >
11
+ <svg
12
+ v-if="chartWidth > 0 && chartHeight > 0"
13
+ class="block h-full w-full"
14
+ :viewBox="`0 0 ${chartWidth} ${chartHeight}`"
15
+ role="img"
16
+ :aria-label="valueField"
17
+ >
18
+ <path
19
+ v-for="segment in segments"
20
+ :key="segment.id"
21
+ :d="segment.path"
22
+ :fill="segment.color"
23
+ fill-opacity="0.9"
24
+ >
25
+ <title>
26
+ {{ segment.label }}: {{ formatChartValue(segment.value) }} ({{ segment.percentLabel }})
27
+ </title>
28
+ </path>
29
+
30
+ <text
31
+ v-for="segment in segments"
32
+ v-show="segment.labelVisible"
33
+ :key="`value-${segment.id}`"
34
+ :x="chartWidth / 2"
35
+ :y="segment.centerY + 4"
36
+ fill="#ffffff"
37
+ font-size="12"
38
+ font-weight="600"
39
+ text-anchor="middle"
40
+ >
41
+ {{ formatChartValue(segment.value) }}
42
+ </text>
43
+ </svg>
44
+ </div>
45
+
46
+ <div class="grid min-w-0 gap-2 text-sm">
47
+ <div
48
+ v-for="segment in segments"
49
+ :key="`legend-${segment.id}`"
50
+ class="grid min-h-[34px] min-w-0 grid-cols-[1fr_auto] items-center gap-3"
51
+ >
52
+ <div class="flex min-w-0 items-center gap-2">
53
+ <span
54
+ class="h-2.5 w-2.5 shrink-0 rounded-full"
55
+ :style="{ backgroundColor: segment.color }"
56
+ />
57
+
58
+ <span class="truncate text-lightNavbarText dark:text-darkNavbarText">
59
+ {{ segment.shortLabel }}
60
+ </span>
61
+ </div>
62
+
63
+ <div class="text-right">
64
+ <div class="font-semibold text-lightNavbarText dark:text-darkNavbarText">
65
+ {{ formatChartValue(segment.value) }}
66
+ </div>
67
+
68
+ <div class="text-xs text-lightListTableText dark:text-darkListTableText">
69
+ {{ segment.percentLabel }}
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </template>
76
+
77
+
78
+
1
79
  <script setup lang="ts">
2
80
  import { computed } from 'vue'
3
- import { useElementSize } from '../../../composables/useElementSize.js'
81
+ import { useElementSize } from '../../composables/useElementSize.js'
4
82
  import {
5
83
  CHART_COLORS,
6
84
  formatChartAxisLabel,
7
85
  formatChartLabel,
8
86
  formatChartValue,
9
87
  toFiniteNumber,
10
- } from '../chart.utils.js'
88
+ } from './chart.utils.js'
11
89
 
12
90
  const props = withDefaults(defineProps<{
13
91
  rows: Record<string, unknown>[]
@@ -121,79 +199,3 @@ const segments = computed(() => funnelRows.value.map((row, index) => {
121
199
  }
122
200
  }))
123
201
  </script>
124
-
125
- <template>
126
- <div
127
- ref="rootEl"
128
- class="grid h-full min-h-0 w-full gap-4 overflow-hidden"
129
- :class="isCompact ? 'grid-rows-[minmax(0,1fr)_auto]' : 'grid-cols-[minmax(0,1fr)_200px]'"
130
- >
131
- <div
132
- ref="svgEl"
133
- class="min-h-0 w-full overflow-hidden"
134
- >
135
- <svg
136
- v-if="chartWidth > 0 && chartHeight > 0"
137
- class="block h-full w-full"
138
- :viewBox="`0 0 ${chartWidth} ${chartHeight}`"
139
- role="img"
140
- :aria-label="valueField"
141
- >
142
- <path
143
- v-for="segment in segments"
144
- :key="segment.id"
145
- :d="segment.path"
146
- :fill="segment.color"
147
- fill-opacity="0.9"
148
- >
149
- <title>
150
- {{ segment.label }}: {{ formatChartValue(segment.value) }} ({{ segment.percentLabel }})
151
- </title>
152
- </path>
153
-
154
- <text
155
- v-for="segment in segments"
156
- v-show="segment.labelVisible"
157
- :key="`value-${segment.id}`"
158
- :x="chartWidth / 2"
159
- :y="segment.centerY + 4"
160
- fill="#ffffff"
161
- font-size="12"
162
- font-weight="600"
163
- text-anchor="middle"
164
- >
165
- {{ formatChartValue(segment.value) }}
166
- </text>
167
- </svg>
168
- </div>
169
-
170
- <div class="grid min-w-0 gap-2 text-sm">
171
- <div
172
- v-for="segment in segments"
173
- :key="`legend-${segment.id}`"
174
- class="grid min-h-[34px] min-w-0 grid-cols-[1fr_auto] items-center gap-3"
175
- >
176
- <div class="flex min-w-0 items-center gap-2">
177
- <span
178
- class="h-2.5 w-2.5 shrink-0 rounded-full"
179
- :style="{ backgroundColor: segment.color }"
180
- />
181
-
182
- <span class="truncate text-lightNavbarText dark:text-darkNavbarText">
183
- {{ segment.shortLabel }}
184
- </span>
185
- </div>
186
-
187
- <div class="text-right">
188
- <div class="font-semibold text-lightNavbarText dark:text-darkNavbarText">
189
- {{ formatChartValue(segment.value) }}
190
- </div>
191
-
192
- <div class="text-xs text-lightListTableText dark:text-darkListTableText">
193
- {{ segment.percentLabel }}
194
- </div>
195
- </div>
196
- </div>
197
- </div>
198
- </div>
199
- </template>