@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.
Files changed (78) hide show
  1. package/README.md +23 -4
  2. package/custom/api/dashboardApi.ts +6 -9
  3. package/custom/model/dashboard.types.ts +60 -275
  4. package/custom/model/dashboardTopics.ts +5 -0
  5. package/custom/runtime/DashboardGroup.vue +2 -2
  6. package/custom/runtime/DashboardPage.vue +17 -7
  7. package/custom/runtime/DashboardRuntime.vue +20 -8
  8. package/custom/runtime/WidgetRenderer.vue +1 -2
  9. package/custom/runtime/WidgetShell.vue +3 -3
  10. package/custom/skills/adminforth-dashboard/SKILL.md +2 -2
  11. package/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  12. package/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  13. package/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  14. package/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  15. package/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  16. package/custom/widgets/chart/ChartWidget.vue +4 -15
  17. package/{dist/custom/widgets/chart/funnel → custom/widgets/chart}/FunnelChart.vue +80 -78
  18. package/{dist/custom/widgets/chart/line → custom/widgets/chart}/LineChart.vue +2 -2
  19. package/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  20. package/{dist/custom/widgets/chart/stacked-bar → custom/widgets/chart}/StackedBarChart.vue +97 -95
  21. package/custom/widgets/chart/chart.types.ts +0 -28
  22. package/dist/custom/api/dashboardApi.d.ts +4 -8
  23. package/dist/custom/api/dashboardApi.ts +6 -9
  24. package/dist/custom/model/dashboard.types.d.ts +38 -32
  25. package/dist/custom/model/dashboard.types.js +2 -155
  26. package/dist/custom/model/dashboard.types.ts +60 -275
  27. package/dist/custom/model/dashboardTopics.d.ts +2 -0
  28. package/dist/custom/model/dashboardTopics.js +8 -0
  29. package/dist/custom/model/dashboardTopics.ts +5 -0
  30. package/dist/custom/queries/useDashboardConfig.d.ts +96 -96
  31. package/dist/custom/queries/useWidgetData.d.ts +96 -96
  32. package/dist/custom/runtime/DashboardGroup.vue +2 -2
  33. package/dist/custom/runtime/DashboardPage.vue +17 -7
  34. package/dist/custom/runtime/DashboardRuntime.vue +20 -8
  35. package/dist/custom/runtime/WidgetRenderer.vue +1 -2
  36. package/dist/custom/runtime/WidgetShell.vue +3 -3
  37. package/dist/custom/skills/adminforth-dashboard/SKILL.md +2 -2
  38. package/dist/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  39. package/dist/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  40. package/dist/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  41. package/dist/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  42. package/dist/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  43. package/dist/custom/widgets/chart/ChartWidget.vue +4 -15
  44. package/{custom/widgets/chart/funnel → dist/custom/widgets/chart}/FunnelChart.vue +80 -78
  45. package/{custom/widgets/chart/line → dist/custom/widgets/chart}/LineChart.vue +2 -2
  46. package/dist/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  47. package/{custom/widgets/chart/stacked-bar → dist/custom/widgets/chart}/StackedBarChart.vue +97 -95
  48. package/dist/custom/widgets/chart/chart.types.d.ts +0 -2
  49. package/dist/custom/widgets/chart/chart.types.js +0 -23
  50. package/dist/custom/widgets/chart/chart.types.ts +0 -28
  51. package/dist/endpoint/dashboard.d.ts +2 -3
  52. package/dist/endpoint/dashboard.js +12 -32
  53. package/dist/endpoint/groups.d.ts +2 -21
  54. package/dist/endpoint/groups.js +18 -16
  55. package/dist/endpoint/widgets.d.ts +0 -3
  56. package/dist/endpoint/widgets.js +27 -74
  57. package/dist/index.js +1 -3
  58. package/dist/schema/api.d.ts +2090 -511
  59. package/dist/schema/api.js +18 -15
  60. package/dist/schema/widget.d.ts +1003 -250
  61. package/dist/schema/widget.js +102 -46
  62. package/dist/services/dashboardConfigService.d.ts +0 -10
  63. package/dist/services/dashboardConfigService.js +6 -21
  64. package/dist/services/widgetDataService.js +226 -196
  65. package/endpoint/dashboard.ts +13 -46
  66. package/endpoint/groups.ts +25 -42
  67. package/endpoint/widgets.ts +36 -95
  68. package/index.ts +0 -3
  69. package/package.json +3 -3
  70. package/schema/api.ts +19 -15
  71. package/schema/widget.ts +113 -52
  72. package/services/dashboardConfigService.ts +6 -25
  73. package/services/widgetDataService.ts +304 -229
  74. package/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  75. package/dist/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  76. package/dist/services/widgetConfigValidator.d.ts +0 -8
  77. package/dist/services/widgetConfigValidator.js +0 -27
  78. 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 '../../../composables/useElementSize.js'
81
+ import { useElementSize } from '../../composables/useElementSize.js'
4
82
  import {
5
83
  CHART_COLORS,
6
84
  formatChartAxisLabel,
7
85
  formatChartLabel,
8
86
  formatChartValue,
9
87
  toFiniteNumber,
10
- } from '../chart.utils.js'
88
+ } from './chart.utils.js'
11
89
 
