@hed-hog/core 0.0.185 → 0.0.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/hedhog/frontend/app/account/2fa/page.tsx.ejs +5 -0
  2. package/hedhog/frontend/app/account/accounts/page.tsx.ejs +5 -0
  3. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +356 -0
  4. package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +379 -0
  5. package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +184 -0
  6. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +144 -0
  7. package/hedhog/frontend/app/account/components/email-request-dialog.tsx.ejs +96 -0
  8. package/hedhog/frontend/app/account/components/mfa-add-buttons.tsx.ejs +43 -0
  9. package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +115 -0
  10. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +236 -0
  11. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +209 -0
  12. package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +192 -0
  13. package/hedhog/frontend/app/account/components/regenerate-codes-dialog.tsx.ejs +372 -0
  14. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +337 -0
  15. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +393 -0
  16. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +332 -0
  17. package/hedhog/frontend/app/account/email/page.tsx.ejs +5 -0
  18. package/hedhog/frontend/app/account/hooks/use-mfa-methods.ts.ejs +27 -0
  19. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +461 -0
  20. package/hedhog/frontend/app/account/layout.tsx.ejs +105 -0
  21. package/hedhog/frontend/app/account/lib/mfa-utils.tsx.ejs +37 -0
  22. package/hedhog/frontend/app/account/page.tsx.ejs +5 -0
  23. package/hedhog/frontend/app/account/password/page.tsx.ejs +5 -0
  24. package/hedhog/frontend/app/account/profile/page.tsx.ejs +5 -0
  25. package/hedhog/frontend/app/account/sessions/page.tsx.ejs +5 -0
  26. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +490 -0
  27. package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +62 -0
  28. package/hedhog/frontend/app/configurations/layout.tsx.ejs +316 -0
  29. package/hedhog/frontend/app/configurations/page.tsx.ejs +35 -0
  30. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +351 -0
  31. package/hedhog/frontend/app/dashboard/[slug]/page.tsx.ejs +11 -0
  32. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +62 -0
  33. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +45 -0
  34. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +196 -0
  35. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +63 -0
  36. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +516 -0
  37. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +753 -0
  38. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +516 -0
  39. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +489 -0
  40. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +621 -0
  41. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -0
  42. package/hedhog/frontend/app/mail/log/page.tsx.ejs +312 -0
  43. package/hedhog/frontend/app/mail/template/page.tsx.ejs +1177 -0
  44. package/hedhog/frontend/app/preferences/page.tsx.ejs +448 -0
  45. package/hedhog/frontend/app/roles/menus.tsx.ejs +504 -0
  46. package/hedhog/frontend/app/roles/page.tsx.ejs +814 -0
  47. package/hedhog/frontend/app/roles/routes.tsx.ejs +397 -0
  48. package/hedhog/frontend/app/roles/users.tsx.ejs +306 -0
  49. package/hedhog/frontend/app/users/active-session.tsx.ejs +159 -0
  50. package/hedhog/frontend/app/users/identifiers.tsx.ejs +279 -0
  51. package/hedhog/frontend/app/users/page.tsx.ejs +1257 -0
  52. package/hedhog/frontend/app/users/permissions.tsx.ejs +155 -0
  53. package/hedhog/frontend/messages/en.json +1080 -0
  54. package/hedhog/frontend/messages/pt.json +1135 -0
  55. package/package.json +4 -4
