@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
|
@@ -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
|
|
@@ -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 '
|
|
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 '
|
|
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>
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
|
|
85
85
|
<script setup lang="ts">
|
|
86
86
|
import { computed } from 'vue'
|
|
87
|
-
import { useElementSize } from '
|
|
87
|
+
import { useElementSize } from '../../composables/useElementSize.js'
|
|
88
88
|
import {
|
|
89
89
|
CHART_COLORS,
|
|
90
90
|
formatChartAxisLabel,
|
|
@@ -92,7 +92,7 @@ import {
|
|
|
92
92
|
formatChartValue,
|
|
93
93
|
getChartYAxisWidth,
|
|
94
94
|
toFiniteNumber,
|
|
95
|
-
} from '
|
|
95
|
+
} from './chart.utils.js'
|
|
96
96
|
|
|
97
97
|
const props = withDefaults(defineProps<{
|
|
98
98
|
rows: Record<string, unknown>[]
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
|
|
90
90
|
<script setup lang="ts">
|
|
91
91
|
import { computed } from 'vue'
|
|
92
|
-
import { useElementSize } from '
|
|
93
|
-
import { CHART_COLORS, formatChartLabel, formatChartValue, toFiniteNumber } from '
|
|
92
|
+
import { useElementSize } from '../../composables/useElementSize.js'
|
|
93
|
+
import { CHART_COLORS, formatChartLabel, formatChartValue, toFiniteNumber } from './chart.utils.js'
|
|
94
94
|
|
|
95
95
|
const props = withDefaults(defineProps<{
|
|
96
96
|
rows: Record<string, unknown>[]
|