@adminforth/dashboard 1.0.0 → 1.1.0
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 +116 -54
- package/custom/api/dashboardApi.ts +9 -0
- package/custom/model/dashboard.types.ts +158 -1
- package/custom/queries/useWidgetData.ts +8 -4
- package/custom/runtime/WidgetShell.vue +8 -4
- package/custom/widgets/chart/chart.utils.ts +2 -2
- package/custom/widgets/gauge-card/GaugeCardWidget.vue +94 -12
- package/custom/widgets/pivot-table/PivotTableWidget.vue +27 -5
- package/custom/widgets/table/TableWidget.vue +155 -30
- package/dist/custom/api/dashboardApi.d.ts +7 -1
- package/dist/custom/api/dashboardApi.js +4 -6
- package/dist/custom/api/dashboardApi.ts +9 -0
- package/dist/custom/model/dashboard.types.d.ts +45 -0
- package/dist/custom/model/dashboard.types.js +82 -1
- package/dist/custom/model/dashboard.types.ts +158 -1
- package/dist/custom/queries/useDashboardConfig.d.ts +42 -0
- package/dist/custom/queries/useWidgetData.d.ts +44 -1
- package/dist/custom/queries/useWidgetData.js +3 -3
- package/dist/custom/queries/useWidgetData.ts +8 -4
- package/dist/custom/runtime/WidgetShell.vue +8 -4
- package/dist/custom/widgets/chart/chart.utils.d.ts +1 -1
- package/dist/custom/widgets/chart/chart.utils.js +2 -2
- package/dist/custom/widgets/chart/chart.utils.ts +2 -2
- package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +94 -12
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +27 -5
- package/dist/custom/widgets/table/TableWidget.vue +155 -30
- package/dist/endpoint/widgets.d.ts +6 -1
- package/dist/endpoint/widgets.js +22 -4
- package/dist/schema/api.d.ts +882 -212
- package/dist/schema/api.js +11 -2
- package/dist/schema/widget.d.ts +542 -4
- package/dist/schema/widget.js +111 -1
- package/dist/services/widgetConfigValidator.js +32 -6
- package/dist/services/widgetDataService.d.ts +8 -6
- package/dist/services/widgetDataService.js +133 -11
- package/endpoint/widgets.ts +31 -4
- package/package.json +1 -1
- package/schema/api.ts +11 -1
- package/schema/widget.ts +114 -1
- package/services/widgetConfigValidator.ts +45 -6
- package/services/widgetDataService.ts +201 -19
|
@@ -23,37 +23,87 @@
|
|
|
23
23
|
|
|
24
24
|
<div
|
|
25
25
|
v-else
|
|
26
|
-
class="min-h-0 flex-1
|
|
26
|
+
class="flex min-h-0 flex-1 flex-col"
|
|
27
27
|
>
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
<div class="min-h-0 flex-1 overflow-auto">
|
|
29
|
+
<table class="min-w-max w-full border-collapse text-left text-sm">
|
|
30
|
+
<thead class="bg-lightTableHeadingBackground text-xs uppercase text-lightTableHeadingText dark:bg-darkTableHeadingBackground dark:text-darkTableHeadingText">
|
|
31
|
+
<tr>
|
|
32
|
+
<th
|
|
33
|
+
v-for="column in columns"
|
|
34
|
+
:key="column"
|
|
35
|
+
class="px-3 py-2 font-semibold"
|
|
36
|
+
>
|
|
37
|
+
{{ column }}
|
|
38
|
+
</th>
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
|
|
42
|
+
<tbody>
|
|
43
|
+
<tr
|
|
44
|
+
v-for="(row, index) in tableData.rows"
|
|
45
|
+
:key="`${currentPage}-${index}`"
|
|
46
|
+
class="border-t border-lightListBorder odd:bg-lightTableOddBackground even:bg-lightTableEvenBackground dark:border-darkListBorder odd:dark:bg-darkTableOddBackground even:dark:bg-darkTableEvenBackground"
|
|
35
47
|
>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
<td
|
|
49
|
+
v-for="column in columns"
|
|
50
|
+
:key="column"
|
|
51
|
+
class="px-3 py-2 text-lightListTableText dark:text-darkListTableText"
|
|
52
|
+
>
|
|
53
|
+
{{ formatCell(row[column]) }}
|
|
54
|
+
</td>
|
|
55
|
+
</tr>
|
|
56
|
+
</tbody>
|
|
57
|
+
</table>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div
|
|
61
|
+
v-if="pagination"
|
|
62
|
+
class="flex flex-wrap items-center justify-between gap-2 border-t border-lightListBorder px-3 py-2 text-sm text-lightListTableText dark:border-darkListBorder dark:text-darkListTableText"
|
|
63
|
+
>
|
|
64
|
+
<div>
|
|
65
|
+
{{ pageStart }}-{{ pageEnd }} of {{ pagination.total }}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="flex items-center gap-2">
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
class="flex h-8 w-8 items-center justify-center rounded border border-lightListBorder text-sm disabled:opacity-45 dark:border-darkListBorder"
|
|
72
|
+
:disabled="currentPage <= 1 || isFetching"
|
|
73
|
+
@click="currentPage -= 1"
|
|
74
|
+
aria-label="Previous page"
|
|
46
75
|
>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
<
|
|
77
|
+
</button>
|
|
78
|
+
|
|
79
|
+
<span class="flex items-center gap-1">
|
|
80
|
+
<span>Page</span>
|
|
81
|
+
<input
|
|
82
|
+
v-model.number="currentPageInput"
|
|
83
|
+
type="number"
|
|
84
|
+
min="1"
|
|
85
|
+
:max="pagination.totalPages"
|
|
86
|
+
class="dashboard-table-page-input h-8 min-w-8 rounded border border-lightListBorder bg-lightTableBackground px-2 text-center text-sm text-lightListTableText dark:border-darkListBorder dark:bg-darkTableBackground dark:text-darkListTableText"
|
|
87
|
+
:style="{ width: `${currentPageInputWidth}ch` }"
|
|
88
|
+
:disabled="isFetching"
|
|
89
|
+
aria-label="Current page"
|
|
90
|
+
@blur="applyCurrentPageInput"
|
|
91
|
+
@keydown.enter="applyCurrentPageInput"
|
|
51
92
|
>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
93
|
+
<span>of {{ pagination.totalPages }}</span>
|
|
94
|
+
</span>
|
|
95
|
+
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
class="flex h-8 w-8 items-center justify-center rounded border border-lightListBorder text-sm disabled:opacity-45 dark:border-darkListBorder"
|
|
99
|
+
:disabled="currentPage >= pagination.totalPages || isFetching"
|
|
100
|
+
@click="currentPage += 1"
|
|
101
|
+
aria-label="Next page"
|
|
102
|
+
>
|
|
103
|
+
>
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
57
107
|
</div>
|
|
58
108
|
</div>
|
|
59
109
|
</template>
|
|
@@ -61,27 +111,52 @@
|
|
|
61
111
|
|
|
62
112
|
|
|
63
113
|
<script setup lang="ts">
|
|
64
|
-
import { computed, watch } from 'vue'
|
|
114
|
+
import { computed, ref, watch } from 'vue'
|
|
65
115
|
import { useWidgetData } from '../../queries/useWidgetData.js'
|
|
66
116
|
import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
|
|
67
117
|
|
|
118
|
+
type TableWidgetConfig = {
|
|
119
|
+
columns?: string[]
|
|
120
|
+
pagination?: boolean
|
|
121
|
+
pageSize?: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const DEFAULT_PAGE_SIZE = 10
|
|
125
|
+
|
|
68
126
|
const props = defineProps<{
|
|
69
127
|
dashboardSlug: string
|
|
70
128
|
widget: DashboardWidgetConfig
|
|
71
129
|
}>()
|
|
72
130
|
|
|
131
|
+
const currentPage = ref(1)
|
|
132
|
+
const currentPageInput = ref(1)
|
|
133
|
+
const tableConfig = computed(() => props.widget.table as TableWidgetConfig | undefined)
|
|
134
|
+
const isPaginationEnabled = computed(() => tableConfig.value?.pagination !== false)
|
|
135
|
+
const pageSize = computed(() => tableConfig.value?.pageSize ?? props.widget.query?.limit ?? DEFAULT_PAGE_SIZE)
|
|
73
136
|
const dashboardSlugRef = computed(() => props.dashboardSlug)
|
|
74
137
|
const widgetIdRef = computed(() => props.widget.id)
|
|
138
|
+
const widgetDataRequest = computed(() => (
|
|
139
|
+
isPaginationEnabled.value
|
|
140
|
+
? {
|
|
141
|
+
pagination: {
|
|
142
|
+
page: currentPage.value,
|
|
143
|
+
pageSize: pageSize.value,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
: {}
|
|
147
|
+
))
|
|
75
148
|
const {
|
|
76
149
|
data,
|
|
77
150
|
isLoading,
|
|
151
|
+
isFetching,
|
|
78
152
|
error,
|
|
79
153
|
refetch,
|
|
80
|
-
} = useWidgetData(dashboardSlugRef, widgetIdRef)
|
|
154
|
+
} = useWidgetData(dashboardSlugRef, widgetIdRef, widgetDataRequest)
|
|
81
155
|
|
|
82
156
|
watch(
|
|
83
157
|
() => props.widget,
|
|
84
158
|
() => {
|
|
159
|
+
currentPage.value = 1
|
|
85
160
|
void refetch()
|
|
86
161
|
},
|
|
87
162
|
{ deep: true },
|
|
@@ -92,10 +167,47 @@ const tableData = computed(() => {
|
|
|
92
167
|
})
|
|
93
168
|
|
|
94
169
|
const columns = computed(() => {
|
|
95
|
-
const configuredColumns =
|
|
170
|
+
const configuredColumns = tableConfig.value?.columns
|
|
96
171
|
return configuredColumns ?? tableData.value?.columns ?? []
|
|
97
172
|
})
|
|
98
173
|
|
|
174
|
+
const pagination = computed(() => tableData.value?.pagination)
|
|
175
|
+
const pageStart = computed(() => {
|
|
176
|
+
if (!pagination.value || pagination.value.total === 0) {
|
|
177
|
+
return 0
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (pagination.value.page - 1) * pagination.value.pageSize + 1
|
|
181
|
+
})
|
|
182
|
+
const pageEnd = computed(() => {
|
|
183
|
+
if (!pagination.value) {
|
|
184
|
+
return 0
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return Math.min(pagination.value.page * pagination.value.pageSize, pagination.value.total)
|
|
188
|
+
})
|
|
189
|
+
const currentPageInputWidth = computed(() => {
|
|
190
|
+
const digits = String(currentPageInput.value || currentPage.value).length
|
|
191
|
+
return Math.max(digits + 3, 4)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
watch(pagination, (nextPagination) => {
|
|
195
|
+
if (nextPagination && currentPage.value > nextPagination.totalPages) {
|
|
196
|
+
currentPage.value = nextPagination.totalPages
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
watch(currentPage, (nextPage) => {
|
|
201
|
+
currentPageInput.value = nextPage
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
function applyCurrentPageInput() {
|
|
205
|
+
const totalPages = pagination.value?.totalPages ?? 1
|
|
206
|
+
const page = Number.isFinite(currentPageInput.value) ? currentPageInput.value : currentPage.value
|
|
207
|
+
currentPage.value = Math.min(Math.max(Math.trunc(page), 1), totalPages)
|
|
208
|
+
currentPageInput.value = currentPage.value
|
|
209
|
+
}
|
|
210
|
+
|
|
99
211
|
function formatCell(value: unknown) {
|
|
100
212
|
if (value === null || value === undefined) {
|
|
101
213
|
return ''
|
|
@@ -108,3 +220,16 @@ function formatCell(value: unknown) {
|
|
|
108
220
|
return String(value)
|
|
109
221
|
}
|
|
110
222
|
</script>
|
|
223
|
+
|
|
224
|
+
<style scoped>
|
|
225
|
+
.dashboard-table-page-input::-webkit-outer-spin-button,
|
|
226
|
+
.dashboard-table-page-input::-webkit-inner-spin-button {
|
|
227
|
+
margin: 0;
|
|
228
|
+
appearance: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.dashboard-table-page-input {
|
|
232
|
+
appearance: textfield;
|
|
233
|
+
-moz-appearance: textfield;
|
|
234
|
+
}
|
|
235
|
+
</style>
|
|
@@ -14,6 +14,12 @@ export type DashboardWidgetDataResponse = {
|
|
|
14
14
|
widget: DashboardWidgetConfig;
|
|
15
15
|
data: unknown;
|
|
16
16
|
};
|
|
17
|
+
export type DashboardWidgetDataRequest = {
|
|
18
|
+
pagination?: {
|
|
19
|
+
page: number;
|
|
20
|
+
pageSize: number;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
17
23
|
export declare class DashboardApiError extends Error {
|
|
18
24
|
validationErrors: DashboardWidgetConfigValidationError[];
|
|
19
25
|
constructor(message: string, validationErrors?: DashboardWidgetConfigValidationError[]);
|
|
@@ -28,5 +34,5 @@ export declare const dashboardApi: {
|
|
|
28
34
|
moveDashboardWidget(slug: string, widgetId: string, direction: DashboardWidgetMoveDirection): Promise<DashboardResponse>;
|
|
29
35
|
removeDashboardWidget(slug: string, widgetId: string): Promise<DashboardResponse>;
|
|
30
36
|
setWidgetConfig(slug: string, widgetId: string, config: DashboardWidgetConfig): Promise<DashboardResponse>;
|
|
31
|
-
getDashboardWidgetData(slug: string, widgetId: string): Promise<DashboardWidgetDataResponse>;
|
|
37
|
+
getDashboardWidgetData(slug: string, widgetId: string, request?: DashboardWidgetDataRequest): Promise<DashboardWidgetDataResponse>;
|
|
32
38
|
};
|
|
@@ -168,12 +168,10 @@ exports.dashboardApi = {
|
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
},
|
|
171
|
-
getDashboardWidgetData(
|
|
172
|
-
return __awaiter(this,
|
|
173
|
-
return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', {
|
|
174
|
-
|
|
175
|
-
widgetId,
|
|
176
|
-
});
|
|
171
|
+
getDashboardWidgetData(slug_1, widgetId_1) {
|
|
172
|
+
return __awaiter(this, arguments, void 0, function* (slug, widgetId, request = {}) {
|
|
173
|
+
return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', Object.assign({ slug,
|
|
174
|
+
widgetId }, request));
|
|
177
175
|
});
|
|
178
176
|
},
|
|
179
177
|
};
|
|
@@ -24,6 +24,13 @@ export type DashboardWidgetDataResponse = {
|
|
|
24
24
|
data: unknown
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export type DashboardWidgetDataRequest = {
|
|
28
|
+
pagination?: {
|
|
29
|
+
page: number
|
|
30
|
+
pageSize: number
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
export class DashboardApiError extends Error {
|
|
28
35
|
validationErrors: DashboardWidgetConfigValidationError[]
|
|
29
36
|
|
|
@@ -204,10 +211,12 @@ export const dashboardApi = {
|
|
|
204
211
|
async getDashboardWidgetData(
|
|
205
212
|
slug: string,
|
|
206
213
|
widgetId: string,
|
|
214
|
+
request: DashboardWidgetDataRequest = {},
|
|
207
215
|
): Promise<DashboardWidgetDataResponse> {
|
|
208
216
|
return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', {
|
|
209
217
|
slug,
|
|
210
218
|
widgetId,
|
|
219
|
+
...request,
|
|
211
220
|
})
|
|
212
221
|
},
|
|
213
222
|
}
|
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js';
|
|
2
|
+
export type AggregationOperation = 'sum' | 'count' | 'avg' | 'min' | 'max' | 'median';
|
|
3
|
+
export type AggregationRule = {
|
|
4
|
+
operation: AggregationOperation;
|
|
5
|
+
field?: string;
|
|
6
|
+
};
|
|
7
|
+
export type GroupByRule = {
|
|
8
|
+
type: 'field';
|
|
9
|
+
field: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'date_trunc';
|
|
12
|
+
field: string;
|
|
13
|
+
truncation: 'day' | 'week' | 'month' | 'year';
|
|
14
|
+
timezone?: string;
|
|
15
|
+
};
|
|
16
|
+
export type ResourceWidgetDataSource = {
|
|
17
|
+
type: 'resource';
|
|
18
|
+
resourceId: string;
|
|
19
|
+
columns?: string[];
|
|
20
|
+
sort?: unknown;
|
|
21
|
+
filters?: unknown;
|
|
22
|
+
};
|
|
23
|
+
export type AggregateWidgetDataSource = {
|
|
24
|
+
type: 'aggregate';
|
|
25
|
+
resourceId: string;
|
|
26
|
+
aggregations: Record<string, AggregationRule>;
|
|
27
|
+
groupBy?: GroupByRule;
|
|
28
|
+
filters?: unknown;
|
|
29
|
+
};
|
|
30
|
+
export type WidgetDataSource = ResourceWidgetDataSource | AggregateWidgetDataSource;
|
|
2
31
|
export type DashboardConfig = {
|
|
3
32
|
version: number;
|
|
4
33
|
groups: DashboardGroupConfig[];
|
|
@@ -31,6 +60,7 @@ export type DashboardWidgetConfig = {
|
|
|
31
60
|
maxWidth?: number | null;
|
|
32
61
|
order: number;
|
|
33
62
|
target: DashboardWidgetTarget;
|
|
63
|
+
dataSource?: WidgetDataSource;
|
|
34
64
|
chart?: ChartWidgetConfig;
|
|
35
65
|
table?: unknown;
|
|
36
66
|
kpi_card?: unknown;
|
|
@@ -39,7 +69,22 @@ export type DashboardWidgetConfig = {
|
|
|
39
69
|
query?: unknown;
|
|
40
70
|
};
|
|
41
71
|
export type DashboardWidgetTableData = {
|
|
72
|
+
kind?: 'table';
|
|
73
|
+
columns: string[];
|
|
74
|
+
rows: Record<string, unknown>[];
|
|
75
|
+
pagination?: {
|
|
76
|
+
page: number;
|
|
77
|
+
pageSize: number;
|
|
78
|
+
total: number;
|
|
79
|
+
totalPages: number;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
export type DashboardWidgetAggregateData = {
|
|
83
|
+
kind: 'aggregate';
|
|
42
84
|
columns: string[];
|
|
43
85
|
rows: Record<string, unknown>[];
|
|
86
|
+
values?: Record<string, unknown>;
|
|
44
87
|
};
|
|
88
|
+
export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData;
|
|
45
89
|
export declare function normalizeDashboardConfig(config: unknown): DashboardConfig;
|
|
90
|
+
export declare function normalizeDashboardWidgetConfig(config: unknown): unknown;
|
|
@@ -1,14 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.normalizeDashboardConfig = normalizeDashboardConfig;
|
|
4
|
+
exports.normalizeDashboardWidgetConfig = normalizeDashboardWidgetConfig;
|
|
4
5
|
function normalizeDashboardConfig(config) {
|
|
5
6
|
const value = isRecord(config) ? config : {};
|
|
6
7
|
return {
|
|
7
8
|
version: typeof value.version === 'number' ? value.version : 1,
|
|
8
9
|
groups: Array.isArray(value.groups) ? value.groups : [],
|
|
9
|
-
widgets: Array.isArray(value.widgets)
|
|
10
|
+
widgets: Array.isArray(value.widgets)
|
|
11
|
+
? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget))
|
|
12
|
+
: [],
|
|
10
13
|
};
|
|
11
14
|
}
|
|
15
|
+
function normalizeDashboardWidgetConfig(config) {
|
|
16
|
+
var _a;
|
|
17
|
+
if (!isRecord(config)) {
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
const normalized = Object.assign({}, config);
|
|
21
|
+
const target = normalizeDashboardWidgetTarget((_a = normalized.target) !== null && _a !== void 0 ? _a : normalized.type);
|
|
22
|
+
if (target && normalized.target === undefined) {
|
|
23
|
+
normalized.target = target;
|
|
24
|
+
}
|
|
25
|
+
if (target === 'kpi_card') {
|
|
26
|
+
const kpiCardConfig = normalizeKpiCardConfig(normalized);
|
|
27
|
+
if (kpiCardConfig !== undefined) {
|
|
28
|
+
normalized.kpi_card = kpiCardConfig;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (target === 'gauge_card') {
|
|
32
|
+
const gaugeCardConfig = normalizeGaugeCardConfig(normalized);
|
|
33
|
+
if (gaugeCardConfig !== undefined) {
|
|
34
|
+
normalized.gauge_card = gaugeCardConfig;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
function normalizeDashboardWidgetTarget(value) {
|
|
40
|
+
switch (value) {
|
|
41
|
+
case 'empty':
|
|
42
|
+
case 'table':
|
|
43
|
+
case 'chart':
|
|
44
|
+
case 'kpi_card':
|
|
45
|
+
case 'pivot_table':
|
|
46
|
+
case 'gauge_card':
|
|
47
|
+
return value;
|
|
48
|
+
default:
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function normalizeKpiCardConfig(value) {
|
|
53
|
+
const config = isRecord(value.kpi_card) ? Object.assign({}, value.kpi_card) : {};
|
|
54
|
+
if (typeof value.valueField === 'string' && config.value_field === undefined) {
|
|
55
|
+
config.value_field = value.valueField;
|
|
56
|
+
}
|
|
57
|
+
if (typeof value.labelField === 'string' && config.label_field === undefined) {
|
|
58
|
+
config.label_field = value.labelField;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value.prefix === 'string' && config.prefix === undefined) {
|
|
61
|
+
config.prefix = value.prefix;
|
|
62
|
+
}
|
|
63
|
+
if (typeof value.suffix === 'string' && config.suffix === undefined) {
|
|
64
|
+
config.suffix = value.suffix;
|
|
65
|
+
}
|
|
66
|
+
return Object.keys(config).length ? config : value.kpi_card;
|
|
67
|
+
}
|
|
68
|
+
function normalizeGaugeCardConfig(value) {
|
|
69
|
+
const config = isRecord(value.gauge_card) ? Object.assign({}, value.gauge_card) : {};
|
|
70
|
+
if (typeof value.valueField === 'string' && config.value_field === undefined) {
|
|
71
|
+
config.value_field = value.valueField;
|
|
72
|
+
}
|
|
73
|
+
if (value.min !== undefined && config.min === undefined) {
|
|
74
|
+
config.min = value.min;
|
|
75
|
+
}
|
|
76
|
+
if (value.max !== undefined && config.max === undefined) {
|
|
77
|
+
config.max = value.max;
|
|
78
|
+
}
|
|
79
|
+
if (typeof value.minField === 'string' && config.min_field === undefined) {
|
|
80
|
+
config.min_field = value.minField;
|
|
81
|
+
}
|
|
82
|
+
if (typeof value.maxField === 'string' && config.max_field === undefined) {
|
|
83
|
+
config.max_field = value.maxField;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value.suffix === 'string' && config.suffix === undefined) {
|
|
86
|
+
config.suffix = value.suffix;
|
|
87
|
+
}
|
|
88
|
+
if (typeof value.color === 'string' && config.color === undefined) {
|
|
89
|
+
config.color = value.color;
|
|
90
|
+
}
|
|
91
|
+
return Object.keys(config).length ? config : value.gauge_card;
|
|
92
|
+
}
|
|
12
93
|
function isRecord(value) {
|
|
13
94
|
return typeof value === 'object' && value !== null;
|
|
14
95
|
}
|
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js'
|
|
2
2
|
|
|
3
|
+
export type AggregationOperation = 'sum' | 'count' | 'avg' | 'min' | 'max' | 'median'
|
|
4
|
+
|
|
5
|
+
export type AggregationRule = {
|
|
6
|
+
operation: AggregationOperation
|
|
7
|
+
field?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type GroupByRule =
|
|
11
|
+
| {
|
|
12
|
+
type: 'field'
|
|
13
|
+
field: string
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
type: 'date_trunc'
|
|
17
|
+
field: string
|
|
18
|
+
truncation: 'day' | 'week' | 'month' | 'year'
|
|
19
|
+
timezone?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ResourceWidgetDataSource = {
|
|
23
|
+
type: 'resource'
|
|
24
|
+
resourceId: string
|
|
25
|
+
columns?: string[]
|
|
26
|
+
sort?: unknown
|
|
27
|
+
filters?: unknown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AggregateWidgetDataSource = {
|
|
31
|
+
type: 'aggregate'
|
|
32
|
+
resourceId: string
|
|
33
|
+
aggregations: Record<string, AggregationRule>
|
|
34
|
+
groupBy?: GroupByRule
|
|
35
|
+
filters?: unknown
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type WidgetDataSource = ResourceWidgetDataSource | AggregateWidgetDataSource
|
|
39
|
+
|
|
3
40
|
export type DashboardConfig = {
|
|
4
41
|
version: number
|
|
5
42
|
groups: DashboardGroupConfig[]
|
|
@@ -45,6 +82,7 @@ export type DashboardWidgetConfig = {
|
|
|
45
82
|
maxWidth?: number | null
|
|
46
83
|
order: number
|
|
47
84
|
target: DashboardWidgetTarget
|
|
85
|
+
dataSource?: WidgetDataSource
|
|
48
86
|
chart?: ChartWidgetConfig
|
|
49
87
|
table?: unknown
|
|
50
88
|
kpi_card?: unknown
|
|
@@ -54,20 +92,139 @@ export type DashboardWidgetConfig = {
|
|
|
54
92
|
}
|
|
55
93
|
|
|
56
94
|
export type DashboardWidgetTableData = {
|
|
95
|
+
kind?: 'table'
|
|
57
96
|
columns: string[]
|
|
58
97
|
rows: Record<string, unknown>[]
|
|
98
|
+
pagination?: {
|
|
99
|
+
page: number
|
|
100
|
+
pageSize: number
|
|
101
|
+
total: number
|
|
102
|
+
totalPages: number
|
|
103
|
+
}
|
|
59
104
|
}
|
|
60
105
|
|
|
106
|
+
export type DashboardWidgetAggregateData = {
|
|
107
|
+
kind: 'aggregate'
|
|
108
|
+
columns: string[]
|
|
109
|
+
rows: Record<string, unknown>[]
|
|
110
|
+
values?: Record<string, unknown>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData
|
|
114
|
+
|
|
61
115
|
export function normalizeDashboardConfig(config: unknown): DashboardConfig {
|
|
62
116
|
const value = isRecord(config) ? config : {}
|
|
63
117
|
|
|
64
118
|
return {
|
|
65
119
|
version: typeof value.version === 'number' ? value.version : 1,
|
|
66
120
|
groups: Array.isArray(value.groups) ? (value.groups as DashboardGroupConfig[]) : [],
|
|
67
|
-
widgets: Array.isArray(value.widgets)
|
|
121
|
+
widgets: Array.isArray(value.widgets)
|
|
122
|
+
? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget) as DashboardWidgetConfig)
|
|
123
|
+
: [],
|
|
68
124
|
}
|
|
69
125
|
}
|
|
70
126
|
|
|
127
|
+
export function normalizeDashboardWidgetConfig(config: unknown) {
|
|
128
|
+
if (!isRecord(config)) {
|
|
129
|
+
return config
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const normalized: Record<string, unknown> = { ...config }
|
|
133
|
+
const target = normalizeDashboardWidgetTarget(normalized.target ?? normalized.type)
|
|
134
|
+
|
|
135
|
+
if (target && normalized.target === undefined) {
|
|
136
|
+
normalized.target = target
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (target === 'kpi_card') {
|
|
140
|
+
const kpiCardConfig = normalizeKpiCardConfig(normalized)
|
|
141
|
+
|
|
142
|
+
if (kpiCardConfig !== undefined) {
|
|
143
|
+
normalized.kpi_card = kpiCardConfig
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (target === 'gauge_card') {
|
|
148
|
+
const gaugeCardConfig = normalizeGaugeCardConfig(normalized)
|
|
149
|
+
|
|
150
|
+
if (gaugeCardConfig !== undefined) {
|
|
151
|
+
normalized.gauge_card = gaugeCardConfig
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return normalized
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function normalizeDashboardWidgetTarget(value: unknown): DashboardWidgetTarget | undefined {
|
|
159
|
+
switch (value) {
|
|
160
|
+
case 'empty':
|
|
161
|
+
case 'table':
|
|
162
|
+
case 'chart':
|
|
163
|
+
case 'kpi_card':
|
|
164
|
+
case 'pivot_table':
|
|
165
|
+
case 'gauge_card':
|
|
166
|
+
return value
|
|
167
|
+
default:
|
|
168
|
+
return undefined
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizeKpiCardConfig(value: Record<string, unknown>) {
|
|
173
|
+
const config = isRecord(value.kpi_card) ? { ...value.kpi_card } : {}
|
|
174
|
+
|
|
175
|
+
if (typeof value.valueField === 'string' && config.value_field === undefined) {
|
|
176
|
+
config.value_field = value.valueField
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (typeof value.labelField === 'string' && config.label_field === undefined) {
|
|
180
|
+
config.label_field = value.labelField
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (typeof value.prefix === 'string' && config.prefix === undefined) {
|
|
184
|
+
config.prefix = value.prefix
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (typeof value.suffix === 'string' && config.suffix === undefined) {
|
|
188
|
+
config.suffix = value.suffix
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return Object.keys(config).length ? config : value.kpi_card
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function normalizeGaugeCardConfig(value: Record<string, unknown>) {
|
|
195
|
+
const config = isRecord(value.gauge_card) ? { ...value.gauge_card } : {}
|
|
196
|
+
|
|
197
|
+
if (typeof value.valueField === 'string' && config.value_field === undefined) {
|
|
198
|
+
config.value_field = value.valueField
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (value.min !== undefined && config.min === undefined) {
|
|
202
|
+
config.min = value.min
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (value.max !== undefined && config.max === undefined) {
|
|
206
|
+
config.max = value.max
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeof value.minField === 'string' && config.min_field === undefined) {
|
|
210
|
+
config.min_field = value.minField
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (typeof value.maxField === 'string' && config.max_field === undefined) {
|
|
214
|
+
config.max_field = value.maxField
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (typeof value.suffix === 'string' && config.suffix === undefined) {
|
|
218
|
+
config.suffix = value.suffix
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof value.color === 'string' && config.color === undefined) {
|
|
222
|
+
config.color = value.color
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return Object.keys(config).length ? config : value.gauge_card
|
|
226
|
+
}
|
|
227
|
+
|
|
71
228
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
72
229
|
return typeof value === 'object' && value !== null
|
|
73
230
|
}
|