@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
|
@@ -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>
|
|
@@ -1,25 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeChartWidgetConfig = normalizeChartWidgetConfig;
|
|
4
|
-
function normalizeChartWidgetConfig(value) {
|
|
5
|
-
if (!isRecord(value) || !normalizeChartWidgetType(value.type)) {
|
|
6
|
-
return undefined;
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
function normalizeChartWidgetType(value) {
|
|
11
|
-
switch (value) {
|
|
12
|
-
case 'line':
|
|
13
|
-
case 'pie':
|
|
14
|
-
case 'bar':
|
|
15
|
-
case 'stacked_bar':
|
|
16
|
-
case 'funnel':
|
|
17
|
-
case 'histogram':
|
|
18
|
-
return value;
|
|
19
|
-
default:
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function isRecord(value) {
|
|
24
|
-
return typeof value === 'object' && value !== null;
|
|
25
|
-
}
|
|
@@ -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,12 +1,11 @@
|
|
|
1
1
|
import type { AdminUser, IHttpServer } from 'adminforth';
|
|
2
|
-
import type { DashboardConfig
|
|
3
|
-
import type { DashboardWidgetConfigValidationError } from '../schema/widget.js';
|
|
2
|
+
import type { DashboardConfig } from '../custom/model/dashboard.types.js';
|
|
4
3
|
import type { DashboardRecord, PersistedDashboardResponse } from '../services/dashboardConfigService.js';
|
|
5
4
|
type DashboardEndpointsContext = {
|
|
6
5
|
canEditDashboard: (adminUser: AdminUser) => boolean;
|
|
7
6
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
7
|
+
parseStoredDashboardConfig: (config: unknown) => DashboardConfig;
|
|
8
8
|
persistDashboardConfig: (dashboard: DashboardRecord, config: DashboardConfig) => Promise<PersistedDashboardResponse>;
|
|
9
|
-
validateDashboardWidgetApiConfig: (widget: DashboardWidgetConfig) => DashboardWidgetConfigValidationError[];
|
|
10
9
|
};
|
|
11
10
|
export declare function registerDashboardEndpoints(server: IHttpServer, ctx: DashboardEndpointsContext): void;
|
|
12
11
|
export {};
|
|
@@ -7,15 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
11
|
-
import { DashboardApiResponseSchema, DashboardConfigZodSchema, SetDashboardConfigRequestSchema, SlugRequestSchema, } from '../schema/api.js';
|
|
12
|
-
import { buildDashboardResponse } from '../services/dashboardConfigService.js';
|
|
13
|
-
function formatDashboardConfigValidationErrors(error) {
|
|
14
|
-
return error.issues.map((issue) => ({
|
|
15
|
-
field: issue.path.length ? issue.path.map(String).join('.') : 'config',
|
|
16
|
-
message: issue.message,
|
|
17
|
-
}));
|
|
18
|
-
}
|
|
10
|
+
import { DashboardApiResponseSchema, SetDashboardConfigRequestSchema, SlugRequestSchema, } from '../schema/api.js';
|
|
19
11
|
export function registerDashboardEndpoints(server, ctx) {
|
|
20
12
|
server.endpoint({
|
|
21
13
|
method: 'POST',
|
|
@@ -24,13 +16,18 @@ export function registerDashboardEndpoints(server, ctx) {
|
|
|
24
16
|
request_schema: SlugRequestSchema,
|
|
25
17
|
response_schema: DashboardApiResponseSchema,
|
|
26
18
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response }) {
|
|
27
|
-
const
|
|
28
|
-
const dashboard = yield ctx.getDashboardRecord(slug);
|
|
19
|
+
const dashboard = yield ctx.getDashboardRecord(body.slug);
|
|
29
20
|
if (!dashboard) {
|
|
30
21
|
response.setStatus(404);
|
|
31
22
|
return { error: 'Dashboard not found' };
|
|
32
23
|
}
|
|
33
|
-
return
|
|
24
|
+
return {
|
|
25
|
+
id: dashboard.id,
|
|
26
|
+
slug: dashboard.slug,
|
|
27
|
+
label: dashboard.label,
|
|
28
|
+
revision: dashboard.revision,
|
|
29
|
+
config: ctx.parseStoredDashboardConfig(dashboard.config),
|
|
30
|
+
};
|
|
34
31
|
}),
|
|
35
32
|
});
|
|
36
33
|
server.endpoint({
|
|
@@ -44,30 +41,13 @@ export function registerDashboardEndpoints(server, ctx) {
|
|
|
44
41
|
response.setStatus(403);
|
|
45
42
|
return { error: 'Dashboard edit is not allowed' };
|
|
46
43
|
}
|
|
47
|
-
const
|
|
48
|
-
const dashboard = yield ctx.getDashboardRecord(slug);
|
|
44
|
+
const dashboard = yield ctx.getDashboardRecord(body.slug);
|
|
49
45
|
if (!dashboard) {
|
|
50
46
|
response.setStatus(404);
|
|
51
47
|
return { error: 'Dashboard not found' };
|
|
52
48
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
if (!parsedConfig.success) {
|
|
56
|
-
response.setStatus(422);
|
|
57
|
-
return {
|
|
58
|
-
error: 'Invalid dashboard config',
|
|
59
|
-
validationErrors: formatDashboardConfigValidationErrors(parsedConfig.error),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
const widgetValidationErrors = parsedConfig.data.widgets.flatMap((widget, index) => (ctx.validateDashboardWidgetApiConfig(widget).map((error) => (Object.assign(Object.assign({}, error), { field: `widgets.${index}.${error.field}` })))));
|
|
63
|
-
if (widgetValidationErrors.length) {
|
|
64
|
-
response.setStatus(422);
|
|
65
|
-
return {
|
|
66
|
-
error: 'Invalid dashboard config',
|
|
67
|
-
validationErrors: widgetValidationErrors,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
return ctx.persistDashboardConfig(dashboard, parsedConfig.data);
|
|
49
|
+
const config = body.config;
|
|
50
|
+
return ctx.persistDashboardConfig(dashboard, config);
|
|
71
51
|
}),
|
|
72
52
|
});
|
|
73
53
|
}
|
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
import type { AdminUser, IHttpServer } from 'adminforth';
|
|
2
2
|
import type { DashboardConfig } from '../custom/model/dashboard.types.js';
|
|
3
|
-
type DashboardRecord
|
|
4
|
-
id: string;
|
|
5
|
-
slug: string;
|
|
6
|
-
label: string;
|
|
7
|
-
revision: number;
|
|
8
|
-
config: unknown;
|
|
9
|
-
};
|
|
3
|
+
import type { DashboardRecord, PersistedDashboardResponse } from '../services/dashboardConfigService.js';
|
|
10
4
|
type GroupEndpointsContext = {
|
|
11
5
|
canEditDashboard: (adminUser: AdminUser) => boolean;
|
|
12
6
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
13
7
|
parseStoredDashboardConfig: (config: unknown) => DashboardConfig;
|
|
14
|
-
persistDashboardConfig: (dashboard: DashboardRecord, config: DashboardConfig) => Promise<
|
|
15
|
-
id: string;
|
|
16
|
-
slug: string;
|
|
17
|
-
label: string;
|
|
18
|
-
revision: number;
|
|
19
|
-
config: DashboardConfig;
|
|
20
|
-
}>;
|
|
21
|
-
buildDashboardResponse: (dashboard: DashboardRecord) => {
|
|
22
|
-
id: string;
|
|
23
|
-
slug: string;
|
|
24
|
-
label: string;
|
|
25
|
-
revision: number;
|
|
26
|
-
config: DashboardConfig;
|
|
27
|
-
};
|
|
8
|
+
persistDashboardConfig: (dashboard: DashboardRecord, config: DashboardConfig) => Promise<PersistedDashboardResponse>;
|
|
28
9
|
};
|
|
29
10
|
export declare function registerGroupEndpoints(server: IHttpServer, ctx: GroupEndpointsContext): void;
|
|
30
11
|
export {};
|
package/dist/endpoint/groups.js
CHANGED
|
@@ -21,8 +21,7 @@ export function registerGroupEndpoints(server, ctx) {
|
|
|
21
21
|
response.setStatus(403);
|
|
22
22
|
return { error: 'Dashboard edit is not allowed' };
|
|
23
23
|
}
|
|
24
|
-
const
|
|
25
|
-
const dashboard = yield ctx.getDashboardRecord(slug);
|
|
24
|
+
const dashboard = yield ctx.getDashboardRecord(body.slug);
|
|
26
25
|
if (!dashboard) {
|
|
27
26
|
response.setStatus(404);
|
|
28
27
|
return { error: 'Dashboard not found' };
|
|
@@ -48,9 +47,8 @@ export function registerGroupEndpoints(server, ctx) {
|
|
|
48
47
|
response.setStatus(403);
|
|
49
48
|
return { error: 'Dashboard edit is not allowed' };
|
|
50
49
|
}
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const dashboard = yield ctx.getDashboardRecord(slug);
|
|
50
|
+
const groupId = body.groupId;
|
|
51
|
+
const dashboard = yield ctx.getDashboardRecord(body.slug);
|
|
54
52
|
if (!dashboard) {
|
|
55
53
|
response.setStatus(404);
|
|
56
54
|
return { error: 'Dashboard not found' };
|
|
@@ -61,8 +59,10 @@ export function registerGroupEndpoints(server, ctx) {
|
|
|
61
59
|
response.setStatus(404);
|
|
62
60
|
return { error: 'Dashboard group not found' };
|
|
63
61
|
}
|
|
62
|
+
const nextGroup = Object.assign(Object.assign({}, body.config), { id: group.id, order: group.order });
|
|
64
63
|
return ctx.persistDashboardConfig(dashboard, Object.assign(Object.assign({}, config), { groups: config.groups.map((item) => item.id === groupId
|
|
65
|
-
?
|
|
64
|
+
? nextGroup
|
|
65
|
+
: item) }));
|
|
66
66
|
}),
|
|
67
67
|
});
|
|
68
68
|
server.endpoint({
|
|
@@ -76,24 +76,27 @@ export function registerGroupEndpoints(server, ctx) {
|
|
|
76
76
|
response.setStatus(403);
|
|
77
77
|
return { error: 'Dashboard edit is not allowed' };
|
|
78
78
|
}
|
|
79
|
-
const
|
|
80
|
-
const groupId = String((body === null || body === void 0 ? void 0 : body.groupId) || '');
|
|
81
|
-
const direction = (body === null || body === void 0 ? void 0 : body.direction) === 'down' ? 'down' : 'up';
|
|
82
|
-
const dashboard = yield ctx.getDashboardRecord(slug);
|
|
79
|
+
const dashboard = yield ctx.getDashboardRecord(body.slug);
|
|
83
80
|
if (!dashboard) {
|
|
84
81
|
response.setStatus(404);
|
|
85
82
|
return { error: 'Dashboard not found' };
|
|
86
83
|
}
|
|
87
84
|
const config = ctx.parseStoredDashboardConfig(dashboard.config);
|
|
88
85
|
const sortedGroups = [...config.groups].sort((a, b) => a.order - b.order);
|
|
89
|
-
const currentIndex = sortedGroups.findIndex((group) => group.id === groupId);
|
|
86
|
+
const currentIndex = sortedGroups.findIndex((group) => group.id === body.groupId);
|
|
90
87
|
if (currentIndex === -1) {
|
|
91
88
|
response.setStatus(404);
|
|
92
89
|
return { error: 'Dashboard group not found' };
|
|
93
90
|
}
|
|
94
|
-
const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
|
91
|
+
const targetIndex = body.direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
|
95
92
|
if (targetIndex < 0 || targetIndex >= sortedGroups.length) {
|
|
96
|
-
return
|
|
93
|
+
return {
|
|
94
|
+
id: dashboard.id,
|
|
95
|
+
slug: dashboard.slug,
|
|
96
|
+
label: dashboard.label,
|
|
97
|
+
revision: dashboard.revision,
|
|
98
|
+
config: ctx.parseStoredDashboardConfig(dashboard.config),
|
|
99
|
+
};
|
|
97
100
|
}
|
|
98
101
|
const reorderedGroups = [...sortedGroups];
|
|
99
102
|
const [group] = reorderedGroups.splice(currentIndex, 1);
|
|
@@ -112,9 +115,8 @@ export function registerGroupEndpoints(server, ctx) {
|
|
|
112
115
|
response.setStatus(403);
|
|
113
116
|
return { error: 'Dashboard edit is not allowed' };
|
|
114
117
|
}
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const dashboard = yield ctx.getDashboardRecord(slug);
|
|
118
|
+
const groupId = body.groupId;
|
|
119
|
+
const dashboard = yield ctx.getDashboardRecord(body.slug);
|
|
118
120
|
if (!dashboard) {
|
|
119
121
|
response.setStatus(404);
|
|
120
122
|
return { error: 'Dashboard not found' };
|