@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.
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +5 -5
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +312 -0
- package/hedhog/frontend/app/dashboard/components/dashboard-grid.tsx.ejs +54 -0
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +132 -0
- package/hedhog/frontend/app/dashboard/components/dynamic-widget.tsx.ejs +88 -0
- package/hedhog/frontend/app/dashboard/components/index.ts.ejs +6 -0
- package/hedhog/frontend/app/dashboard/components/stats.tsx.ejs +93 -0
- package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +150 -0
- package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +184 -0
- package/hedhog/frontend/app/dashboard/components/widgets/active-users-card.tsx.ejs +58 -0
- package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +219 -0
- package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +191 -0
- package/hedhog/frontend/app/dashboard/components/widgets/locale-config.tsx.ejs +309 -0
- package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +111 -0
- package/hedhog/frontend/app/dashboard/components/widgets/mail-config.tsx.ejs +445 -0
- package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-card.tsx.ejs +58 -0
- package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-chart.tsx.ejs +149 -0
- package/hedhog/frontend/app/dashboard/components/widgets/oauth-config.tsx.ejs +296 -0
- package/hedhog/frontend/app/dashboard/components/widgets/permissions-card.tsx.ejs +61 -0
- package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +152 -0
- package/hedhog/frontend/app/dashboard/components/widgets/profile-card.tsx.ejs +186 -0
- package/hedhog/frontend/app/dashboard/components/widgets/session-activity-chart.tsx.ejs +183 -0
- package/hedhog/frontend/app/dashboard/components/widgets/sessions-today-card.tsx.ejs +62 -0
- package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +57 -0
- package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +57 -0
- package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +57 -0
- package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +57 -0
- package/hedhog/frontend/app/dashboard/components/widgets/storage-config.tsx.ejs +340 -0
- package/hedhog/frontend/app/dashboard/components/widgets/theme-config.tsx.ejs +275 -0
- package/hedhog/frontend/app/dashboard/components/widgets/user-growth-chart.tsx.ejs +210 -0
- package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +130 -0
- package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +233 -0
- package/hedhog/frontend/messages/en.json +143 -1
- package/hedhog/frontend/messages/pt.json +143 -1
- package/package.json +3 -3
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Badge } from '@/components/ui/badge';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from '@/components/ui/card';
|
|
12
|
+
import { Input } from '@/components/ui/input';
|
|
13
|
+
import { Label } from '@/components/ui/label';
|
|
14
|
+
import {
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectTrigger,
|
|
19
|
+
SelectValue,
|
|
20
|
+
} from '@/components/ui/select';
|
|
21
|
+
import { Switch } from '@/components/ui/switch';
|
|
22
|
+
import { AlertCircle, CheckCircle2, Mail, Send, Server } from 'lucide-react';
|
|
23
|
+
import { useState } from 'react';
|
|
24
|
+
|
|
25
|
+
type EmailProvider = 'smtp' | 'gmail' | 'aws_ses';
|
|
26
|
+
|
|
27
|
+
interface SmtpConfig {
|
|
28
|
+
host: string;
|
|
29
|
+
port: string;
|
|
30
|
+
username: string;
|
|
31
|
+
password: string;
|
|
32
|
+
encryption: 'tls' | 'ssl' | 'none';
|
|
33
|
+
fromName: string;
|
|
34
|
+
fromEmail: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface GmailConfig {
|
|
38
|
+
clientId: string;
|
|
39
|
+
clientSecret: string;
|
|
40
|
+
refreshToken: string;
|
|
41
|
+
fromName: string;
|
|
42
|
+
fromEmail: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface AwsSesConfig {
|
|
46
|
+
accessKeyId: string;
|
|
47
|
+
secretAccessKey: string;
|
|
48
|
+
region: string;
|
|
49
|
+
fromName: string;
|
|
50
|
+
fromEmail: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const initialSmtp: SmtpConfig = {
|
|
54
|
+
host: 'smtp.empresa.com',
|
|
55
|
+
port: '587',
|
|
56
|
+
username: 'noreply@empresa.com',
|
|
57
|
+
password: '',
|
|
58
|
+
encryption: 'tls',
|
|
59
|
+
fromName: 'HeroAdmin',
|
|
60
|
+
fromEmail: 'noreply@empresa.com',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const initialGmail: GmailConfig = {
|
|
64
|
+
clientId: '',
|
|
65
|
+
clientSecret: '',
|
|
66
|
+
refreshToken: '',
|
|
67
|
+
fromName: 'HeroAdmin',
|
|
68
|
+
fromEmail: '',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const initialAwsSes: AwsSesConfig = {
|
|
72
|
+
accessKeyId: '',
|
|
73
|
+
secretAccessKey: '',
|
|
74
|
+
region: 'us-east-1',
|
|
75
|
+
fromName: 'HeroAdmin',
|
|
76
|
+
fromEmail: '',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const providers: {
|
|
80
|
+
value: EmailProvider;
|
|
81
|
+
label: string;
|
|
82
|
+
icon: React.ReactNode;
|
|
83
|
+
description: string;
|
|
84
|
+
}[] = [
|
|
85
|
+
{
|
|
86
|
+
value: 'smtp',
|
|
87
|
+
label: 'SMTP',
|
|
88
|
+
icon: <Server className="h-4 w-4" />,
|
|
89
|
+
description: 'Servidor SMTP personalizado',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
value: 'gmail',
|
|
93
|
+
label: 'Gmail',
|
|
94
|
+
icon: <Mail className="h-4 w-4" />,
|
|
95
|
+
description: 'API do Google Gmail',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
value: 'aws_ses',
|
|
99
|
+
label: 'AWS SES',
|
|
100
|
+
icon: <Send className="h-4 w-4" />,
|
|
101
|
+
description: 'Amazon Simple Email Service',
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
export default function EmailConfig() {
|
|
106
|
+
const [provider, setProvider] = useState<EmailProvider>('smtp');
|
|
107
|
+
const [smtp, setSmtp] = useState(initialSmtp);
|
|
108
|
+
const [gmail, setGmail] = useState(initialGmail);
|
|
109
|
+
const [awsSes, setAwsSes] = useState(initialAwsSes);
|
|
110
|
+
const [testSent, setTestSent] = useState(false);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Card className="h-full">
|
|
114
|
+
<CardHeader>
|
|
115
|
+
<div className="flex items-center justify-between">
|
|
116
|
+
<div className="flex items-center gap-3">
|
|
117
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-50">
|
|
118
|
+
<Mail className="h-5 w-5 text-blue-600" />
|
|
119
|
+
</div>
|
|
120
|
+
<div>
|
|
121
|
+
<CardTitle className="text-base">Envio de E-mails</CardTitle>
|
|
122
|
+
<CardDescription>
|
|
123
|
+
Configure o provedor de e-mail do sistema
|
|
124
|
+
</CardDescription>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<Badge variant="secondary" className="bg-emerald-50 text-emerald-700">
|
|
128
|
+
<CheckCircle2 className="mr-1 h-3 w-3" />
|
|
129
|
+
Ativo
|
|
130
|
+
</Badge>
|
|
131
|
+
</div>
|
|
132
|
+
</CardHeader>
|
|
133
|
+
<CardContent className="space-y-6">
|
|
134
|
+
{/* Provider Selector */}
|
|
135
|
+
<div className="grid grid-cols-3 gap-3">
|
|
136
|
+
{providers.map((p) => (
|
|
137
|
+
<button
|
|
138
|
+
key={p.value}
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={() => setProvider(p.value)}
|
|
141
|
+
className={`flex flex-col items-center gap-2 rounded-lg border-2 p-4 text-center transition-all ${
|
|
142
|
+
provider === p.value
|
|
143
|
+
? 'border-foreground bg-foreground/3'
|
|
144
|
+
: 'border-border hover:border-foreground/20'
|
|
145
|
+
}`}
|
|
146
|
+
>
|
|
147
|
+
<div
|
|
148
|
+
className={`flex h-9 w-9 items-center justify-center rounded-full ${
|
|
149
|
+
provider === p.value
|
|
150
|
+
? 'bg-foreground text-background'
|
|
151
|
+
: 'bg-muted text-muted-foreground'
|
|
152
|
+
}`}
|
|
153
|
+
>
|
|
154
|
+
{p.icon}
|
|
155
|
+
</div>
|
|
156
|
+
<span className="text-sm font-medium">{p.label}</span>
|
|
157
|
+
<span className="text-xs text-muted-foreground">
|
|
158
|
+
{p.description}
|
|
159
|
+
</span>
|
|
160
|
+
</button>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
{/* SMTP Fields */}
|
|
165
|
+
{provider === 'smtp' && (
|
|
166
|
+
<div className="space-y-4">
|
|
167
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
168
|
+
<div className="space-y-2">
|
|
169
|
+
<Label htmlFor="smtp-host">Servidor SMTP</Label>
|
|
170
|
+
<Input
|
|
171
|
+
id="smtp-host"
|
|
172
|
+
value={smtp.host}
|
|
173
|
+
onChange={(e) => setSmtp({ ...smtp, host: e.target.value })}
|
|
174
|
+
placeholder="smtp.exemplo.com"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="space-y-2">
|
|
178
|
+
<Label htmlFor="smtp-port">Porta</Label>
|
|
179
|
+
<Input
|
|
180
|
+
id="smtp-port"
|
|
181
|
+
value={smtp.port}
|
|
182
|
+
onChange={(e) => setSmtp({ ...smtp, port: e.target.value })}
|
|
183
|
+
placeholder="587"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
188
|
+
<div className="space-y-2">
|
|
189
|
+
<Label htmlFor="smtp-user">Usuario</Label>
|
|
190
|
+
<Input
|
|
191
|
+
id="smtp-user"
|
|
192
|
+
value={smtp.username}
|
|
193
|
+
onChange={(e) =>
|
|
194
|
+
setSmtp({ ...smtp, username: e.target.value })
|
|
195
|
+
}
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
<div className="space-y-2">
|
|
199
|
+
<Label htmlFor="smtp-pass">Senha</Label>
|
|
200
|
+
<Input
|
|
201
|
+
id="smtp-pass"
|
|
202
|
+
type="password"
|
|
203
|
+
value={smtp.password}
|
|
204
|
+
onChange={(e) =>
|
|
205
|
+
setSmtp({ ...smtp, password: e.target.value })
|
|
206
|
+
}
|
|
207
|
+
placeholder="********"
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div className="space-y-2">
|
|
212
|
+
<Label>Criptografia</Label>
|
|
213
|
+
<Select
|
|
214
|
+
value={smtp.encryption}
|
|
215
|
+
onValueChange={(val: 'tls' | 'ssl' | 'none') =>
|
|
216
|
+
setSmtp({ ...smtp, encryption: val })
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
<SelectTrigger>
|
|
220
|
+
<SelectValue />
|
|
221
|
+
</SelectTrigger>
|
|
222
|
+
<SelectContent>
|
|
223
|
+
<SelectItem value="tls">TLS (Recomendado)</SelectItem>
|
|
224
|
+
<SelectItem value="ssl">SSL</SelectItem>
|
|
225
|
+
<SelectItem value="none">Nenhuma</SelectItem>
|
|
226
|
+
</SelectContent>
|
|
227
|
+
</Select>
|
|
228
|
+
</div>
|
|
229
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
230
|
+
<div className="space-y-2">
|
|
231
|
+
<Label htmlFor="smtp-from-name">Nome do remetente</Label>
|
|
232
|
+
<Input
|
|
233
|
+
id="smtp-from-name"
|
|
234
|
+
value={smtp.fromName}
|
|
235
|
+
onChange={(e) =>
|
|
236
|
+
setSmtp({ ...smtp, fromName: e.target.value })
|
|
237
|
+
}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
<div className="space-y-2">
|
|
241
|
+
<Label htmlFor="smtp-from-email">E-mail do remetente</Label>
|
|
242
|
+
<Input
|
|
243
|
+
id="smtp-from-email"
|
|
244
|
+
type="email"
|
|
245
|
+
value={smtp.fromEmail}
|
|
246
|
+
onChange={(e) =>
|
|
247
|
+
setSmtp({ ...smtp, fromEmail: e.target.value })
|
|
248
|
+
}
|
|
249
|
+
/>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{/* Gmail Fields */}
|
|
256
|
+
{provider === 'gmail' && (
|
|
257
|
+
<div className="space-y-4">
|
|
258
|
+
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3">
|
|
259
|
+
<div className="flex items-start gap-2">
|
|
260
|
+
<AlertCircle className="mt-0.5 h-4 w-4 text-amber-600" />
|
|
261
|
+
<p className="text-xs text-amber-800">
|
|
262
|
+
Configure as credenciais OAuth2 no Google Cloud Console.
|
|
263
|
+
Habilite a API do Gmail e gere o Client ID e Secret.
|
|
264
|
+
</p>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div className="space-y-2">
|
|
268
|
+
<Label htmlFor="gmail-client-id">Client ID</Label>
|
|
269
|
+
<Input
|
|
270
|
+
id="gmail-client-id"
|
|
271
|
+
value={gmail.clientId}
|
|
272
|
+
onChange={(e) =>
|
|
273
|
+
setGmail({ ...gmail, clientId: e.target.value })
|
|
274
|
+
}
|
|
275
|
+
placeholder="xxxxx.apps.googleusercontent.com"
|
|
276
|
+
className="font-mono text-sm"
|
|
277
|
+
/>
|
|
278
|
+
</div>
|
|
279
|
+
<div className="space-y-2">
|
|
280
|
+
<Label htmlFor="gmail-client-secret">Client Secret</Label>
|
|
281
|
+
<Input
|
|
282
|
+
id="gmail-client-secret"
|
|
283
|
+
type="password"
|
|
284
|
+
value={gmail.clientSecret}
|
|
285
|
+
onChange={(e) =>
|
|
286
|
+
setGmail({ ...gmail, clientSecret: e.target.value })
|
|
287
|
+
}
|
|
288
|
+
placeholder="GOCSPX-..."
|
|
289
|
+
className="font-mono text-sm"
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
<div className="space-y-2">
|
|
293
|
+
<Label htmlFor="gmail-refresh-token">Refresh Token</Label>
|
|
294
|
+
<Input
|
|
295
|
+
id="gmail-refresh-token"
|
|
296
|
+
type="password"
|
|
297
|
+
value={gmail.refreshToken}
|
|
298
|
+
onChange={(e) =>
|
|
299
|
+
setGmail({ ...gmail, refreshToken: e.target.value })
|
|
300
|
+
}
|
|
301
|
+
className="font-mono text-sm"
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
305
|
+
<div className="space-y-2">
|
|
306
|
+
<Label htmlFor="gmail-from-name">Nome do remetente</Label>
|
|
307
|
+
<Input
|
|
308
|
+
id="gmail-from-name"
|
|
309
|
+
value={gmail.fromName}
|
|
310
|
+
onChange={(e) =>
|
|
311
|
+
setGmail({ ...gmail, fromName: e.target.value })
|
|
312
|
+
}
|
|
313
|
+
/>
|
|
314
|
+
</div>
|
|
315
|
+
<div className="space-y-2">
|
|
316
|
+
<Label htmlFor="gmail-from-email">E-mail do remetente</Label>
|
|
317
|
+
<Input
|
|
318
|
+
id="gmail-from-email"
|
|
319
|
+
type="email"
|
|
320
|
+
value={gmail.fromEmail}
|
|
321
|
+
onChange={(e) =>
|
|
322
|
+
setGmail({ ...gmail, fromEmail: e.target.value })
|
|
323
|
+
}
|
|
324
|
+
placeholder="email@gmail.com"
|
|
325
|
+
/>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
)}
|
|
330
|
+
|
|
331
|
+
{/* AWS SES Fields */}
|
|
332
|
+
{provider === 'aws_ses' && (
|
|
333
|
+
<div className="space-y-4">
|
|
334
|
+
<div className="space-y-2">
|
|
335
|
+
<Label htmlFor="ses-access-key">Access Key ID</Label>
|
|
336
|
+
<Input
|
|
337
|
+
id="ses-access-key"
|
|
338
|
+
value={awsSes.accessKeyId}
|
|
339
|
+
onChange={(e) =>
|
|
340
|
+
setAwsSes({ ...awsSes, accessKeyId: e.target.value })
|
|
341
|
+
}
|
|
342
|
+
placeholder="AKIAIOSFODNN7EXAMPLE"
|
|
343
|
+
className="font-mono text-sm"
|
|
344
|
+
/>
|
|
345
|
+
</div>
|
|
346
|
+
<div className="space-y-2">
|
|
347
|
+
<Label htmlFor="ses-secret-key">Secret Access Key</Label>
|
|
348
|
+
<Input
|
|
349
|
+
id="ses-secret-key"
|
|
350
|
+
type="password"
|
|
351
|
+
value={awsSes.secretAccessKey}
|
|
352
|
+
onChange={(e) =>
|
|
353
|
+
setAwsSes({ ...awsSes, secretAccessKey: e.target.value })
|
|
354
|
+
}
|
|
355
|
+
className="font-mono text-sm"
|
|
356
|
+
/>
|
|
357
|
+
</div>
|
|
358
|
+
<div className="space-y-2">
|
|
359
|
+
<Label>Regiao</Label>
|
|
360
|
+
<Select
|
|
361
|
+
value={awsSes.region}
|
|
362
|
+
onValueChange={(val) => setAwsSes({ ...awsSes, region: val })}
|
|
363
|
+
>
|
|
364
|
+
<SelectTrigger>
|
|
365
|
+
<SelectValue />
|
|
366
|
+
</SelectTrigger>
|
|
367
|
+
<SelectContent>
|
|
368
|
+
<SelectItem value="us-east-1">
|
|
369
|
+
US East (N. Virginia)
|
|
370
|
+
</SelectItem>
|
|
371
|
+
<SelectItem value="us-west-2">US West (Oregon)</SelectItem>
|
|
372
|
+
<SelectItem value="eu-west-1">EU (Ireland)</SelectItem>
|
|
373
|
+
<SelectItem value="eu-central-1">EU (Frankfurt)</SelectItem>
|
|
374
|
+
<SelectItem value="sa-east-1">
|
|
375
|
+
South America (Sao Paulo)
|
|
376
|
+
</SelectItem>
|
|
377
|
+
<SelectItem value="ap-southeast-1">
|
|
378
|
+
Asia Pacific (Singapore)
|
|
379
|
+
</SelectItem>
|
|
380
|
+
</SelectContent>
|
|
381
|
+
</Select>
|
|
382
|
+
</div>
|
|
383
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
384
|
+
<div className="space-y-2">
|
|
385
|
+
<Label htmlFor="ses-from-name">Nome do remetente</Label>
|
|
386
|
+
<Input
|
|
387
|
+
id="ses-from-name"
|
|
388
|
+
value={awsSes.fromName}
|
|
389
|
+
onChange={(e) =>
|
|
390
|
+
setAwsSes({ ...awsSes, fromName: e.target.value })
|
|
391
|
+
}
|
|
392
|
+
/>
|
|
393
|
+
</div>
|
|
394
|
+
<div className="space-y-2">
|
|
395
|
+
<Label htmlFor="ses-from-email">E-mail do remetente</Label>
|
|
396
|
+
<Input
|
|
397
|
+
id="ses-from-email"
|
|
398
|
+
type="email"
|
|
399
|
+
value={awsSes.fromEmail}
|
|
400
|
+
onChange={(e) =>
|
|
401
|
+
setAwsSes({ ...awsSes, fromEmail: e.target.value })
|
|
402
|
+
}
|
|
403
|
+
placeholder="noreply@seudominio.com"
|
|
404
|
+
/>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
)}
|
|
409
|
+
|
|
410
|
+
{/* Actions */}
|
|
411
|
+
<div className="flex items-center justify-between border-t pt-4">
|
|
412
|
+
<div className="flex items-center gap-2">
|
|
413
|
+
<Switch id="email-active" defaultChecked />
|
|
414
|
+
<Label
|
|
415
|
+
htmlFor="email-active"
|
|
416
|
+
className="text-sm text-muted-foreground"
|
|
417
|
+
>
|
|
418
|
+
Envio de e-mails ativo
|
|
419
|
+
</Label>
|
|
420
|
+
</div>
|
|
421
|
+
<div className="flex gap-2">
|
|
422
|
+
<Button
|
|
423
|
+
variant="outline"
|
|
424
|
+
size="sm"
|
|
425
|
+
onClick={() => {
|
|
426
|
+
setTestSent(true);
|
|
427
|
+
setTimeout(() => setTestSent(false), 3000);
|
|
428
|
+
}}
|
|
429
|
+
>
|
|
430
|
+
{testSent ? (
|
|
431
|
+
<>
|
|
432
|
+
<CheckCircle2 className="mr-1 h-3.5 w-3.5 text-emerald-600" />
|
|
433
|
+
Enviado
|
|
434
|
+
</>
|
|
435
|
+
) : (
|
|
436
|
+
'Enviar teste'
|
|
437
|
+
)}
|
|
438
|
+
</Button>
|
|
439
|
+
<Button size="sm">Salvar</Button>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</CardContent>
|
|
443
|
+
</Card>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
2
|
+
import { Mail } from 'lucide-react';
|
|
3
|
+
import { useTranslations } from 'next-intl';
|
|
4
|
+
import StatCard from '../stats';
|
|
5
|
+
import { WidgetWrapper } from '../widget-wrapper';
|
|
6
|
+
|
|
7
|
+
interface MailSentProps {
|
|
8
|
+
widget?: any;
|
|
9
|
+
onRemove?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface MailStatsData {
|
|
13
|
+
cards?: {
|
|
14
|
+
emailsSent?: {
|
|
15
|
+
value: number;
|
|
16
|
+
change: number | null;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function MailSent({ widget, onRemove }: MailSentProps) {
|
|
22
|
+
const t = useTranslations('core.Dashboard');
|
|
23
|
+
|
|
24
|
+
const { data, isLoading, isAccessDenied, isError } =
|
|
25
|
+
useWidgetData<MailStatsData>({
|
|
26
|
+
endpoint: '/dashboard-core/stats/overview/mails',
|
|
27
|
+
queryKey: 'dashboard-stats-mails',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const value = data?.cards?.emailsSent?.value?.toLocaleString('pt-BR') || '0';
|
|
31
|
+
const change = data?.cards?.emailsSent?.change;
|
|
32
|
+
const changeType =
|
|
33
|
+
change !== null && change !== undefined && change >= 0 ? 'up' : 'down';
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<WidgetWrapper
|
|
37
|
+
isLoading={isLoading}
|
|
38
|
+
isAccessDenied={isAccessDenied}
|
|
39
|
+
isError={isError}
|
|
40
|
+
widgetName={widget?.name || t('emailsSent')}
|
|
41
|
+
onRemove={onRemove}
|
|
42
|
+
>
|
|
43
|
+
<StatCard
|
|
44
|
+
title={t('emailsSent')}
|
|
45
|
+
value={value}
|
|
46
|
+
change={
|
|
47
|
+
change !== null && change !== undefined
|
|
48
|
+
? `${change > 0 ? '+' : ''}${change}%`
|
|
49
|
+
: undefined
|
|
50
|
+
}
|
|
51
|
+
changeType={changeType}
|
|
52
|
+
icon={<Mail className="h-6 w-6 text-amber-500" />}
|
|
53
|
+
iconBg="bg-amber-500/10"
|
|
54
|
+
delay={150}
|
|
55
|
+
/>
|
|
56
|
+
</WidgetWrapper>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
} from '@/components/ui/card';
|
|
10
|
+
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
11
|
+
import { IconGripVertical } from '@tabler/icons-react';
|
|
12
|
+
import { useTranslations } from 'next-intl';
|
|
13
|
+
import {
|
|
14
|
+
Bar,
|
|
15
|
+
BarChart,
|
|
16
|
+
CartesianGrid,
|
|
17
|
+
ResponsiveContainer,
|
|
18
|
+
Tooltip,
|
|
19
|
+
XAxis,
|
|
20
|
+
YAxis,
|
|
21
|
+
} from 'recharts';
|
|
22
|
+
import { WidgetWrapper } from '../widget-wrapper';
|
|
23
|
+
|
|
24
|
+
function CustomTooltip({
|
|
25
|
+
active,
|
|
26
|
+
payload,
|
|
27
|
+
label,
|
|
28
|
+
}: {
|
|
29
|
+
active?: boolean;
|
|
30
|
+
payload?: Array<{ value: number; dataKey: string }>;
|
|
31
|
+
label?: string;
|
|
32
|
+
}) {
|
|
33
|
+
const t = useTranslations('core.Dashboard');
|
|
34
|
+
if (!active || !payload?.length) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="rounded-lg border bg-card px-3 py-2 shadow-xl">
|
|
38
|
+
<p className="mb-1 text-xs font-medium text-card-foreground">{label}</p>
|
|
39
|
+
{payload.map((entry) => (
|
|
40
|
+
<p key={entry.dataKey} className="text-xs text-muted-foreground">
|
|
41
|
+
<span
|
|
42
|
+
className="mr-1.5 inline-block h-2 w-2 rounded-full"
|
|
43
|
+
style={{ backgroundColor: '#3b82f6' }}
|
|
44
|
+
/>
|
|
45
|
+
{t('sent')}: {entry.value.toLocaleString('pt-BR')}
|
|
46
|
+
</p>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface EmailStatsChartProps {
|
|
53
|
+
widget?: any;
|
|
54
|
+
onRemove?: () => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface MailStatsData {
|
|
58
|
+
charts?: {
|
|
59
|
+
emailsPerWeek?: Array<{
|
|
60
|
+
day: string;
|
|
61
|
+
sent: number;
|
|
62
|
+
}>;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default function EmailStatsChart({
|
|
67
|
+
widget,
|
|
68
|
+
onRemove,
|
|
69
|
+
}: EmailStatsChartProps) {
|
|
70
|
+
const t = useTranslations('core.Dashboard');
|
|
71
|
+
|
|
72
|
+
const { data, isLoading, isAccessDenied, isError } =
|
|
73
|
+
useWidgetData<MailStatsData>({
|
|
74
|
+
endpoint: '/dashboard-core/stats/overview/mails',
|
|
75
|
+
queryKey: 'dashboard-stats-mails',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const chartData = data?.charts?.emailsPerWeek || [];
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<WidgetWrapper
|
|
82
|
+
isLoading={isLoading}
|
|
83
|
+
isAccessDenied={isAccessDenied}
|
|
84
|
+
isError={isError}
|
|
85
|
+
widgetName={widget?.name || t('emailSendingTitle')}
|
|
86
|
+
onRemove={onRemove}
|
|
87
|
+
>
|
|
88
|
+
<Card className="h-full flex flex-col group">
|
|
89
|
+
<div
|
|
90
|
+
className="drag-handle absolute top-3 left-4 z-10"
|
|
91
|
+
style={{ cursor: 'grab' }}
|
|
92
|
+
>
|
|
93
|
+
<IconGripVertical className="text-muted-foreground/50 size-4 shrink-0" />
|
|
94
|
+
</div>
|
|
95
|
+
<CardHeader className="pb-2 pt-4 pl-10">
|
|
96
|
+
<CardTitle className="text-base font-semibold">
|
|
97
|
+
{t('emailSendingTitle')}
|
|
98
|
+
</CardTitle>
|
|
99
|
+
<CardDescription>{t('emailSendingDescription')}</CardDescription>
|
|
100
|
+
</CardHeader>
|
|
101
|
+
<CardContent className="flex-1 pt-0">
|
|
102
|
+
<div className="h-[280px] w-full">
|
|
103
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
104
|
+
<BarChart
|
|
105
|
+
data={chartData}
|
|
106
|
+
margin={{ top: 5, right: 10, left: -20, bottom: 0 }}
|
|
107
|
+
>
|
|
108
|
+
<CartesianGrid
|
|
109
|
+
strokeDasharray="3 3"
|
|
110
|
+
stroke="currentColor"
|
|
111
|
+
opacity={0.1}
|
|
112
|
+
/>
|
|
113
|
+
<XAxis
|
|
114
|
+
dataKey="day"
|
|
115
|
+
tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: 12 }}
|
|
116
|
+
axisLine={false}
|
|
117
|
+
tickLine={false}
|
|
118
|
+
/>
|
|
119
|
+
<YAxis
|
|
120
|
+
tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: 12 }}
|
|
121
|
+
axisLine={false}
|
|
122
|
+
tickLine={false}
|
|
123
|
+
/>
|
|
124
|
+
<Tooltip content={<CustomTooltip />} />
|
|
125
|
+
<Bar
|
|
126
|
+
dataKey="sent"
|
|
127
|
+
fill="#3b82f6"
|
|
128
|
+
radius={[4, 4, 0, 0]}
|
|
129
|
+
animationDuration={1200}
|
|
130
|
+
/>
|
|
131
|
+
</BarChart>
|
|
132
|
+
</ResponsiveContainer>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="mt-3 flex items-center justify-center">
|
|
135
|
+
<div className="flex items-center gap-2">
|
|
136
|
+
<div
|
|
137
|
+
className="h-2.5 w-2.5 rounded-full"
|
|
138
|
+
style={{ backgroundColor: '#3b82f6' }}
|
|
139
|
+
/>
|
|
140
|
+
<span className="text-xs text-muted-foreground">
|
|
141
|
+
{t('emailsSent')}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</CardContent>
|
|
146
|
+
</Card>
|
|
147
|
+
</WidgetWrapper>
|
|
148
|
+
);
|
|
149
|
+
}
|