@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.
Files changed (55) hide show
  1. package/README.md +43 -52
  2. package/custom/composables/useElementSize.ts +17 -2
  3. package/custom/model/dashboard.types.ts +385 -98
  4. package/custom/runtime/DashboardRuntime.vue +2 -1
  5. package/custom/runtime/WidgetRenderer.vue +2 -1
  6. package/custom/skills/adminforth-dashboard/SKILL.md +8 -4
  7. package/custom/widgets/chart/ChartWidget.vue +36 -35
  8. package/custom/widgets/chart/bar/BarChart.vue +20 -12
  9. package/custom/widgets/chart/chart.types.ts +42 -8
  10. package/custom/widgets/chart/chart.utils.ts +11 -0
  11. package/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  12. package/custom/widgets/chart/line/LineChart.vue +23 -15
  13. package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  14. package/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -43
  15. package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -10
  16. package/custom/widgets/pivot-table/PivotTableWidget.vue +10 -11
  17. package/custom/widgets/table/TableWidget.vue +9 -4
  18. package/dist/custom/composables/useElementSize.js +14 -2
  19. package/dist/custom/composables/useElementSize.ts +17 -2
  20. package/dist/custom/model/dashboard.types.d.ts +179 -38
  21. package/dist/custom/model/dashboard.types.js +108 -42
  22. package/dist/custom/model/dashboard.types.ts +385 -98
  23. package/dist/custom/queries/useDashboardConfig.d.ts +832 -68
  24. package/dist/custom/queries/useWidgetData.d.ts +828 -64
  25. package/dist/custom/runtime/DashboardRuntime.vue +2 -1
  26. package/dist/custom/runtime/WidgetRenderer.vue +2 -1
  27. package/dist/custom/skills/adminforth-dashboard/SKILL.md +8 -4
  28. package/dist/custom/widgets/chart/ChartWidget.vue +36 -35
  29. package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
  30. package/dist/custom/widgets/chart/chart.types.d.ts +14 -8
  31. package/dist/custom/widgets/chart/chart.types.js +23 -0
  32. package/dist/custom/widgets/chart/chart.types.ts +42 -8
  33. package/dist/custom/widgets/chart/chart.utils.d.ts +1 -0
  34. package/dist/custom/widgets/chart/chart.utils.js +7 -0
  35. package/dist/custom/widgets/chart/chart.utils.ts +11 -0
  36. package/dist/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  37. package/dist/custom/widgets/chart/line/LineChart.vue +23 -15
  38. package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  39. package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -43
  40. package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -10
  41. package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +10 -11
  42. package/dist/custom/widgets/table/TableWidget.vue +9 -4
  43. package/dist/endpoint/widgets.js +23 -3
  44. package/dist/schema/api.d.ts +2637 -933
  45. package/dist/schema/widget.d.ts +1562 -582
  46. package/dist/schema/widget.js +207 -127
  47. package/dist/services/widgetConfigValidator.js +16 -80
  48. package/dist/services/widgetDataService.d.ts +0 -9
  49. package/dist/services/widgetDataService.js +356 -97
  50. package/endpoint/dashboard.ts +1 -1
  51. package/endpoint/widgets.ts +29 -3
  52. package/package.json +1 -1
  53. package/schema/widget.ts +221 -121
  54. package/services/widgetConfigValidator.ts +29 -100
  55. 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(() => parseGaugeCardConfig(props.widget.gauge_card))
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?.value_field || gaugeConfig.value?.valueField || columns.value[0])
94
- const minField = computed(() => gaugeConfig.value?.min_field || gaugeConfig.value?.minField)
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
- const dynamicMin = minField.value ? parseOptionalNumber(firstRow.value[minField.value]) : undefined
98
- return dynamicMin ?? parseOptionalNumber(gaugeConfig.value?.min) ?? 0
62
+ return 0
99
63
  })
100
64
  const maxValue = computed(() => {
101
- const dynamicMax = maxField.value ? parseOptionalNumber(firstRow.value[maxField.value]) : undefined
102
- return dynamicMax ?? parseOptionalNumber(gaugeConfig.value?.max) ?? 100
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 as {
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?.value_field || columns.value[0])
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(() => labelField.value ? String(firstRow.value[labelField.value]) : props.widget.label)
42
- const formattedValue = computed(() => `${kpiConfig.value?.prefix ?? ''}${formatChartValue(value.value)}${kpiConfig.value?.suffix ?? ''}`)
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 as {
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?.column_field)
40
- const rowField = computed(() => pivotConfig.value?.row_field || (isAggregateData.value ? 'group' : columns.value[0]))
41
- const columnField = computed(() => pivotConfig.value?.column_field || columns.value[1])
42
- const valueField = computed(() => pivotConfig.value?.value_field || columns.value[2] || columns.value[1])
43
- const aggregation = computed(() => pivotConfig.value?.aggregation || (valueField.value ? 'sum' : 'count'))
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 type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
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?: string[]
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 ?? props.widget.query?.limit ?? DEFAULT_PAGE_SIZE)
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
- return configuredColumns ?? tableData.value?.columns ?? []
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)
@@ -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 query result data for one dashboard widget by dashboard slug and widget id.',
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 }) {