@adminforth/dashboard 1.1.0 → 1.3.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 +43 -52
- package/custom/composables/useElementSize.ts +17 -2
- package/custom/model/dashboard.types.ts +385 -98
- package/custom/runtime/DashboardRuntime.vue +2 -1
- package/custom/runtime/WidgetRenderer.vue +2 -1
- package/custom/skills/adminforth-dashboard/SKILL.md +8 -4
- package/custom/widgets/chart/ChartWidget.vue +36 -35
- package/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/custom/widgets/chart/chart.types.ts +42 -8
- package/custom/widgets/chart/chart.utils.ts +11 -0
- package/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
- package/custom/widgets/chart/line/LineChart.vue +23 -15
- package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
- package/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -43
- package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -10
- package/custom/widgets/pivot-table/PivotTableWidget.vue +10 -11
- package/custom/widgets/table/TableWidget.vue +9 -4
- package/dist/custom/composables/useElementSize.js +14 -2
- package/dist/custom/composables/useElementSize.ts +17 -2
- package/dist/custom/model/dashboard.types.d.ts +179 -38
- package/dist/custom/model/dashboard.types.js +108 -42
- package/dist/custom/model/dashboard.types.ts +385 -98
- package/dist/custom/queries/useDashboardConfig.d.ts +832 -68
- package/dist/custom/queries/useWidgetData.d.ts +828 -64
- package/dist/custom/runtime/DashboardRuntime.vue +2 -1
- package/dist/custom/runtime/WidgetRenderer.vue +2 -1
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +8 -4
- package/dist/custom/widgets/chart/ChartWidget.vue +36 -35
- package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/dist/custom/widgets/chart/chart.types.d.ts +14 -8
- package/dist/custom/widgets/chart/chart.types.js +23 -0
- package/dist/custom/widgets/chart/chart.types.ts +42 -8
- package/dist/custom/widgets/chart/chart.utils.d.ts +1 -0
- package/dist/custom/widgets/chart/chart.utils.js +7 -0
- package/dist/custom/widgets/chart/chart.utils.ts +11 -0
- package/dist/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
- package/dist/custom/widgets/chart/line/LineChart.vue +23 -15
- package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
- package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -43
- package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -10
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +10 -11
- package/dist/custom/widgets/table/TableWidget.vue +9 -4
- package/dist/endpoint/widgets.js +23 -3
- package/dist/schema/api.d.ts +2637 -933
- package/dist/schema/widget.d.ts +1562 -582
- package/dist/schema/widget.js +207 -127
- package/dist/services/widgetConfigValidator.js +16 -80
- package/dist/services/widgetDataService.d.ts +0 -9
- package/dist/services/widgetDataService.js +356 -97
- package/endpoint/dashboard.ts +1 -1
- package/endpoint/widgets.ts +29 -3
- package/package.json +1 -1
- package/schema/widget.ts +221 -121
- package/services/widgetConfigValidator.ts +29 -100
- package/services/widgetDataService.ts +478 -129
|
@@ -18,40 +18,6 @@ const {
|
|
|
18
18
|
refetch,
|
|
19
19
|
} = useWidgetData(dashboardSlugRef, widgetIdRef)
|
|
20
20
|
|
|
21
|
-
type GaugeCardConfig = {
|
|
22
|
-
value_field?: string
|
|
23
|
-
valueField?: string
|
|
24
|
-
min?: number | string
|
|
25
|
-
max?: number | string
|
|
26
|
-
min_field?: string
|
|
27
|
-
minField?: string
|
|
28
|
-
max_field?: string
|
|
29
|
-
maxField?: string
|
|
30
|
-
suffix?: string
|
|
31
|
-
color?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
35
|
-
return typeof value === 'object' && value !== null
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function parseGaugeCardConfig(value: unknown): GaugeCardConfig | undefined {
|
|
39
|
-
if (isRecord(value)) {
|
|
40
|
-
return value as GaugeCardConfig
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (typeof value !== 'string') {
|
|
44
|
-
return undefined
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
const parsed = JSON.parse(value) as unknown
|
|
49
|
-
return isRecord(parsed) ? parsed as GaugeCardConfig : undefined
|
|
50
|
-
} catch {
|
|
51
|
-
return undefined
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
21
|
function parseOptionalNumber(value: unknown): number | undefined {
|
|
56
22
|
if (value === null || value === undefined || value === '') {
|
|
57
23
|
return undefined
|
|
@@ -86,20 +52,18 @@ watch(
|
|
|
86
52
|
{ deep: true },
|
|
87
53
|
)
|
|
88
54
|
|
|
89
|
-
const gaugeConfig = computed(() =>
|
|
55
|
+
const gaugeConfig = computed(() => props.widget.target === 'gauge_card' ? props.widget.card : undefined)
|
|
90
56
|
const widgetData = computed(() => data.value?.data as DashboardWidgetTableData | null)
|
|
91
57
|
const columns = computed(() => widgetData.value?.columns ?? [])
|
|
92
58
|
const firstRow = computed(() => widgetData.value?.rows[0] ?? {})
|
|
93
|
-
const valueField = computed(() => gaugeConfig.value?.
|
|
94
|
-
const
|
|
95
|
-
const maxField = computed(() => gaugeConfig.value?.max_field || gaugeConfig.value?.maxField)
|
|
59
|
+
const valueField = computed(() => gaugeConfig.value?.value.field || columns.value[0])
|
|
60
|
+
const targetField = computed(() => gaugeConfig.value?.target?.field ?? gaugeConfig.value?.progress?.targetField)
|
|
96
61
|
const minValue = computed(() => {
|
|
97
|
-
|
|
98
|
-
return dynamicMin ?? parseOptionalNumber(gaugeConfig.value?.min) ?? 0
|
|
62
|
+
return 0
|
|
99
63
|
})
|
|
100
64
|
const maxValue = computed(() => {
|
|
101
|
-
const dynamicMax =
|
|
102
|
-
return dynamicMax ?? parseOptionalNumber(gaugeConfig.value?.
|
|
65
|
+
const dynamicMax = targetField.value ? parseOptionalNumber(firstRow.value[targetField.value]) : undefined
|
|
66
|
+
return dynamicMax ?? parseOptionalNumber(gaugeConfig.value?.target?.value ?? gaugeConfig.value?.progress?.targetValue) ?? 100
|
|
103
67
|
})
|
|
104
68
|
const value = computed(() => toFiniteNumber(firstRow.value[valueField.value]))
|
|
105
69
|
const fractionDigits = computed(() => Math.min([
|
|
@@ -179,7 +143,7 @@ const gaugeColor = computed(() => gaugeConfig.value?.color || CHART_COLORS[0])
|
|
|
179
143
|
</svg>
|
|
180
144
|
|
|
181
145
|
<div class="text-3xl font-bold text-lightNavbarText dark:text-darkNavbarText">
|
|
182
|
-
{{ formattedValue }}{{ gaugeConfig?.suffix ?? '' }}
|
|
146
|
+
{{ gaugeConfig?.value.prefix ?? '' }}{{ formattedValue }}{{ gaugeConfig?.value.suffix ?? '' }}
|
|
183
147
|
</div>
|
|
184
148
|
<div class="text-sm text-lightListTableText dark:text-darkListTableText">
|
|
185
149
|
{{ formattedMinValue }} - {{ formattedMaxValue }}
|
|
@@ -26,20 +26,16 @@ watch(
|
|
|
26
26
|
{ deep: true },
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
-
const kpiConfig = computed(() => props.widget.kpi_card
|
|
30
|
-
value_field?: string
|
|
31
|
-
label_field?: string
|
|
32
|
-
prefix?: string
|
|
33
|
-
suffix?: string
|
|
34
|
-
} | undefined)
|
|
29
|
+
const kpiConfig = computed(() => props.widget.target === 'kpi_card' ? props.widget.card : undefined)
|
|
35
30
|
const widgetData = computed(() => data.value?.data as DashboardWidgetTableData | null)
|
|
36
31
|
const columns = computed(() => widgetData.value?.columns ?? [])
|
|
37
32
|
const firstRow = computed(() => widgetData.value?.rows[0] ?? {})
|
|
38
|
-
const valueField = computed(() => kpiConfig.value?.
|
|
39
|
-
const labelField = computed(() => kpiConfig.value?.label_field)
|
|
33
|
+
const valueField = computed(() => kpiConfig.value?.value.field || columns.value[0])
|
|
40
34
|
const value = computed(() => toFiniteNumber(firstRow.value[valueField.value]))
|
|
41
|
-
const label = computed(() =>
|
|
42
|
-
|
|
35
|
+
const label = computed(() => kpiConfig.value?.subtitle?.field
|
|
36
|
+
? String(firstRow.value[kpiConfig.value.subtitle.field])
|
|
37
|
+
: kpiConfig.value?.subtitle?.text ?? kpiConfig.value?.title ?? props.widget.label)
|
|
38
|
+
const formattedValue = computed(() => `${kpiConfig.value?.value.prefix ?? ''}${formatChartValue(value.value)}${kpiConfig.value?.value.suffix ?? ''}`)
|
|
43
39
|
</script>
|
|
44
40
|
|
|
45
41
|
<template>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, watch } from 'vue'
|
|
3
3
|
import { useWidgetData } from '../../queries/useWidgetData.js'
|
|
4
|
+
import {
|
|
5
|
+
getFieldRefField,
|
|
6
|
+
} from '../../model/dashboard.types.js'
|
|
4
7
|
import type { DashboardWidgetConfig, DashboardWidgetData } from '../../model/dashboard.types.js'
|
|
5
8
|
import { formatChartLabel, formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
|
|
6
9
|
|
|
@@ -26,21 +29,17 @@ watch(
|
|
|
26
29
|
{ deep: true },
|
|
27
30
|
)
|
|
28
31
|
|
|
29
|
-
const pivotConfig = computed(() => props.widget.pivot_table
|
|
30
|
-
row_field?: string
|
|
31
|
-
column_field?: string
|
|
32
|
-
value_field?: string
|
|
33
|
-
aggregation?: 'count' | 'sum'
|
|
34
|
-
} | undefined)
|
|
32
|
+
const pivotConfig = computed(() => props.widget.target === 'pivot_table' ? props.widget.pivot : undefined)
|
|
35
33
|
const widgetData = computed(() => data.value?.data as DashboardWidgetData | null)
|
|
36
34
|
const rows = computed(() => widgetData.value?.rows ?? [])
|
|
37
35
|
const columns = computed(() => widgetData.value?.columns ?? [])
|
|
38
36
|
const isAggregateData = computed(() => widgetData.value?.kind === 'aggregate')
|
|
39
|
-
const shouldRenderAggregateMatrix = computed(() => isAggregateData.value && !pivotConfig.value?.
|
|
40
|
-
const rowField = computed(() => pivotConfig.value?.
|
|
41
|
-
const columnField = computed(() => pivotConfig.value?.
|
|
42
|
-
const
|
|
43
|
-
const
|
|
37
|
+
const shouldRenderAggregateMatrix = computed(() => isAggregateData.value && !pivotConfig.value?.columns?.length)
|
|
38
|
+
const rowField = computed(() => getFieldRefField(pivotConfig.value?.rows[0]) || columns.value[0])
|
|
39
|
+
const columnField = computed(() => getFieldRefField(pivotConfig.value?.columns?.[0]) || columns.value[1])
|
|
40
|
+
const valueConfig = computed(() => pivotConfig.value?.values[0])
|
|
41
|
+
const valueField = computed(() => valueConfig.value?.field || columns.value[2] || columns.value[1])
|
|
42
|
+
const aggregation = computed(() => valueConfig.value?.aggregation || (valueField.value ? 'sum' : 'count'))
|
|
44
43
|
const pivotColumnLabels = computed(() => {
|
|
45
44
|
if (shouldRenderAggregateMatrix.value) {
|
|
46
45
|
return columns.value.filter((column) => column !== rowField.value)
|
|
@@ -113,10 +113,11 @@
|
|
|
113
113
|
<script setup lang="ts">
|
|
114
114
|
import { computed, ref, watch } from 'vue'
|
|
115
115
|
import { useWidgetData } from '../../queries/useWidgetData.js'
|
|
116
|
-
import
|
|
116
|
+
import { getFieldRefField } from '../../model/dashboard.types.js'
|
|
117
|
+
import type { DashboardWidgetConfig, DashboardWidgetTableData, FieldRef } from '../../model/dashboard.types.js'
|
|
117
118
|
|
|
118
119
|
type TableWidgetConfig = {
|
|
119
|
-
columns?:
|
|
120
|
+
columns?: FieldRef[]
|
|
120
121
|
pagination?: boolean
|
|
121
122
|
pageSize?: number
|
|
122
123
|
}
|
|
@@ -132,7 +133,7 @@ const currentPage = ref(1)
|
|
|
132
133
|
const currentPageInput = ref(1)
|
|
133
134
|
const tableConfig = computed(() => props.widget.table as TableWidgetConfig | undefined)
|
|
134
135
|
const isPaginationEnabled = computed(() => tableConfig.value?.pagination !== false)
|
|
135
|
-
const pageSize = computed(() => tableConfig.value?.pageSize ??
|
|
136
|
+
const pageSize = computed(() => tableConfig.value?.pageSize ?? DEFAULT_PAGE_SIZE)
|
|
136
137
|
const dashboardSlugRef = computed(() => props.dashboardSlug)
|
|
137
138
|
const widgetIdRef = computed(() => props.widget.id)
|
|
138
139
|
const widgetDataRequest = computed(() => (
|
|
@@ -168,7 +169,11 @@ const tableData = computed(() => {
|
|
|
168
169
|
|
|
169
170
|
const columns = computed(() => {
|
|
170
171
|
const configuredColumns = tableConfig.value?.columns
|
|
171
|
-
|
|
172
|
+
if (configuredColumns) {
|
|
173
|
+
return configuredColumns.map((column) => getFieldRefField(column)).filter(Boolean) as string[]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return tableData.value?.columns ?? []
|
|
172
177
|
})
|
|
173
178
|
|
|
174
179
|
const pagination = computed(() => tableData.value?.pagination)
|
package/dist/endpoint/widgets.js
CHANGED
|
@@ -13,10 +13,30 @@ import { DashboardApiResponseSchema, DashboardWidgetDataResponseSchema, GroupIdR
|
|
|
13
13
|
import { StoredWidgetConfigSchema } from '../schema/widget.js';
|
|
14
14
|
function formatWidgetConfigValidationErrors(error) {
|
|
15
15
|
return error.issues.map((issue) => ({
|
|
16
|
-
field: issue.path.length ? issue.path.map(String).join('.') : 'config',
|
|
16
|
+
field: issue.path.length ? formatWidgetConfigFieldPath(issue.path.map(String).join('.')) : 'config',
|
|
17
17
|
message: issue.message,
|
|
18
18
|
}));
|
|
19
19
|
}
|
|
20
|
+
function formatWidgetConfigApiValidationErrors(errors) {
|
|
21
|
+
return errors.map((error) => (Object.assign(Object.assign({}, error), { field: formatWidgetConfigFieldPath(error.field) })));
|
|
22
|
+
}
|
|
23
|
+
function formatWidgetConfigFieldPath(field) {
|
|
24
|
+
const fieldAliases = new Map([
|
|
25
|
+
['minWidth', 'min_width'],
|
|
26
|
+
['maxWidth', 'max_width'],
|
|
27
|
+
['groupBy', 'group_by'],
|
|
28
|
+
['orderBy', 'order_by'],
|
|
29
|
+
['pageSize', 'page_size'],
|
|
30
|
+
['timeSeries', 'time_series'],
|
|
31
|
+
['valueField', 'value_field'],
|
|
32
|
+
['targetValue', 'target_value'],
|
|
33
|
+
['targetField', 'target_field'],
|
|
34
|
+
]);
|
|
35
|
+
return field
|
|
36
|
+
.split('.')
|
|
37
|
+
.map((segment) => { var _a; return (_a = fieldAliases.get(segment)) !== null && _a !== void 0 ? _a : segment; })
|
|
38
|
+
.join('.');
|
|
39
|
+
}
|
|
20
40
|
export function registerWidgetEndpoints(server, ctx) {
|
|
21
41
|
server.endpoint({
|
|
22
42
|
method: 'POST',
|
|
@@ -162,7 +182,7 @@ export function registerWidgetEndpoints(server, ctx) {
|
|
|
162
182
|
response.setStatus(422);
|
|
163
183
|
return {
|
|
164
184
|
error: 'Invalid widget config',
|
|
165
|
-
validationErrors: apiValidationErrors,
|
|
185
|
+
validationErrors: formatWidgetConfigApiValidationErrors(apiValidationErrors),
|
|
166
186
|
};
|
|
167
187
|
}
|
|
168
188
|
return ctx.persistDashboardConfig(dashboard, Object.assign(Object.assign({}, config), { widgets: config.widgets.map((item) => item.id === widgetId
|
|
@@ -172,7 +192,7 @@ export function registerWidgetEndpoints(server, ctx) {
|
|
|
172
192
|
server.endpoint({
|
|
173
193
|
method: 'POST',
|
|
174
194
|
path: '/dashboard/get_dashboard_widget_data',
|
|
175
|
-
description: 'Loads
|
|
195
|
+
description: 'Loads widget data for one dashboard widget by dashboard slug and widget id.',
|
|
176
196
|
request_schema: WidgetDataRequestSchema,
|
|
177
197
|
response_schema: DashboardWidgetDataResponseSchema,
|
|
178
198
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response }) {
|