@hed-hog/core 0.0.299 → 0.0.301
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/dist/ai/ai.service.d.ts +13 -2
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +104 -2
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -0
- package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -0
- package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +2 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js +6 -3
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +7 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.js +76 -33
- package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +82 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +117 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +93 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +654 -20
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -0
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -0
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +2 -0
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +2 -0
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mail/mail.service.d.ts +9 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +56 -4
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts +6 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +188 -15
- package/dist/setting/setting.service.js.map +1 -1
- package/hedhog/data/dashboard.yaml +12 -6
- package/hedhog/data/dashboard_component_role.yaml +66 -0
- package/hedhog/data/dashboard_role.yaml +2 -8
- package/hedhog/data/route.yaml +72 -0
- package/hedhog/data/setting_group.yaml +28 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +333 -128
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +277 -53
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +179 -231
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +64 -18
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +1619 -0
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +37 -0
- package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +8 -8
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +3 -3
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +3 -25
- package/hedhog/frontend/messages/en.json +124 -2
- package/hedhog/frontend/messages/pt.json +123 -1
- package/hedhog/frontend/widgets/account-security.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/active-users-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/email-notifications.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/locale-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/mail-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/menus-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/oauth-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/permissions-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/profile-card.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/routes-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/storage-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/theme-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-roles.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-sessions.tsx.ejs +1 -1
- package/hedhog/table/dashboard.yaml +6 -0
- package/package.json +3 -3
- package/src/ai/ai.service.ts +129 -1
- package/src/dashboard/dashboard-component/dashboard-component.controller.ts +15 -2
- package/src/dashboard/dashboard-component/dashboard-component.service.ts +107 -43
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +119 -1
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +876 -20
- package/src/index.ts +7 -6
- package/src/mail/mail.service.ts +67 -3
- package/src/setting/setting.service.ts +222 -15
- package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +0 -11
- package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +0 -192
- package/hedhog/frontend/app/dashboard/components/widgets/core.active-users-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.activity-timeline.tsx.ejs +0 -223
- package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +0 -226
- package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +0 -168
- package/hedhog/frontend/app/dashboard/components/widgets/core.login-history-chart.tsx.ejs +0 -115
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +0 -199
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-chart.tsx.ejs +0 -149
- package/hedhog/frontend/app/dashboard/components/widgets/core.menus-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +0 -175
- package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-card.tsx.ejs +0 -61
- package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-chart.tsx.ejs +0 -156
- package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +0 -186
- package/hedhog/frontend/app/dashboard/components/widgets/core.routes-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.session-activity-chart.tsx.ejs +0 -183
- package/hedhog/frontend/app/dashboard/components/widgets/core.sessions-today-card.tsx.ejs +0 -62
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-access-level.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-actions-today.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-consecutive-days.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-online-time.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +0 -196
- package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +0 -213
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-growth-chart.tsx.ejs +0 -210
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +0 -132
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +0 -236
- package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +0 -108
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +0 -66
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +0 -122
- package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +0 -63
- package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +0 -73
- package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +0 -73
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +0 -123
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +0 -118
|
@@ -13,12 +13,15 @@ import { Separator } from '@/components/ui/separator';
|
|
|
13
13
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
|
14
14
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
15
15
|
import { useIsMobile } from '@/components/ui/use-mobile';
|
|
16
|
+
import { useDebounce } from '@/hooks/use-debounce';
|
|
17
|
+
import { useProgress } from '@bprogress/next';
|
|
16
18
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
17
|
-
import { IconDeviceFloppy } from '@tabler/icons-react';
|
|
19
|
+
import { IconDeviceFloppy, IconLoader2 } from '@tabler/icons-react';
|
|
18
20
|
import { toBlob } from 'html-to-image';
|
|
19
21
|
import { useTranslations } from 'next-intl';
|
|
20
22
|
import { useRouter } from 'next/navigation';
|
|
21
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
23
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
24
|
+
import { createPortal } from 'react-dom';
|
|
22
25
|
import {
|
|
23
26
|
AddWidgetSelectorDialog,
|
|
24
27
|
DraggableGrid,
|
|
@@ -34,6 +37,10 @@ import { WidgetRenderer } from './widget-renderer';
|
|
|
34
37
|
|
|
35
38
|
interface DashboardContentProps {
|
|
36
39
|
dashboardSlug: string;
|
|
40
|
+
showHeader?: boolean;
|
|
41
|
+
headerActionsTargetId?: string;
|
|
42
|
+
openWidgetPickerSignal?: number;
|
|
43
|
+
onOpenWidgetPickerHandled?: () => void;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
interface DashboardComponentsPage {
|
|
@@ -44,6 +51,7 @@ interface DashboardComponentsPage {
|
|
|
44
51
|
pageSize: number;
|
|
45
52
|
prev: number | null;
|
|
46
53
|
next: number | null;
|
|
54
|
+
modules?: string[];
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
const USER_STATS_WIDGETS = new Set([
|
|
@@ -59,6 +67,19 @@ const USER_POST_HISTORY_WIDGETS = new Set([
|
|
|
59
67
|
]);
|
|
60
68
|
|
|
61
69
|
const USER_BOTTOM_WIDGETS = new Set(['user-roles', 'activity-timeline']);
|
|
70
|
+
const LAYOUT_AUTOSAVE_DELAY = 1000;
|
|
71
|
+
|
|
72
|
+
const normalizeLayoutForSave = (layout: LayoutItem[]) =>
|
|
73
|
+
layout.map(({ i, x, y, w, h }) => ({
|
|
74
|
+
i,
|
|
75
|
+
x,
|
|
76
|
+
y,
|
|
77
|
+
w,
|
|
78
|
+
h,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const getLayoutSignature = (layout: LayoutItem[]) =>
|
|
82
|
+
JSON.stringify(normalizeLayoutForSave(layout));
|
|
62
83
|
|
|
63
84
|
const getWidgetBaseSlug = (slug: string): string => {
|
|
64
85
|
const parts = slug.split('.');
|
|
@@ -129,9 +150,20 @@ const normalizeUserDashboardLayout = (item: WidgetLayout): LayoutItem => {
|
|
|
129
150
|
return layoutItem;
|
|
130
151
|
};
|
|
131
152
|
|
|
132
|
-
export const DashboardContent = ({
|
|
153
|
+
export const DashboardContent = ({
|
|
154
|
+
dashboardSlug,
|
|
155
|
+
showHeader = true,
|
|
156
|
+
headerActionsTargetId,
|
|
157
|
+
openWidgetPickerSignal,
|
|
158
|
+
onOpenWidgetPickerHandled,
|
|
159
|
+
}: DashboardContentProps) => {
|
|
133
160
|
const t = useTranslations('core.DashboardPage');
|
|
134
161
|
const { request } = useApp();
|
|
162
|
+
const {
|
|
163
|
+
start: startProgress,
|
|
164
|
+
stop: stopProgress,
|
|
165
|
+
set: setProgress,
|
|
166
|
+
} = useProgress();
|
|
135
167
|
const router = useRouter();
|
|
136
168
|
const isMobile = useIsMobile();
|
|
137
169
|
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
@@ -142,6 +174,23 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
142
174
|
const [isSaving, setIsSaving] = useState(false);
|
|
143
175
|
const [componentsPage, setComponentsPage] = useState(1);
|
|
144
176
|
const [componentsPageSize, setComponentsPageSize] = useState(12);
|
|
177
|
+
const [componentsSearchQuery, setComponentsSearchQuery] = useState('');
|
|
178
|
+
const [componentsModuleFilter, setComponentsModuleFilter] = useState('all');
|
|
179
|
+
const [headerActionsTarget, setHeaderActionsTarget] =
|
|
180
|
+
useState<HTMLElement | null>(null);
|
|
181
|
+
const lastSavedLayoutSignatureRef = useRef('[]');
|
|
182
|
+
const latestLayoutRef = useRef(layout);
|
|
183
|
+
|
|
184
|
+
const debouncedComponentsSearch = useDebounce(componentsSearchQuery, 400);
|
|
185
|
+
const debouncedLayout = useDebounce(layout, LAYOUT_AUTOSAVE_DELAY);
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
latestLayoutRef.current = layout;
|
|
189
|
+
}, [layout]);
|
|
190
|
+
const excludedComponentKeys = useMemo(
|
|
191
|
+
() => widgets.map((widget) => getWidgetIdentityKey(widget)),
|
|
192
|
+
[widgets]
|
|
193
|
+
);
|
|
145
194
|
|
|
146
195
|
const { data: dashboardAccess, isLoading: isCheckingAccess } =
|
|
147
196
|
useQuery<DashboardAccessResponse>({
|
|
@@ -165,6 +214,12 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
165
214
|
}
|
|
166
215
|
}, [dashboardAccess, dashboardSlug, router]);
|
|
167
216
|
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
setComponentsPage(1);
|
|
219
|
+
setComponentsSearchQuery('');
|
|
220
|
+
setComponentsModuleFilter('all');
|
|
221
|
+
}, [dashboardSlug]);
|
|
222
|
+
|
|
168
223
|
const {
|
|
169
224
|
data: availableComponentsResponse,
|
|
170
225
|
isLoading: isLoadingComponents,
|
|
@@ -175,14 +230,26 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
175
230
|
dashboardSlug,
|
|
176
231
|
componentsPage,
|
|
177
232
|
componentsPageSize,
|
|
233
|
+
debouncedComponentsSearch,
|
|
234
|
+
componentsModuleFilter,
|
|
235
|
+
excludedComponentKeys.join(','),
|
|
178
236
|
],
|
|
179
237
|
queryFn: async () => {
|
|
238
|
+
const trimmedSearch = debouncedComponentsSearch.trim();
|
|
239
|
+
|
|
180
240
|
const { data } = await request<DashboardComponentsPage>({
|
|
181
241
|
url: '/dashboard-component/user',
|
|
182
242
|
method: 'GET',
|
|
183
243
|
params: {
|
|
184
244
|
page: componentsPage,
|
|
185
245
|
pageSize: componentsPageSize,
|
|
246
|
+
...(trimmedSearch ? { search: trimmedSearch } : {}),
|
|
247
|
+
...(componentsModuleFilter !== 'all'
|
|
248
|
+
? { librarySlug: componentsModuleFilter }
|
|
249
|
+
: {}),
|
|
250
|
+
...(excludedComponentKeys.length > 0
|
|
251
|
+
? { exclude: excludedComponentKeys.join(',') }
|
|
252
|
+
: {}),
|
|
186
253
|
},
|
|
187
254
|
});
|
|
188
255
|
return data;
|
|
@@ -226,9 +293,11 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
226
293
|
}
|
|
227
294
|
);
|
|
228
295
|
|
|
296
|
+
lastSavedLayoutSignatureRef.current = getLayoutSignature(gridLayout);
|
|
229
297
|
setLayout(gridLayout);
|
|
230
298
|
setWidgets(userLayout);
|
|
231
299
|
} else {
|
|
300
|
+
lastSavedLayoutSignatureRef.current = '[]';
|
|
232
301
|
setLayout([]);
|
|
233
302
|
setWidgets([]);
|
|
234
303
|
}
|
|
@@ -236,18 +305,50 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
236
305
|
}
|
|
237
306
|
}, [userLayout, dashboardSlug]);
|
|
238
307
|
|
|
239
|
-
const
|
|
240
|
-
const totalAvailableComponents =
|
|
241
|
-
|
|
308
|
+
const availableComponents = availableComponentsResponse?.data ?? [];
|
|
309
|
+
const totalAvailableComponents = availableComponentsResponse?.total ?? 0;
|
|
310
|
+
const availableComponentModules = availableComponentsResponse?.modules ?? [];
|
|
311
|
+
const layoutSignature = useMemo(() => getLayoutSignature(layout), [layout]);
|
|
312
|
+
const debouncedLayoutSignature = useMemo(
|
|
313
|
+
() => getLayoutSignature(debouncedLayout),
|
|
314
|
+
[debouncedLayout]
|
|
315
|
+
);
|
|
316
|
+
const isAutosavePending =
|
|
317
|
+
hasChanges && !isSaving && layoutSignature !== debouncedLayoutSignature;
|
|
318
|
+
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
if (!isAutosavePending && !isSaving) {
|
|
321
|
+
stopProgress(150);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
startProgress(isSaving ? 0.55 : 0.2, 0, true);
|
|
326
|
+
setProgress(isSaving ? 0.8 : 0.35);
|
|
327
|
+
}, [isAutosavePending, isSaving, setProgress, startProgress, stopProgress]);
|
|
328
|
+
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
const lastAvailablePage = Math.max(
|
|
331
|
+
availableComponentsResponse?.lastPage ?? 1,
|
|
332
|
+
1
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (componentsPage > lastAvailablePage) {
|
|
336
|
+
setComponentsPage(lastAvailablePage);
|
|
337
|
+
}
|
|
338
|
+
}, [availableComponentsResponse?.lastPage, componentsPage]);
|
|
339
|
+
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
if (
|
|
342
|
+
!headerActionsTargetId ||
|
|
343
|
+
showHeader ||
|
|
344
|
+
typeof document === 'undefined'
|
|
345
|
+
) {
|
|
346
|
+
setHeaderActionsTarget(null);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
242
349
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
(component: DashboardComponent) =>
|
|
246
|
-
!widgets.some(
|
|
247
|
-
(widget) =>
|
|
248
|
-
getWidgetIdentityKey(widget) === getWidgetIdentityKey(component)
|
|
249
|
-
)
|
|
250
|
-
) || [];
|
|
350
|
+
setHeaderActionsTarget(document.getElementById(headerActionsTargetId));
|
|
351
|
+
}, [headerActionsTargetId, showHeader]);
|
|
251
352
|
|
|
252
353
|
const handleLayoutChange = useCallback((newLayout: LayoutItem[]) => {
|
|
253
354
|
setLayout((prevLayout) => {
|
|
@@ -261,21 +362,51 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
261
362
|
});
|
|
262
363
|
}, []);
|
|
263
364
|
|
|
264
|
-
const handleSaveLayout =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
setIsSaving(
|
|
365
|
+
const handleSaveLayout = useCallback(
|
|
366
|
+
async (layoutToSave: LayoutItem[] = layout) => {
|
|
367
|
+
const normalizedLayout = normalizeLayoutForSave(layoutToSave);
|
|
368
|
+
const nextLayoutSignature = JSON.stringify(normalizedLayout);
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
isSaving ||
|
|
372
|
+
nextLayoutSignature === lastSavedLayoutSignatureRef.current
|
|
373
|
+
) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
setIsSaving(true);
|
|
378
|
+
try {
|
|
379
|
+
await request({
|
|
380
|
+
url: `/dashboard-core/layout/${dashboardSlug}`,
|
|
381
|
+
method: 'POST',
|
|
382
|
+
data: { layout: normalizedLayout },
|
|
383
|
+
});
|
|
384
|
+
lastSavedLayoutSignatureRef.current = nextLayoutSignature;
|
|
385
|
+
setHasChanges(
|
|
386
|
+
getLayoutSignature(latestLayoutRef.current) !== nextLayoutSignature
|
|
387
|
+
);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error('❌ Erro ao salvar layout:', error);
|
|
390
|
+
} finally {
|
|
391
|
+
setIsSaving(false);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
[dashboardSlug, isSaving, layout, request]
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
if (!hasChanges || isSaving) {
|
|
399
|
+
return;
|
|
277
400
|
}
|
|
278
|
-
|
|
401
|
+
|
|
402
|
+
const nextLayoutSignature = getLayoutSignature(debouncedLayout);
|
|
403
|
+
|
|
404
|
+
if (nextLayoutSignature === lastSavedLayoutSignatureRef.current) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
void handleSaveLayout(debouncedLayout);
|
|
409
|
+
}, [debouncedLayout, handleSaveLayout, hasChanges, isSaving]);
|
|
279
410
|
|
|
280
411
|
const handleAddWidget = async (slugs: string[]) => {
|
|
281
412
|
if (!slugs.length) return;
|
|
@@ -331,6 +462,10 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
331
462
|
cacheBust: true,
|
|
332
463
|
pixelRatio: 2,
|
|
333
464
|
backgroundColor: '#ffffff',
|
|
465
|
+
filter: (node) =>
|
|
466
|
+
!(
|
|
467
|
+
node instanceof HTMLElement && node.dataset.widgetAction === 'true'
|
|
468
|
+
),
|
|
334
469
|
});
|
|
335
470
|
|
|
336
471
|
if (!screenshot) {
|
|
@@ -376,28 +511,30 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
376
511
|
if (isCheckingAccess || isLoadingLayout) {
|
|
377
512
|
return (
|
|
378
513
|
<>
|
|
379
|
-
|
|
380
|
-
<
|
|
381
|
-
<div className="flex items-center gap-2">
|
|
382
|
-
<
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
<
|
|
389
|
-
<
|
|
390
|
-
<
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
<
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
514
|
+
{showHeader ? (
|
|
515
|
+
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
|
516
|
+
<div className="flex w-full items-center justify-between gap-2 px-4">
|
|
517
|
+
<div className="flex items-center gap-2">
|
|
518
|
+
<SidebarTrigger className="-ml-1" />
|
|
519
|
+
<Separator
|
|
520
|
+
orientation="vertical"
|
|
521
|
+
className="mr-2 data-[orientation=vertical]:h-4"
|
|
522
|
+
/>
|
|
523
|
+
<Breadcrumb>
|
|
524
|
+
<BreadcrumbList>
|
|
525
|
+
<BreadcrumbItem className="hidden md:block">
|
|
526
|
+
<BreadcrumbLink href="#">{t('dashboard')}</BreadcrumbLink>
|
|
527
|
+
</BreadcrumbItem>
|
|
528
|
+
<BreadcrumbSeparator className="hidden md:block" />
|
|
529
|
+
<BreadcrumbItem>
|
|
530
|
+
<BreadcrumbPage>{t('overview')}</BreadcrumbPage>
|
|
531
|
+
</BreadcrumbItem>
|
|
532
|
+
</BreadcrumbList>
|
|
533
|
+
</Breadcrumb>
|
|
534
|
+
</div>
|
|
398
535
|
</div>
|
|
399
|
-
</
|
|
400
|
-
|
|
536
|
+
</header>
|
|
537
|
+
) : null}
|
|
401
538
|
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
|
402
539
|
<Skeleton className="h-32 w-full" />
|
|
403
540
|
<Skeleton className="h-32 w-full" />
|
|
@@ -410,28 +547,30 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
410
547
|
if (!dashboardAccess?.hasAccess) {
|
|
411
548
|
return (
|
|
412
549
|
<>
|
|
413
|
-
|
|
414
|
-
<
|
|
415
|
-
<div className="flex items-center gap-2">
|
|
416
|
-
<
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
<
|
|
423
|
-
<
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
550
|
+
{showHeader ? (
|
|
551
|
+
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
|
552
|
+
<div className="flex w-full items-center justify-between gap-2 px-4">
|
|
553
|
+
<div className="flex items-center gap-2">
|
|
554
|
+
<SidebarTrigger className="-ml-1" />
|
|
555
|
+
<Separator
|
|
556
|
+
orientation="vertical"
|
|
557
|
+
className="mr-2 data-[orientation=vertical]:h-4"
|
|
558
|
+
/>
|
|
559
|
+
<Breadcrumb>
|
|
560
|
+
<BreadcrumbList>
|
|
561
|
+
<BreadcrumbItem className="hidden md:block">
|
|
562
|
+
<BreadcrumbLink href="#">{t('dashboard')}</BreadcrumbLink>
|
|
563
|
+
</BreadcrumbItem>
|
|
564
|
+
<BreadcrumbSeparator className="hidden md:block" />
|
|
565
|
+
<BreadcrumbItem>
|
|
566
|
+
<BreadcrumbPage>{t('accessDenied')}</BreadcrumbPage>
|
|
567
|
+
</BreadcrumbItem>
|
|
568
|
+
</BreadcrumbList>
|
|
569
|
+
</Breadcrumb>
|
|
570
|
+
</div>
|
|
432
571
|
</div>
|
|
433
|
-
</
|
|
434
|
-
|
|
572
|
+
</header>
|
|
573
|
+
) : null}
|
|
435
574
|
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-4 pt-0">
|
|
436
575
|
<div className="text-center">
|
|
437
576
|
<h2 className="text-2xl font-bold">{t('accessDenied')}</h2>
|
|
@@ -445,67 +584,109 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
445
584
|
}
|
|
446
585
|
|
|
447
586
|
const dashboardName = dashboardAccess?.dashboard?.name || dashboardSlug;
|
|
587
|
+
const autosaveStatusLabel = isSaving
|
|
588
|
+
? 'Salvando layout...'
|
|
589
|
+
: isAutosavePending
|
|
590
|
+
? 'Salvando automaticamente...'
|
|
591
|
+
: null;
|
|
592
|
+
|
|
593
|
+
const dashboardActions = (
|
|
594
|
+
<>
|
|
595
|
+
{autosaveStatusLabel ? (
|
|
596
|
+
<div className="text-muted-foreground bg-muted inline-flex items-center gap-2 rounded-md px-2.5 py-1 text-xs">
|
|
597
|
+
<IconLoader2 className="size-3.5 animate-spin" />
|
|
598
|
+
<span>{autosaveStatusLabel}</span>
|
|
599
|
+
</div>
|
|
600
|
+
) : null}
|
|
601
|
+
{hasChanges ? (
|
|
602
|
+
<Button
|
|
603
|
+
size="sm"
|
|
604
|
+
variant="default"
|
|
605
|
+
className="gap-1 px-2 sm:gap-2 sm:px-3"
|
|
606
|
+
onClick={() => void handleSaveLayout()}
|
|
607
|
+
disabled={isSaving}
|
|
608
|
+
aria-label={isSaving ? t('saving') : t('saveLayout')}
|
|
609
|
+
>
|
|
610
|
+
{isSaving ? (
|
|
611
|
+
<IconLoader2 className="size-4 animate-spin" />
|
|
612
|
+
) : (
|
|
613
|
+
<IconDeviceFloppy className="size-4" />
|
|
614
|
+
)}
|
|
615
|
+
<span className="hidden sm:inline">
|
|
616
|
+
{isSaving ? t('saving') : t('saveLayout')}
|
|
617
|
+
</span>
|
|
618
|
+
</Button>
|
|
619
|
+
) : null}
|
|
620
|
+
<AddWidgetSelectorDialog
|
|
621
|
+
availableComponents={availableComponents}
|
|
622
|
+
totalItems={totalAvailableComponents}
|
|
623
|
+
currentPage={availableComponentsResponse?.page ?? componentsPage}
|
|
624
|
+
pageSize={availableComponentsResponse?.pageSize ?? componentsPageSize}
|
|
625
|
+
isLoading={isLoadingComponents}
|
|
626
|
+
searchQuery={componentsSearchQuery}
|
|
627
|
+
onSearchQueryChange={(value) => {
|
|
628
|
+
setComponentsSearchQuery(value);
|
|
629
|
+
setComponentsPage(1);
|
|
630
|
+
}}
|
|
631
|
+
moduleFilter={componentsModuleFilter}
|
|
632
|
+
modules={availableComponentModules}
|
|
633
|
+
onModuleFilterChange={(value) => {
|
|
634
|
+
setComponentsModuleFilter(value);
|
|
635
|
+
setComponentsPage(1);
|
|
636
|
+
}}
|
|
637
|
+
onPageChange={setComponentsPage}
|
|
638
|
+
onPageSizeChange={(nextPageSize) => {
|
|
639
|
+
setComponentsPageSize(nextPageSize);
|
|
640
|
+
setComponentsPage(1);
|
|
641
|
+
}}
|
|
642
|
+
onAdd={handleAddWidget}
|
|
643
|
+
openSignal={openWidgetPickerSignal}
|
|
644
|
+
onOpenSignalHandled={onOpenWidgetPickerHandled}
|
|
645
|
+
/>
|
|
646
|
+
</>
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
const hasExternalActionTarget = !showHeader && Boolean(headerActionsTarget);
|
|
448
650
|
|
|
449
651
|
return (
|
|
450
652
|
<>
|
|
451
|
-
|
|
452
|
-
<
|
|
453
|
-
<div className="flex
|
|
454
|
-
<
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
<
|
|
461
|
-
<
|
|
462
|
-
<
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
<
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
variant="default"
|
|
478
|
-
className="gap-1 px-2 sm:gap-2 sm:px-3"
|
|
479
|
-
onClick={handleSaveLayout}
|
|
480
|
-
disabled={isSaving}
|
|
481
|
-
aria-label={isSaving ? t('saving') : t('saveLayout')}
|
|
482
|
-
>
|
|
483
|
-
<IconDeviceFloppy className="size-4" />
|
|
484
|
-
<span className="hidden sm:inline">
|
|
485
|
-
{isSaving ? t('saving') : t('saveLayout')}
|
|
486
|
-
</span>
|
|
487
|
-
</Button>
|
|
488
|
-
)}
|
|
489
|
-
<AddWidgetSelectorDialog
|
|
490
|
-
availableComponents={filteredComponents}
|
|
491
|
-
totalItems={totalAvailableComponents}
|
|
492
|
-
currentPage={availableComponentsResponse?.page ?? componentsPage}
|
|
493
|
-
pageSize={
|
|
494
|
-
availableComponentsResponse?.pageSize ?? componentsPageSize
|
|
495
|
-
}
|
|
496
|
-
isLoading={isLoadingComponents}
|
|
497
|
-
onPageChange={setComponentsPage}
|
|
498
|
-
onPageSizeChange={(nextPageSize) => {
|
|
499
|
-
setComponentsPageSize(nextPageSize);
|
|
500
|
-
setComponentsPage(1);
|
|
501
|
-
}}
|
|
502
|
-
onAdd={handleAddWidget}
|
|
503
|
-
currentSlug={dashboardSlug}
|
|
504
|
-
/>
|
|
653
|
+
{showHeader ? (
|
|
654
|
+
<header className="flex min-h-16 shrink-0 items-center gap-2 py-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 sm:h-16 sm:py-0">
|
|
655
|
+
<div className="flex w-full flex-col gap-2 px-4 sm:flex-row sm:items-center sm:justify-between">
|
|
656
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
657
|
+
<SidebarTrigger className="-ml-1" />
|
|
658
|
+
<Separator
|
|
659
|
+
orientation="vertical"
|
|
660
|
+
className="mr-2 data-[orientation=vertical]:h-4"
|
|
661
|
+
/>
|
|
662
|
+
<Breadcrumb className="min-w-0">
|
|
663
|
+
<BreadcrumbList>
|
|
664
|
+
<BreadcrumbItem className="hidden md:block">
|
|
665
|
+
<BreadcrumbLink href="#">{t('dashboard')}</BreadcrumbLink>
|
|
666
|
+
</BreadcrumbItem>
|
|
667
|
+
<BreadcrumbSeparator className="hidden md:block" />
|
|
668
|
+
<BreadcrumbItem>
|
|
669
|
+
<BreadcrumbPage className="truncate">
|
|
670
|
+
{dashboardName}
|
|
671
|
+
</BreadcrumbPage>
|
|
672
|
+
</BreadcrumbItem>
|
|
673
|
+
</BreadcrumbList>
|
|
674
|
+
</Breadcrumb>
|
|
675
|
+
</div>
|
|
676
|
+
<div className="flex w-full items-center justify-end gap-2 sm:w-auto">
|
|
677
|
+
{dashboardActions}
|
|
678
|
+
</div>
|
|
505
679
|
</div>
|
|
680
|
+
</header>
|
|
681
|
+
) : !hasExternalActionTarget ? (
|
|
682
|
+
<div className="flex w-full items-center justify-end gap-2 px-4 pt-2">
|
|
683
|
+
{dashboardActions}
|
|
506
684
|
</div>
|
|
507
|
-
|
|
508
|
-
|
|
685
|
+
) : null}
|
|
686
|
+
{hasExternalActionTarget && headerActionsTarget
|
|
687
|
+
? createPortal(dashboardActions, headerActionsTarget)
|
|
688
|
+
: null}
|
|
689
|
+
<div className="flex flex-1 flex-col gap-4 overflow-auto pt-0">
|
|
509
690
|
{widgets.length > 0 ? (
|
|
510
691
|
<div className="min-h-105 sm:min-h-130 lg:min-h-150">
|
|
511
692
|
<DraggableGrid
|
|
@@ -514,6 +695,7 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
514
695
|
onLayoutChange={handleLayoutChange}
|
|
515
696
|
cols={12}
|
|
516
697
|
rowHeight={80}
|
|
698
|
+
compactType={null}
|
|
517
699
|
preventCollision
|
|
518
700
|
isDraggable={!isMobile}
|
|
519
701
|
isResizable={!isMobile}
|
|
@@ -532,10 +714,33 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
532
714
|
</p>
|
|
533
715
|
<div className="mt-4">
|
|
534
716
|
<AddWidgetSelectorDialog
|
|
535
|
-
availableComponents={
|
|
717
|
+
availableComponents={availableComponents}
|
|
718
|
+
totalItems={totalAvailableComponents}
|
|
719
|
+
currentPage={
|
|
720
|
+
availableComponentsResponse?.page ?? componentsPage
|
|
721
|
+
}
|
|
722
|
+
pageSize={
|
|
723
|
+
availableComponentsResponse?.pageSize ?? componentsPageSize
|
|
724
|
+
}
|
|
536
725
|
isLoading={isLoadingComponents}
|
|
726
|
+
searchQuery={componentsSearchQuery}
|
|
727
|
+
onSearchQueryChange={(value) => {
|
|
728
|
+
setComponentsSearchQuery(value);
|
|
729
|
+
setComponentsPage(1);
|
|
730
|
+
}}
|
|
731
|
+
moduleFilter={componentsModuleFilter}
|
|
732
|
+
modules={availableComponentModules}
|
|
733
|
+
onModuleFilterChange={(value) => {
|
|
734
|
+
setComponentsModuleFilter(value);
|
|
735
|
+
setComponentsPage(1);
|
|
736
|
+
}}
|
|
737
|
+
onPageChange={setComponentsPage}
|
|
738
|
+
onPageSizeChange={(nextPageSize) => {
|
|
739
|
+
setComponentsPageSize(nextPageSize);
|
|
740
|
+
setComponentsPage(1);
|
|
741
|
+
}}
|
|
537
742
|
onAdd={handleAddWidget}
|
|
538
|
-
|
|
743
|
+
buttonVariant="default"
|
|
539
744
|
/>
|
|
540
745
|
</div>
|
|
541
746
|
</div>
|