12
90
  const props = withDefaults(defineProps<{
13
91
  rows: Record<string, unknown>[]
@@ -121,79 +199,3 @@ const segments = computed(() => funnelRows.value.map((row, index) => {
121
199
  }
122
200
  }))
123
201
  </script>
124
-
125
- <template>
126
- <div
127
- ref="rootEl"
128
- class="grid h-full min-h-0 w-full gap-4 overflow-hidden"
129
- :class="isCompact ? 'grid-rows-[minmax(0,1fr)_auto]' : 'grid-cols-[minmax(0,1fr)_200px]'"
130
- >
131
- <div
132
- ref="svgEl"
133
- class="min-h-0 w-full overflow-hidden"
134
- >
135
- <svg
136
- v-if="chartWidth > 0 && chartHeight > 0"
137
- class="block h-full w-full"
138
- :viewBox="`0 0 ${chartWidth} ${chartHeight}`"
139
- role="img"
140
- :aria-label="valueField"
141
- >
142
- <path
143
- v-for="segment in segments"
144
- :key="segment.id"
145
- :d="segment.path"
146
- :fill="segment.color"
147
- fill-opacity="0.9"
148
- >
149
- <title>
150
- {{ segment.label }}: {{ formatChartValue(segment.value) }} ({{ segment.percentLabel }})
151
- </title>
152
- </path>
153
-
154
- <text
155
- v-for="segment in segments"
156
- v-show="segment.labelVisible"
157
- :key="`value-${segment.id}`"
158
- :x="chartWidth / 2"
159
- :y="segment.centerY + 4"
160
- fill="#ffffff"
161
- font-size="12"
162
- font-weight="600"
163
- text-anchor="middle"
164
- >
165
- {{ formatChartValue(segment.value) }}
166
- </text>
167
- </svg>
168
- </div>
169
-
170
- <div class="grid min-w-0 gap-2 text-sm">
171
- <div
172
- v-for="segment in segments"
173
- :key="`legend-${segment.id}`"
174
- class="grid min-h-[34px] min-w-0 grid-cols-[1fr_auto] items-center gap-3"
175
- >
176
- <div class="flex min-w-0 items-center gap-2">
177
- <span
178
- class="h-2.5 w-2.5 shrink-0 rounded-full"
179
- :style="{ backgroundColor: segment.color }"
180
- />
181
-
182
- <span class="truncate text-lightNavbarText dark:text-darkNavbarText">
183
- {{ segment.shortLabel }}
184
- </span>
185
- </div>
186
-
187
- <div class="text-right">
188
- <div class="font-semibold text-lightNavbarText dark:text-darkNavbarText">
189
- {{ formatChartValue(segment.value) }}
190
- </div>
191
-
192
- <div class="text-xs text-lightListTableText dark:text-darkListTableText">
193
- {{ segment.percentLabel }}
194
- </div>
195
- </div>
196
- </div>
197
- </div>
198
- </div>
199
- </template>
@@ -84,7 +84,7 @@
84
84
 
85
85
  <script setup lang="ts">
86
86
  import { computed } from 'vue'
87
- import { useElementSize } from '../../../composables/useElementSize.js'
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 '../chart.utils.js'
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 '../../../composables/useElementSize.js'
93
- import { CHART_COLORS, formatChartLabel, formatChartValue, toFiniteNumber } from '../chart.utils.js'
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 '../../../composables/useElementSize.js'
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 '../chart.utils.js'
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>
@@ -27,5 +27,3 @@ export type ChartWidgetConfig = {
27
27
  color?: string;
28
28
  colors?: string[];
29
29
  };
30
- export type NormalizedChartWidgetConfig = ChartWidgetConfig;
31
- export declare function normalizeChartWidgetConfig(value: unknown): NormalizedChartWidgetConfig | undefined;
@@ -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, DashboardWidgetConfig } from '../custom/model/dashboard.types.js';
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 { normalizeDashboardConfig } from '../custom/model/dashboard.types.js';
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 slug = String((body === null || body === void 0 ? void 0 : body.slug) || 'default');
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 buildDashboardResponse(dashboard);
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 slug = String((body === null || body === void 0 ? void 0 : body.slug) || 'default');
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 normalizedConfig = normalizeDashboardConfig(body === null || body === void 0 ? void 0 : body.config);
54
- const parsedConfig = DashboardConfigZodSchema.safeParse(normalizedConfig);
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 {};
@@ -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 slug = String((body === null || body === void 0 ? void 0 : body.slug) || 'default');
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 slug = String((body === null || body === void 0 ? void 0 : body.slug) || 'default');
52
- const groupId = String((body === null || body === void 0 ? void 0 : body.groupId) || '');
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
- ? Object.assign(Object.assign({}, body.config), { id: group.id, order: group.order }) : item) }));
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 slug = String((body === null || body === void 0 ? void 0 : body.slug) || 'default');
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 ctx.buildDashboardResponse(dashboard);
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 slug = String((body === null || body === void 0 ? void 0 : body.slug) || 'default');
116
- const groupId = String((body === null || body === void 0 ? void 0 : body.groupId) || '');
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' };