@hed-hog/core 0.0.299 → 0.0.300
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/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 +65 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +111 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +69 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +526 -19
- 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/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/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 +1389 -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 +112 -2
- package/hedhog/frontend/messages/pt.json +111 -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 +5 -5
- 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 +112 -1
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +674 -19
- 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
|
@@ -3,27 +3,17 @@
|
|
|
3
3
|
import { PaginationFooter } from '@/components/entity-list/pagination-footer';
|
|
4
4
|
import { Badge } from '@/components/ui/badge';
|
|
5
5
|
import { Button } from '@/components/ui/button';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
CardHeader,
|
|
10
|
-
CardTitle,
|
|
11
|
-
} from '@/components/ui/card';
|
|
12
|
-
import {
|
|
13
|
-
Dialog,
|
|
14
|
-
DialogContent,
|
|
15
|
-
DialogDescription,
|
|
16
|
-
DialogFooter,
|
|
17
|
-
DialogHeader,
|
|
18
|
-
DialogTitle,
|
|
19
|
-
} from '@/components/ui/dialog';
|
|
20
|
-
import {
|
|
21
|
-
DropdownMenu,
|
|
22
|
-
DropdownMenuContent,
|
|
23
|
-
DropdownMenuItem,
|
|
24
|
-
DropdownMenuTrigger,
|
|
25
|
-
} from '@/components/ui/dropdown-menu';
|
|
6
|
+
import { Card, CardDescription, CardTitle } from '@/components/ui/card';
|
|
7
|
+
import { Checkbox } from '@/components/ui/checkbox';
|
|
8
|
+
import { Input } from '@/components/ui/input';
|
|
26
9
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
10
|
+
import {
|
|
11
|
+
Select,
|
|
12
|
+
SelectContent,
|
|
13
|
+
SelectItem,
|
|
14
|
+
SelectTrigger,
|
|
15
|
+
SelectValue,
|
|
16
|
+
} from '@/components/ui/select';
|
|
27
17
|
import {
|
|
28
18
|
Sheet,
|
|
29
19
|
SheetContent,
|
|
@@ -34,16 +24,9 @@ import {
|
|
|
34
24
|
} from '@/components/ui/sheet';
|
|
35
25
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
36
26
|
import { cn } from '@/lib/utils';
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
IconArrowsRightLeft,
|
|
40
|
-
IconLayoutDashboard,
|
|
41
|
-
IconPlus,
|
|
42
|
-
IconSettings,
|
|
43
|
-
} from '@tabler/icons-react';
|
|
27
|
+
import { IconLayoutDashboard, IconPlus } from '@tabler/icons-react';
|
|
44
28
|
import { useTranslations } from 'next-intl';
|
|
45
|
-
import {
|
|
46
|
-
import { useState } from 'react';
|
|
29
|
+
import { useEffect, useRef, useState } from 'react';
|
|
47
30
|
|
|
48
31
|
interface DashboardComponent {
|
|
49
32
|
id: number;
|
|
@@ -65,27 +48,24 @@ interface DashboardComponent {
|
|
|
65
48
|
}>;
|
|
66
49
|
}
|
|
67
50
|
|
|
68
|
-
interface Dashboard {
|
|
69
|
-
id: number;
|
|
70
|
-
slug: string;
|
|
71
|
-
dashboard_locale?: Array<{
|
|
72
|
-
name: string;
|
|
73
|
-
locale: {
|
|
74
|
-
code: string;
|
|
75
|
-
};
|
|
76
|
-
}>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
51
|
interface AddWidgetSelectorDialogProps {
|
|
80
52
|
availableComponents: DashboardComponent[];
|
|
81
53
|
totalItems: number;
|
|
82
54
|
currentPage: number;
|
|
83
55
|
pageSize: number;
|
|
84
56
|
isLoading: boolean;
|
|
57
|
+
searchQuery: string;
|
|
58
|
+
moduleFilter: string;
|
|
59
|
+
modules: string[];
|
|
60
|
+
onSearchQueryChange: (value: string) => void;
|
|
61
|
+
onModuleFilterChange: (value: string) => void;
|
|
85
62
|
onPageChange: (page: number) => void;
|
|
86
63
|
onPageSizeChange: (pageSize: number) => void;
|
|
87
64
|
onAdd: (slugs: string[]) => Promise<void> | void;
|
|
88
|
-
|
|
65
|
+
buttonLabel?: string;
|
|
66
|
+
buttonVariant?: 'default' | 'outline';
|
|
67
|
+
openSignal?: number;
|
|
68
|
+
onOpenSignalHandled?: () => void;
|
|
89
69
|
}
|
|
90
70
|
|
|
91
71
|
const getWidgetLookupKey = (slug: string, librarySlug?: string): string => {
|
|
@@ -104,6 +84,12 @@ const getWidgetPreviewSlug = (slug: string): string => {
|
|
|
104
84
|
return parts[parts.length - 1] || slug;
|
|
105
85
|
};
|
|
106
86
|
|
|
87
|
+
const formatModuleLabel = (moduleSlug?: string): string => {
|
|
88
|
+
const normalized = (moduleSlug || 'core').replace(/[._-]+/g, ' ').trim();
|
|
89
|
+
|
|
90
|
+
return normalized.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
91
|
+
};
|
|
92
|
+
|
|
107
93
|
function WidgetPreview({
|
|
108
94
|
name,
|
|
109
95
|
slug,
|
|
@@ -125,33 +111,27 @@ function WidgetPreview({
|
|
|
125
111
|
const previewUrl = `/libraries/core/dashboard-previews/${previewSlug}.png`;
|
|
126
112
|
|
|
127
113
|
return (
|
|
128
|
-
<div className="
|
|
129
|
-
<div className="flex
|
|
114
|
+
<div className="overflow-hidden rounded-lg border bg-background/80 p-2">
|
|
115
|
+
<div className="flex items-center gap-2">
|
|
130
116
|
<img
|
|
131
117
|
src={previewUrl}
|
|
132
118
|
alt={name}
|
|
133
|
-
className="
|
|
134
|
-
onError={(
|
|
135
|
-
|
|
119
|
+
className="h-16 w-24 shrink-0 rounded-md border object-cover"
|
|
120
|
+
onError={(event) => {
|
|
121
|
+
event.currentTarget.style.display = 'none';
|
|
136
122
|
}}
|
|
137
123
|
/>
|
|
138
|
-
<div className="
|
|
139
|
-
<div className="
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
</
|
|
148
|
-
<Badge variant="outline" className="text-[10px]">
|
|
149
|
-
{isResizable ? resizableLabel : fixedLabel}
|
|
150
|
-
</Badge>
|
|
124
|
+
<div className="min-w-0 flex-1">
|
|
125
|
+
<div className="mb-1.5 flex flex-wrap items-center gap-1.5">
|
|
126
|
+
<Badge variant="secondary" className="px-1.5 py-0 text-[10px]">
|
|
127
|
+
{width}x{height}
|
|
128
|
+
</Badge>
|
|
129
|
+
<Badge variant="outline" className="px-1.5 py-0 text-[10px]">
|
|
130
|
+
{isResizable ? resizableLabel : fixedLabel}
|
|
131
|
+
</Badge>
|
|
132
|
+
</div>
|
|
133
|
+
<p className="truncate text-[10px] text-muted-foreground">{name}</p>
|
|
151
134
|
</div>
|
|
152
|
-
<p className="mt-2 truncate text-[10px] text-muted-foreground">
|
|
153
|
-
{name}
|
|
154
|
-
</p>
|
|
155
135
|
</div>
|
|
156
136
|
</div>
|
|
157
137
|
);
|
|
@@ -163,54 +143,61 @@ export function AddWidgetSelectorDialog({
|
|
|
163
143
|
currentPage,
|
|
164
144
|
pageSize,
|
|
165
145
|
isLoading,
|
|
146
|
+
searchQuery,
|
|
147
|
+
moduleFilter,
|
|
148
|
+
modules,
|
|
149
|
+
onSearchQueryChange,
|
|
150
|
+
onModuleFilterChange,
|
|
166
151
|
onPageChange,
|
|
167
152
|
onPageSizeChange,
|
|
168
153
|
onAdd,
|
|
169
|
-
|
|
154
|
+
buttonLabel,
|
|
155
|
+
buttonVariant = 'outline',
|
|
156
|
+
openSignal,
|
|
157
|
+
onOpenSignalHandled,
|
|
170
158
|
}: AddWidgetSelectorDialogProps) {
|
|
171
159
|
const tWidget = useTranslations('core.AddWidgetDialog');
|
|
172
160
|
const tMenu = useTranslations('core.DashboardMenu');
|
|
173
|
-
const { request } = useApp();
|
|
174
|
-
const router = useRouter();
|
|
175
161
|
|
|
176
162
|
const [openWidgets, setOpenWidgets] = useState(false);
|
|
177
|
-
const [openDashboards, setOpenDashboards] = useState(false);
|
|
178
163
|
const [selectedWidgets, setSelectedWidgets] = useState<Set<string>>(
|
|
179
164
|
new Set()
|
|
180
165
|
);
|
|
181
166
|
const [isAdding, setIsAdding] = useState(false);
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
);
|
|
167
|
+
const lastHandledOpenSignalRef = useRef<number | null>(null);
|
|
168
|
+
|
|
169
|
+
const availableModuleOptions = Array.from(new Set(modules.filter(Boolean)));
|
|
185
170
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
});
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!openSignal) {
|
|
173
|
+
lastHandledOpenSignalRef.current = 0;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (openSignal !== lastHandledOpenSignalRef.current) {
|
|
178
|
+
lastHandledOpenSignalRef.current = openSignal;
|
|
179
|
+
setOpenWidgets(true);
|
|
180
|
+
onOpenSignalHandled?.();
|
|
181
|
+
}
|
|
182
|
+
}, [onOpenSignalHandled, openSignal]);
|
|
199
183
|
|
|
200
184
|
const toggleSelectedWidget = (slug: string) => {
|
|
201
185
|
setSelectedWidgets((prev) => {
|
|
202
186
|
const next = new Set(prev);
|
|
187
|
+
|
|
203
188
|
if (next.has(slug)) {
|
|
204
189
|
next.delete(slug);
|
|
205
190
|
} else {
|
|
206
191
|
next.add(slug);
|
|
207
192
|
}
|
|
193
|
+
|
|
208
194
|
return next;
|
|
209
195
|
});
|
|
210
196
|
};
|
|
211
197
|
|
|
212
198
|
const handleAdd = async () => {
|
|
213
199
|
if (selectedWidgets.size === 0 || isAdding) return;
|
|
200
|
+
|
|
214
201
|
setIsAdding(true);
|
|
215
202
|
try {
|
|
216
203
|
await onAdd(Array.from(selectedWidgets));
|
|
@@ -221,34 +208,18 @@ export function AddWidgetSelectorDialog({
|
|
|
221
208
|
}
|
|
222
209
|
};
|
|
223
210
|
|
|
224
|
-
const handleSwitchDashboard = () => {
|
|
225
|
-
if (selectedDashboard && selectedDashboard !== currentSlug) {
|
|
226
|
-
router.push(`/core/dashboard/${selectedDashboard}`);
|
|
227
|
-
setOpenDashboards(false);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
211
|
return (
|
|
232
212
|
<>
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
{tMenu('addWidgets')}
|
|
243
|
-
</DropdownMenuItem>
|
|
244
|
-
<DropdownMenuItem onClick={() => setOpenDashboards(true)}>
|
|
245
|
-
<IconArrowsRightLeft className="mr-2 size-4" />
|
|
246
|
-
{tMenu('switchDashboard')}
|
|
247
|
-
</DropdownMenuItem>
|
|
248
|
-
</DropdownMenuContent>
|
|
249
|
-
</DropdownMenu>
|
|
213
|
+
<Button
|
|
214
|
+
size="sm"
|
|
215
|
+
variant={buttonVariant}
|
|
216
|
+
className="cursor-pointer gap-2"
|
|
217
|
+
onClick={() => setOpenWidgets(true)}
|
|
218
|
+
>
|
|
219
|
+
<IconPlus className="size-4" />
|
|
220
|
+
{buttonLabel || tMenu('addWidgets')}
|
|
221
|
+
</Button>
|
|
250
222
|
|
|
251
|
-
{/* Sheet de Adicionar Widgets */}
|
|
252
223
|
<Sheet
|
|
253
224
|
open={openWidgets}
|
|
254
225
|
onOpenChange={(open) => {
|
|
@@ -270,16 +241,61 @@ export function AddWidgetSelectorDialog({
|
|
|
270
241
|
</SheetDescription>
|
|
271
242
|
</SheetHeader>
|
|
272
243
|
|
|
244
|
+
<div className="grid gap-3 border-b px-4 py-4 md:grid-cols-[minmax(0,1fr)_220px]">
|
|
245
|
+
<div className="space-y-2">
|
|
246
|
+
<label
|
|
247
|
+
htmlFor="dashboard-widget-search"
|
|
248
|
+
className="text-sm font-medium"
|
|
249
|
+
>
|
|
250
|
+
{tWidget('search')}
|
|
251
|
+
</label>
|
|
252
|
+
<Input
|
|
253
|
+
id="dashboard-widget-search"
|
|
254
|
+
value={searchQuery}
|
|
255
|
+
onChange={(event) => onSearchQueryChange(event.target.value)}
|
|
256
|
+
placeholder={tWidget('searchPlaceholder')}
|
|
257
|
+
/>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className="space-y-2">
|
|
261
|
+
<label
|
|
262
|
+
htmlFor="dashboard-widget-module-filter"
|
|
263
|
+
className="text-sm font-medium"
|
|
264
|
+
>
|
|
265
|
+
{tWidget('moduleFilterLabel')}
|
|
266
|
+
</label>
|
|
267
|
+
<Select
|
|
268
|
+
value={moduleFilter}
|
|
269
|
+
onValueChange={onModuleFilterChange}
|
|
270
|
+
>
|
|
271
|
+
<SelectTrigger
|
|
272
|
+
id="dashboard-widget-module-filter"
|
|
273
|
+
className="w-full"
|
|
274
|
+
>
|
|
275
|
+
<SelectValue placeholder={tWidget('allModules')} />
|
|
276
|
+
</SelectTrigger>
|
|
277
|
+
<SelectContent>
|
|
278
|
+
<SelectItem value="all">{tWidget('allModules')}</SelectItem>
|
|
279
|
+
{availableModuleOptions.map((module) => (
|
|
280
|
+
<SelectItem key={module} value={module}>
|
|
281
|
+
{formatModuleLabel(module)}
|
|
282
|
+
</SelectItem>
|
|
283
|
+
))}
|
|
284
|
+
</SelectContent>
|
|
285
|
+
</Select>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
273
289
|
<div className="grid min-h-0 flex-1 gap-4 p-4">
|
|
274
290
|
<ScrollArea className="min-h-0 pr-4">
|
|
275
291
|
{isLoading ? (
|
|
276
|
-
<div className="grid gap-
|
|
277
|
-
<Skeleton className="h-
|
|
278
|
-
<Skeleton className="h-
|
|
279
|
-
<Skeleton className="h-
|
|
292
|
+
<div className="grid gap-2.5">
|
|
293
|
+
<Skeleton className="h-24 w-full" />
|
|
294
|
+
<Skeleton className="h-24 w-full" />
|
|
295
|
+
<Skeleton className="h-24 w-full" />
|
|
280
296
|
</div>
|
|
281
297
|
) : (
|
|
282
|
-
<div className="grid gap-
|
|
298
|
+
<div className="grid gap-2.5">
|
|
283
299
|
{availableComponents.length === 0 ? (
|
|
284
300
|
<div className="flex min-h-55 flex-col items-center justify-center rounded-xl border border-dashed text-center">
|
|
285
301
|
<p className="text-sm text-muted-foreground">
|
|
@@ -295,64 +311,84 @@ export function AddWidgetSelectorDialog({
|
|
|
295
311
|
component.slug,
|
|
296
312
|
component.library_slug
|
|
297
313
|
);
|
|
314
|
+
const isSelected = selectedWidgets.has(selectionKey);
|
|
315
|
+
const moduleLabel = formatModuleLabel(
|
|
316
|
+
component.library_slug
|
|
317
|
+
);
|
|
298
318
|
|
|
299
319
|
return (
|
|
300
320
|
<Card
|
|
301
321
|
key={selectionKey}
|
|
322
|
+
role="checkbox"
|
|
323
|
+
tabIndex={0}
|
|
324
|
+
aria-checked={isSelected}
|
|
302
325
|
className={cn(
|
|
303
|
-
'cursor-pointer border
|
|
304
|
-
|
|
305
|
-
'border-primary bg-
|
|
326
|
+
'cursor-pointer border px-3 py-2.5 transition-all hover:border-primary/40 hover:bg-accent/30',
|
|
327
|
+
isSelected &&
|
|
328
|
+
'border-primary bg-primary/10 shadow-sm ring-1 ring-primary/25'
|
|
306
329
|
)}
|
|
307
330
|
onClick={() => toggleSelectedWidget(selectionKey)}
|
|
331
|
+
onKeyDown={(event) => {
|
|
332
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
toggleSelectedWidget(selectionKey);
|
|
335
|
+
}
|
|
336
|
+
}}
|
|
308
337
|
>
|
|
309
|
-
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)
|
|
338
|
+
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_180px] lg:items-center">
|
|
310
339
|
<div className="flex items-start gap-3">
|
|
311
|
-
<div
|
|
312
|
-
|
|
340
|
+
<div
|
|
341
|
+
className={cn(
|
|
342
|
+
'flex size-9 items-center justify-center rounded-lg',
|
|
343
|
+
isSelected
|
|
344
|
+
? 'bg-primary text-primary-foreground'
|
|
345
|
+
: 'bg-primary/10 text-primary'
|
|
346
|
+
)}
|
|
347
|
+
>
|
|
348
|
+
<IconLayoutDashboard className="size-4" />
|
|
313
349
|
</div>
|
|
314
350
|
<div className="min-w-0 flex-1">
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
351
|
+
<div className="flex items-start justify-between gap-3">
|
|
352
|
+
<div className="min-w-0">
|
|
353
|
+
<CardTitle className="truncate text-sm">
|
|
354
|
+
{name}
|
|
355
|
+
</CardTitle>
|
|
356
|
+
<CardDescription className="mt-0.5 truncate text-[11px]">
|
|
357
|
+
{selectionKey}
|
|
358
|
+
</CardDescription>
|
|
359
|
+
</div>
|
|
360
|
+
<Checkbox
|
|
361
|
+
checked={isSelected}
|
|
362
|
+
aria-label={name}
|
|
363
|
+
className="pointer-events-none mt-0.5"
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
366
|
+
<div className="mt-2 flex flex-wrap items-center gap-1.5">
|
|
367
|
+
<Badge
|
|
368
|
+
variant="outline"
|
|
369
|
+
className="px-1.5 py-0 text-[10px]"
|
|
370
|
+
>
|
|
371
|
+
{tWidget('module')}: {moduleLabel}
|
|
372
|
+
</Badge>
|
|
322
373
|
<Badge
|
|
323
374
|
variant="secondary"
|
|
324
|
-
className="text-[
|
|
375
|
+
className="px-1.5 py-0 text-[10px]"
|
|
325
376
|
>
|
|
326
377
|
{tWidget('dimensions')}: {component.width}
|
|
327
378
|
x{component.height}
|
|
328
379
|
</Badge>
|
|
329
380
|
<Badge
|
|
330
381
|
variant="outline"
|
|
331
|
-
className="text-[
|
|
382
|
+
className="px-1.5 py-0 text-[10px]"
|
|
332
383
|
>
|
|
333
384
|
{component.is_resizable
|
|
334
385
|
? tWidget('resizable')
|
|
335
386
|
: tWidget('fixedSize')}
|
|
336
387
|
</Badge>
|
|
337
388
|
</div>
|
|
338
|
-
<div className="mt-3">
|
|
339
|
-
<Button
|
|
340
|
-
type="button"
|
|
341
|
-
size="sm"
|
|
342
|
-
className="cursor-pointer"
|
|
343
|
-
onClick={(event) => {
|
|
344
|
-
event.stopPropagation();
|
|
345
|
-
toggleSelectedWidget(selectionKey);
|
|
346
|
-
}}
|
|
347
|
-
>
|
|
348
|
-
{selectedWidgets.has(selectionKey)
|
|
349
|
-
? tWidget('selected')
|
|
350
|
-
: tWidget('select')}
|
|
351
|
-
</Button>
|
|
352
|
-
</div>
|
|
353
389
|
</div>
|
|
354
390
|
</div>
|
|
355
|
-
<div className="
|
|
391
|
+
<div className="lg:block">
|
|
356
392
|
<WidgetPreview
|
|
357
393
|
name={name}
|
|
358
394
|
slug={component.slug}
|
|
@@ -400,94 +436,6 @@ export function AddWidgetSelectorDialog({
|
|
|
400
436
|
</div>
|
|
401
437
|
</SheetContent>
|
|
402
438
|
</Sheet>
|
|
403
|
-
|
|
404
|
-
{/* Diálogo de Trocar Dashboard */}
|
|
405
|
-
<Dialog open={openDashboards} onOpenChange={setOpenDashboards}>
|
|
406
|
-
<DialogContent className="sm:max-w-150">
|
|
407
|
-
<DialogHeader>
|
|
408
|
-
<DialogTitle>{tMenu('selectDashboardTitle')}</DialogTitle>
|
|
409
|
-
<DialogDescription>
|
|
410
|
-
{tMenu('selectDashboardDescription')}
|
|
411
|
-
</DialogDescription>
|
|
412
|
-
</DialogHeader>
|
|
413
|
-
<ScrollArea className="max-h-100 pr-4">
|
|
414
|
-
{isLoadingDashboards ? (
|
|
415
|
-
<div className="grid gap-3">
|
|
416
|
-
<Skeleton className="h-24 w-full" />
|
|
417
|
-
<Skeleton className="h-24 w-full" />
|
|
418
|
-
<Skeleton className="h-24 w-full" />
|
|
419
|
-
</div>
|
|
420
|
-
) : (
|
|
421
|
-
<div className="grid gap-3">
|
|
422
|
-
{!userDashboards || userDashboards.length === 0 ? (
|
|
423
|
-
<div className="flex min-h-50 flex-col items-center justify-center text-center">
|
|
424
|
-
<p className="text-muted-foreground text-sm">
|
|
425
|
-
{tMenu('noDashboardsAvailable')}
|
|
426
|
-
</p>
|
|
427
|
-
</div>
|
|
428
|
-
) : (
|
|
429
|
-
userDashboards.map((dashboard) => {
|
|
430
|
-
const name =
|
|
431
|
-
dashboard.dashboard_locale?.[0]?.name || dashboard.slug;
|
|
432
|
-
const isCurrent = dashboard.slug === currentSlug;
|
|
433
|
-
return (
|
|
434
|
-
<Card
|
|
435
|
-
key={dashboard.slug}
|
|
436
|
-
className={cn(
|
|
437
|
-
'cursor-pointer transition-colors py-4 hover:bg-accent',
|
|
438
|
-
selectedDashboard === dashboard.slug &&
|
|
439
|
-
'border-primary bg-accent',
|
|
440
|
-
isCurrent && 'opacity-50'
|
|
441
|
-
)}
|
|
442
|
-
onClick={() =>
|
|
443
|
-
!isCurrent && setSelectedDashboard(dashboard.slug)
|
|
444
|
-
}
|
|
445
|
-
>
|
|
446
|
-
<CardHeader>
|
|
447
|
-
<div className="flex items-start gap-3">
|
|
448
|
-
<div className="bg-primary/10 flex size-10 items-center justify-center rounded-lg">
|
|
449
|
-
<IconArrowsRightLeft className="text-primary size-5" />
|
|
450
|
-
</div>
|
|
451
|
-
<div className="flex-1">
|
|
452
|
-
<CardTitle className="text-base">
|
|
453
|
-
{name}
|
|
454
|
-
{isCurrent && ' (atual)'}
|
|
455
|
-
</CardTitle>
|
|
456
|
-
<CardDescription className="text-sm">
|
|
457
|
-
{dashboard.slug}
|
|
458
|
-
</CardDescription>
|
|
459
|
-
</div>
|
|
460
|
-
</div>
|
|
461
|
-
</CardHeader>
|
|
462
|
-
</Card>
|
|
463
|
-
);
|
|
464
|
-
})
|
|
465
|
-
)}
|
|
466
|
-
</div>
|
|
467
|
-
)}
|
|
468
|
-
</ScrollArea>
|
|
469
|
-
<DialogFooter>
|
|
470
|
-
<Button
|
|
471
|
-
type="button"
|
|
472
|
-
variant="outline"
|
|
473
|
-
onClick={() => setOpenDashboards(false)}
|
|
474
|
-
>
|
|
475
|
-
{tWidget('cancel')}
|
|
476
|
-
</Button>
|
|
477
|
-
<Button
|
|
478
|
-
type="button"
|
|
479
|
-
onClick={handleSwitchDashboard}
|
|
480
|
-
disabled={
|
|
481
|
-
!selectedDashboard ||
|
|
482
|
-
isLoadingDashboards ||
|
|
483
|
-
selectedDashboard === currentSlug
|
|
484
|
-
}
|
|
485
|
-
>
|
|
486
|
-
{tMenu('switch')}
|
|
487
|
-
</Button>
|
|
488
|
-
</DialogFooter>
|
|
489
|
-
</DialogContent>
|
|
490
|
-
</Dialog>
|
|
491
439
|
</>
|
|
492
440
|
);
|
|
493
441
|
}
|
|
@@ -84,6 +84,60 @@ const deriveResponsiveLayout = (
|
|
|
84
84
|
});
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
const scaleToColumnCount = (
|
|
88
|
+
value: number,
|
|
89
|
+
sourceCols: number,
|
|
90
|
+
targetCols: number
|
|
91
|
+
) => {
|
|
92
|
+
if (sourceCols === targetCols) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Math.round((value * targetCols) / sourceCols);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const mapLayoutToBaseColumns = (
|
|
100
|
+
nextLayout: RGLLayout,
|
|
101
|
+
sourceCols: number,
|
|
102
|
+
targetCols: number,
|
|
103
|
+
previousLayout: Layout
|
|
104
|
+
): Layout => {
|
|
105
|
+
const previousItems = new Map(previousLayout.map((item) => [item.i, item]));
|
|
106
|
+
|
|
107
|
+
return nextLayout.map((item) => {
|
|
108
|
+
const previousItem = previousItems.get(item.i);
|
|
109
|
+
const nextW =
|
|
110
|
+
sourceCols === 1
|
|
111
|
+
? (previousItem?.w ?? 1)
|
|
112
|
+
: clamp(
|
|
113
|
+
scaleToColumnCount(item.w, sourceCols, targetCols),
|
|
114
|
+
1,
|
|
115
|
+
targetCols
|
|
116
|
+
);
|
|
117
|
+
const nextX =
|
|
118
|
+
sourceCols === 1
|
|
119
|
+
? clamp(previousItem?.x ?? 0, 0, Math.max(0, targetCols - nextW))
|
|
120
|
+
: clamp(
|
|
121
|
+
scaleToColumnCount(item.x, sourceCols, targetCols),
|
|
122
|
+
0,
|
|
123
|
+
Math.max(0, targetCols - nextW)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
i: item.i,
|
|
128
|
+
x: nextX,
|
|
129
|
+
y: item.y,
|
|
130
|
+
w: nextW,
|
|
131
|
+
h: item.h,
|
|
132
|
+
minW: previousItem?.minW ?? item.minW,
|
|
133
|
+
maxW: previousItem?.maxW ?? item.maxW,
|
|
134
|
+
minH: previousItem?.minH ?? item.minH,
|
|
135
|
+
maxH: previousItem?.maxH ?? item.maxH,
|
|
136
|
+
static: previousItem?.static ?? item.static,
|
|
137
|
+
} satisfies LayoutItem;
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
87
141
|
// Simple compaction function for grid layout
|
|
88
142
|
const compactLayout = (
|
|
89
143
|
layout: RGLLayout,
|
|
@@ -161,24 +215,15 @@ export function DraggableGrid({
|
|
|
161
215
|
};
|
|
162
216
|
}, []);
|
|
163
217
|
|
|
164
|
-
const
|
|
165
|
-
if (effectiveCols !== cols) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
218
|
+
const emitUserLayoutChange = (newLayout: RGLLayout) => {
|
|
169
219
|
const layouts = Array.isArray(newLayout) ? newLayout : [newLayout];
|
|
170
|
-
const convertedLayout =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
maxW: item.maxW,
|
|
178
|
-
minH: item.minH,
|
|
179
|
-
maxH: item.maxH,
|
|
180
|
-
static: item.static,
|
|
181
|
-
})) as LayoutItem[];
|
|
220
|
+
const convertedLayout = mapLayoutToBaseColumns(
|
|
221
|
+
layouts,
|
|
222
|
+
effectiveCols,
|
|
223
|
+
cols,
|
|
224
|
+
layout
|
|
225
|
+
);
|
|
226
|
+
|
|
182
227
|
onLayoutChange(convertedLayout);
|
|
183
228
|
};
|
|
184
229
|
|
|
@@ -208,7 +253,8 @@ export function DraggableGrid({
|
|
|
208
253
|
allowOverlap: !preventCollision,
|
|
209
254
|
compact: (layout, cols) => compactLayout(layout, cols, compactType),
|
|
210
255
|
}}
|
|
211
|
-
|
|
256
|
+
onDragStop={(nextLayout) => emitUserLayoutChange(nextLayout)}
|
|
257
|
+
onResizeStop={(nextLayout) => emitUserLayoutChange(nextLayout)}
|
|
212
258
|
>
|
|
213
259
|
{children}
|
|
214
260
|
</GridLayout>
|