@hed-hog/core 0.0.278 → 0.0.285

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 (51) hide show
  1. package/README.md +60 -0
  2. package/dist/auth/auth.controller.d.ts +3 -3
  3. package/dist/auth/auth.service.d.ts +8 -8
  4. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +12 -0
  5. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  6. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +9 -0
  7. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  8. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +12 -0
  9. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  10. package/dist/dashboard/dashboard-core/dashboard-core.service.js +25 -0
  11. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  12. package/dist/file/file.controller.d.ts +2 -2
  13. package/dist/file/file.service.d.ts +4 -4
  14. package/dist/role/guards/role.guard.d.ts.map +1 -1
  15. package/dist/role/guards/role.guard.js +1 -1
  16. package/dist/role/guards/role.guard.js.map +1 -1
  17. package/dist/session/session.controller.d.ts +1 -1
  18. package/dist/session/session.service.d.ts +3 -3
  19. package/dist/user/user.controller.d.ts +2 -2
  20. package/dist/user/user.service.d.ts +6 -6
  21. package/hedhog/data/dashboard_component.yaml +95 -77
  22. package/hedhog/data/dashboard_component_role.yaml +91 -79
  23. package/hedhog/data/dashboard_item.yaml +121 -101
  24. package/hedhog/data/route.yaml +8 -0
  25. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +69 -62
  26. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +23 -12
  27. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +80 -5
  28. package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +33 -29
  29. package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +18 -14
  30. package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +39 -32
  31. package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +22 -19
  32. package/hedhog/frontend/app/dashboard/components/widgets/menus-card.tsx.ejs +58 -0
  33. package/hedhog/frontend/app/dashboard/components/widgets/routes-card.tsx.ejs +58 -0
  34. package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +18 -18
  35. package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +18 -18
  36. package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +18 -18
  37. package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +18 -18
  38. package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +15 -11
  39. package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +39 -37
  40. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +20 -4
  41. package/hedhog/frontend/app/mail/log/page.tsx.ejs +36 -47
  42. package/hedhog/frontend/app/mail/template/page.tsx.ejs +176 -126
  43. package/hedhog/frontend/app/menu/page.tsx.ejs +45 -39
  44. package/hedhog/frontend/app/roles/page.tsx.ejs +45 -46
  45. package/hedhog/frontend/app/users/page.tsx.ejs +70 -73
  46. package/hedhog/frontend/messages/en.json +15 -2
  47. package/hedhog/frontend/messages/pt.json +15 -2
  48. package/package.json +4 -4
  49. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
  50. package/src/dashboard/dashboard-core/dashboard-core.service.ts +34 -0
  51. package/src/role/guards/role.guard.ts +9 -8
@@ -34,24 +34,24 @@ export default function StatOnlineTime({
34
34
  widgetName={widget?.name ?? 'stat-online-time'}
35
35
  onRemove={onRemove}
36
36
  >
37
- <Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
38
- <CardContent className="flex h-full items-center gap-3 p-3 md:gap-4 md:p-4">
39
- <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-blue-50 dark:bg-blue-950/40 md:h-11 md:w-11">
40
- <Clock className="h-4 w-4 text-blue-600 dark:text-blue-400 md:h-5 md:w-5" />
41
- </div>
42
- <div className="flex min-w-0 flex-col">
43
- <span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
44
- {t('onlineTime')}
45
- </span>
46
- <span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
47
- {data ?? '—'}
48
- </span>
49
- <span className="hidden text-[11px] text-muted-foreground sm:block">
50
- {t('onlineTimeSubtitle')}
51
- </span>
52
- </div>
53
- </CardContent>
54
- </Card>
37
+ <Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
38
+ <CardContent className="flex h-full items-center gap-2.5 p-2.5 sm:gap-3 sm:p-3 md:gap-4 md:p-4">
39
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-blue-50 dark:bg-blue-950/40 sm:h-9 sm:w-9 md:h-11 md:w-11">
40
+ <Clock className="h-3.5 w-3.5 text-blue-600 dark:text-blue-400 sm:h-4 sm:w-4 md:h-5 md:w-5" />
41
+ </div>
42
+ <div className="flex min-w-0 flex-col">
43
+ <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground sm:text-[11px]">
44
+ {t('onlineTime')}
45
+ </span>
46
+ <span className="truncate text-lg font-bold tracking-tight text-foreground sm:text-xl md:text-2xl">
47
+ {data ?? '—'}
48
+ </span>
49
+ <span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
50
+ {t('onlineTimeSubtitle')}
51
+ </span>
52
+ </div>
53
+ </CardContent>
54
+ </Card>
55
55
  </WidgetWrapper>
56
56
  );
57
57
  }
