@adminforth/dashboard 1.0.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/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/README.md +59 -0
- package/custom/api/dashboardApi.ts +213 -0
- package/custom/composables/useElementSize.ts +41 -0
- package/custom/model/dashboard.types.ts +73 -0
- package/custom/package.json +9 -0
- package/custom/pnpm-lock.yaml +24 -0
- package/custom/queries/useDashboardConfig.ts +51 -0
- package/custom/queries/useWidgetData.ts +51 -0
- package/custom/runtime/DashboardGroup.vue +185 -0
- package/custom/runtime/DashboardPage.vue +122 -0
- package/custom/runtime/DashboardRuntime.vue +435 -0
- package/custom/runtime/WidgetRenderer.vue +60 -0
- package/custom/runtime/WidgetShell.vue +152 -0
- package/custom/skills/adminforth-dashboard/SKILL.md +125 -0
- package/custom/widgets/chart/ChartWidget.vue +188 -0
- package/custom/widgets/chart/bar/BarChart.vue +167 -0
- package/custom/widgets/chart/chart.types.ts +34 -0
- package/custom/widgets/chart/chart.utils.ts +54 -0
- package/custom/widgets/chart/funnel/FunnelChart.vue +197 -0
- package/custom/widgets/chart/histogram/HistogramChart.vue +21 -0
- package/custom/widgets/chart/line/LineChart.vue +175 -0
- package/custom/widgets/chart/pie/PieChart.vue +161 -0
- package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +256 -0
- package/custom/widgets/gauge-card/GaugeCardWidget.vue +107 -0
- package/custom/widgets/kpi-card/KpiCardWidget.vue +73 -0
- package/custom/widgets/pivot-table/PivotTableWidget.vue +122 -0
- package/custom/widgets/registry.ts +51 -0
- package/custom/widgets/table/TableWidget.vue +110 -0
- package/dist/custom/api/dashboardApi.d.ts +32 -0
- package/dist/custom/api/dashboardApi.js +179 -0
- package/dist/custom/api/dashboardApi.ts +213 -0
- package/dist/custom/composables/useElementSize.d.ts +8 -0
- package/dist/custom/composables/useElementSize.js +30 -0
- package/dist/custom/composables/useElementSize.ts +41 -0
- package/dist/custom/model/dashboard.types.d.ts +45 -0
- package/dist/custom/model/dashboard.types.js +14 -0
- package/dist/custom/model/dashboard.types.ts +73 -0
- package/dist/custom/package.json +9 -0
- package/dist/custom/pnpm-lock.yaml +24 -0
- package/dist/custom/queries/useDashboardConfig.d.ts +112 -0
- package/dist/custom/queries/useDashboardConfig.js +57 -0
- package/dist/custom/queries/useDashboardConfig.ts +51 -0
- package/dist/custom/queries/useWidgetData.d.ts +90 -0
- package/dist/custom/queries/useWidgetData.js +57 -0
- package/dist/custom/queries/useWidgetData.ts +51 -0
- package/dist/custom/runtime/DashboardGroup.vue +185 -0
- package/dist/custom/runtime/DashboardPage.vue +122 -0
- package/dist/custom/runtime/DashboardRuntime.vue +435 -0
- package/dist/custom/runtime/WidgetRenderer.vue +60 -0
- package/dist/custom/runtime/WidgetShell.vue +152 -0
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +125 -0
- package/dist/custom/widgets/chart/ChartWidget.vue +188 -0
- package/dist/custom/widgets/chart/bar/BarChart.vue +167 -0
- package/dist/custom/widgets/chart/chart.types.d.ts +25 -0
- package/dist/custom/widgets/chart/chart.types.js +2 -0
- package/dist/custom/widgets/chart/chart.types.ts +34 -0
- package/dist/custom/widgets/chart/chart.utils.d.ts +5 -0
- package/dist/custom/widgets/chart/chart.utils.js +52 -0
- package/dist/custom/widgets/chart/chart.utils.ts +54 -0
- package/dist/custom/widgets/chart/funnel/FunnelChart.vue +197 -0
- package/dist/custom/widgets/chart/histogram/HistogramChart.vue +21 -0
- package/dist/custom/widgets/chart/line/LineChart.vue +175 -0
- package/dist/custom/widgets/chart/pie/PieChart.vue +161 -0
- package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +256 -0
- package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +107 -0
- package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +73 -0
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +122 -0
- package/dist/custom/widgets/registry.d.ts +11 -0
- package/dist/custom/widgets/registry.js +47 -0
- package/dist/custom/widgets/registry.ts +51 -0
- package/dist/custom/widgets/table/TableWidget.vue +110 -0
- package/dist/endpoint/dashboard.d.ts +7 -0
- package/dist/endpoint/dashboard.js +29 -0
- package/dist/endpoint/groups.d.ts +30 -0
- package/dist/endpoint/groups.js +131 -0
- package/dist/endpoint/widgets.d.ts +15 -0
- package/dist/endpoint/widgets.js +182 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +124 -0
- package/dist/schema/api.d.ts +1205 -0
- package/dist/schema/api.js +84 -0
- package/dist/schema/widget.d.ts +514 -0
- package/dist/schema/widget.js +133 -0
- package/dist/services/dashboardConfigService.d.ts +35 -0
- package/dist/services/dashboardConfigService.js +79 -0
- package/dist/services/widgetConfigValidator.d.ts +8 -0
- package/dist/services/widgetConfigValidator.js +65 -0
- package/dist/services/widgetDataService.d.ts +20 -0
- package/dist/services/widgetDataService.js +32 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +1 -0
- package/endpoint/dashboard.ts +32 -0
- package/endpoint/groups.ts +213 -0
- package/endpoint/widgets.ts +255 -0
- package/index.ts +141 -0
- package/package.json +64 -0
- package/schema/api.ts +99 -0
- package/schema/widget.ts +159 -0
- package/services/dashboardConfigService.ts +136 -0
- package/services/widgetConfigValidator.ts +93 -0
- package/services/widgetDataService.ts +57 -0
- package/shims-vue.d.ts +5 -0
- package/tsconfig.json +18 -0
- package/types.ts +8 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex h-full min-h-0 flex-col overflow-hidden rounded-lg border border-lightListBorder bg-lightTableBackground dark:border-darkListBorder dark:bg-darkTableBackground">
|
|
3
|
+
<div
|
|
4
|
+
v-if="isLoading"
|
|
5
|
+
class="p-4 text-sm text-lightListTableText dark:text-darkListTableText"
|
|
6
|
+
>
|
|
7
|
+
Loading...
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div
|
|
11
|
+
v-else-if="error"
|
|
12
|
+
class="p-4 text-sm text-lightInputErrorColor"
|
|
13
|
+
>
|
|
14
|
+
Failed to load table data
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div
|
|
18
|
+
v-else-if="!tableData?.rows.length"
|
|
19
|
+
class="p-4 text-sm text-lightListTableText dark:text-darkListTableText"
|
|
20
|
+
>
|
|
21
|
+
No data available
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div
|
|
25
|
+
v-else
|
|
26
|
+
class="min-h-0 flex-1 overflow-auto"
|
|
27
|
+
>
|
|
28
|
+
<table class="min-w-max w-full border-collapse text-left text-sm">
|
|
29
|
+
<thead class="bg-lightTableHeadingBackground text-xs uppercase text-lightTableHeadingText dark:bg-darkTableHeadingBackground dark:text-darkTableHeadingText">
|
|
30
|
+
<tr>
|
|
31
|
+
<th
|
|
32
|
+
v-for="column in columns"
|
|
33
|
+
:key="column"
|
|
34
|
+
class="px-3 py-2 font-semibold"
|
|
35
|
+
>
|
|
36
|
+
{{ column }}
|
|
37
|
+
</th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
|
|
41
|
+
<tbody>
|
|
42
|
+
<tr
|
|
43
|
+
v-for="(row, index) in tableData.rows"
|
|
44
|
+
:key="index"
|
|
45
|
+
class="border-t border-lightListBorder odd:bg-lightTableOddBackground even:bg-lightTableEvenBackground dark:border-darkListBorder odd:dark:bg-darkTableOddBackground even:dark:bg-darkTableEvenBackground"
|
|
46
|
+
>
|
|
47
|
+
<td
|
|
48
|
+
v-for="column in columns"
|
|
49
|
+
:key="column"
|
|
50
|
+
class="px-3 py-2 text-lightListTableText dark:text-darkListTableText"
|
|
51
|
+
>
|
|
52
|
+
{{ formatCell(row[column]) }}
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
import { computed, watch } from 'vue'
|
|
65
|
+
import { useWidgetData } from '../../queries/useWidgetData.js'
|
|
66
|
+
import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
|
|
67
|
+
|
|
68
|
+
const props = defineProps<{
|
|
69
|
+
dashboardSlug: string
|
|
70
|
+
widget: DashboardWidgetConfig
|
|
71
|
+
}>()
|
|
72
|
+
|
|
73
|
+
const dashboardSlugRef = computed(() => props.dashboardSlug)
|
|
74
|
+
const widgetIdRef = computed(() => props.widget.id)
|
|
75
|
+
const {
|
|
76
|
+
data,
|
|
77
|
+
isLoading,
|
|
78
|
+
error,
|
|
79
|
+
refetch,
|
|
80
|
+
} = useWidgetData(dashboardSlugRef, widgetIdRef)
|
|
81
|
+
|
|
82
|
+
watch(
|
|
83
|
+
() => props.widget,
|
|
84
|
+
() => {
|
|
85
|
+
void refetch()
|
|
86
|
+
},
|
|
87
|
+
{ deep: true },
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const tableData = computed(() => {
|
|
91
|
+
return data.value?.data as DashboardWidgetTableData | null
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const columns = computed(() => {
|
|
95
|
+
const configuredColumns = (props.widget.table as { columns?: string[] } | undefined)?.columns
|
|
96
|
+
return configuredColumns ?? tableData.value?.columns ?? []
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
function formatCell(value: unknown) {
|
|
100
|
+
if (value === null || value === undefined) {
|
|
101
|
+
return ''
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof value === 'object') {
|
|
105
|
+
return JSON.stringify(value)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return String(value)
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DashboardConfig, DashboardGroupConfig, DashboardGroupMoveDirection, DashboardWidgetConfig, DashboardWidgetMoveDirection } from '../model/dashboard.types.js';
|
|
2
|
+
export type DashboardWidgetConfigValidationError = {
|
|
3
|
+
field: string;
|
|
4
|
+
message: string;
|
|
5
|
+
};
|
|
6
|
+
export type DashboardResponse = {
|
|
7
|
+
id: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
label: string;
|
|
10
|
+
revision: number;
|
|
11
|
+
config: DashboardConfig;
|
|
12
|
+
};
|
|
13
|
+
export type DashboardWidgetDataResponse = {
|
|
14
|
+
widget: DashboardWidgetConfig;
|
|
15
|
+
data: unknown;
|
|
16
|
+
};
|
|
17
|
+
export declare class DashboardApiError extends Error {
|
|
18
|
+
validationErrors: DashboardWidgetConfigValidationError[];
|
|
19
|
+
constructor(message: string, validationErrors?: DashboardWidgetConfigValidationError[]);
|
|
20
|
+
}
|
|
21
|
+
export declare const dashboardApi: {
|
|
22
|
+
getDashboardConfig(slug: string): Promise<DashboardResponse>;
|
|
23
|
+
addDashboardGroup(slug: string): Promise<DashboardResponse>;
|
|
24
|
+
moveDashboardGroup(slug: string, groupId: string, direction: DashboardGroupMoveDirection): Promise<DashboardResponse>;
|
|
25
|
+
removeDashboardGroup(slug: string, groupId: string): Promise<DashboardResponse>;
|
|
26
|
+
setDashboardGroupConfig(slug: string, groupId: string, config: DashboardGroupConfig): Promise<DashboardResponse>;
|
|
27
|
+
addDashboardWidget(slug: string, groupId: string): Promise<DashboardResponse>;
|
|
28
|
+
moveDashboardWidget(slug: string, widgetId: string, direction: DashboardWidgetMoveDirection): Promise<DashboardResponse>;
|
|
29
|
+
removeDashboardWidget(slug: string, widgetId: string): Promise<DashboardResponse>;
|
|
30
|
+
setWidgetConfig(slug: string, widgetId: string, config: DashboardWidgetConfig): Promise<DashboardResponse>;
|
|
31
|
+
getDashboardWidgetData(slug: string, widgetId: string): Promise<DashboardWidgetDataResponse>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.dashboardApi = exports.DashboardApiError = void 0;
|
|
13
|
+
class DashboardApiError extends Error {
|
|
14
|
+
constructor(message, validationErrors = []) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'DashboardApiError';
|
|
17
|
+
this.validationErrors = validationErrors;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.DashboardApiError = DashboardApiError;
|
|
21
|
+
function normalizeValidationErrors(response) {
|
|
22
|
+
if (Array.isArray(response === null || response === void 0 ? void 0 : response.validationErrors)) {
|
|
23
|
+
return response.validationErrors;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(response === null || response === void 0 ? void 0 : response.details)) {
|
|
26
|
+
return response.details.map((detail) => ({
|
|
27
|
+
field: Array.isArray(detail.instancePath)
|
|
28
|
+
? detail.instancePath.join('.')
|
|
29
|
+
: String(detail.instancePath || detail.path || 'config').replace(/^\//, '').replaceAll('/', '.'),
|
|
30
|
+
message: String(detail.message || 'Invalid value'),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
function parseDashboardResponse(rawResponse) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
const responseText = yield rawResponse.text();
|
|
38
|
+
if (!responseText) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(responseText);
|
|
43
|
+
}
|
|
44
|
+
catch (_a) {
|
|
45
|
+
return {
|
|
46
|
+
error: responseText,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function callDashboardApi(path, body) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
const rawResponse = yield fetch(path, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
'accept-language': localStorage.getItem('af_lang') || 'en',
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify(body),
|
|
60
|
+
});
|
|
61
|
+
const response = yield parseDashboardResponse(rawResponse);
|
|
62
|
+
if (!rawResponse.ok) {
|
|
63
|
+
throw new DashboardApiError((response === null || response === void 0 ? void 0 : response.error) || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`, normalizeValidationErrors(response));
|
|
64
|
+
}
|
|
65
|
+
if (!response || response.error) {
|
|
66
|
+
throw new DashboardApiError((response === null || response === void 0 ? void 0 : response.error) || 'Dashboard request failed', normalizeValidationErrors(response));
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
id: response.id,
|
|
70
|
+
slug: response.slug,
|
|
71
|
+
label: response.label,
|
|
72
|
+
revision: response.revision,
|
|
73
|
+
config: response.config,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function callDashboardWidgetDataApi(path, body) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
const rawResponse = yield fetch(path, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
'accept-language': localStorage.getItem('af_lang') || 'en',
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
const response = yield parseDashboardResponse(rawResponse);
|
|
88
|
+
if (!rawResponse.ok) {
|
|
89
|
+
throw new DashboardApiError((response === null || response === void 0 ? void 0 : response.error) || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`, normalizeValidationErrors(response));
|
|
90
|
+
}
|
|
91
|
+
if (!response || response.error) {
|
|
92
|
+
throw new DashboardApiError((response === null || response === void 0 ? void 0 : response.error) || 'Dashboard request failed', normalizeValidationErrors(response));
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
widget: response.widget,
|
|
96
|
+
data: response.data,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
exports.dashboardApi = {
|
|
101
|
+
getDashboardConfig(slug) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug });
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
addDashboardGroup(slug) {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug });
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
moveDashboardGroup(slug, groupId, direction) {
|
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
return callDashboardApi('/adminapi/v1/dashboard/move_dashboard_group', {
|
|
114
|
+
slug,
|
|
115
|
+
groupId,
|
|
116
|
+
direction,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
removeDashboardGroup(slug, groupId) {
|
|
121
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
return callDashboardApi('/adminapi/v1/dashboard/remove_dashboard_group', {
|
|
123
|
+
slug,
|
|
124
|
+
groupId,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
setDashboardGroupConfig(slug, groupId, config) {
|
|
129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
|
|
131
|
+
slug,
|
|
132
|
+
groupId,
|
|
133
|
+
config,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
addDashboardWidget(slug, groupId) {
|
|
138
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
139
|
+
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_widget', {
|
|
140
|
+
slug,
|
|
141
|
+
groupId,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
moveDashboardWidget(slug, widgetId, direction) {
|
|
146
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
147
|
+
return callDashboardApi('/adminapi/v1/dashboard/move_dashboard_widget', {
|
|
148
|
+
slug,
|
|
149
|
+
widgetId,
|
|
150
|
+
direction,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
removeDashboardWidget(slug, widgetId) {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
return callDashboardApi('/adminapi/v1/dashboard/remove_dashboard_widget', {
|
|
157
|
+
slug,
|
|
158
|
+
widgetId,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
setWidgetConfig(slug, widgetId, config) {
|
|
163
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_widget_config', {
|
|
165
|
+
slug,
|
|
166
|
+
widgetId,
|
|
167
|
+
config,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
getDashboardWidgetData(slug, widgetId) {
|
|
172
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
173
|
+
return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', {
|
|
174
|
+
slug,
|
|
175
|
+
widgetId,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DashboardConfig,
|
|
3
|
+
DashboardGroupConfig,
|
|
4
|
+
DashboardGroupMoveDirection,
|
|
5
|
+
DashboardWidgetConfig,
|
|
6
|
+
DashboardWidgetMoveDirection,
|
|
7
|
+
} from '../model/dashboard.types.js'
|
|
8
|
+
|
|
9
|
+
export type DashboardWidgetConfigValidationError = {
|
|
10
|
+
field: string
|
|
11
|
+
message: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type DashboardResponse = {
|
|
15
|
+
id: string
|
|
16
|
+
slug: string
|
|
17
|
+
label: string
|
|
18
|
+
revision: number
|
|
19
|
+
config: DashboardConfig
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DashboardWidgetDataResponse = {
|
|
23
|
+
widget: DashboardWidgetConfig
|
|
24
|
+
data: unknown
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class DashboardApiError extends Error {
|
|
28
|
+
validationErrors: DashboardWidgetConfigValidationError[]
|
|
29
|
+
|
|
30
|
+
constructor(message: string, validationErrors: DashboardWidgetConfigValidationError[] = []) {
|
|
31
|
+
super(message)
|
|
32
|
+
this.name = 'DashboardApiError'
|
|
33
|
+
this.validationErrors = validationErrors
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeValidationErrors(response: any): DashboardWidgetConfigValidationError[] {
|
|
38
|
+
if (Array.isArray(response?.validationErrors)) {
|
|
39
|
+
return response.validationErrors
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(response?.details)) {
|
|
43
|
+
return response.details.map((detail: any) => ({
|
|
44
|
+
field: Array.isArray(detail.instancePath)
|
|
45
|
+
? detail.instancePath.join('.')
|
|
46
|
+
: String(detail.instancePath || detail.path || 'config').replace(/^\//, '').replaceAll('/', '.'),
|
|
47
|
+
message: String(detail.message || 'Invalid value'),
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return []
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function parseDashboardResponse(rawResponse: Response) {
|
|
55
|
+
const responseText = await rawResponse.text()
|
|
56
|
+
|
|
57
|
+
if (!responseText) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(responseText)
|
|
63
|
+
} catch {
|
|
64
|
+
return {
|
|
65
|
+
error: responseText,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function callDashboardApi(path: string, body: Record<string, unknown>): Promise<DashboardResponse> {
|
|
71
|
+
const rawResponse = await fetch(path, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'accept-language': localStorage.getItem('af_lang') || 'en',
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const response = await parseDashboardResponse(rawResponse)
|
|
81
|
+
|
|
82
|
+
if (!rawResponse.ok) {
|
|
83
|
+
throw new DashboardApiError(
|
|
84
|
+
response?.error || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`,
|
|
85
|
+
normalizeValidationErrors(response),
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!response || response.error) {
|
|
90
|
+
throw new DashboardApiError(response?.error || 'Dashboard request failed', normalizeValidationErrors(response))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
id: response.id,
|
|
95
|
+
slug: response.slug,
|
|
96
|
+
label: response.label,
|
|
97
|
+
revision: response.revision,
|
|
98
|
+
config: response.config,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function callDashboardWidgetDataApi(
|
|
103
|
+
path: string,
|
|
104
|
+
body: Record<string, unknown>,
|
|
105
|
+
): Promise<DashboardWidgetDataResponse> {
|
|
106
|
+
const rawResponse = await fetch(path, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
'accept-language': localStorage.getItem('af_lang') || 'en',
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify(body),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const response = await parseDashboardResponse(rawResponse)
|
|
116
|
+
|
|
117
|
+
if (!rawResponse.ok) {
|
|
118
|
+
throw new DashboardApiError(
|
|
119
|
+
response?.error || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`,
|
|
120
|
+
normalizeValidationErrors(response),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!response || response.error) {
|
|
125
|
+
throw new DashboardApiError(response?.error || 'Dashboard request failed', normalizeValidationErrors(response))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
widget: response.widget,
|
|
130
|
+
data: response.data,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const dashboardApi = {
|
|
135
|
+
async getDashboardConfig(slug: string): Promise<DashboardResponse> {
|
|
136
|
+
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async addDashboardGroup(slug: string): Promise<DashboardResponse> {
|
|
140
|
+
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async moveDashboardGroup(
|
|
144
|
+
slug: string,
|
|
145
|
+
groupId: string,
|
|
146
|
+
direction: DashboardGroupMoveDirection,
|
|
147
|
+
): Promise<DashboardResponse> {
|
|
148
|
+
return callDashboardApi('/adminapi/v1/dashboard/move_dashboard_group', {
|
|
149
|
+
slug,
|
|
150
|
+
groupId,
|
|
151
|
+
direction,
|
|
152
|
+
})
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
async removeDashboardGroup(slug: string, groupId: string): Promise<DashboardResponse> {
|
|
156
|
+
return callDashboardApi('/adminapi/v1/dashboard/remove_dashboard_group', {
|
|
157
|
+
slug,
|
|
158
|
+
groupId,
|
|
159
|
+
})
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
async setDashboardGroupConfig(slug: string, groupId: string, config: DashboardGroupConfig): Promise<DashboardResponse> {
|
|
163
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
|
|
164
|
+
slug,
|
|
165
|
+
groupId,
|
|
166
|
+
config,
|
|
167
|
+
})
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async addDashboardWidget(slug: string, groupId: string): Promise<DashboardResponse> {
|
|
171
|
+
return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_widget', {
|
|
172
|
+
slug,
|
|
173
|
+
groupId,
|
|
174
|
+
})
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
async moveDashboardWidget(
|
|
178
|
+
slug: string,
|
|
179
|
+
widgetId: string,
|
|
180
|
+
direction: DashboardWidgetMoveDirection,
|
|
181
|
+
): Promise<DashboardResponse> {
|
|
182
|
+
return callDashboardApi('/adminapi/v1/dashboard/move_dashboard_widget', {
|
|
183
|
+
slug,
|
|
184
|
+
widgetId,
|
|
185
|
+
direction,
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
async removeDashboardWidget(slug: string, widgetId: string): Promise<DashboardResponse> {
|
|
190
|
+
return callDashboardApi('/adminapi/v1/dashboard/remove_dashboard_widget', {
|
|
191
|
+
slug,
|
|
192
|
+
widgetId,
|
|
193
|
+
})
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async setWidgetConfig(slug: string, widgetId: string, config: DashboardWidgetConfig): Promise<DashboardResponse> {
|
|
197
|
+
return callDashboardApi('/adminapi/v1/dashboard/set_widget_config', {
|
|
198
|
+
slug,
|
|
199
|
+
widgetId,
|
|
200
|
+
config,
|
|
201
|
+
})
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async getDashboardWidgetData(
|
|
205
|
+
slug: string,
|
|
206
|
+
widgetId: string,
|
|
207
|
+
): Promise<DashboardWidgetDataResponse> {
|
|
208
|
+
return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', {
|
|
209
|
+
slug,
|
|
210
|
+
widgetId,
|
|
211
|
+
})
|
|
212
|
+
},
|
|
213
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Ref, ShallowRef } from 'vue';
|
|
2
|
+
type ElementSizeState<T extends HTMLElement> = {
|
|
3
|
+
el: ShallowRef<T | null>;
|
|
4
|
+
width: Ref<number>;
|
|
5
|
+
height: Ref<number>;
|
|
6
|
+
};
|
|
7
|
+
export declare function useElementSize<T extends HTMLElement>(): ElementSizeState<T>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useElementSize = useElementSize;
|
|
4
|
+
const vue_1 = require("vue");
|
|
5
|
+
function useElementSize() {
|
|
6
|
+
const el = (0, vue_1.shallowRef)(null);
|
|
7
|
+
const width = (0, vue_1.ref)(0);
|
|
8
|
+
const height = (0, vue_1.ref)(0);
|
|
9
|
+
let observer;
|
|
10
|
+
(0, vue_1.onMounted)(() => {
|
|
11
|
+
observer = new ResizeObserver(([entry]) => {
|
|
12
|
+
if (!entry) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
width.value = Math.floor(entry.contentRect.width);
|
|
16
|
+
height.value = Math.floor(entry.contentRect.height);
|
|
17
|
+
});
|
|
18
|
+
if (el.value) {
|
|
19
|
+
observer.observe(el.value);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
(0, vue_1.onBeforeUnmount)(() => {
|
|
23
|
+
observer === null || observer === void 0 ? void 0 : observer.disconnect();
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
el: el,
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
|
|
2
|
+
import type { Ref, ShallowRef } from 'vue'
|
|
3
|
+
|
|
4
|
+
type ElementSizeState<T extends HTMLElement> = {
|
|
5
|
+
el: ShallowRef<T | null>
|
|
6
|
+
width: Ref<number>
|
|
7
|
+
height: Ref<number>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
|
|
11
|
+
const el = shallowRef<T | null>(null)
|
|
12
|
+
const width = ref(0)
|
|
13
|
+
const height = ref(0)
|
|
14
|
+
|
|
15
|
+
let observer: ResizeObserver | undefined
|
|
16
|
+
|
|
17
|
+
onMounted(() => {
|
|
18
|
+
observer = new ResizeObserver(([entry]) => {
|
|
19
|
+
if (!entry) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
width.value = Math.floor(entry.contentRect.width)
|
|
24
|
+
height.value = Math.floor(entry.contentRect.height)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (el.value) {
|
|
28
|
+
observer.observe(el.value)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
onBeforeUnmount(() => {
|
|
33
|
+
observer?.disconnect()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
el: el as ShallowRef<T | null>,
|
|
38
|
+
width,
|
|
39
|
+
height,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js';
|
|
2
|
+
export type DashboardConfig = {
|
|
3
|
+
version: number;
|
|
4
|
+
groups: DashboardGroupConfig[];
|
|
5
|
+
widgets: DashboardWidgetConfig[];
|
|
6
|
+
};
|
|
7
|
+
export type DashboardGroupConfig = {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
order: number;
|
|
11
|
+
};
|
|
12
|
+
export type DashboardGroupMoveDirection = 'up' | 'down';
|
|
13
|
+
export type DashboardWidgetMoveDirection = 'up' | 'down';
|
|
14
|
+
export type DashboardWidgetTarget = 'empty' | 'table' | 'chart' | 'kpi_card' | 'pivot_table' | 'gauge_card';
|
|
15
|
+
export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full';
|
|
16
|
+
export type WidgetLayout = {
|
|
17
|
+
size?: DashboardWidgetSize;
|
|
18
|
+
width?: number;
|
|
19
|
+
minWidth?: number;
|
|
20
|
+
maxWidth?: number | null;
|
|
21
|
+
height?: number;
|
|
22
|
+
};
|
|
23
|
+
export type DashboardWidgetConfig = {
|
|
24
|
+
id: string;
|
|
25
|
+
group_id: string;
|
|
26
|
+
label?: string;
|
|
27
|
+
size?: DashboardWidgetSize;
|
|
28
|
+
width?: number;
|
|
29
|
+
height?: number;
|
|
30
|
+
minWidth?: number;
|
|
31
|
+
maxWidth?: number | null;
|
|
32
|
+
order: number;
|
|
33
|
+
target: DashboardWidgetTarget;
|
|
34
|
+
chart?: ChartWidgetConfig;
|
|
35
|
+
table?: unknown;
|
|
36
|
+
kpi_card?: unknown;
|
|
37
|
+
pivot_table?: unknown;
|
|
38
|
+
gauge_card?: unknown;
|
|
39
|
+
query?: unknown;
|
|
40
|
+
};
|
|
41
|
+
export type DashboardWidgetTableData = {
|
|
42
|
+
columns: string[];
|
|
43
|
+
rows: Record<string, unknown>[];
|
|
44
|
+
};
|
|
45
|
+
export declare function normalizeDashboardConfig(config: unknown): DashboardConfig;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeDashboardConfig = normalizeDashboardConfig;
|
|
4
|
+
function normalizeDashboardConfig(config) {
|
|
5
|
+
const value = isRecord(config) ? config : {};
|
|
6
|
+
return {
|
|
7
|
+
version: typeof value.version === 'number' ? value.version : 1,
|
|
8
|
+
groups: Array.isArray(value.groups) ? value.groups : [],
|
|
9
|
+
widgets: Array.isArray(value.widgets) ? value.widgets : [],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null;
|
|
14
|
+
}
|