@adminforth/dashboard 1.3.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 +103 -15
- package/custom/api/dashboardApi.ts +9 -8
- package/custom/model/dashboard.types.ts +63 -270
- 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 +110 -3
- 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 +24 -18
- 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 -7
- package/dist/custom/api/dashboardApi.js +5 -0
- package/dist/custom/api/dashboardApi.ts +9 -8
- package/dist/custom/model/dashboard.types.d.ts +40 -31
- package/dist/custom/model/dashboard.types.js +13 -152
- package/dist/custom/model/dashboard.types.ts +63 -270
- 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 +116 -96
- package/dist/custom/queries/useWidgetData.d.ts +116 -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 +110 -3
- 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 +24 -18
- 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 +6 -2
- package/dist/endpoint/dashboard.js +29 -5
- package/dist/endpoint/groups.d.ts +2 -21
- package/dist/endpoint/groups.js +18 -16
- package/dist/endpoint/widgets.d.ts +2 -4
- package/dist/endpoint/widgets.js +28 -74
- package/dist/index.js +1 -3
- package/dist/schema/api.d.ts +2172 -500
- package/dist/schema/api.js +21 -13
- package/dist/schema/widget.d.ts +1076 -263
- package/dist/schema/widget.js +108 -49
- package/dist/services/dashboardConfigService.d.ts +0 -10
- package/dist/services/dashboardConfigService.js +6 -21
- package/dist/services/widgetDataService.d.ts +2 -1
- package/dist/services/widgetDataService.js +266 -206
- package/endpoint/dashboard.ts +47 -7
- package/endpoint/groups.ts +25 -42
- package/endpoint/widgets.ts +41 -96
- package/index.ts +0 -3
- package/package.json +3 -3
- package/schema/api.ts +23 -13
- package/schema/widget.ts +119 -55
- package/services/dashboardConfigService.ts +6 -25
- package/services/widgetDataService.ts +350 -237
- 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
|
@@ -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"
|
|
@@ -69,10 +60,10 @@
|
|
|
69
60
|
|
|
70
61
|
<StackedBarChart
|
|
71
62
|
v-else-if="chartConfig?.type === 'stacked_bar'"
|
|
72
|
-
:rows="
|
|
63
|
+
:rows="stackedBarRows"
|
|
73
64
|
:x-field="xField"
|
|
74
|
-
:y-field="
|
|
75
|
-
:series-field="
|
|
65
|
+
:y-field="stackedBarYField"
|
|
66
|
+
:series-field="stackedBarSeriesField"
|
|
76
67
|
:colors="chartConfig.colors"
|
|
77
68
|
:height="chartHeight"
|
|
78
69
|
/>
|
|
@@ -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
|
|
@@ -155,6 +144,21 @@ const valueField = computed(() => chartConfig.value?.value?.field || columns.val
|
|
|
155
144
|
const pieRows = computed(() => rows.value)
|
|
156
145
|
const pieLabelField = computed(() => labelField.value)
|
|
157
146
|
const pieValueField = computed(() => valueField.value)
|
|
147
|
+
const stackedBarYItems = computed(() => {
|
|
148
|
+
const y = chartConfig.value?.y
|
|
149
|
+
return Array.isArray(y) ? y : []
|
|
150
|
+
})
|
|
151
|
+
const stackedBarRows = computed(() => {
|
|
152
|
+
if (chartConfig.value?.type !== 'stacked_bar' || !stackedBarYItems.value.length) {
|
|
153
|
+
return rows.value
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return rows.value.flatMap((row) => stackedBarYItems.value.map((item) => ({
|
|
157
|
+
[xField.value]: row[xField.value],
|
|
158
|
+
__series: item.label ?? item.field,
|
|
159
|
+
__value: row[item.field],
|
|
160
|
+
})))
|
|
161
|
+
})
|
|
158
162
|
const barRows = computed(() => {
|
|
159
163
|
const bucketField = chartConfig.value?.type === 'histogram'
|
|
160
164
|
? chartConfig.value.x?.field
|
|
@@ -178,6 +182,8 @@ const barRows = computed(() => {
|
|
|
178
182
|
const barLabelField = computed(() => chartConfig.value?.type === 'histogram' && chartConfig.value.buckets ? 'label' : xField.value)
|
|
179
183
|
const barValueField = computed(() => chartConfig.value?.type === 'histogram' && chartConfig.value.buckets ? 'count' : yField.value)
|
|
180
184
|
const seriesField = computed(() => chartConfig.value?.series?.field || columns.value[2] || '')
|
|
185
|
+
const stackedBarYField = computed(() => stackedBarYItems.value.length ? '__value' : yField.value)
|
|
186
|
+
const stackedBarSeriesField = computed(() => stackedBarYItems.value.length ? '__series' : seriesField.value)
|
|
181
187
|
const lineSeriesName = computed(() => {
|
|
182
188
|
const y = chartConfig.value?.y
|
|
183
189
|
return Array.isArray(y) ? y[0]?.label : undefined
|
|
@@ -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>[]
|
|
@@ -1,6 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="rootEl"
|
|
4
|
+
class="grid h-full min-h-0 w-full grid-rows-[auto_minmax(0,1fr)] gap-3 overflow-hidden"
|
|
5
|
+
>
|
|
6
|
+
<div
|
|
7
|
+
v-if="showLegend"
|
|
8
|
+
class="flex flex-wrap items-center gap-3 text-xs text-lightListTableText dark:text-darkListTableText"
|
|
9
|
+
:class="isCompact ? 'justify-start' : 'justify-end'"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
v-for="series in normalizedSeries"
|
|
13
|
+
:key="series.name"
|
|
14
|
+
class="flex items-center gap-1.5"
|
|
15
|
+
>
|
|
16
|
+
<span
|
|
17
|
+
class="h-2.5 w-2.5 rounded-full"
|
|
18
|
+
:style="{ backgroundColor: series.color }"
|
|
19
|
+
/>
|
|
20
|
+
<span>{{ series.name }}</span>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div
|
|
25
|
+
ref="svgEl"
|
|
26
|
+
class="min-h-0 overflow-hidden"
|
|
27
|
+
>
|
|
28
|
+
<svg
|
|
29
|
+
v-if="chartWidth > 0 && chartHeight > 0"
|
|
30
|
+
class="block h-full w-full"
|
|
31
|
+
:viewBox="`0 0 ${chartWidth} ${chartHeight}`"
|
|
32
|
+
role="img"
|
|
33
|
+
:aria-label="xField"
|
|
34
|
+
>
|
|
35
|
+
<g class="text-lightListTableText dark:text-darkListTableText">
|
|
36
|
+
<line
|
|
37
|
+
v-for="tick in yTicks"
|
|
38
|
+
:key="tick.y"
|
|
39
|
+
:x1="padding.left"
|
|
40
|
+
:x2="chartWidth - padding.right"
|
|
41
|
+
:y1="tick.y"
|
|
42
|
+
:y2="tick.y"
|
|
43
|
+
stroke="currentColor"
|
|
44
|
+
stroke-opacity="0.14"
|
|
45
|
+
/>
|
|
46
|
+
<text
|
|
47
|
+
v-for="tick in yTicks"
|
|
48
|
+
:key="`label-${tick.y}`"
|
|
49
|
+
:x="padding.left - 8"
|
|
50
|
+
:y="tick.y + 4"
|
|
51
|
+
fill="currentColor"
|
|
52
|
+
font-size="11"
|
|
53
|
+
text-anchor="end"
|
|
54
|
+
>
|
|
55
|
+
{{ formatChartValue(tick.value) }}
|
|
56
|
+
</text>
|
|
57
|
+
</g>
|
|
58
|
+
|
|
59
|
+
<g
|
|
60
|
+
v-for="(bar, barIndex) in bars"
|
|
61
|
+
:key="bar.label"
|
|
62
|
+
>
|
|
63
|
+
<rect
|
|
64
|
+
v-for="segment in bar.segments"
|
|
65
|
+
:key="segment.id"
|
|
66
|
+
v-show="segment.height > 0"
|
|
67
|
+
:x="bar.x"
|
|
68
|
+
:y="segment.y"
|
|
69
|
+
:width="barWidth"
|
|
70
|
+
:height="segment.height"
|
|
71
|
+
:fill="segment.color"
|
|
72
|
+
rx="3"
|
|
73
|
+
>
|
|
74
|
+
<title>{{ getBarTooltip(bar) }}</title>
|
|
75
|
+
</rect>
|
|
76
|
+
|
|
77
|
+
<text
|
|
78
|
+
v-if="visibleLabelIndexes.has(barIndex)"
|
|
79
|
+
:x="bar.x + barWidth / 2"
|
|
80
|
+
:y="padding.top + innerHeight + 24"
|
|
81
|
+
fill="currentColor"
|
|
82
|
+
font-size="11"
|
|
83
|
+
text-anchor="middle"
|
|
84
|
+
class="text-lightListTableText dark:text-darkListTableText"
|
|
85
|
+
>
|
|
86
|
+
{{ bar.axisLabel }}
|
|
87
|
+
</text>
|
|
88
|
+
</g>
|
|
89
|
+
</svg>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
1
96
|
<script setup lang="ts">
|
|
2
97
|
import { computed } from 'vue'
|
|
3
|
-
import { useElementSize } from '
|
|
98
|
+
import { useElementSize } from '../../composables/useElementSize.js'
|
|
4
99
|
import {
|
|
5
100
|
CHART_COLORS,
|
|
6
101
|
formatChartAxisLabel,
|
|
@@ -8,7 +103,7 @@ import {
|
|
|
8
103
|
formatChartValue,
|
|
9
104
|
getChartYAxisWidth,
|
|
10
105
|
toFiniteNumber,
|
|
11
|
-
} from '
|
|
106
|
+
} from './chart.utils.js'
|
|
12
107
|
|
|
13
108
|
const props = withDefaults(defineProps<{
|
|
14
109
|
rows: Record<string, unknown>[]
|
|
@@ -146,96 +241,3 @@ function getBarTooltip(bar: { label: string, total: number, segments: Array<{ na
|
|
|
146
241
|
].join('\n')
|
|
147
242
|
}
|
|
148
243
|
</script>
|
|
149
|
-
|
|
150
|
-
<template>
|
|
151
|
-
<div
|
|
152
|
-
ref="rootEl"
|
|
153
|
-
class="grid h-full min-h-0 w-full grid-rows-[auto_minmax(0,1fr)] gap-3 overflow-hidden"
|
|
154
|
-
>
|
|
155
|
-
<div
|
|
156
|
-
v-if="showLegend"
|
|
157
|
-
class="flex flex-wrap items-center gap-3 text-xs text-lightListTableText dark:text-darkListTableText"
|
|
158
|
-
:class="isCompact ? 'justify-start' : 'justify-end'"
|
|
159
|
-
>
|
|
160
|
-
<div
|
|
161
|
-
v-for="series in normalizedSeries"
|
|
162
|
-
:key="series.name"
|
|
163
|
-
class="flex items-center gap-1.5"
|
|
164
|
-
>
|
|
165
|
-
<span
|
|
166
|
-
class="h-2.5 w-2.5 rounded-full"
|
|
167
|
-
:style="{ backgroundColor: series.color }"
|
|
168
|
-
/>
|
|
169
|
-
<span>{{ series.name }}</span>
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div
|
|
174
|
-
ref="svgEl"
|
|
175
|
-
class="min-h-0 overflow-hidden"
|
|
176
|
-
>
|
|
177
|
-
<svg
|
|
178
|
-
v-if="chartWidth > 0 && chartHeight > 0"
|
|
179
|
-
class="block h-full w-full"
|
|
180
|
-
:viewBox="`0 0 ${chartWidth} ${chartHeight}`"
|
|
181
|
-
role="img"
|
|
182
|
-
:aria-label="xField"
|
|
183
|
-
>
|
|
184
|
-
<g class="text-lightListTableText dark:text-darkListTableText">
|
|
185
|
-
<line
|
|
186
|
-
v-for="tick in yTicks"
|
|
187
|
-
:key="tick.y"
|
|
188
|
-
:x1="padding.left"
|
|
189
|
-
:x2="chartWidth - padding.right"
|
|
190
|
-
:y1="tick.y"
|
|
191
|
-
:y2="tick.y"
|
|
192
|
-
stroke="currentColor"
|
|
193
|
-
stroke-opacity="0.14"
|
|
194
|
-
/>
|
|
195
|
-
<text
|
|
196
|
-
v-for="tick in yTicks"
|
|
197
|
-
:key="`label-${tick.y}`"
|
|
198
|
-
:x="padding.left - 8"
|
|
199
|
-
:y="tick.y + 4"
|
|
200
|
-
fill="currentColor"
|
|
201
|
-
font-size="11"
|
|
202
|
-
text-anchor="end"
|
|
203
|
-
>
|
|
204
|
-
{{ formatChartValue(tick.value) }}
|
|
205
|
-
</text>
|
|
206
|
-
</g>
|
|
207
|
-
|
|
208
|
-
<g
|
|
209
|
-
v-for="(bar, barIndex) in bars"
|
|
210
|
-
:key="bar.label"
|
|
211
|
-
>
|
|
212
|
-
<rect
|
|
213
|
-
v-for="segment in bar.segments"
|
|
214
|
-
:key="segment.id"
|
|
215
|
-
v-show="segment.height > 0"
|
|
216
|
-
:x="bar.x"
|
|
217
|
-
:y="segment.y"
|
|
218
|
-
:width="barWidth"
|
|
219
|
-
:height="segment.height"
|
|
220
|
-
:fill="segment.color"
|
|
221
|
-
rx="3"
|
|
222
|
-
>
|
|
223
|
-
<title>{{ getBarTooltip(bar) }}</title>
|
|
224
|
-
</rect>
|
|
225
|
-
|
|
226
|
-
<text
|
|
227
|
-
v-if="visibleLabelIndexes.has(barIndex)"
|
|
228
|
-
:x="bar.x + barWidth / 2"
|
|
229
|
-
:y="padding.top + innerHeight + 24"
|
|
230
|
-
fill="currentColor"
|
|
231
|
-
font-size="11"
|
|
232
|
-
text-anchor="middle"
|
|
233
|
-
class="text-lightListTableText dark:text-darkListTableText"
|
|
234
|
-
>
|
|
235
|
-
{{ bar.axisLabel }}
|
|
236
|
-
</text>
|
|
237
|
-
</g>
|
|
238
|
-
</svg>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
</template>
|
|
@@ -38,31 +38,3 @@ export type ChartWidgetConfig = {
|
|
|
38
38
|
color?: string
|
|
39
39
|
colors?: string[]
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
export type NormalizedChartWidgetConfig = ChartWidgetConfig
|
|
43
|
-
|
|
44
|
-
export function normalizeChartWidgetConfig(value: unknown): NormalizedChartWidgetConfig | undefined {
|
|
45
|
-
if (!isRecord(value) || !normalizeChartWidgetType(value.type)) {
|
|
46
|
-
return undefined
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return value as ChartWidgetConfig
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function normalizeChartWidgetType(value: unknown): ChartWidgetType | undefined {
|
|
53
|
-
switch (value) {
|
|
54
|
-
case 'line':
|
|
55
|
-
case 'pie':
|
|
56
|
-
case 'bar':
|
|
57
|
-
case 'stacked_bar':
|
|
58
|
-
case 'funnel':
|
|
59
|
-
case 'histogram':
|
|
60
|
-
return value
|
|
61
|
-
default:
|
|
62
|
-
return undefined
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
67
|
-
return typeof value === 'object' && value !== null
|
|
68
|
-
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import type { DashboardConfig,
|
|
2
|
-
export type DashboardWidgetConfigValidationError = {
|
|
3
|
-
field: string;
|
|
4
|
-
message: string;
|
|
5
|
-
};
|
|
1
|
+
import type { DashboardConfig, EditableDashboardGroupConfig, DashboardGroupMoveDirection, DashboardWidgetConfig, DashboardWidgetConfigValidationError, DashboardWidgetMoveDirection } from '../model/dashboard.types.js';
|
|
6
2
|
export type DashboardResponse = {
|
|
7
3
|
id: string;
|
|
8
4
|
slug: string;
|
|
@@ -26,13 +22,14 @@ export declare class DashboardApiError extends Error {
|
|
|
26
22
|
}
|
|
27
23
|
export declare const dashboardApi: {
|
|
28
24
|
getDashboardConfig(slug: string): Promise<DashboardResponse>;
|
|
25
|
+
setDashboardConfig(slug: string, config: unknown): Promise<DashboardResponse>;
|
|
29
26
|
addDashboardGroup(slug: string): Promise<DashboardResponse>;
|
|
30
27
|
moveDashboardGroup(slug: string, groupId: string, direction: DashboardGroupMoveDirection): Promise<DashboardResponse>;
|
|
31
28
|
removeDashboardGroup(slug: string, groupId: string): Promise<DashboardResponse>;
|
|
32
|
-
setDashboardGroupConfig(slug: string, groupId: string, config:
|
|
29
|
+
setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<DashboardResponse>;
|
|
33
30
|
addDashboardWidget(slug: string, groupId: string): Promise<DashboardResponse>;
|
|
34
31
|
moveDashboardWidget(slug: string, widgetId: string, direction: DashboardWidgetMoveDirection): Promise<DashboardResponse>;
|
|
35
32
|
removeDashboardWidget(slug: string, widgetId: string): Promise<DashboardResponse>;
|
|
36
|
-
setWidgetConfig(slug: string, widgetId: string, config:
|
|
33
|
+
setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<DashboardResponse>;
|
|
37
34
|
getDashboardWidgetData(slug: string, widgetId: string, request?: DashboardWidgetDataRequest): Promise<DashboardWidgetDataResponse>;
|
|
38
35
|
};
|
|
@@ -103,6 +103,11 @@ exports.dashboardApi = {
|
|
|
103
103
|
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug });
|
|
104
104
|
});
|
|
105
105
|
},
|
|
106
|
+
setDashboardConfig(slug, config) {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config });
|
|
109
|
+
});
|
|
110
|
+
},
|
|
106
111
|
addDashboardGroup(slug) {
|
|
107
112
|
return __awaiter(this, void 0, void 0, function* () {
|
|
108
113
|
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug });
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
DashboardConfig,
|
|
3
|
-
|
|
3
|
+
EditableDashboardGroupConfig,
|
|
4
|
+
EditableDashboardWidgetConfig,
|
|
4
5
|
DashboardGroupMoveDirection,
|
|
5
6
|
DashboardWidgetConfig,
|
|
7
|
+
DashboardWidgetConfigValidationError,
|
|
6
8
|
DashboardWidgetMoveDirection,
|
|
7
9
|
} from '../model/dashboard.types.js'
|
|
8
10
|
|
|
9
|
-
export type DashboardWidgetConfigValidationError = {
|
|
10
|
-
field: string
|
|
11
|
-
message: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
11
|
export type DashboardResponse = {
|
|
15
12
|
id: string
|
|
16
13
|
slug: string
|
|
@@ -143,6 +140,10 @@ export const dashboardApi = {
|
|
|
143
140
|
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
|
|
144
141
|
},
|
|
145
142
|
|
|
143
|
+
async setDashboardConfig(slug: string, config: unknown): Promise<DashboardResponse> {
|
|
144
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
|
|
145
|
+
},
|
|
146
|
+
|
|
146
147
|
async addDashboardGroup(slug: string): Promise<DashboardResponse> {
|
|
147
148
|
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
|
|
148
149
|
},
|
|
@@ -166,7 +167,7 @@ export const dashboardApi = {
|
|
|
166
167
|
})
|
|
167
168
|
},
|
|
168
169
|
|
|
169
|
-
async setDashboardGroupConfig(slug: string, groupId: string, config:
|
|
170
|
+
async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<DashboardResponse> {
|
|
170
171
|
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
|
|
171
172
|
slug,
|
|
172
173
|
groupId,
|
|
@@ -200,7 +201,7 @@ export const dashboardApi = {
|
|
|
200
201
|
})
|
|
201
202
|
},
|
|
202
203
|
|
|
203
|
-
async setWidgetConfig(slug: string, widgetId: string, config:
|
|
204
|
+
async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<DashboardResponse> {
|
|
204
205
|
return callDashboardApi('/adminapi/v1/dashboard/set_widget_config', {
|
|
205
206
|
slug,
|
|
206
207
|
widgetId,
|