@hed-hog/core 0.0.215 → 0.0.216

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 (36) hide show
  1. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +5 -5
  2. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +1 -1
  3. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +312 -0
  4. package/hedhog/frontend/app/dashboard/components/dashboard-grid.tsx.ejs +54 -0
  5. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +132 -0
  6. package/hedhog/frontend/app/dashboard/components/dynamic-widget.tsx.ejs +88 -0
  7. package/hedhog/frontend/app/dashboard/components/index.ts.ejs +6 -0
  8. package/hedhog/frontend/app/dashboard/components/stats.tsx.ejs +93 -0
  9. package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +150 -0
  10. package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +184 -0
  11. package/hedhog/frontend/app/dashboard/components/widgets/active-users-card.tsx.ejs +58 -0
  12. package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +219 -0
  13. package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +191 -0
  14. package/hedhog/frontend/app/dashboard/components/widgets/locale-config.tsx.ejs +309 -0
  15. package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +111 -0
  16. package/hedhog/frontend/app/dashboard/components/widgets/mail-config.tsx.ejs +445 -0
  17. package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-card.tsx.ejs +58 -0
  18. package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-chart.tsx.ejs +149 -0
  19. package/hedhog/frontend/app/dashboard/components/widgets/oauth-config.tsx.ejs +296 -0
  20. package/hedhog/frontend/app/dashboard/components/widgets/permissions-card.tsx.ejs +61 -0
  21. package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +152 -0
  22. package/hedhog/frontend/app/dashboard/components/widgets/profile-card.tsx.ejs +186 -0
  23. package/hedhog/frontend/app/dashboard/components/widgets/session-activity-chart.tsx.ejs +183 -0
  24. package/hedhog/frontend/app/dashboard/components/widgets/sessions-today-card.tsx.ejs +62 -0
  25. package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +57 -0
  26. package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +57 -0
  27. package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +57 -0
  28. package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +57 -0
  29. package/hedhog/frontend/app/dashboard/components/widgets/storage-config.tsx.ejs +340 -0
  30. package/hedhog/frontend/app/dashboard/components/widgets/theme-config.tsx.ejs +275 -0
  31. package/hedhog/frontend/app/dashboard/components/widgets/user-growth-chart.tsx.ejs +210 -0
  32. package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +130 -0
  33. package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +233 -0
  34. package/hedhog/frontend/messages/en.json +143 -1
  35. package/hedhog/frontend/messages/pt.json +143 -1
  36. package/package.json +3 -3