@@ -52,37 +52,41 @@ function RolesContent({ roles }: { roles: RoleData[] }) {
52
52
  const t = useTranslations('core.DashboardPage.userRoles');
53
53
 
54
54
  return (
55
- <Card className="flex h-full min-h-0 flex-col overflow-hidden">
55
+ <Card className="flex h-full min-h-0 flex-col overflow-hidden">
56
56
  <CardHeader className="shrink-0 pb-3">
57
57
  <div className="flex items-center gap-2">
58
- <Crown className="h-5 w-5 text-amber-600 dark:text-amber-400" />
58
+ <Crown className="h-4 w-4 text-amber-600 dark:text-amber-400 sm:h-5 sm:w-5" />
59
59
  <div>
60
- <CardTitle className="text-base font-semibold">
60
+ <CardTitle className="text-sm font-semibold sm:text-base">
61
61
  {t('title')}
62
62
  </CardTitle>
63
- <CardDescription>{t('description')}</CardDescription>
63
+ <CardDescription className="text-xs sm:text-sm">
64
+ {t('description')}
65
+ </CardDescription>
64
66
  </div>
65
67
  </div>
66
68
  </CardHeader>
67
- <CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
68
- <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
69
+ <CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
70
+ <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
69
71
  {roles.map((role, index) => {
70
72
  const style = levelStyles[index % levelStyles.length]!;
71
73
  return (
72
74
  <div
73
75
  key={role.id}
74
- className={`flex items-center gap-3 rounded-xl border p-3 transition-all duration-200 hover:shadow-sm ${style.border} ${style.bg}`}
76
+ className={`flex items-center gap-2.5 rounded-xl border p-2.5 transition-all duration-200 hover:shadow-sm sm:gap-3 sm:p-3 ${style.border} ${style.bg}`}
75
77
  >
76
78
  <div
77
- className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${style.iconBg}`}
79
+ className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-9 sm:w-9 ${style.iconBg}`}
78
80
  >
79
- <ShieldCheck className={`h-4 w-4 ${style.iconColor}`} />
81
+ <ShieldCheck
82
+ className={`h-3.5 w-3.5 sm:h-4 sm:w-4 ${style.iconColor}`}
83
+ />
80
84
  </div>
81
85
  <div className="flex min-w-0 flex-1 flex-col gap-0.5">
82
- <span className="text-sm font-medium text-foreground">
86
+ <span className="truncate text-[13px] font-medium text-foreground sm:text-sm">
83
87
  {role.name}
84
88
  </span>
85
- <span className="text-xs text-muted-foreground">
89
+ <span className="truncate text-[11px] text-muted-foreground sm:text-xs">
86
90
  {role.slug}
87
91
  </span>
88
92
  </div>
@@ -86,31 +86,33 @@ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
86
86
  const router = useRouter();
87
87
 
88
88
  return (
89
- <Card className="flex h-full min-h-0 flex-col overflow-hidden">
90
- <CardHeader className="shrink-0 pb-3">
91
- <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
92
- <div className="flex min-w-0 items-center gap-2">
93
- <Globe className="h-5 w-5 text-blue-600" />
94
- <div className="min-w-0">
95
- <CardTitle className="text-base font-semibold">
96
- {t('title')}
97
- </CardTitle>
98
- <CardDescription>{t('description')}</CardDescription>
99
- </div>
100
- </div>
101
- <Button
102
- variant="outline"
103
- size="sm"
104
- onClick={() => router.push('/core/account/sessions')}
105
- className="w-full shrink-0 sm:w-auto"
106
- >
107
- <Info className="h-3.5 w-3.5" />
108
- {t('moreInfo')}
109
- </Button>
110
- </div>
111
- </CardHeader>
112
- <CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
113
- <div className="flex flex-col gap-2">
89
+ <Card className="flex h-full min-h-0 flex-col overflow-hidden">
90
+ <CardHeader className="shrink-0 pb-3">
91
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
92
+ <div className="flex min-w-0 items-center gap-2">
93
+ <Globe className="h-4 w-4 text-blue-600 sm:h-5 sm:w-5" />
94
+ <div className="min-w-0">
95
+ <CardTitle className="text-sm font-semibold sm:text-base">
96
+ {t('title')}
97
+ </CardTitle>
98
+ <CardDescription className="text-xs sm:text-sm">
99
+ {t('description')}
100
+ </CardDescription>
101
+ </div>
102
+ </div>
103
+ <Button
104
+ variant="outline"
105
+ size="sm"
106
+ onClick={() => router.push('/core/account/sessions')}
107
+ className="h-8 w-full shrink-0 gap-1.5 px-2.5 text-xs sm:h-9 sm:w-auto sm:text-sm"
108
+ >
109
+ <Info className="h-3.5 w-3.5" />
110
+ {t('moreInfo')}
111
+ </Button>
112
+ </div>
113
+ </CardHeader>
114
+ <CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
115
+ <div className="flex flex-col gap-2">
114
116
  {sessions.map((session, index) => {
115
117
  const ua = session.user_agent ?? '';
116
118
  const deviceType = detectDeviceType(ua);
@@ -135,42 +137,42 @@ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
135
137
  return (
136
138
  <div
137
139
  key={session.id}
138
- className={`group flex items-center gap-3 rounded-xl border p-3.5 transition-all duration-200 hover:shadow-sm ${
140
+ className={`group flex items-center gap-2.5 rounded-xl border p-3 transition-all duration-200 hover:shadow-sm sm:gap-3 sm:p-3.5 ${
139
141
  isCurrent
140
142
  ? 'border-emerald-200 bg-emerald-50/50 dark:border-emerald-800 dark:bg-emerald-950/30'
141
143
  : 'bg-card hover:bg-muted/30'
142
144
  }`}
143
145
  >
144
146
  <div
145
- className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${
147
+ className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-10 sm:w-10 ${
146
148
  isCurrent
147
149
  ? 'bg-emerald-100 dark:bg-emerald-900/50'
148
150
  : 'bg-muted'
149
151
  }`}
150
152
  >
151
153
  <DeviceIcon
152
- className={`h-5 w-5 ${
154
+ className={`h-4 w-4 sm:h-5 sm:w-5 ${
153
155
  isCurrent
154
156
  ? 'text-emerald-600 dark:text-emerald-400'
155
157
  : 'text-muted-foreground'
156
158
  }`}
157
159
  />
158
160
  </div>
159
- <div className="flex min-w-0 flex-1 flex-col gap-0.5">
160
- <div className="flex flex-wrap items-center gap-2">
161
- <span className="min-w-0 break-words text-sm font-medium text-foreground">
162
- {device}
163
- </span>
161
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
162
+ <div className="flex flex-wrap items-center gap-2">
163
+ <span className="min-w-0 wrap-break-word text-[13px] font-medium text-foreground sm:text-sm">
164
+ {device}
165
+ </span>
164
166
  {isCurrent && (
165
167
  <Badge className="bg-emerald-100 text-[10px] text-emerald-700 hover:bg-emerald-100 dark:bg-emerald-900/50 dark:text-emerald-400 dark:hover:bg-emerald-900/50">
166
168
  {t('thisSession')}
167
169
  </Badge>
168
170
  )}
169
171
  </div>
170
- <span className="break-words text-xs text-muted-foreground">
171
- {browser} &middot; {ip}
172
- </span>
173
- <div className="flex flex-wrap items-center gap-3 text-[11px] text-muted-foreground/70">
172
+ <span className="wrap-break-word text-[11px] text-muted-foreground sm:text-xs">
173
+ {browser} &middot; {ip}
174
+ </span>
175
+ <div className="flex flex-wrap items-center gap-2 text-[10px] text-muted-foreground/70 sm:gap-3 sm:text-[11px]">
174
176
  <span className="flex items-center gap-1">
175
177
  <Clock className="h-3 w-3" />
176
178
  {relativeTime}
@@ -34,8 +34,12 @@
34
34
  opacity: 0.45;
35
35
  }
36
36
 
37
- .dashboard-grid .react-grid-item:hover > .react-resizable-handle.react-resizable-handle-se,
38
- .dashboard-grid .react-grid-item.resizing > .react-resizable-handle.react-resizable-handle-se {
37
+ .dashboard-grid
38
+ .react-grid-item:hover
39
+ > .react-resizable-handle.react-resizable-handle-se,
40
+ .dashboard-grid
41
+ .react-grid-item.resizing
42
+ > .react-resizable-handle.react-resizable-handle-se {
39
43
  opacity: 1;
40
44
  }
41
45
 
@@ -43,11 +47,15 @@
43
47
  display: none;
44
48
  }
45
49
 
46
- .dashboard-grid .react-grid-item > .react-resizable-handle:not(.react-resizable-handle-se) {
50
+ .dashboard-grid
51
+ .react-grid-item
52
+ > .react-resizable-handle:not(.react-resizable-handle-se) {
47
53
  display: none;
48
54
  }
49
55
 
50
- .dashboard-grid .react-grid-item > .react-resizable-handle.react-resizable-handle-se {
56
+ .dashboard-grid
57
+ .react-grid-item
58
+ > .react-resizable-handle.react-resizable-handle-se {
51
59
  bottom: 0;
52
60
  right: 0;
53
61
  cursor: se-resize;
@@ -102,3 +110,11 @@
102
110
  .dashboard-widget > [data-slot='card'] > [data-slot='card-header'] {
103
111
  padding-top: 0;
104
112
  }
113
+
114
+ @media (max-width: 639px) {
115
+ .dashboard-widget > [data-slot='card'] {
116
+ gap: 0.625rem;
117
+ padding-top: 0.625rem;
118
+ padding-bottom: 0.625rem;
119
+ }
120
+ }
@@ -1,6 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { PageHeader, PaginationFooter } from '@/components/entity-list';
3
+ import {
4
+ EmptyState,
5
+ Page,
6
+ PageHeader,
7
+ PaginationFooter,
8
+ SearchBar,
9
+ } from '@/components/entity-list';
4
10
  import { Badge } from '@/components/ui/badge';
5
11
  import { Button } from '@/components/ui/button';
6
12
  import { Card, CardContent } from '@/components/ui/card';
@@ -11,14 +17,13 @@ import {
11
17
  DialogHeader,
12
18
  DialogTitle,
13
19
  } from '@/components/ui/dialog';
14
- import { Input } from '@/components/ui/input';
15
20
  import { ScrollArea } from '@/components/ui/scroll-area';
16
21
  import { useDebounce } from '@/hooks/use-debounce';
17
22
  import { formatDate } from '@/lib/format-date';
18
23
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
19
- import { Clock, Mail, Search, User } from 'lucide-react';
24
+ import { Clock, Mail, User } from 'lucide-react';
20
25
  import { useTranslations } from 'next-intl';
21
- import { useEffect, useState } from 'react';
26
+ import { useState } from 'react';
22
27
 
23
28
  type PaginationResult<T> = {
24
29
  data: T[];
@@ -42,7 +47,6 @@ type MailSent = {
42
47
 
43
48
  export default function MailLogPage() {
44
49
  const t = useTranslations('core.MailLog');
45
- const [logs, setLogs] = useState<MailSent[]>([]);
46
50
  const [selectedLog, setSelectedLog] = useState<MailSent | null>(null);
47
51
  const [isDetailDialogOpen, setIsDetailDialogOpen] = useState(false);
48
52
  const [searchTerm, setSearchTerm] = useState('');
@@ -51,10 +55,9 @@ export default function MailLogPage() {
51
55
  const [pageSize, setPageSize] = useState(10);
52
56
  const { request, getSettingValue } = useApp();
53
57
 
54
- const {
55
- data: { data, total },
56
- refetch: refetchLogs,
57
- } = useQuery<PaginationResult<MailSent>>({
58
+ const { data: logsResult, refetch: refetchLogs } = useQuery<
59
+ PaginationResult<MailSent>
60
+ >({
58
61
  queryKey: ['mail-sent', debouncedSearch, page, pageSize],
59
62
  queryFn: async () => {
60
63
  const response = await request({
@@ -67,19 +70,8 @@ export default function MailLogPage() {
67
70
  });
68
71
  return response.data as PaginationResult<MailSent>;
69
72
  },
70
- initialData: {
71
- data: [],
72
- total: 0,
73
- page: 1,
74
- pageSize: 10,
75
- },
76
73
  });
77
-
78
- useEffect(() => {
79
- if (data) {
80
- setLogs(data);
81
- }
82
- }, [data]);
74
+ const { data: logs = [], total = 0 } = logsResult ?? {};
83
75
 
84
76
  const handleViewDetails = (log: MailSent): void => {
85
77
  setSelectedLog(log);
@@ -98,12 +90,8 @@ export default function MailLogPage() {
98
90
  : text;
99
91
  };
100
92
 
101
- useEffect(() => {
102
- refetchLogs();
103
- }, [debouncedSearch, page, pageSize]);
104
-
105
93
  return (
106
- <div className="flex flex-col h-screen px-4">
94
+ <Page>
107
95
  <PageHeader
108
96
  breadcrumbs={[
109
97
  { label: t('breadcrumbHome'), href: '/' },
@@ -147,29 +135,30 @@ export default function MailLogPage() {
147
135
  </Card>
148
136
  </div>
149
137
 
150
- <div className="relative my-4">
151
- <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
152
- <Input
153
- placeholder={t('searchPlaceholder')}
154
- value={searchTerm}
155
- onChange={(e) => handleSearchChange(e.target.value)}
156
- className="pl-10"
157
- />
158
- </div>
138
+ <SearchBar
139
+ searchQuery={searchTerm}
140
+ onSearchChange={handleSearchChange}
141
+ onSearch={() => setPage(1)}
142
+ placeholder={t('searchPlaceholder')}
143
+ />
159
144
 
160
145
  <div className="space-y-3 mb-4">
161
146
  {logs.length === 0 ? (
162
- <Card>
163
- <CardContent className="flex flex-col items-center justify-center p-12">
164
- <Mail className="mb-4 h-12 w-12 text-muted-foreground" />
165
- <p className="text-lg font-medium text-muted-foreground">
166
- {t('noLogsFound')}
167
- </p>
168
- <p className="text-sm text-muted-foreground">
169
- {searchTerm ? t('adjustSearch') : t('noEmailsSent')}
170
- </p>
171
- </CardContent>
172
- </Card>
147
+ <EmptyState
148
+ icon={<Mail className="h-12 w-12" />}
149
+ title={t('noLogsFound')}
150
+ description={searchTerm ? t('adjustSearch') : t('noEmailsSent')}
151
+ actionLabel={searchTerm ? t('clearSearch') : t('refreshList')}
152
+ onAction={() => {
153
+ if (searchTerm) {
154
+ setSearchTerm('');
155
+ setPage(1);
156
+ return;
157
+ }
158
+
159
+ refetchLogs();
160
+ }}
161
+ />
173
162
  ) : (
174
163
  logs.map((log) => (
175
164
  <Card
@@ -307,6 +296,6 @@ export default function MailLogPage() {
307
296
  </DialogContent>
308
297
  </Dialog>
309
298
  )}
310
- </div>
299
+ </Page>
311
300
  );
312
301
  }