@hed-hog/core 0.0.298 → 0.0.299
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 +3 -0
- package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard/dashboard.service.d.ts +3 -0
- package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +12 -0
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js +22 -0
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +15 -0
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.js +110 -3
- package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
- package/dist/dashboard/dashboard-component/dto/create.dto.d.ts +1 -0
- package/dist/dashboard/dashboard-component/dto/create.dto.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dto/create.dto.js +5 -0
- package/dist/dashboard/dashboard-component/dto/create.dto.js.map +1 -1
- package/dist/dashboard/dashboard-component/dto/update.dto.d.ts +1 -0
- package/dist/dashboard/dashboard-component/dto/update.dto.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dto/update.dto.js +5 -0
- package/dist/dashboard/dashboard-component/dto/update.dto.js.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +7 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +7 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +88 -4
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +1 -0
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +1 -0
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
- package/hedhog/data/dashboard_item.yaml +1 -1
- package/hedhog/data/route.yaml +12 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +141 -24
- package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +3 -0
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +136 -23
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +266 -85
- package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +11 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +192 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +236 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +108 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +66 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +122 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +63 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +73 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +73 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +123 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +118 -0
- package/hedhog/frontend/messages/en.json +3 -0
- package/hedhog/frontend/messages/pt.json +3 -0
- package/hedhog/frontend/public/dashboard-previews/.gitkeep +12 -0
- package/hedhog/frontend/public/dashboard-previews/account-security.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/active-users-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/activity-timeline.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/cash-balance-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/cash-flow-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/default-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/email-notifications.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/financial-alerts.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/login-history-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/mail-sent-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/mail-sent-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/menus-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/payable-30d-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/permissions-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/permissions-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/profile-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/receivable-30d-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/routes-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/session-activity-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/sessions-today-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-access-level.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-actions-today.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-consecutive-days.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-online-time.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/upcoming-payable.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/upcoming-receivable.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/user-growth-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/user-roles.png +0 -0
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/account-security.tsx.ejs +33 -29
- package/hedhog/frontend/widgets/active-users-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +223 -0
- package/hedhog/frontend/widgets/email-notifications.tsx.ejs +226 -0
- package/hedhog/frontend/widgets/locale-config.tsx.ejs +168 -0
- package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +115 -0
- package/hedhog/frontend/widgets/mail-config.tsx.ejs +199 -0
- package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +149 -0
- package/hedhog/frontend/widgets/menus-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/oauth-config.tsx.ejs +175 -0
- package/hedhog/frontend/widgets/permissions-card.tsx.ejs +61 -0
- package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +156 -0
- package/hedhog/frontend/widgets/profile-card.tsx.ejs +186 -0
- package/hedhog/frontend/widgets/routes-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +183 -0
- package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +62 -0
- package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/storage-config.tsx.ejs +196 -0
- package/hedhog/frontend/widgets/theme-config.tsx.ejs +213 -0
- package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +210 -0
- package/hedhog/frontend/widgets/user-roles.tsx.ejs +132 -0
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-sessions.tsx.ejs +1 -1
- package/hedhog/table/dashboard_component.yaml +7 -0
- package/package.json +4 -4
- package/src/dashboard/dashboard-component/dashboard-component.controller.ts +36 -12
- package/src/dashboard/dashboard-component/dashboard-component.service.ts +150 -3
- package/src/dashboard/dashboard-component/dto/create.dto.ts +4 -0
- package/src/dashboard/dashboard-component/dto/update.dto.ts +4 -0
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +108 -5
- /package/hedhog/frontend/app/dashboard/components/widgets/{active-users-card.tsx.ejs → core.active-users-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{activity-timeline.tsx.ejs → core.activity-timeline.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{email-notifications.tsx.ejs → core.email-notifications.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{locale-config.tsx.ejs → core.locale-config.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{login-history-chart.tsx.ejs → core.login-history-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{mail-config.tsx.ejs → core.mail-config.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-card.tsx.ejs → core.mail-sent-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-chart.tsx.ejs → core.mail-sent-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{menus-card.tsx.ejs → core.menus-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{oauth-config.tsx.ejs → core.oauth-config.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-card.tsx.ejs → core.permissions-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-chart.tsx.ejs → core.permissions-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{profile-card.tsx.ejs → core.profile-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{routes-card.tsx.ejs → core.routes-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{session-activity-chart.tsx.ejs → core.session-activity-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{sessions-today-card.tsx.ejs → core.sessions-today-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-access-level.tsx.ejs → core.stat-access-level.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-actions-today.tsx.ejs → core.stat-actions-today.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-consecutive-days.tsx.ejs → core.stat-consecutive-days.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-online-time.tsx.ejs → core.stat-online-time.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{storage-config.tsx.ejs → core.storage-config.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{theme-config.tsx.ejs → core.theme-config.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{user-growth-chart.tsx.ejs → core.user-growth-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{user-roles.tsx.ejs → core.user-roles.tsx.ejs} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { PaginationFooter } from '@/components/entity-list/pagination-footer';
|
|
4
|
+
import { Badge } from '@/components/ui/badge';
|
|
3
5
|
import { Button } from '@/components/ui/button';
|
|
4
6
|
import {
|
|
5
7
|
Card,
|
|
@@ -22,11 +24,20 @@ import {
|
|
|
22
24
|
DropdownMenuTrigger,
|
|
23
25
|
} from '@/components/ui/dropdown-menu';
|
|
24
26
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
27
|
+
import {
|
|
28
|
+
Sheet,
|
|
29
|
+
SheetContent,
|
|
30
|
+
SheetDescription,
|
|
31
|
+
SheetFooter,
|
|
32
|
+
SheetHeader,
|
|
33
|
+
SheetTitle,
|
|
34
|
+
} from '@/components/ui/sheet';
|
|
25
35
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
26
36
|
import { cn } from '@/lib/utils';
|
|
27
37
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
28
38
|
import {
|
|
29
39
|
IconArrowsRightLeft,
|
|
40
|
+
IconLayoutDashboard,
|
|
30
41
|
IconPlus,
|
|
31
42
|
IconSettings,
|
|
32
43
|
} from '@tabler/icons-react';
|
|
@@ -37,6 +48,7 @@ import { useState } from 'react';
|
|
|
37
48
|
interface DashboardComponent {
|
|
38
49
|
id: number;
|
|
39
50
|
slug: string;
|
|
51
|
+
library_slug?: string;
|
|
40
52
|
path: string;
|
|
41
53
|
min_width: number;
|
|
42
54
|
max_width?: number;
|
|
@@ -66,14 +78,93 @@ interface Dashboard {
|
|
|
66
78
|
|
|
67
79
|
interface AddWidgetSelectorDialogProps {
|
|
68
80
|
availableComponents: DashboardComponent[];
|
|
81
|
+
totalItems: number;
|
|
82
|
+
currentPage: number;
|
|
83
|
+
pageSize: number;
|
|
69
84
|
isLoading: boolean;
|
|
70
|
-
|
|
85
|
+
onPageChange: (page: number) => void;
|
|
86
|
+
onPageSizeChange: (pageSize: number) => void;
|
|
87
|
+
onAdd: (slugs: string[]) => Promise<void> | void;
|
|
71
88
|
currentSlug?: string;
|
|
72
89
|
}
|
|
73
90
|
|
|
91
|
+
const getWidgetLookupKey = (slug: string, librarySlug?: string): string => {
|
|
92
|
+
const parts = slug.split('.');
|
|
93
|
+
const baseSlug = parts[parts.length - 1] || slug;
|
|
94
|
+
|
|
95
|
+
if (librarySlug) {
|
|
96
|
+
return `${librarySlug}.${baseSlug}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return slug;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const getWidgetPreviewSlug = (slug: string): string => {
|
|
103
|
+
const parts = slug.split('.');
|
|
104
|
+
return parts[parts.length - 1] || slug;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
function WidgetPreview({
|
|
108
|
+
name,
|
|
109
|
+
slug,
|
|
110
|
+
width,
|
|
111
|
+
height,
|
|
112
|
+
isResizable,
|
|
113
|
+
resizableLabel,
|
|
114
|
+
fixedLabel,
|
|
115
|
+
}: {
|
|
116
|
+
name: string;
|
|
117
|
+
slug: string;
|
|
118
|
+
width: number;
|
|
119
|
+
height: number;
|
|
120
|
+
isResizable: boolean;
|
|
121
|
+
resizableLabel: string;
|
|
122
|
+
fixedLabel: string;
|
|
123
|
+
}) {
|
|
124
|
+
const previewSlug = getWidgetPreviewSlug(slug);
|
|
125
|
+
const previewUrl = `/libraries/core/dashboard-previews/${previewSlug}.png`;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="h-44 overflow-hidden rounded-xl border bg-background p-3">
|
|
129
|
+
<div className="flex h-full flex-col rounded-lg border border-dashed bg-muted/20 p-3">
|
|
130
|
+
<img
|
|
131
|
+
src={previewUrl}
|
|
132
|
+
alt={name}
|
|
133
|
+
className="mb-2 h-full w-full rounded-md border object-cover"
|
|
134
|
+
onError={(e) => {
|
|
135
|
+
e.currentTarget.style.display = 'none';
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
<div className="mb-2 flex items-center gap-2">
|
|
139
|
+
<div className="size-2 rounded-full bg-primary/70" />
|
|
140
|
+
<div className="h-2.5 w-20 rounded bg-muted" />
|
|
141
|
+
</div>
|
|
142
|
+
<div className="h-3 w-2/3 rounded bg-muted" />
|
|
143
|
+
<div className="mt-2 h-2.5 w-1/2 rounded bg-muted" />
|
|
144
|
+
<div className="mt-auto flex items-center gap-2">
|
|
145
|
+
<Badge variant="secondary" className="text-[10px]">
|
|
146
|
+
{width}x{height}
|
|
147
|
+
</Badge>
|
|
148
|
+
<Badge variant="outline" className="text-[10px]">
|
|
149
|
+
{isResizable ? resizableLabel : fixedLabel}
|
|
150
|
+
</Badge>
|
|
151
|
+
</div>
|
|
152
|
+
<p className="mt-2 truncate text-[10px] text-muted-foreground">
|
|
153
|
+
{name}
|
|
154
|
+
</p>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
74
160
|
export function AddWidgetSelectorDialog({
|
|
75
161
|
availableComponents,
|
|
162
|
+
totalItems,
|
|
163
|
+
currentPage,
|
|
164
|
+
pageSize,
|
|
76
165
|
isLoading,
|
|
166
|
+
onPageChange,
|
|
167
|
+
onPageSizeChange,
|
|
77
168
|
onAdd,
|
|
78
169
|
currentSlug = 'default',
|
|
79
170
|
}: AddWidgetSelectorDialogProps) {
|
|
@@ -84,7 +175,10 @@ export function AddWidgetSelectorDialog({
|
|
|
84
175
|
|
|
85
176
|
const [openWidgets, setOpenWidgets] = useState(false);
|
|
86
177
|
const [openDashboards, setOpenDashboards] = useState(false);
|
|
87
|
-
const [
|
|
178
|
+
const [selectedWidgets, setSelectedWidgets] = useState<Set<string>>(
|
|
179
|
+
new Set()
|
|
180
|
+
);
|
|
181
|
+
const [isAdding, setIsAdding] = useState(false);
|
|
88
182
|
const [selectedDashboard, setSelectedDashboard] = useState<string | null>(
|
|
89
183
|
null
|
|
90
184
|
);
|
|
@@ -103,11 +197,27 @@ export function AddWidgetSelectorDialog({
|
|
|
103
197
|
},
|
|
104
198
|
});
|
|
105
199
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
200
|
+
const toggleSelectedWidget = (slug: string) => {
|
|
201
|
+
setSelectedWidgets((prev) => {
|
|
202
|
+
const next = new Set(prev);
|
|
203
|
+
if (next.has(slug)) {
|
|
204
|
+
next.delete(slug);
|
|
205
|
+
} else {
|
|
206
|
+
next.add(slug);
|
|
207
|
+
}
|
|
208
|
+
return next;
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const handleAdd = async () => {
|
|
213
|
+
if (selectedWidgets.size === 0 || isAdding) return;
|
|
214
|
+
setIsAdding(true);
|
|
215
|
+
try {
|
|
216
|
+
await onAdd(Array.from(selectedWidgets));
|
|
217
|
+
setSelectedWidgets(new Set());
|
|
110
218
|
setOpenWidgets(false);
|
|
219
|
+
} finally {
|
|
220
|
+
setIsAdding(false);
|
|
111
221
|
}
|
|
112
222
|
};
|
|
113
223
|
|
|
@@ -138,98 +248,169 @@ export function AddWidgetSelectorDialog({
|
|
|
138
248
|
</DropdownMenuContent>
|
|
139
249
|
</DropdownMenu>
|
|
140
250
|
|
|
141
|
-
{/*
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
</
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
251
|
+
{/* Sheet de Adicionar Widgets */}
|
|
252
|
+
<Sheet
|
|
253
|
+
open={openWidgets}
|
|
254
|
+
onOpenChange={(open) => {
|
|
255
|
+
setOpenWidgets(open);
|
|
256
|
+
if (!open) {
|
|
257
|
+
setSelectedWidgets(new Set());
|
|
258
|
+
}
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
<SheetContent side="right" className="w-full p-0 sm:max-w-4xl">
|
|
262
|
+
<div className="flex h-full flex-col">
|
|
263
|
+
<SheetHeader className="border-b px-6 py-5 text-left">
|
|
264
|
+
<SheetTitle>{tWidget('title')}</SheetTitle>
|
|
265
|
+
<SheetDescription>
|
|
266
|
+
{tWidget('selectedOfTotal', {
|
|
267
|
+
selected: selectedWidgets.size,
|
|
268
|
+
total: totalItems,
|
|
269
|
+
})}
|
|
270
|
+
</SheetDescription>
|
|
271
|
+
</SheetHeader>
|
|
272
|
+
|
|
273
|
+
<div className="grid min-h-0 flex-1 gap-4 p-4">
|
|
274
|
+
<ScrollArea className="min-h-0 pr-4">
|
|
275
|
+
{isLoading ? (
|
|
276
|
+
<div className="grid gap-3">
|
|
277
|
+
<Skeleton className="h-28 w-full" />
|
|
278
|
+
<Skeleton className="h-28 w-full" />
|
|
279
|
+
<Skeleton className="h-28 w-full" />
|
|
162
280
|
</div>
|
|
163
281
|
) : (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
282
|
+
<div className="grid gap-3">
|
|
283
|
+
{availableComponents.length === 0 ? (
|
|
284
|
+
<div className="flex min-h-55 flex-col items-center justify-center rounded-xl border border-dashed text-center">
|
|
285
|
+
<p className="text-sm text-muted-foreground">
|
|
286
|
+
{tWidget('noComponentsAvailable')}
|
|
287
|
+
</p>
|
|
288
|
+
</div>
|
|
289
|
+
) : (
|
|
290
|
+
availableComponents.map((component) => {
|
|
291
|
+
const name =
|
|
292
|
+
component.dashboard_component_locale?.[0]?.name ||
|
|
293
|
+
component.slug;
|
|
294
|
+
const selectionKey = getWidgetLookupKey(
|
|
295
|
+
component.slug,
|
|
296
|
+
component.library_slug
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<Card
|
|
301
|
+
key={selectionKey}
|
|
302
|
+
className={cn(
|
|
303
|
+
'cursor-pointer border p-4 transition-all hover:border-primary/40 hover:bg-accent/40',
|
|
304
|
+
selectedWidgets.has(selectionKey) &&
|
|
305
|
+
'border-primary bg-accent/50'
|
|
306
|
+
)}
|
|
307
|
+
onClick={() => toggleSelectedWidget(selectionKey)}
|
|
308
|
+
>
|
|
309
|
+
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_220px]">
|
|
310
|
+
<div className="flex items-start gap-3">
|
|
311
|
+
<div className="bg-primary/10 text-primary flex size-10 items-center justify-center rounded-xl">
|
|
312
|
+
<IconLayoutDashboard className="size-5" />
|
|
313
|
+
</div>
|
|
314
|
+
<div className="min-w-0 flex-1">
|
|
315
|
+
<CardTitle className="truncate text-base">
|
|
316
|
+
{name}
|
|
317
|
+
</CardTitle>
|
|
318
|
+
<CardDescription className="mt-1 text-xs">
|
|
319
|
+
{selectionKey}
|
|
320
|
+
</CardDescription>
|
|
321
|
+
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
322
|
+
<Badge
|
|
323
|
+
variant="secondary"
|
|
324
|
+
className="text-[11px]"
|
|
325
|
+
>
|
|
326
|
+
{tWidget('dimensions')}: {component.width}
|
|
327
|
+
x{component.height}
|
|
328
|
+
</Badge>
|
|
329
|
+
<Badge
|
|
330
|
+
variant="outline"
|
|
331
|
+
className="text-[11px]"
|
|
332
|
+
>
|
|
333
|
+
{component.is_resizable
|
|
334
|
+
? tWidget('resizable')
|
|
335
|
+
: tWidget('fixedSize')}
|
|
336
|
+
</Badge>
|
|
337
|
+
</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
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
<div className="hidden lg:block">
|
|
356
|
+
<WidgetPreview
|
|
357
|
+
name={name}
|
|
358
|
+
slug={component.slug}
|
|
359
|
+
width={component.width}
|
|
360
|
+
height={component.height}
|
|
361
|
+
isResizable={component.is_resizable}
|
|
362
|
+
resizableLabel={tWidget('resizable')}
|
|
363
|
+
fixedLabel={tWidget('fixedSize')}
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
194
366
|
</div>
|
|
195
|
-
</
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
)
|
|
199
|
-
|
|
367
|
+
</Card>
|
|
368
|
+
);
|
|
369
|
+
})
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
200
372
|
)}
|
|
373
|
+
</ScrollArea>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<SheetFooter className="border-t px-6 py-4">
|
|
377
|
+
<div className="flex w-full flex-col gap-4">
|
|
378
|
+
<PaginationFooter
|
|
379
|
+
currentPage={currentPage}
|
|
380
|
+
pageSize={pageSize}
|
|
381
|
+
totalItems={totalItems}
|
|
382
|
+
selectedCount={selectedWidgets.size}
|
|
383
|
+
onPageChange={onPageChange}
|
|
384
|
+
onPageSizeChange={onPageSizeChange}
|
|
385
|
+
/>
|
|
386
|
+
<div className="flex justify-end">
|
|
387
|
+
<Button
|
|
388
|
+
type="button"
|
|
389
|
+
className="cursor-pointer"
|
|
390
|
+
onClick={handleAdd}
|
|
391
|
+
disabled={
|
|
392
|
+
selectedWidgets.size === 0 || isLoading || isAdding
|
|
393
|
+
}
|
|
394
|
+
>
|
|
395
|
+
{tWidget('add')}
|
|
396
|
+
</Button>
|
|
397
|
+
</div>
|
|
201
398
|
</div>
|
|
202
|
-
|
|
203
|
-
</
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
type="button"
|
|
207
|
-
variant="outline"
|
|
208
|
-
onClick={() => setOpenWidgets(false)}
|
|
209
|
-
>
|
|
210
|
-
{tWidget('cancel')}
|
|
211
|
-
</Button>
|
|
212
|
-
<Button
|
|
213
|
-
type="button"
|
|
214
|
-
onClick={handleAdd}
|
|
215
|
-
disabled={!selectedWidget || isLoading}
|
|
216
|
-
>
|
|
217
|
-
{tWidget('add')}
|
|
218
|
-
</Button>
|
|
219
|
-
</DialogFooter>
|
|
220
|
-
</DialogContent>
|
|
221
|
-
</Dialog>
|
|
399
|
+
</SheetFooter>
|
|
400
|
+
</div>
|
|
401
|
+
</SheetContent>
|
|
402
|
+
</Sheet>
|
|
222
403
|
|
|
223
404
|
{/* Diálogo de Trocar Dashboard */}
|
|
224
405
|
<Dialog open={openDashboards} onOpenChange={setOpenDashboards}>
|
|
225
|
-
<DialogContent className="sm:max-w-
|
|
406
|
+
<DialogContent className="sm:max-w-150">
|
|
226
407
|
<DialogHeader>
|
|
227
408
|
<DialogTitle>{tMenu('selectDashboardTitle')}</DialogTitle>
|
|
228
409
|
<DialogDescription>
|
|
229
410
|
{tMenu('selectDashboardDescription')}
|
|
230
411
|
</DialogDescription>
|
|
231
412
|
</DialogHeader>
|
|
232
|
-
<ScrollArea className="max-h-
|
|
413
|
+
<ScrollArea className="max-h-100 pr-4">
|
|
233
414
|
{isLoadingDashboards ? (
|
|
234
415
|
<div className="grid gap-3">
|
|
235
416
|
<Skeleton className="h-24 w-full" />
|
|
@@ -239,7 +420,7 @@ export function AddWidgetSelectorDialog({
|
|
|
239
420
|
) : (
|
|
240
421
|
<div className="grid gap-3">
|
|
241
422
|
{!userDashboards || userDashboards.length === 0 ? (
|
|
242
|
-
<div className="flex min-h-
|
|
423
|
+
<div className="flex min-h-50 flex-col items-center justify-center text-center">
|
|
243
424
|
<p className="text-muted-foreground text-sm">
|
|
244
425
|
{tMenu('noDashboardsAvailable')}
|
|
245
426
|
</p>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Reserved for widget templates contributed directly by this library.
|
|
2
|
+
|
|
3
|
+
Expected naming convention:
|
|
4
|
+
- core widgets: <slug>.tsx.ejs (legacy-compatible)
|
|
5
|
+
- cross-library-safe widgets: <library>.<slug>.tsx.ejs
|
|
6
|
+
|
|
7
|
+
The Hedhog CLI should copy these templates to:
|
|
8
|
+
apps/admin/src/app/(app)/(libraries)/core/dashboard/components/widgets/
|
|
9
|
+
|
|
10
|
+
Optional static previews can follow the same namespaced slug and be copied to:
|
|
11
|
+
apps/admin/public/libraries/core/dashboard-previews/
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
} from '@/components/ui/card';
|
|
9
|
+
import { Progress } from '@/components/ui/progress';
|
|
10
|
+
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
11
|
+
import type { AllWidgetsData } from '@/types/widget-data';
|
|
12
|
+
import {
|
|
13
|
+
AlertTriangle,
|
|
14
|
+
CheckCircle2,
|
|
15
|
+
ChevronRight,
|
|
16
|
+
Lock,
|
|
17
|
+
Mail,
|
|
18
|
+
ShieldCheck,
|
|
19
|
+
Smartphone,
|
|
20
|
+
} from 'lucide-react';
|
|
21
|
+
import { useTranslations } from 'next-intl';
|
|
22
|
+
import { useEffect, useState } from 'react';
|
|
23
|
+
|
|
24
|
+
import type { AccountSecurityData } from '@/types/widget-data';
|
|
25
|
+
import { useRouter } from 'next/navigation';
|
|
26
|
+
import { WidgetWrapper } from '../widget-wrapper';
|
|
27
|
+
|
|
28
|
+
const ICON_MAP: Record<string, React.ElementType> = {
|
|
29
|
+
password: Lock,
|
|
30
|
+
'2fa': Smartphone,
|
|
31
|
+
email: Mail,
|
|
32
|
+
sessions: AlertTriangle,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const CHECK_ROUTE_MAP: Record<string, string> = {
|
|
36
|
+
password: '/core/account/password',
|
|
37
|
+
'2fa': '/core/account/2fa',
|
|
38
|
+
email: '/core/account/email',
|
|
39
|
+
sessions: '/core/account/sessions',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
43
|
+
const t = useTranslations('core.DashboardPage.accountSecurity');
|
|
44
|
+
const router = useRouter();
|
|
45
|
+
const [score, setScore] = useState(0);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const timer = setTimeout(() => setScore(data.score), 300);
|
|
49
|
+
return () => clearTimeout(timer);
|
|
50
|
+
}, [data.score]);
|
|
51
|
+
|
|
52
|
+
const scoreColor =
|
|
53
|
+
score >= 80
|
|
54
|
+
? 'text-emerald-600'
|
|
55
|
+
: score >= 50
|
|
56
|
+
? 'text-amber-600'
|
|
57
|
+
: 'text-red-600';
|
|
58
|
+
|
|
59
|
+
const progressColor =
|
|
60
|
+
score >= 80
|
|
61
|
+
? 'hsl(160, 84%, 39%)'
|
|
62
|
+
: score >= 50
|
|
63
|
+
? 'hsl(38, 92%, 50%)'
|
|
64
|
+
: 'hsl(0, 84%, 60%)';
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
68
|
+
<CardHeader className="shrink-0 px-4 pb-2 pt-4 sm:px-5">
|
|
69
|
+
<div className="flex items-center gap-2">
|
|
70
|
+
<ShieldCheck className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
|
71
|
+
<div>
|
|
72
|
+
<CardTitle className="text-base font-semibold">
|
|
73
|
+
{t('title')}
|
|
74
|
+
</CardTitle>
|
|
75
|
+
<CardDescription className="text-xs sm:text-sm">
|
|
76
|
+
{t('description')}
|
|
77
|
+
</CardDescription>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</CardHeader>
|
|
81
|
+
<CardContent className="flex min-h-0 flex-1 flex-col overflow-auto px-4 pb-3 pt-0 sm:px-5 sm:pb-4">
|
|
82
|
+
<div className="mb-2 flex flex-col items-center gap-1.5 rounded-xl bg-muted/50 p-2 sm:mb-3 sm:gap-2 sm:p-3">
|
|
83
|
+
<div className="flex items-baseline gap-1">
|
|
84
|
+
<span
|
|
85
|
+
className={`text-2xl font-bold tracking-tight sm:text-3xl ${scoreColor}`}
|
|
86
|
+
>
|
|
87
|
+
{score}
|
|
88
|
+
</span>
|
|
89
|
+
<span className="text-xs text-muted-foreground sm:text-sm">
|
|
90
|
+
/100
|
|
91
|
+
</span>
|
|
92
|
+
</div>
|
|
93
|
+
<Progress
|
|
94
|
+
value={score}
|
|
95
|
+
className="h-1.5 w-full max-w-70"
|
|
96
|
+
style={
|
|
97
|
+
{
|
|
98
|
+
'--progress-foreground': progressColor,
|
|
99
|
+
} as any
|
|
100
|
+
}
|
|
101
|
+
/>
|
|
102
|
+
<p className="text-[10px] text-muted-foreground sm:text-[11px]">
|
|
103
|
+
{score >= 80 ? t('wellProtected') : t('recommendProtections')}
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="flex flex-col gap-1">
|
|
108
|
+
{data.checks.map((item) => {
|
|
109
|
+
const Icon = ICON_MAP[item.id] ?? ShieldCheck;
|
|
110
|
+
return (
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
key={item.id}
|
|
114
|
+
onClick={() =>
|
|
115
|
+
router.push(CHECK_ROUTE_MAP[item.id] ?? '/core/account')
|
|
116
|
+
}
|
|
117
|
+
className="group flex w-full cursor-pointer flex-wrap items-start gap-2 rounded-lg p-2 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:items-center sm:gap-2.5 sm:p-2.5"
|
|
118
|
+
>
|
|
119
|
+
<div
|
|
120
|
+
className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-lg sm:h-8 sm:w-8 ${
|
|
121
|
+
item.enabled
|
|
122
|
+
? 'bg-emerald-50 dark:bg-emerald-950/40'
|
|
123
|
+
: 'bg-muted'
|
|
124
|
+
}`}
|
|
125
|
+
>
|
|
126
|
+
<Icon
|
|
127
|
+
className={`h-3.5 w-3.5 ${
|
|
128
|
+
item.enabled
|
|
129
|
+
? 'text-emerald-600 dark:text-emerald-400'
|
|
130
|
+
: 'text-muted-foreground'
|
|
131
|
+
}`}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
135
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
136
|
+
<span className="wrap-break-word text-[12px] font-medium text-foreground sm:text-[13px]">
|
|
137
|
+
{t(`labels.${item.labelKey}` as any) || item.labelKey}
|
|
138
|
+
</span>
|
|
139
|
+
{item.enabled ? (
|
|
140
|
+
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
|
|
141
|
+
) : (
|
|
142
|
+
<AlertTriangle className="h-3.5 w-3.5 text-amber-500" />
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
<span className="text-[10px] text-muted-foreground sm:text-[11px]">
|
|
146
|
+
{t(`descriptions.${item.descriptionKey}` as any) ||
|
|
147
|
+
item.descriptionKey}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="mt-0.5 flex w-full shrink-0 items-center justify-end gap-1 text-[11px] font-medium text-foreground sm:mt-0 sm:w-auto sm:text-xs">
|
|
151
|
+
{!item.enabled && <span>{t('activate')}</span>}
|
|
152
|
+
<ChevronRight className="h-3 w-3" />
|
|
153
|
+
</div>
|
|
154
|
+
</button>
|
|
155
|
+
);
|
|
156
|
+
})}
|
|
157
|
+
</div>
|
|
158
|
+
</CardContent>
|
|
159
|
+
</Card>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface AccountSecurityProps {
|
|
164
|
+
widget?: { name?: string };
|
|
165
|
+
onRemove?: () => void;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default function AccountSecurity({
|
|
169
|
+
widget,
|
|
170
|
+
onRemove,
|
|
171
|
+
}: AccountSecurityProps) {
|
|
172
|
+
const { data, isLoading, isError, isAccessDenied } = useWidgetData<
|
|
173
|
+
AllWidgetsData,
|
|
174
|
+
AccountSecurityData
|
|
175
|
+
>({
|
|
176
|
+
endpoint: '/dashboard-core/widgets/me',
|
|
177
|
+
queryKey: 'widget-me',
|
|
178
|
+
select: (d) => d.accountSecurity,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<WidgetWrapper
|
|
183
|
+
isLoading={isLoading}
|
|
184
|
+
isError={isError}
|
|
185
|
+
isAccessDenied={isAccessDenied}
|
|
186
|
+
widgetName={widget?.name ?? 'account-security'}
|
|
187
|
+
onRemove={onRemove}
|
|
188
|
+
>
|
|
189
|
+
{data && <AccountSecurityContent data={data} />}
|
|
190
|
+
</WidgetWrapper>
|
|
191
|
+
);
|
|
192
|
+
}
|