@@ -0,0 +1,219 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/ui/card';
11
+ import { ScrollArea } from '@/components/ui/scroll-area';
12
+ import { useWidgetData } from '@/hooks/use-widget-data';
13
+ import type { ActivityEvent, AllWidgetsData } from '@/types/widget-data';
14
+ import {
15
+ FileEdit,
16
+ LogIn,
17
+ LogOut,
18
+ Mail,
19
+ Settings,
20
+ ShieldAlert,
21
+ UserCheck,
22
+ } from 'lucide-react';
23
+ import { useTranslations } from 'next-intl';
24
+ import { WidgetWrapper } from '../widget-wrapper';
25
+
26
+ const typeIconMap: Record<string, React.ElementType> = {
27
+ login: LogIn,
28
+ logout: LogOut,
29
+ security: ShieldAlert,
30
+ settings: Settings,
31
+ edit: FileEdit,
32
+ permission: UserCheck,
33
+ email: Mail,
34
+ };
35
+
36
+ const typeColorMap: Record<string, { color: string; bg: string }> = {
37
+ login: {
38
+ color: 'text-blue-600 dark:text-blue-400',
39
+ bg: 'bg-blue-50 dark:bg-blue-950/40',
40
+ },
41
+ logout: { color: 'text-muted-foreground', bg: 'bg-muted' },
42
+ security: {
43
+ color: 'text-amber-600 dark:text-amber-400',
44
+ bg: 'bg-amber-50 dark:bg-amber-950/40',
45
+ },
46
+ settings: { color: 'text-foreground', bg: 'bg-muted' },
47
+ edit: {
48
+ color: 'text-indigo-600 dark:text-indigo-400',
49
+ bg: 'bg-indigo-50 dark:bg-indigo-950/40',
50
+ },
51
+ permission: {
52
+ color: 'text-emerald-600 dark:text-emerald-400',
53
+ bg: 'bg-emerald-50 dark:bg-emerald-950/40',
54
+ },
55
+ email: {
56
+ color: 'text-rose-600 dark:text-rose-400',
57
+ bg: 'bg-rose-50 dark:bg-rose-950/40',
58
+ },
59
+ };
60
+
61
+ function detectType(action: string): string {
62
+ const a = action.toLowerCase();
63
+ if (a.includes('login') || a.includes('entrou') || a.includes('acesso'))
64
+ return 'login';
65
+ if (a.includes('logout') || a.includes('saiu') || a.includes('encerr'))
66
+ return 'logout';
67
+ if (
68
+ a.includes('senha') ||
69
+ a.includes('2fa') ||
70
+ a.includes('mfa') ||
71
+ a.includes('segur')
72
+ )
73
+ return 'security';
74
+ if (a.includes('config') || a.includes('idioma') || a.includes('preferên'))
75
+ return 'settings';
76
+ if (
77
+ a.includes('edit') ||
78
+ a.includes('atualiz') ||
79
+ a.includes('alter') ||
80
+ a.includes('perfil')
81
+ )
82
+ return 'edit';
83
+ if (a.includes('permiss') || a.includes('acesso') || a.includes('role'))
84
+ return 'permission';
85
+ if (a.includes('email') || a.includes('e-mail') || a.includes('notif'))
86
+ return 'email';
87
+ return 'settings';
88
+ }
89
+
90
+ function formatDate(
91
+ dateStr: string,
92
+ tToday: string,
93
+ tYesterday: string
94
+ ): string {
95
+ const date = new Date(dateStr);
96
+ const now = new Date();
97
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
98
+ const yesterday = new Date(today);
99
+ yesterday.setDate(yesterday.getDate() - 1);
100
+ const eventDay = new Date(
101
+ date.getFullYear(),
102
+ date.getMonth(),
103
+ date.getDate()
104
+ );
105
+ if (eventDay.getTime() === today.getTime()) return tToday;
106
+ if (eventDay.getTime() === yesterday.getTime()) return tYesterday;
107
+ return date.toLocaleDateString(undefined, { day: '2-digit', month: 'short' });
108
+ }
109
+
110
+ function formatTime(dateStr: string): string {
111
+ return new Date(dateStr).toLocaleTimeString('pt-BR', {
112
+ hour: '2-digit',
113
+ minute: '2-digit',
114
+ });
115
+ }
116
+
117
+ function TimelineContent({ events }: { events: ActivityEvent[] }) {
118
+ const t = useTranslations('core.DashboardPage.activityTimeline');
119
+ let lastDate = '';
120
+
121
+ return (
122
+ <Card className="flex h-full flex-col">
123
+ <CardHeader className="shrink-0 pb-3">
124
+ <div className="flex items-center justify-between">
125
+ <div>
126
+ <CardTitle className="text-base font-semibold">
127
+ {t('title')}
128
+ </CardTitle>
129
+ <CardDescription>{t('description')}</CardDescription>
130
+ </div>
131
+ <Badge variant="secondary">
132
+ {t('events', { count: events.length })}
133
+ </Badge>
134
+ </div>
135
+ </CardHeader>
136
+ <CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
137
+ <ScrollArea className="h-full pr-3">
138
+ <div className="relative flex flex-col pb-2">
139
+ <div className="absolute bottom-0 left-[15px] top-0 w-px bg-border/60" />
140
+ {events.map((event, index) => {
141
+ const type = detectType(event.action);
142
+ const config =
143
+ typeColorMap[type] ??
144
+ (typeColorMap.settings as { color: string; bg: string });
145
+ const Icon = (typeIconMap[type] ?? Settings) as React.ElementType;
146
+ const dateLabel = formatDate(
147
+ event.created_at,
148
+ t('today'),
149
+ t('yesterday')
150
+ );
151
+ const showDate = dateLabel !== lastDate;
152
+ lastDate = dateLabel;
153
+
154
+ return (
155
+ <div key={event.id}>
156
+ {showDate && (
157
+ <div className="relative z-10 mb-1 mt-4 first:mt-0">
158
+ <span className="ml-11 inline-block rounded-md border border-border bg-muted px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
159
+ {dateLabel}
160
+ </span>
161
+ </div>
162
+ )}
163
+ <div className="group relative flex items-start gap-3 py-1.5">
164
+ <div
165
+ className={`relative z-10 mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${config.bg}`}
166
+ >
167
+ <Icon className={`h-3.5 w-3.5 ${config.color}`} />
168
+ </div>
169
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5 rounded-lg px-2 py-1.5 transition-colors group-hover:bg-muted/40">
170
+ <div className="flex items-center justify-between gap-2">
171
+ <span className="text-sm font-medium leading-snug text-foreground">
172
+ {event.action}
173
+ </span>
174
+ <span className="shrink-0 text-[11px] tabular-nums text-muted-foreground">
175
+ {formatTime(event.created_at)}
176
+ </span>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ );
182
+ })}
183
+ </div>
184
+ </ScrollArea>
185
+ </CardContent>
186
+ </Card>
187
+ );
188
+ }
189
+
190
+ interface ActivityTimelineProps {
191
+ widget?: { name?: string };
192
+ onRemove?: () => void;
193
+ }
194
+
195
+ export default function ActivityTimeline({
196
+ widget,
197
+ onRemove,
198
+ }: ActivityTimelineProps) {
199
+ const { data, isLoading, isError, isAccessDenied } = useWidgetData<
200
+ AllWidgetsData,
201
+ ActivityEvent[]
202
+ >({
203
+ endpoint: '/dashboard-core/widgets/me',
204
+ queryKey: 'widget-me',
205
+ select: (d) => d.activityTimeline,
206
+ });
207
+
208
+ return (
209
+ <WidgetWrapper
210
+ isLoading={isLoading}
211
+ isError={isError}
212
+ isAccessDenied={isAccessDenied}
213
+ widgetName={widget?.name ?? 'activity-timeline'}
214
+ onRemove={onRemove}
215
+ >
216
+ {data && <TimelineContent events={data} />}
217
+ </WidgetWrapper>
218
+ );
219
+ }
@@ -0,0 +1,191 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from '@/components/ui/card';
10
+ import {
11
+ ChartContainer,
12
+ ChartTooltip,
13
+ ChartTooltipContent,
14
+ } from '@/components/ui/chart';
15
+ import { Mail, MailCheck, MailWarning, MailX } from 'lucide-react';
16
+ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts';
17
+ import { WidgetWrapper } from '../widget-wrapper';
18
+
19
+ const data = [
20
+ { date: '01 Fev', recebidos: 5, lidos: 4 },
21
+ { date: '03 Fev', recebidos: 3, lidos: 2 },
22
+ { date: '05 Fev', recebidos: 8, lidos: 6 },
23
+ { date: '07 Fev', recebidos: 4, lidos: 4 },
24
+ { date: '09 Fev', recebidos: 6, lidos: 3 },
25
+ { date: '11 Fev', recebidos: 2, lidos: 2 },
26
+ { date: '13 Fev', recebidos: 7, lidos: 5 },
27
+ ];
28
+
29
+ const chartConfig = {
30
+ recebidos: {
31
+ label: 'Recebidos',
32
+ color: 'hsl(221, 83%, 53%)',
33
+ },
34
+ lidos: {
35
+ label: 'Lidos',
36
+ color: 'hsl(160, 84%, 39%)',
37
+ },
38
+ };
39
+
40
+ const emailStats = [
41
+ {
42
+ label: 'Recebidos',
43
+ value: '35',
44
+ icon: Mail,
45
+ color: 'text-blue-600 dark:text-blue-400',
46
+ bg: 'bg-blue-50 dark:bg-blue-950/40',
47
+ },
48
+ {
49
+ label: 'Lidos',
50
+ value: '26',
51
+ icon: MailCheck,
52
+ color: 'text-emerald-600 dark:text-emerald-400',
53
+ bg: 'bg-emerald-50 dark:bg-emerald-950/40',
54
+ },
55
+ {
56
+ label: 'Nao lidos',
57
+ value: '9',
58
+ icon: MailWarning,
59
+ color: 'text-amber-600 dark:text-amber-400',
60
+ bg: 'bg-amber-50 dark:bg-amber-950/40',
61
+ },
62
+ {
63
+ label: 'Com erro',
64
+ value: '2',
65
+ icon: MailX,
66
+ color: 'text-red-600 dark:text-red-400',
67
+ bg: 'bg-red-50 dark:bg-red-950/40',
68
+ },
69
+ ];
70
+
71
+ interface EmailNotificationsProps {
72
+ widget?: { name?: string };
73
+ onRemove?: () => void;
74
+ }
75
+
76
+ export default function EmailNotifications({
77
+ widget,
78
+ onRemove,
79
+ }: EmailNotificationsProps) {
80
+ return (
81
+ <WidgetWrapper
82
+ isLoading={false}
83
+ isError={false}
84
+ isAccessDenied={false}
85
+ widgetName={widget?.name ?? 'email-notifications'}
86
+ onRemove={onRemove}
87
+ >
88
+ <Card className="flex h-full flex-col">
89
+ <CardHeader className="shrink-0 pb-2">
90
+ <div className="flex items-center gap-2">
91
+ <Mail className="h-5 w-5 text-rose-600 dark:text-rose-400" />
92
+ <div>
93
+ <CardTitle className="text-base font-semibold">
94
+ Notificacoes por E-mail
95
+ </CardTitle>
96
+ <CardDescription>
97
+ E-mails do sistema nos ultimos 14 dias
98
+ </CardDescription>
99
+ </div>
100
+ </div>
101
+ </CardHeader>
102
+ <CardContent className="flex-1 overflow-auto pt-0">
103
+ <div className="mb-4 grid grid-cols-4 gap-2">
104
+ {emailStats.map((stat) => {
105
+ const Icon = stat.icon;
106
+ return (
107
+ <div
108
+ key={stat.label}
109
+ className="flex flex-col items-center gap-1 rounded-lg border p-2.5 transition-colors hover:bg-muted/30"
110
+ >
111
+ <div
112
+ className={`flex h-7 w-7 items-center justify-center rounded-md ${stat.bg}`}
113
+ >
114
+ <Icon className={`h-3.5 w-3.5 ${stat.color}`} />
115
+ </div>
116
+ <span className="text-lg font-bold text-foreground">
117
+ {stat.value}
118
+ </span>
119
+ <span className="text-[10px] text-muted-foreground">
120
+ {stat.label}
121
+ </span>
122
+ </div>
123
+ );
124
+ })}
125
+ </div>
126
+
127
+ <ChartContainer config={chartConfig} className="h-[180px] w-full">
128
+ <AreaChart data={data}>
129
+ <CartesianGrid vertical={false} strokeDasharray="3 3" />
130
+ <XAxis
131
+ dataKey="date"
132
+ tickLine={false}
133
+ axisLine={false}
134
+ fontSize={11}
135
+ tickMargin={8}
136
+ />
137
+ <YAxis
138
+ tickLine={false}
139
+ axisLine={false}
140
+ fontSize={11}
141
+ tickMargin={4}
142
+ allowDecimals={false}
143
+ />
144
+ <ChartTooltip content={<ChartTooltipContent />} />
145
+ <defs>
146
+ <linearGradient id="fillRecebidos" x1="0" y1="0" x2="0" y2="1">
147
+ <stop
148
+ offset="5%"
149
+ stopColor="hsl(221, 83%, 53%)"
150
+ stopOpacity={0.2}
151
+ />
152
+ <stop
153
+ offset="95%"
154
+ stopColor="hsl(221, 83%, 53%)"
155
+ stopOpacity={0}
156
+ />
157
+ </linearGradient>
158
+ <linearGradient id="fillLidos" x1="0" y1="0" x2="0" y2="1">
159
+ <stop
160
+ offset="5%"
161
+ stopColor="hsl(160, 84%, 39%)"
162
+ stopOpacity={0.2}
163
+ />
164
+ <stop
165
+ offset="95%"
166
+ stopColor="hsl(160, 84%, 39%)"
167
+ stopOpacity={0}
168
+ />
169
+ </linearGradient>
170
+ </defs>
171
+ <Area
172
+ type="monotone"
173
+ dataKey="recebidos"
174
+ stroke="hsl(221, 83%, 53%)"
175
+ fill="url(#fillRecebidos)"
176
+ strokeWidth={2}
177
+ />
178
+ <Area
179
+ type="monotone"
180
+ dataKey="lidos"
181
+ stroke="hsl(160, 84%, 39%)"
182
+ fill="url(#fillLidos)"
183
+ strokeWidth={2}
184
+ />
185
+ </AreaChart>
186
+ </ChartContainer>
187
+ </CardContent>
188
+ </Card>
189
+ </WidgetWrapper>
190
+ );
191
+ }