@adminforth/dashboard 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/custom/api/dashboardApi.ts +6 -9
- package/custom/model/dashboard.types.ts +60 -275
- package/custom/model/dashboardTopics.ts +5 -0
- package/custom/runtime/DashboardGroup.vue +2 -2
- package/custom/runtime/DashboardPage.vue +17 -7
- package/custom/runtime/DashboardRuntime.vue +20 -8
- package/custom/runtime/WidgetRenderer.vue +1 -2
- package/custom/runtime/WidgetShell.vue +3 -3
- package/custom/skills/adminforth-dashboard/SKILL.md +2 -2
- package/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
- package/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
- package/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
- package/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
- package/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
- package/custom/widgets/chart/ChartWidget.vue +4 -15
- package/{dist/custom/widgets/chart/funnel → custom/widgets/chart}/FunnelChart.vue +80 -78
- package/{dist/custom/widgets/chart/line → custom/widgets/chart}/LineChart.vue +2 -2
- package/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
- package/{dist/custom/widgets/chart/stacked-bar → custom/widgets/chart}/StackedBarChart.vue +97 -95
- package/custom/widgets/chart/chart.types.ts +0 -28
- package/dist/custom/api/dashboardApi.d.ts +4 -8
- package/dist/custom/api/dashboardApi.ts +6 -9
- package/dist/custom/model/dashboard.types.d.ts +38 -32
- package/dist/custom/model/dashboard.types.js +2 -155
- package/dist/custom/model/dashboard.types.ts +60 -275
- package/dist/custom/model/dashboardTopics.d.ts +2 -0
- package/dist/custom/model/dashboardTopics.js +8 -0
- package/dist/custom/model/dashboardTopics.ts +5 -0
- package/dist/custom/queries/useDashboardConfig.d.ts +96 -96
- package/dist/custom/queries/useWidgetData.d.ts +96 -96
- package/dist/custom/runtime/DashboardGroup.vue +2 -2
- package/dist/custom/runtime/DashboardPage.vue +17 -7
- package/dist/custom/runtime/DashboardRuntime.vue +20 -8
- package/dist/custom/runtime/WidgetRenderer.vue +1 -2
- package/dist/custom/runtime/WidgetShell.vue +3 -3
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +2 -2
- package/dist/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
- package/dist/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
- package/dist/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
- package/dist/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
- package/dist/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
- package/dist/custom/widgets/chart/ChartWidget.vue +4 -15
- package/{custom/widgets/chart/funnel → dist/custom/widgets/chart}/FunnelChart.vue +80 -78
- package/{custom/widgets/chart/line → dist/custom/widgets/chart}/LineChart.vue +2 -2
- package/dist/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
- package/{custom/widgets/chart/stacked-bar → dist/custom/widgets/chart}/StackedBarChart.vue +97 -95
- package/dist/custom/widgets/chart/chart.types.d.ts +0 -2
- package/dist/custom/widgets/chart/chart.types.js +0 -23
- package/dist/custom/widgets/chart/chart.types.ts +0 -28
- package/dist/endpoint/dashboard.d.ts +2 -3
- package/dist/endpoint/dashboard.js +12 -32
- package/dist/endpoint/groups.d.ts +2 -21
- package/dist/endpoint/groups.js +18 -16
- package/dist/endpoint/widgets.d.ts +0 -3
- package/dist/endpoint/widgets.js +27 -74
- package/dist/index.js +1 -3
- package/dist/schema/api.d.ts +2090 -511
- package/dist/schema/api.js +18 -15
- package/dist/schema/widget.d.ts +1003 -250
- package/dist/schema/widget.js +102 -46
- package/dist/services/dashboardConfigService.d.ts +0 -10
- package/dist/services/dashboardConfigService.js +6 -21
- package/dist/services/widgetDataService.js +226 -196
- package/endpoint/dashboard.ts +13 -46
- package/endpoint/groups.ts +25 -42
- package/endpoint/widgets.ts +36 -95
- package/index.ts +0 -3
- package/package.json +3 -3
- package/schema/api.ts +19 -15
- package/schema/widget.ts +113 -52
- package/services/dashboardConfigService.ts +6 -25
- package/services/widgetDataService.ts +304 -229
- package/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
- package/dist/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
- package/dist/services/widgetConfigValidator.d.ts +0 -8
- package/dist/services/widgetConfigValidator.js +0 -27
- package/services/widgetConfigValidator.ts +0 -61
|
@@ -207,6 +207,8 @@ import { DashboardApiError, dashboardApi, type DashboardResponse } from '../api/
|
|
|
207
207
|
import type {
|
|
208
208
|
DashboardConfig,
|
|
209
209
|
DashboardGroupConfig,
|
|
210
|
+
EditableDashboardGroupConfig,
|
|
211
|
+
EditableDashboardWidgetConfig,
|
|
210
212
|
DashboardGroupMoveDirection,
|
|
211
213
|
DashboardWidgetConfig,
|
|
212
214
|
DashboardWidgetMoveDirection,
|
|
@@ -252,13 +254,15 @@ const sortedGroups = computed(() => {
|
|
|
252
254
|
return [...draftConfig.value.groups].sort((a, b) => a.order - b.order)
|
|
253
255
|
})
|
|
254
256
|
|
|
255
|
-
|
|
257
|
+
function groupWidgetsByGroupId(widgets: DashboardWidgetConfig[]) {
|
|
256
258
|
const result = new Map<string, DashboardWidgetConfig[]>()
|
|
257
259
|
|
|
258
|
-
for (const widget of
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
for (const widget of widgets) {
|
|
261
|
+
const nextWidgets = result.get(widget.group_id)
|
|
262
|
+
? [...result.get(widget.group_id)!, widget]
|
|
263
|
+
: [widget]
|
|
264
|
+
|
|
265
|
+
result.set(widget.group_id, nextWidgets)
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
for (const [groupId, widgets] of result.entries()) {
|
|
@@ -266,6 +270,10 @@ const widgetsByGroupId = computed<Map<string, DashboardWidgetConfig[]>>(() => {
|
|
|
266
270
|
}
|
|
267
271
|
|
|
268
272
|
return result
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const widgetsByGroupId = computed<Map<string, DashboardWidgetConfig[]>>(() => {
|
|
276
|
+
return groupWidgetsByGroupId(draftConfig.value.widgets as DashboardWidgetConfig[])
|
|
269
277
|
})
|
|
270
278
|
|
|
271
279
|
const visibleGroups = computed(() => {
|
|
@@ -329,8 +337,12 @@ async function removeGroup(groupId: string) {
|
|
|
329
337
|
}
|
|
330
338
|
|
|
331
339
|
function editGroup(group: DashboardGroupConfig) {
|
|
340
|
+
const editableGroupConfig: EditableDashboardGroupConfig = {
|
|
341
|
+
label: group.label,
|
|
342
|
+
}
|
|
343
|
+
|
|
332
344
|
editingGroupId.value = group.id
|
|
333
|
-
groupConfigCode.value = stringifyYaml(
|
|
345
|
+
groupConfigCode.value = stringifyYaml(editableGroupConfig)
|
|
334
346
|
groupConfigError.value = ''
|
|
335
347
|
}
|
|
336
348
|
|
|
@@ -340,7 +352,7 @@ async function saveGroupConfig() {
|
|
|
340
352
|
}
|
|
341
353
|
|
|
342
354
|
try {
|
|
343
|
-
const groupConfig = parseYaml(groupConfigCode.value) as
|
|
355
|
+
const groupConfig = parseYaml(groupConfigCode.value) as EditableDashboardGroupConfig
|
|
344
356
|
|
|
345
357
|
applyDashboardResponse(
|
|
346
358
|
await dashboardApi.setDashboardGroupConfig(
|
|
@@ -402,7 +414,7 @@ async function saveWidgetConfig() {
|
|
|
402
414
|
try {
|
|
403
415
|
widgetConfigError.value = ''
|
|
404
416
|
widgetConfigFieldErrors.value = []
|
|
405
|
-
const widgetConfig = parseYaml(widgetConfigCode.value) as
|
|
417
|
+
const widgetConfig = parseYaml(widgetConfigCode.value) as EditableDashboardWidgetConfig
|
|
406
418
|
|
|
407
419
|
applyDashboardResponse(
|
|
408
420
|
await dashboardApi.setWidgetConfig(
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
<script setup lang="ts">
|
|
27
27
|
import { computed } from 'vue'
|
|
28
28
|
import type { DashboardWidgetConfig } from '../model/dashboard.types.js'
|
|
29
|
-
import { normalizeChartWidgetConfig } from '../widgets/chart/chart.types.js'
|
|
30
29
|
import { getWidgetLabel, getWidgetRegistration } from '../widgets/registry.js'
|
|
31
30
|
|
|
32
31
|
const props = defineProps<{
|
|
@@ -53,7 +52,7 @@ const widgetTitle = computed(() => {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
if (props.widget.target === 'chart') {
|
|
56
|
-
return
|
|
55
|
+
return props.widget.chart.title || 'Untitled chart'
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
return getWidgetLabel(props.widget.target)
|
|
@@ -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?.
|
|
140
|
-
'--widget-max-width': props.layout?.
|
|
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?.
|
|
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 `
|
|
143
|
-
- Use `resource`, not `
|
|
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 '
|
|
4
|
-
import type { DashboardWidgetConfig, DashboardWidgetTableData } from '
|
|
5
|
-
import { CHART_COLORS, formatChartValue, toFiniteNumber } from '
|
|
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?.
|
|
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?.
|
|
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 '
|
|
4
|
-
import type { DashboardWidgetConfig, DashboardWidgetTableData } from '
|
|
5
|
-
import { formatChartValue, toFiniteNumber } from '
|
|
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 '
|
|
70
|
+
import { useWidgetData } from '../queries/useWidgetData.js'
|
|
4
71
|
import {
|
|
5
72
|
getFieldRefField,
|
|
6
|
-
} from '
|
|
7
|
-
import type { DashboardWidgetConfig, DashboardWidgetData } from '
|
|
8
|
-
import { formatChartLabel, formatChartValue, toFiniteNumber } from '
|
|
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 '
|
|
116
|
-
import { getFieldRefField } from '
|
|
117
|
-
import type { DashboardWidgetConfig, DashboardWidgetTableData, FieldRef } from '
|
|
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
|
-
|
|
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?.
|
|
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 '
|
|
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 '
|
|
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 {
|
|
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:
|
|
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(() =>
|
|
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
|