@@ -0,0 +1,489 @@
1
+ 'use client';
2
+
3
+ import {
4
+ AlertDialog,
5
+ AlertDialogAction,
6
+ AlertDialogCancel,
7
+ AlertDialogContent,
8
+ AlertDialogDescription,
9
+ AlertDialogFooter,
10
+ AlertDialogHeader,
11
+ AlertDialogTitle,
12
+ } from '@/components/ui/alert-dialog';
13
+ import { Button } from '@/components/ui/button';
14
+ import {
15
+ Dialog,
16
+ DialogContent,
17
+ DialogDescription,
18
+ DialogFooter,
19
+ DialogHeader,
20
+ DialogTitle,
21
+ DialogTrigger,
22
+ } from '@/components/ui/dialog';
23
+ import { Input } from '@/components/ui/input';
24
+ import { Label } from '@/components/ui/label';
25
+ import {
26
+ Select,
27
+ SelectContent,
28
+ SelectItem,
29
+ SelectTrigger,
30
+ SelectValue,
31
+ } from '@/components/ui/select';
32
+ import {
33
+ Table,
34
+ TableBody,
35
+ TableCell,
36
+ TableHead,
37
+ TableHeader,
38
+ TableRow,
39
+ } from '@/components/ui/table';
40
+ import { useDebounce } from '@/hooks/use-debounce';
41
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
42
+ import {
43
+ IconChevronLeft,
44
+ IconChevronRight,
45
+ IconChevronsLeft,
46
+ IconChevronsRight,
47
+ IconEdit,
48
+ IconGlobe,
49
+ IconPlus,
50
+ IconTrash,
51
+ } from '@tabler/icons-react';
52
+ import { useTranslations } from 'next-intl';
53
+ import { useState } from 'react';
54
+ import { toast } from 'sonner';
55
+
56
+ interface Dashboard {
57
+ id: number;
58
+ slug: string;
59
+ dashboard_locale: Array<{
60
+ locale: { code: string };
61
+ name: string;
62
+ }>;
63
+ }
64
+
65
+ export function DashboardsTab() {
66
+ const t = useTranslations('core.DashboardManagement');
67
+ const { request, locales, currentLocaleCode } = useApp();
68
+ const [open, setOpen] = useState(false);
69
+ const [selectedDashboard, setSelectedDashboard] = useState<any | null>(null);
70
+ const [isNew, setIsNew] = useState(false);
71
+ const [selectedLocale, setSelectedLocale] = useState(currentLocaleCode);
72
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
73
+ const [dashboardToDelete, setDashboardToDelete] = useState<number | null>(
74
+ null
75
+ );
76
+ const [page, setPage] = useState(1);
77
+ const [pageSize, setPageSize] = useState(10);
78
+ const [searchQuery, setSearchQuery] = useState('');
79
+ const debouncedSearch = useDebounce(searchQuery, 500);
80
+
81
+ const {
82
+ data: paginatedData,
83
+ isLoading,
84
+ refetch,
85
+ } = useQuery<{ data: Dashboard[]; total: number }>({
86
+ queryKey: [
87
+ 'dashboards',
88
+ page,
89
+ pageSize,
90
+ debouncedSearch,
91
+ currentLocaleCode,
92
+ ],
93
+ queryFn: async () => {
94
+ const params = new URLSearchParams();
95
+ params.set('page', String(page));
96
+ params.set('pageSize', String(pageSize));
97
+ if (debouncedSearch) params.set('search', debouncedSearch);
98
+
99
+ const response = await request<{ data: Dashboard[]; total: number }>({
100
+ url: `/dashboard?${params.toString()}`,
101
+ method: 'GET',
102
+ });
103
+ return response.data;
104
+ },
105
+ });
106
+
107
+ const dashboards = paginatedData?.data ?? [];
108
+ const total = paginatedData?.total ?? 0;
109
+ const totalPages = Math.ceil(total / pageSize);
110
+
111
+ const handleNew = () => {
112
+ const newDashboard: any = {
113
+ slug: '',
114
+ locale: {},
115
+ };
116
+
117
+ locales.forEach((locale: any) => {
118
+ newDashboard.locale[locale.code] = { name: '' };
119
+ });
120
+
121
+ setSelectedDashboard(newDashboard);
122
+ setIsNew(true);
123
+ setSelectedLocale(currentLocaleCode);
124
+ setOpen(true);
125
+ };
126
+
127
+ const handleEdit = async (dashboard: Dashboard) => {
128
+ try {
129
+ const { data } = await request<any>({
130
+ url: `/dashboard/${dashboard.id}`,
131
+ method: 'GET',
132
+ });
133
+
134
+ const localeData: Record<string, { name: string }> = {};
135
+ if (data.dashboard_locale && Array.isArray(data.dashboard_locale)) {
136
+ data.dashboard_locale.forEach((dl: any) => {
137
+ const localeCode = locales.find(
138
+ (l: any) => l.code === dl.locale?.code
139
+ )?.code;
140
+ if (localeCode) {
141
+ localeData[localeCode] = { name: dl.name || '' };
142
+ }
143
+ });
144
+ }
145
+
146
+ locales.forEach((locale: any) => {
147
+ if (!localeData[locale.code]) {
148
+ localeData[locale.code] = { name: '' };
149
+ }
150
+ });
151
+
152
+ setSelectedDashboard({
153
+ ...data,
154
+ locale: localeData,
155
+ });
156
+ setIsNew(false);
157
+ setSelectedLocale(currentLocaleCode);
158
+ setOpen(true);
159
+ } catch (error) {
160
+ console.error('Erro ao carregar dashboard:', error);
161
+ toast.error('Erro ao carregar dashboard');
162
+ }
163
+ };
164
+
165
+ const handleSave = async () => {
166
+ if (!selectedDashboard || !selectedDashboard.locale) return;
167
+
168
+ const payload = {
169
+ slug: selectedDashboard.slug,
170
+ locale: selectedDashboard.locale,
171
+ };
172
+
173
+ try {
174
+ if (selectedDashboard.id) {
175
+ await request({
176
+ url: `/dashboard/${selectedDashboard.id}`,
177
+ method: 'PATCH',
178
+ data: payload,
179
+ });
180
+ toast.success(t('dashboardUpdated'));
181
+ } else {
182
+ await request({
183
+ url: '/dashboard',
184
+ method: 'POST',
185
+ data: payload,
186
+ });
187
+ toast.success(t('dashboardCreated'));
188
+ }
189
+
190
+ setOpen(false);
191
+ setSelectedDashboard(null);
192
+ refetch();
193
+ } catch (error) {
194
+ console.error('Erro ao salvar dashboard:', error);
195
+ toast.error('Erro ao salvar dashboard');
196
+ }
197
+ };
198
+
199
+ const handleDeleteClick = (id: number) => {
200
+ setDashboardToDelete(id);
201
+ setDeleteDialogOpen(true);
202
+ };
203
+
204
+ const handleDeleteConfirm = async () => {
205
+ if (!dashboardToDelete) return;
206
+
207
+ try {
208
+ await request({
209
+ url: `/dashboard/${dashboardToDelete}`,
210
+ method: 'DELETE',
211
+ });
212
+ toast.success(t('dashboardUpdated'));
213
+ refetch();
214
+ } catch (error) {
215
+ console.error('Erro ao excluir dashboard:', error);
216
+ toast.error('Erro ao excluir dashboard');
217
+ } finally {
218
+ setDeleteDialogOpen(false);
219
+ setDashboardToDelete(null);
220
+ }
221
+ };
222
+
223
+ return (
224
+ <div className="space-y-4">
225
+ <div className="flex justify-between items-center">
226
+ <div>
227
+ <h2 className="text-lg font-semibold">{t('dashboardsTab')}</h2>
228
+ <p className="text-muted-foreground text-sm">
229
+ {t('manageDashboards')}
230
+ </p>
231
+ </div>
232
+ <Dialog open={open} onOpenChange={setOpen}>
233
+ <DialogTrigger asChild>
234
+ <Button size="sm" className="gap-2" onClick={handleNew}>
235
+ <IconPlus className="size-4" />
236
+ {t('newDashboard')}
237
+ </Button>
238
+ </DialogTrigger>
239
+ <DialogContent className="max-w-xl max-h-[95vh] overflow-y-auto">
240
+ <DialogHeader>
241
+ <DialogTitle>
242
+ {isNew ? t('newDashboard') : t('editDashboard')}
243
+ </DialogTitle>
244
+ <DialogDescription>{t('fillDashboardData')}</DialogDescription>
245
+ </DialogHeader>
246
+ {selectedDashboard && (
247
+ <div className="space-y-4">
248
+ {!isNew && (
249
+ <div className="flex items-center gap-2">
250
+ <IconGlobe className="size-4" />
251
+ <Select
252
+ value={selectedLocale}
253
+ onValueChange={setSelectedLocale}
254
+ >
255
+ <SelectTrigger className="w-[200px]">
256
+ <SelectValue />
257
+ </SelectTrigger>
258
+ <SelectContent>
259
+ {locales.map((locale: any) => (
260
+ <SelectItem key={locale.code} value={locale.code}>
261
+ {locale.name}
262
+ </SelectItem>
263
+ ))}
264
+ </SelectContent>
265
+ </Select>
266
+ </div>
267
+ )}
268
+ <div className="space-y-2">
269
+ <Label htmlFor="slug">{t('slug')}</Label>
270
+ <Input
271
+ id="slug"
272
+ placeholder="admin-dashboard"
273
+ value={selectedDashboard.slug || ''}
274
+ onChange={(e) =>
275
+ setSelectedDashboard({
276
+ ...selectedDashboard,
277
+ slug: e.target.value,
278
+ })
279
+ }
280
+ />
281
+ </div>
282
+ <div className="space-y-2">
283
+ <Label htmlFor="name">{t('name')}</Label>
284
+ <Input
285
+ id="name"
286
+ placeholder="Dashboard Admin"
287
+ value={
288
+ selectedDashboard.locale?.[selectedLocale]?.name || ''
289
+ }
290
+ onChange={(e) =>
291
+ setSelectedDashboard({
292
+ ...selectedDashboard,
293
+ locale: {
294
+ ...selectedDashboard.locale,
295
+ [selectedLocale]: {
296
+ ...selectedDashboard.locale?.[selectedLocale],
297
+ name: e.target.value,
298
+ },
299
+ },
300
+ })
301
+ }
302
+ />
303
+ </div>
304
+ </div>
305
+ )}
306
+ <DialogFooter>
307
+ <Button onClick={handleSave}>{t('save')}</Button>
308
+ </DialogFooter>
309
+ </DialogContent>
310
+ </Dialog>
311
+ </div>
312
+
313
+ <div className="flex items-center gap-2 mb-4">
314
+ <Input
315
+ placeholder={`${t('searchPlaceholder') || 'Buscar'}...`}
316
+ value={searchQuery}
317
+ onChange={(e) => {
318
+ setSearchQuery(e.target.value);
319
+ setPage(1);
320
+ }}
321
+ className="max-w-sm"
322
+ />
323
+ </div>
324
+
325
+ <div className="rounded-md border">
326
+ <Table>
327
+ <TableHeader>
328
+ <TableRow>
329
+ <TableHead>ID</TableHead>
330
+ <TableHead>{t('slug')}</TableHead>
331
+ <TableHead>{t('name')}</TableHead>
332
+ <TableHead className="w-[100px]">{t('actions')}</TableHead>
333
+ </TableRow>
334
+ </TableHeader>
335
+ <TableBody>
336
+ {isLoading ? (
337
+ <TableRow>
338
+ <TableCell colSpan={4} className="text-center">
339
+ {t('loading')}
340
+ </TableCell>
341
+ </TableRow>
342
+ ) : dashboards && dashboards.length > 0 ? (
343
+ dashboards.map((dashboard) => {
344
+ const name =
345
+ dashboard.dashboard_locale.find(
346
+ (l) => l.locale.code === currentLocaleCode
347
+ )?.name || dashboard.slug;
348
+ return (
349
+ <TableRow key={dashboard.id}>
350
+ <TableCell>{dashboard.id}</TableCell>
351
+ <TableCell className="font-mono">
352
+ {dashboard.slug}
353
+ </TableCell>
354
+ <TableCell>{name}</TableCell>
355
+ <TableCell>
356
+ <div className="flex gap-2">
357
+ <Button
358
+ size="icon"
359
+ variant="ghost"
360
+ onClick={() => handleEdit(dashboard)}
361
+ title={t('editDashboardTitle')}
362
+ >
363
+ <IconEdit className="size-4" />
364
+ </Button>
365
+ <Button
366
+ size="icon"
367
+ variant="ghost"
368
+ onClick={() => handleDeleteClick(dashboard.id)}
369
+ title={t('deleteDashboardTitle')}
370
+ >
371
+ <IconTrash className="size-4" />
372
+ </Button>
373
+ </div>
374
+ </TableCell>
375
+ </TableRow>
376
+ );
377
+ })
378
+ ) : (
379
+ <TableRow>
380
+ <TableCell colSpan={4} className="text-center">
381
+ {t('noDashboards')}
382
+ </TableCell>
383
+ </TableRow>
384
+ )}
385
+ </TableBody>
386
+ </Table>
387
+ </div>
388
+
389
+ {/* Paginação */}
390
+ {totalPages > 1 && (
391
+ <div className="flex items-center justify-between px-2">
392
+ <div className="text-muted-foreground text-sm">
393
+ {t('showingResults', {
394
+ from: (page - 1) * pageSize + 1,
395
+ to: Math.min(page * pageSize, total),
396
+ total: total,
397
+ })}
398
+ </div>
399
+ <div className="flex items-center gap-2">
400
+ <div className="flex items-center gap-2">
401
+ <Label htmlFor="pageSize" className="text-sm">
402
+ {t('itemsPerPage')}
403
+ </Label>
404
+ <Select
405
+ value={String(pageSize)}
406
+ onValueChange={(value) => {
407
+ setPageSize(Number(value));
408
+ setPage(1);
409
+ }}
410
+ >
411
+ <SelectTrigger className="w-20" id="pageSize">
412
+ <SelectValue />
413
+ </SelectTrigger>
414
+ <SelectContent>
415
+ {[10, 20, 30, 50].map((size) => (
416
+ <SelectItem key={size} value={String(size)}>
417
+ {size}
418
+ </SelectItem>
419
+ ))}
420
+ </SelectContent>
421
+ </Select>
422
+ </div>
423
+ <div className="text-sm">
424
+ {t('pageOf', { page: page, total: totalPages })}
425
+ </div>
426
+ <div className="flex gap-1">
427
+ <Button
428
+ variant="outline"
429
+ size="icon"
430
+ className="size-8"
431
+ onClick={() => setPage(1)}
432
+ disabled={page === 1}
433
+ >
434
+ <IconChevronsLeft className="size-4" />
435
+ </Button>
436
+ <Button
437
+ variant="outline"
438
+ size="icon"
439
+ className="size-8"
440
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
441
+ disabled={page === 1}
442
+ >
443
+ <IconChevronLeft className="size-4" />
444
+ </Button>
445
+ <Button
446
+ variant="outline"
447
+ size="icon"
448
+ className="size-8"
449
+ onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
450
+ disabled={page === totalPages}
451
+ >
452
+ <IconChevronRight className="size-4" />
453
+ </Button>
454
+ <Button
455
+ variant="outline"
456
+ size="icon"
457
+ className="size-8"
458
+ onClick={() => setPage(totalPages)}
459
+ disabled={page === totalPages}
460
+ >
461
+ <IconChevronsRight className="size-4" />
462
+ </Button>
463
+ </div>
464
+ </div>
465
+ </div>
466
+ )}
467
+
468
+ <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
469
+ <AlertDialogContent>
470
+ <AlertDialogHeader>
471
+ <AlertDialogTitle>{t('confirmDelete')}</AlertDialogTitle>
472
+ <AlertDialogDescription>
473
+ {t('deleteDescription')}
474
+ </AlertDialogDescription>
475
+ </AlertDialogHeader>
476
+ <AlertDialogFooter>
477
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
478
+ <AlertDialogAction
479
+ onClick={handleDeleteConfirm}
480
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
481
+ >
482
+ {t('delete')}
483
+ </AlertDialogAction>
484
+ </AlertDialogFooter>
485
+ </AlertDialogContent>
486
+ </AlertDialog>
487
+ </div>
488
+ );
489
+ }