@adminforth/dashboard 1.2.0 → 1.4.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 -39
- package/custom/api/dashboardApi.ts +4 -0
- package/custom/composables/useElementSize.ts +17 -2
- package/custom/model/dashboard.types.ts +337 -236
- package/custom/skills/adminforth-dashboard/SKILL.md +113 -2
- package/custom/widgets/chart/ChartWidget.vue +38 -53
- package/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/custom/widgets/chart/chart.types.ts +17 -66
- 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 -12
- package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
- package/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
- package/custom/widgets/table/TableWidget.vue +8 -3
- package/dist/custom/api/dashboardApi.d.ts +1 -0
- package/dist/custom/api/dashboardApi.js +5 -0
- package/dist/custom/api/dashboardApi.ts +4 -0
- package/dist/custom/composables/useElementSize.js +14 -2
- package/dist/custom/composables/useElementSize.ts +17 -2
- package/dist/custom/model/dashboard.types.d.ts +181 -61
- package/dist/custom/model/dashboard.types.js +82 -93
- package/dist/custom/model/dashboard.types.ts +337 -236
- package/dist/custom/queries/useDashboardConfig.d.ts +852 -66
- package/dist/custom/queries/useWidgetData.d.ts +848 -62
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +113 -2
- package/dist/custom/widgets/chart/ChartWidget.vue +38 -53
- package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/dist/custom/widgets/chart/chart.types.d.ts +13 -22
- package/dist/custom/widgets/chart/chart.types.js +2 -25
- package/dist/custom/widgets/chart/chart.types.ts +17 -66
- 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 -12
- package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
- package/dist/custom/widgets/table/TableWidget.vue +8 -3
- package/dist/endpoint/dashboard.d.ts +7 -2
- package/dist/endpoint/dashboard.js +45 -1
- package/dist/endpoint/widgets.d.ts +2 -1
- package/dist/endpoint/widgets.js +6 -2
- package/dist/schema/api.d.ts +2773 -736
- package/dist/schema/api.js +5 -0
- package/dist/schema/widget.d.ts +1648 -476
- package/dist/schema/widget.js +208 -139
- package/dist/services/widgetConfigValidator.js +16 -40
- package/dist/services/widgetDataService.d.ts +2 -1
- package/dist/services/widgetDataService.js +389 -82
- package/endpoint/dashboard.ts +77 -4
- package/endpoint/widgets.ts +11 -4
- package/package.json +1 -1
- package/schema/api.ts +6 -0
- package/schema/widget.ts +225 -139
- package/services/widgetConfigValidator.ts +29 -53
- package/services/widgetDataService.ts +522 -100
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import { useElementSize } from '../../../composables/useElementSize.js'
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
CHART_COLORS,
|
|
6
|
+
formatChartAxisLabel,
|
|
7
|
+
formatChartLabel,
|
|
8
|
+
formatChartValue,
|
|
9
|
+
getChartYAxisWidth,
|
|
10
|
+
toFiniteNumber,
|
|
11
|
+
} from '../chart.utils.js'
|
|
6
12
|
|
|
7
13
|
const props = withDefaults(defineProps<{
|
|
8
14
|
rows: Record<string, unknown>[]
|
|
9
15
|
xField: string
|
|
10
|
-
|
|
16
|
+
yField: string
|
|
17
|
+
seriesField: string
|
|
11
18
|
colors?: string[]
|
|
12
19
|
height?: number
|
|
13
20
|
}>(), {
|
|
@@ -17,16 +24,11 @@ const props = withDefaults(defineProps<{
|
|
|
17
24
|
const { el: rootEl, width: rootWidth } = useElementSize<HTMLDivElement>()
|
|
18
25
|
const { el: svgEl, width: svgWidth, height: svgHeight } = useElementSize<HTMLDivElement>()
|
|
19
26
|
|
|
20
|
-
const padding = {
|
|
21
|
-
top: 24,
|
|
22
|
-
right: 6,
|
|
23
|
-
bottom: 34,
|
|
24
|
-
left: 38,
|
|
25
|
-
}
|
|
26
27
|
const barGap = 10
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const seriesNames = computed(() => Array.from(new Set(props.rows.map((row) => formatChartLabel(row[props.seriesField])))))
|
|
29
|
+
const normalizedSeries = computed(() => seriesNames.value.map((name, index) => ({
|
|
30
|
+
name,
|
|
31
|
+
color: props.colors?.[index] || CHART_COLORS[index % CHART_COLORS.length],
|
|
30
32
|
})))
|
|
31
33
|
const showLegend = computed(() => normalizedSeries.value.length > 0)
|
|
32
34
|
const isCompact = computed(() => rootWidth.value > 0 && rootWidth.value < 420)
|
|
@@ -38,19 +40,15 @@ const chartHeight = computed(() => {
|
|
|
38
40
|
|
|
39
41
|
return Math.max(props.height - (showLegend.value ? 28 : 0), 96)
|
|
40
42
|
})
|
|
41
|
-
const innerWidth = computed(() => Math.max(chartWidth.value - padding.left - padding.right, 1))
|
|
42
|
-
const innerHeight = computed(() => Math.max(chartHeight.value - padding.top - padding.bottom, 1))
|
|
43
43
|
const groupedRows = computed(() => {
|
|
44
44
|
const grouped = new Map<string, Record<string, unknown>>()
|
|
45
45
|
|
|
46
46
|
for (const row of props.rows) {
|
|
47
47
|
const label = formatChartLabel(row[props.xField])
|
|
48
48
|
const item = grouped.get(label) ?? { [props.xField]: label }
|
|
49
|
+
const seriesName = formatChartLabel(row[props.seriesField])
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
item[series.name] = toFiniteNumber(item[series.name])
|
|
52
|
-
+ getSeriesContribution(row[series.field], series.name)
|
|
53
|
-
}
|
|
51
|
+
item[seriesName] = toFiniteNumber(item[seriesName]) + toFiniteNumber(row[props.yField])
|
|
54
52
|
|
|
55
53
|
grouped.set(label, item)
|
|
56
54
|
}
|
|
@@ -65,15 +63,24 @@ const totalChartWidth = computed(() => {
|
|
|
65
63
|
const count = Math.max(groupedRows.value.length, 1)
|
|
66
64
|
return count * barWidth.value + (count - 1) * barGap
|
|
67
65
|
})
|
|
68
|
-
const chartStartX = computed(() => padding.left + Math.max((innerWidth.value - totalChartWidth.value) / 2, 0))
|
|
66
|
+
const chartStartX = computed(() => padding.value.left + Math.max((innerWidth.value - totalChartWidth.value) / 2, 0))
|
|
69
67
|
const totals = computed(() => groupedRows.value.map((row) => normalizedSeries.value.reduce(
|
|
70
68
|
(sum, series) => sum + toFiniteNumber(row[series.name]),
|
|
71
69
|
0,
|
|
72
70
|
)))
|
|
73
71
|
const maxTotal = computed(() => Math.max(...totals.value, 1))
|
|
72
|
+
const yTickValues = computed(() => [maxTotal.value, maxTotal.value * 0.5, 0])
|
|
73
|
+
const padding = computed(() => ({
|
|
74
|
+
top: 24,
|
|
75
|
+
right: 6,
|
|
76
|
+
bottom: 34,
|
|
77
|
+
left: getChartYAxisWidth(yTickValues.value, chartWidth.value),
|
|
78
|
+
}))
|
|
79
|
+
const innerWidth = computed(() => Math.max(chartWidth.value - padding.value.left - padding.value.right, 1))
|
|
80
|
+
const innerHeight = computed(() => Math.max(chartHeight.value - padding.value.top - padding.value.bottom, 1))
|
|
74
81
|
|
|
75
82
|
const bars = computed(() => groupedRows.value.map((row, rowIndex) => {
|
|
76
|
-
let y = padding.top + innerHeight.value
|
|
83
|
+
let y = padding.value.top + innerHeight.value
|
|
77
84
|
|
|
78
85
|
return {
|
|
79
86
|
label: String(row[props.xField]),
|
|
@@ -118,31 +125,9 @@ const visibleLabelIndexes = computed(() => {
|
|
|
118
125
|
|
|
119
126
|
const yTicks = computed(() => [0, 0.5, 1].map((ratio) => ({
|
|
120
127
|
value: maxTotal.value * (1 - ratio),
|
|
121
|
-
y: padding.top + innerHeight.value * ratio,
|
|
128
|
+
y: padding.value.top + innerHeight.value * ratio,
|
|
122
129
|
})))
|
|
123
130
|
|
|
124
|
-
function getSeriesContribution(value: unknown, seriesName: string) {
|
|
125
|
-
if (typeof value === 'boolean') {
|
|
126
|
-
return value === getBooleanSeriesValue(seriesName) ? 1 : 0
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (typeof value === 'string' && ['true', 'false'].includes(value.toLowerCase())) {
|
|
130
|
-
return (value.toLowerCase() === 'true') === getBooleanSeriesValue(seriesName) ? 1 : 0
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return toFiniteNumber(value)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function getBooleanSeriesValue(seriesName: string) {
|
|
137
|
-
const normalizedName = seriesName.toLowerCase()
|
|
138
|
-
|
|
139
|
-
if (normalizedName.includes('unlisted') || normalizedName.includes('false')) {
|
|
140
|
-
return false
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return true
|
|
144
|
-
}
|
|
145
|
-
|
|
146
131
|
function getBarTooltip(bar: { label: string, total: number, segments: Array<{ name: string, value: number }> }) {
|
|
147
132
|
const percentFormatter = new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 })
|
|
148
133
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, watch } from 'vue'
|
|
3
3
|
import { useWidgetData } from '../../queries/useWidgetData.js'
|
|
4
|
-
import {
|
|
5
|
-
normalizeGaugeCardWidgetConfig,
|
|
6
|
-
} from '../../model/dashboard.types.js'
|
|
7
4
|
import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
|
|
8
5
|
import { CHART_COLORS, formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
|
|
9
6
|
|
|
@@ -55,20 +52,18 @@ watch(
|
|
|
55
52
|
{ deep: true },
|
|
56
53
|
)
|
|
57
54
|
|
|
58
|
-
const gaugeConfig = computed(() =>
|
|
55
|
+
const gaugeConfig = computed(() => props.widget.target === 'gauge_card' ? props.widget.card : undefined)
|
|
59
56
|
const widgetData = computed(() => data.value?.data as DashboardWidgetTableData | null)
|
|
60
57
|
const columns = computed(() => widgetData.value?.columns ?? [])
|
|
61
58
|
const firstRow = computed(() => widgetData.value?.rows[0] ?? {})
|
|
62
|
-
const valueField = computed(() => gaugeConfig.value?.
|
|
63
|
-
const
|
|
64
|
-
const maxField = computed(() => 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)
|
|
65
61
|
const minValue = computed(() => {
|
|
66
|
-
|
|
67
|
-
return dynamicMin ?? parseOptionalNumber(gaugeConfig.value?.min) ?? 0
|
|
62
|
+
return 0
|
|
68
63
|
})
|
|
69
64
|
const maxValue = computed(() => {
|
|
70
|
-
const dynamicMax =
|
|
71
|
-
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
|
|
72
67
|
})
|
|
73
68
|
const value = computed(() => toFiniteNumber(firstRow.value[valueField.value]))
|
|
74
69
|
const fractionDigits = computed(() => Math.min([
|
|
@@ -148,7 +143,7 @@ const gaugeColor = computed(() => gaugeConfig.value?.color || CHART_COLORS[0])
|
|
|
148
143
|
</svg>
|
|
149
144
|
|
|
150
145
|
<div class="text-3xl font-bold text-lightNavbarText dark:text-darkNavbarText">
|
|
151
|
-
{{ formattedValue }}{{ gaugeConfig?.suffix ?? '' }}
|
|
146
|
+
{{ gaugeConfig?.value.prefix ?? '' }}{{ formattedValue }}{{ gaugeConfig?.value.suffix ?? '' }}
|
|
152
147
|
</div>
|
|
153
148
|
<div class="text-sm text-lightListTableText dark:text-darkListTableText">
|
|
154
149
|
{{ formattedMinValue }} - {{ formattedMaxValue }}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, watch } from 'vue'
|
|
3
3
|
import { useWidgetData } from '../../queries/useWidgetData.js'
|
|
4
|
-
import {
|
|
5
|
-
normalizeKpiCardWidgetConfig,
|
|
6
|
-
} from '../../model/dashboard.types.js'
|
|
7
4
|
import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
|
|
8
5
|
import { formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
|
|
9
6
|
|
|
@@ -29,15 +26,16 @@ watch(
|
|
|
29
26
|
{ deep: true },
|
|
30
27
|
)
|
|
31
28
|
|
|
32
|
-
const kpiConfig = computed(() =>
|
|
29
|
+
const kpiConfig = computed(() => props.widget.target === 'kpi_card' ? props.widget.card : undefined)
|
|
33
30
|
const widgetData = computed(() => data.value?.data as DashboardWidgetTableData | null)
|
|
34
31
|
const columns = computed(() => widgetData.value?.columns ?? [])
|
|
35
32
|
const firstRow = computed(() => widgetData.value?.rows[0] ?? {})
|
|
36
|
-
const valueField = computed(() => kpiConfig.value?.
|
|
37
|
-
const labelField = computed(() => kpiConfig.value?.labelField)
|
|
33
|
+
const valueField = computed(() => kpiConfig.value?.value.field || columns.value[0])
|
|
38
34
|
const value = computed(() => toFiniteNumber(firstRow.value[valueField.value]))
|
|
39
|
-
const label = computed(() =>
|
|
40
|
-
|
|
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 ?? ''}`)
|
|
41
39
|
</script>
|
|
42
40
|
|
|
43
41
|
<template>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed, watch } from 'vue'
|
|
3
3
|
import { useWidgetData } from '../../queries/useWidgetData.js'
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
getFieldRefField,
|
|
6
6
|
} from '../../model/dashboard.types.js'
|
|
7
7
|
import type { DashboardWidgetConfig, DashboardWidgetData } from '../../model/dashboard.types.js'
|
|
8
8
|
import { formatChartLabel, formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
|
|
@@ -29,16 +29,17 @@ watch(
|
|
|
29
29
|
{ deep: true },
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
const pivotConfig = computed(() =>
|
|
32
|
+
const pivotConfig = computed(() => props.widget.target === 'pivot_table' ? props.widget.pivot : undefined)
|
|
33
33
|
const widgetData = computed(() => data.value?.data as DashboardWidgetData | null)
|
|
34
34
|
const rows = computed(() => widgetData.value?.rows ?? [])
|
|
35
35
|
const columns = computed(() => widgetData.value?.columns ?? [])
|
|
36
36
|
const isAggregateData = computed(() => widgetData.value?.kind === 'aggregate')
|
|
37
|
-
const shouldRenderAggregateMatrix = computed(() => isAggregateData.value && !pivotConfig.value?.
|
|
38
|
-
const rowField = computed(() => pivotConfig.value?.
|
|
39
|
-
const columnField = computed(() => pivotConfig.value?.
|
|
40
|
-
const
|
|
41
|
-
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'))
|
|
42
43
|
const pivotColumnLabels = computed(() => {
|
|
43
44
|
if (shouldRenderAggregateMatrix.value) {
|
|
44
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
|
}
|
|
@@ -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)
|
|
@@ -26,6 +26,7 @@ export declare class DashboardApiError extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
export declare const dashboardApi: {
|
|
28
28
|
getDashboardConfig(slug: string): Promise<DashboardResponse>;
|
|
29
|
+
setDashboardConfig(slug: string, config: DashboardConfig): Promise<DashboardResponse>;
|
|
29
30
|
addDashboardGroup(slug: string): Promise<DashboardResponse>;
|
|
30
31
|
moveDashboardGroup(slug: string, groupId: string, direction: DashboardGroupMoveDirection): Promise<DashboardResponse>;
|
|
31
32
|
removeDashboardGroup(slug: string, groupId: string): Promise<DashboardResponse>;
|
|
@@ -103,6 +103,11 @@ exports.dashboardApi = {
|
|
|
103
103
|
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug });
|
|
104
104
|
});
|
|
105
105
|
},
|
|
106
|
+
setDashboardConfig(slug, config) {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config });
|
|
109
|
+
});
|
|
110
|
+
},
|
|
106
111
|
addDashboardGroup(slug) {
|
|
107
112
|
return __awaiter(this, void 0, void 0, function* () {
|
|
108
113
|
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug });
|
|
@@ -143,6 +143,10 @@ export const dashboardApi = {
|
|
|
143
143
|
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
|
|
144
144
|
},
|
|
145
145
|
|
|
146
|
+
async setDashboardConfig(slug: string, config: DashboardConfig): Promise<DashboardResponse> {
|
|
147
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
|
|
148
|
+
},
|
|
149
|
+
|
|
146
150
|
async addDashboardGroup(slug: string): Promise<DashboardResponse> {
|
|
147
151
|
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
|
|
148
152
|
},
|
|
@@ -7,19 +7,31 @@ function useElementSize() {
|
|
|
7
7
|
const width = (0, vue_1.ref)(0);
|
|
8
8
|
const height = (0, vue_1.ref)(0);
|
|
9
9
|
let observer;
|
|
10
|
+
let frameId;
|
|
10
11
|
(0, vue_1.onMounted)(() => {
|
|
11
12
|
observer = new ResizeObserver(([entry]) => {
|
|
12
13
|
if (!entry) {
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const nextWidth = Math.floor(entry.contentRect.width);
|
|
17
|
+
const nextHeight = Math.floor(entry.contentRect.height);
|
|
18
|
+
if (frameId !== undefined) {
|
|
19
|
+
cancelAnimationFrame(frameId);
|
|
20
|
+
}
|
|
21
|
+
frameId = requestAnimationFrame(() => {
|
|
22
|
+
frameId = undefined;
|
|
23
|
+
width.value = nextWidth;
|
|
24
|
+
height.value = nextHeight;
|
|
25
|
+
});
|
|
17
26
|
});
|
|
18
27
|
if (el.value) {
|
|
19
28
|
observer.observe(el.value);
|
|
20
29
|
}
|
|
21
30
|
});
|
|
22
31
|
(0, vue_1.onBeforeUnmount)(() => {
|
|
32
|
+
if (frameId !== undefined) {
|
|
33
|
+
cancelAnimationFrame(frameId);
|
|
34
|
+
}
|
|
23
35
|
observer === null || observer === void 0 ? void 0 : observer.disconnect();
|
|
24
36
|
});
|
|
25
37
|
return {
|
|
@@ -13,6 +13,7 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
|
|
|
13
13
|
const height = ref(0)
|
|
14
14
|
|
|
15
15
|
let observer: ResizeObserver | undefined
|
|
16
|
+
let frameId: number | undefined
|
|
16
17
|
|
|
17
18
|
onMounted(() => {
|
|
18
19
|
observer = new ResizeObserver(([entry]) => {
|
|
@@ -20,8 +21,18 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
|
|
|
20
21
|
return
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const nextWidth = Math.floor(entry.contentRect.width)
|
|
25
|
+
const nextHeight = Math.floor(entry.contentRect.height)
|
|
26
|
+
|
|
27
|
+
if (frameId !== undefined) {
|
|
28
|
+
cancelAnimationFrame(frameId)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
frameId = requestAnimationFrame(() => {
|
|
32
|
+
frameId = undefined
|
|
33
|
+
width.value = nextWidth
|
|
34
|
+
height.value = nextHeight
|
|
35
|
+
})
|
|
25
36
|
})
|
|
26
37
|
|
|
27
38
|
if (el.value) {
|
|
@@ -30,6 +41,10 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
|
|
|
30
41
|
})
|
|
31
42
|
|
|
32
43
|
onBeforeUnmount(() => {
|
|
44
|
+
if (frameId !== undefined) {
|
|
45
|
+
cancelAnimationFrame(frameId)
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
observer?.disconnect()
|
|
34
49
|
})
|
|
35
50
|
|
|
@@ -1,38 +1,10 @@
|
|
|
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;
|
|
31
2
|
export type DashboardConfig = {
|
|
32
3
|
version: number;
|
|
33
4
|
groups: DashboardGroupConfig[];
|
|
34
5
|
widgets: DashboardWidgetConfig[];
|
|
35
6
|
};
|
|
7
|
+
export type DashboardVariables = Record<string, unknown>;
|
|
36
8
|
export type DashboardGroupConfig = {
|
|
37
9
|
id: string;
|
|
38
10
|
label: string;
|
|
@@ -42,6 +14,9 @@ export type DashboardGroupMoveDirection = 'up' | 'down';
|
|
|
42
14
|
export type DashboardWidgetMoveDirection = 'up' | 'down';
|
|
43
15
|
export type DashboardWidgetTarget = 'empty' | 'table' | 'chart' | 'kpi_card' | 'pivot_table' | 'gauge_card';
|
|
44
16
|
export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full';
|
|
17
|
+
export type QueryAggregateOperation = 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median';
|
|
18
|
+
export type TimeGrain = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
19
|
+
export type ValueFormat = 'number' | 'compact_number' | 'currency' | 'percent' | 'percent_delta' | 'number_delta' | 'currency_delta';
|
|
45
20
|
export type WidgetLayout = {
|
|
46
21
|
size?: DashboardWidgetSize;
|
|
47
22
|
width?: number;
|
|
@@ -49,24 +24,185 @@ export type WidgetLayout = {
|
|
|
49
24
|
maxWidth?: number | null;
|
|
50
25
|
height?: number;
|
|
51
26
|
};
|
|
52
|
-
export type
|
|
27
|
+
export type WidgetBaseConfig = {
|
|
53
28
|
id: string;
|
|
54
29
|
group_id: string;
|
|
55
30
|
label?: string;
|
|
31
|
+
variables?: DashboardVariables;
|
|
56
32
|
size?: DashboardWidgetSize;
|
|
57
33
|
width?: number;
|
|
58
34
|
height?: number;
|
|
59
35
|
minWidth?: number;
|
|
60
36
|
maxWidth?: number | null;
|
|
61
37
|
order: number;
|
|
62
|
-
target: DashboardWidgetTarget;
|
|
63
|
-
dataSource?: WidgetDataSource;
|
|
64
|
-
chart?: ChartWidgetConfig;
|
|
65
|
-
table?: unknown;
|
|
66
|
-
kpi_card?: unknown;
|
|
67
|
-
pivot_table?: unknown;
|
|
68
|
-
gauge_card?: unknown;
|
|
69
38
|
};
|
|
39
|
+
export type FilterExpression = {
|
|
40
|
+
and: FilterExpression[];
|
|
41
|
+
} | {
|
|
42
|
+
or: FilterExpression[];
|
|
43
|
+
} | Array<FilterExpression> | {
|
|
44
|
+
field: string;
|
|
45
|
+
eq?: unknown;
|
|
46
|
+
neq?: unknown;
|
|
47
|
+
gt?: unknown;
|
|
48
|
+
gte?: unknown;
|
|
49
|
+
lt?: unknown;
|
|
50
|
+
lte?: unknown;
|
|
51
|
+
in?: unknown[];
|
|
52
|
+
not_in?: unknown[];
|
|
53
|
+
like?: unknown;
|
|
54
|
+
ilike?: unknown;
|
|
55
|
+
};
|
|
56
|
+
export type QueryFieldSelectItem = {
|
|
57
|
+
field: string;
|
|
58
|
+
as?: string;
|
|
59
|
+
grain?: TimeGrain;
|
|
60
|
+
};
|
|
61
|
+
export type QueryAggregateSelectItem = {
|
|
62
|
+
agg: QueryAggregateOperation;
|
|
63
|
+
field?: string;
|
|
64
|
+
as: string;
|
|
65
|
+
filters?: FilterExpression;
|
|
66
|
+
};
|
|
67
|
+
export type QueryCalcSelectItem = {
|
|
68
|
+
calc: string;
|
|
69
|
+
as: string;
|
|
70
|
+
};
|
|
71
|
+
export type QuerySelectItem = QueryFieldSelectItem | QueryAggregateSelectItem | QueryCalcSelectItem;
|
|
72
|
+
export type QueryGroupByItem = string | {
|
|
73
|
+
field: string;
|
|
74
|
+
as?: string;
|
|
75
|
+
grain?: TimeGrain;
|
|
76
|
+
timezone?: string;
|
|
77
|
+
};
|
|
78
|
+
export type QueryOrderByItem = {
|
|
79
|
+
field: string;
|
|
80
|
+
direction?: 'asc' | 'desc';
|
|
81
|
+
};
|
|
82
|
+
export type QueryConfig = {
|
|
83
|
+
resource: string;
|
|
84
|
+
select?: QuerySelectItem[];
|
|
85
|
+
filters?: FilterExpression;
|
|
86
|
+
groupBy?: QueryGroupByItem[];
|
|
87
|
+
orderBy?: QueryOrderByItem[];
|
|
88
|
+
limit?: number;
|
|
89
|
+
offset?: number;
|
|
90
|
+
timeSeries?: {
|
|
91
|
+
field: string;
|
|
92
|
+
grain: TimeGrain;
|
|
93
|
+
timezone?: string;
|
|
94
|
+
};
|
|
95
|
+
period?: {
|
|
96
|
+
field: string;
|
|
97
|
+
gte?: unknown;
|
|
98
|
+
lt?: unknown;
|
|
99
|
+
};
|
|
100
|
+
bucket?: {
|
|
101
|
+
field: string;
|
|
102
|
+
buckets: Array<{
|
|
103
|
+
label: string;
|
|
104
|
+
min?: number;
|
|
105
|
+
max?: number;
|
|
106
|
+
}>;
|
|
107
|
+
};
|
|
108
|
+
calcs?: QueryCalcSelectItem[];
|
|
109
|
+
formatting?: Record<string, unknown>;
|
|
110
|
+
};
|
|
111
|
+
export type FunnelQueryConfig = {
|
|
112
|
+
steps: FunnelQueryStep[];
|
|
113
|
+
calcs?: QueryCalcSelectItem[];
|
|
114
|
+
};
|
|
115
|
+
export type FunnelQueryStep = {
|
|
116
|
+
name: string;
|
|
117
|
+
resource: string;
|
|
118
|
+
metric: QueryAggregateSelectItem;
|
|
119
|
+
filters?: FilterExpression;
|
|
120
|
+
};
|
|
121
|
+
export type FieldRef = string | {
|
|
122
|
+
field: string;
|
|
123
|
+
label?: string;
|
|
124
|
+
format?: ValueFormat;
|
|
125
|
+
};
|
|
126
|
+
export type TableViewConfig = {
|
|
127
|
+
columns?: FieldRef[];
|
|
128
|
+
pagination?: boolean;
|
|
129
|
+
pageSize?: number;
|
|
130
|
+
};
|
|
131
|
+
export type KpiCardViewConfig = {
|
|
132
|
+
title?: string;
|
|
133
|
+
value: {
|
|
134
|
+
field: string;
|
|
135
|
+
format?: ValueFormat;
|
|
136
|
+
prefix?: string;
|
|
137
|
+
suffix?: string;
|
|
138
|
+
};
|
|
139
|
+
subtitle?: {
|
|
140
|
+
text?: string;
|
|
141
|
+
field?: string;
|
|
142
|
+
};
|
|
143
|
+
comparison?: unknown;
|
|
144
|
+
sparkline?: unknown;
|
|
145
|
+
};
|
|
146
|
+
export type GaugeCardViewConfig = {
|
|
147
|
+
title?: string;
|
|
148
|
+
value: {
|
|
149
|
+
field: string;
|
|
150
|
+
format?: ValueFormat;
|
|
151
|
+
prefix?: string;
|
|
152
|
+
suffix?: string;
|
|
153
|
+
};
|
|
154
|
+
target?: {
|
|
155
|
+
value?: number;
|
|
156
|
+
field?: string;
|
|
157
|
+
label?: string;
|
|
158
|
+
};
|
|
159
|
+
progress?: {
|
|
160
|
+
valueField: string;
|
|
161
|
+
targetValue?: number;
|
|
162
|
+
targetField?: string;
|
|
163
|
+
format?: ValueFormat;
|
|
164
|
+
};
|
|
165
|
+
color?: string;
|
|
166
|
+
};
|
|
167
|
+
export type PivotTableViewConfig = {
|
|
168
|
+
rows: FieldRef[];
|
|
169
|
+
columns?: FieldRef[];
|
|
170
|
+
values: Array<{
|
|
171
|
+
field: string;
|
|
172
|
+
label?: string;
|
|
173
|
+
format?: ValueFormat;
|
|
174
|
+
aggregation?: 'sum' | 'count' | 'avg' | 'min' | 'max';
|
|
175
|
+
}>;
|
|
176
|
+
};
|
|
177
|
+
export type EmptyWidgetConfig = WidgetBaseConfig & {
|
|
178
|
+
target: 'empty';
|
|
179
|
+
};
|
|
180
|
+
export type TableWidgetConfig = WidgetBaseConfig & {
|
|
181
|
+
target: 'table';
|
|
182
|
+
table?: TableViewConfig;
|
|
183
|
+
query: QueryConfig;
|
|
184
|
+
};
|
|
185
|
+
export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
|
|
186
|
+
target: 'chart';
|
|
187
|
+
chart: ChartWidgetConfig;
|
|
188
|
+
query: QueryConfig | FunnelQueryConfig;
|
|
189
|
+
};
|
|
190
|
+
export type KpiCardWidgetConfig = WidgetBaseConfig & {
|
|
191
|
+
target: 'kpi_card';
|
|
192
|
+
card: KpiCardViewConfig;
|
|
193
|
+
query: QueryConfig;
|
|
194
|
+
};
|
|
195
|
+
export type GaugeCardWidgetConfig = WidgetBaseConfig & {
|
|
196
|
+
target: 'gauge_card';
|
|
197
|
+
card: GaugeCardViewConfig;
|
|
198
|
+
query: QueryConfig;
|
|
199
|
+
};
|
|
200
|
+
export type PivotTableWidgetConfig = WidgetBaseConfig & {
|
|
201
|
+
target: 'pivot_table';
|
|
202
|
+
pivot: PivotTableViewConfig;
|
|
203
|
+
query: QueryConfig;
|
|
204
|
+
};
|
|
205
|
+
export type DashboardWidgetConfig = EmptyWidgetConfig | TableWidgetConfig | ChartDashboardWidgetConfig | KpiCardWidgetConfig | GaugeCardWidgetConfig | PivotTableWidgetConfig;
|
|
70
206
|
export type DashboardWidgetTableData = {
|
|
71
207
|
kind?: 'table';
|
|
72
208
|
columns: string[];
|
|
@@ -83,32 +219,16 @@ export type DashboardWidgetAggregateData = {
|
|
|
83
219
|
columns: string[];
|
|
84
220
|
rows: Record<string, unknown>[];
|
|
85
221
|
values?: Record<string, unknown>;
|
|
222
|
+
pagination?: {
|
|
223
|
+
page: number;
|
|
224
|
+
pageSize: number;
|
|
225
|
+
total: number;
|
|
226
|
+
totalPages: number;
|
|
227
|
+
};
|
|
86
228
|
};
|
|
87
229
|
export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData;
|
|
88
|
-
export type NormalizedKpiCardWidgetConfig = {
|
|
89
|
-
valueField?: string;
|
|
90
|
-
labelField?: string;
|
|
91
|
-
prefix?: string;
|
|
92
|
-
suffix?: string;
|
|
93
|
-
};
|
|
94
|
-
export type NormalizedGaugeCardWidgetConfig = {
|
|
95
|
-
valueField?: string;
|
|
96
|
-
min?: number | string;
|
|
97
|
-
max?: number | string;
|
|
98
|
-
minField?: string;
|
|
99
|
-
maxField?: string;
|
|
100
|
-
suffix?: string;
|
|
101
|
-
color?: string;
|
|
102
|
-
};
|
|
103
|
-
export type NormalizedPivotTableWidgetConfig = {
|
|
104
|
-
rowField?: string;
|
|
105
|
-
columnField?: string;
|
|
106
|
-
valueField?: string;
|
|
107
|
-
aggregation?: 'count' | 'sum';
|
|
108
|
-
};
|
|
109
230
|
export declare function normalizeDashboardConfig(config: unknown): DashboardConfig;
|
|
110
231
|
export declare function normalizeDashboardWidgetConfig(config: unknown): unknown;
|
|
111
232
|
export declare function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig): Record<string, unknown>;
|
|
112
|
-
export declare function
|
|
113
|
-
export declare function
|
|
114
|
-
export declare function normalizePivotTableWidgetConfig(value: unknown): NormalizedPivotTableWidgetConfig | undefined;
|
|
233
|
+
export declare function getFieldRefField(value: FieldRef | undefined): string | undefined;
|
|
234
|
+
export declare function getFieldRefLabel(value: FieldRef | undefined): string | undefined;
|