@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.
- package/README.md +60 -0
- package/dist/auth/auth.controller.d.ts +3 -3
- package/dist/auth/auth.service.d.ts +8 -8
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +12 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +9 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +12 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +25 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/file/file.controller.d.ts +2 -2
- package/dist/file/file.service.d.ts +4 -4
- package/dist/role/guards/role.guard.d.ts.map +1 -1
- package/dist/role/guards/role.guard.js +1 -1
- package/dist/role/guards/role.guard.js.map +1 -1
- package/dist/session/session.controller.d.ts +1 -1
- package/dist/session/session.service.d.ts +3 -3
- package/dist/user/user.controller.d.ts +2 -2
- package/dist/user/user.service.d.ts +6 -6
- package/hedhog/data/dashboard_component.yaml +95 -77
- package/hedhog/data/dashboard_component_role.yaml +91 -79
- package/hedhog/data/dashboard_item.yaml +121 -101
- package/hedhog/data/route.yaml +8 -0
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +69 -62
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +23 -12
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +80 -5
- package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +33 -29
- package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +18 -14
- package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +39 -32
- package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +22 -19
- package/hedhog/frontend/app/dashboard/components/widgets/menus-card.tsx.ejs +58 -0
- package/hedhog/frontend/app/dashboard/components/widgets/routes-card.tsx.ejs +58 -0
- package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +15 -11
- package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +39 -37
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +20 -4
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +36 -47
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +176 -126
- package/hedhog/frontend/app/menu/page.tsx.ejs +45 -39
- package/hedhog/frontend/app/roles/page.tsx.ejs +45 -46
- package/hedhog/frontend/app/users/page.tsx.ejs +70 -73
- package/hedhog/frontend/messages/en.json +15 -2
- package/hedhog/frontend/messages/pt.json +15 -2
- package/package.json +4 -4
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +34 -0
- package/src/role/guards/role.guard.ts +9 -8
|
@@ -59,7 +59,7 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
59
59
|
: 'hsl(0, 84%, 60%)';
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
|
-
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
62
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
63
63
|
<CardHeader className="shrink-0 pb-3">
|
|
64
64
|
<div className="flex items-center gap-2">
|
|
65
65
|
<ShieldCheck className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
|
@@ -71,13 +71,17 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
71
71
|
</div>
|
|
72
72
|
</div>
|
|
73
73
|
</CardHeader>
|
|
74
|
-
<CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
|
|
75
|
-
<div className="mb-
|
|
74
|
+
<CardContent className="flex min-h-0 flex-1 flex-col overflow-auto pt-0">
|
|
75
|
+
<div className="mb-4 flex flex-col items-center gap-2.5 rounded-xl bg-muted/50 p-3 sm:mb-5 sm:gap-3 sm:p-5">
|
|
76
76
|
<div className="flex items-baseline gap-1">
|
|
77
|
-
<span
|
|
77
|
+
<span
|
|
78
|
+
className={`text-4xl font-bold tracking-tight sm:text-5xl ${scoreColor}`}
|
|
79
|
+
>
|
|
78
80
|
{score}
|
|
79
81
|
</span>
|
|
80
|
-
<span className="text-
|
|
82
|
+
<span className="text-base text-muted-foreground sm:text-lg">
|
|
83
|
+
/100
|
|
84
|
+
</span>
|
|
81
85
|
</div>
|
|
82
86
|
<Progress
|
|
83
87
|
value={score}
|
|
@@ -88,7 +92,7 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
88
92
|
} as any
|
|
89
93
|
}
|
|
90
94
|
/>
|
|
91
|
-
<p className="text-
|
|
95
|
+
<p className="text-[11px] text-muted-foreground sm:text-xs">
|
|
92
96
|
{score >= 80 ? t('wellProtected') : t('recommendProtections')}
|
|
93
97
|
</p>
|
|
94
98
|
</div>
|
|
@@ -97,48 +101,48 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
97
101
|
{data.checks.map((item) => {
|
|
98
102
|
const Icon = ICON_MAP[item.id] ?? ShieldCheck;
|
|
99
103
|
return (
|
|
100
|
-
<div
|
|
101
|
-
key={item.id}
|
|
102
|
-
className="group flex flex-wrap items-
|
|
103
|
-
>
|
|
104
|
+
<div
|
|
105
|
+
key={item.id}
|
|
106
|
+
className="group flex flex-wrap items-start gap-2.5 rounded-lg p-2.5 transition-colors hover:bg-muted/50 sm:items-center sm:gap-3 sm:p-3"
|
|
107
|
+
>
|
|
104
108
|
<div
|
|
105
|
-
className={`flex h-
|
|
109
|
+
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-9 sm:w-9 ${
|
|
106
110
|
item.enabled
|
|
107
111
|
? 'bg-emerald-50 dark:bg-emerald-950/40'
|
|
108
112
|
: 'bg-muted'
|
|
109
113
|
}`}
|
|
110
114
|
>
|
|
111
115
|
<Icon
|
|
112
|
-
className={`h-4 w-4 ${
|
|
116
|
+
className={`h-3.5 w-3.5 sm:h-4 sm:w-4 ${
|
|
113
117
|
item.enabled
|
|
114
118
|
? 'text-emerald-600 dark:text-emerald-400'
|
|
115
119
|
: 'text-muted-foreground'
|
|
116
120
|
}`}
|
|
117
121
|
/>
|
|
118
122
|
</div>
|
|
119
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
120
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
121
|
-
<span className="break-
|
|
122
|
-
{t(`labels.${item.labelKey}` as any) || item.labelKey}
|
|
123
|
-
</span>
|
|
123
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
124
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
125
|
+
<span className="wrap-break-word text-[13px] font-medium text-foreground sm:text-sm">
|
|
126
|
+
{t(`labels.${item.labelKey}` as any) || item.labelKey}
|
|
127
|
+
</span>
|
|
124
128
|
{item.enabled ? (
|
|
125
129
|
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
|
|
126
130
|
) : (
|
|
127
131
|
<AlertTriangle className="h-3.5 w-3.5 text-amber-500" />
|
|
128
132
|
)}
|
|
129
133
|
</div>
|
|
130
|
-
<span className="text-
|
|
131
|
-
{t(`descriptions.${item.descriptionKey}` as any) ||
|
|
132
|
-
item.descriptionKey}
|
|
133
|
-
</span>
|
|
134
|
-
</div>
|
|
135
|
-
{!item.enabled && (
|
|
136
|
-
<Button
|
|
137
|
-
variant="ghost"
|
|
138
|
-
size="sm"
|
|
139
|
-
onClick={() => router.push('/core/account/2fa')}
|
|
140
|
-
className="mt-
|
|
141
|
-
>
|
|
134
|
+
<span className="text-[11px] text-muted-foreground sm:text-xs">
|
|
135
|
+
{t(`descriptions.${item.descriptionKey}` as any) ||
|
|
136
|
+
item.descriptionKey}
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
{!item.enabled && (
|
|
140
|
+
<Button
|
|
141
|
+
variant="ghost"
|
|
142
|
+
size="sm"
|
|
143
|
+
onClick={() => router.push('/core/account/2fa')}
|
|
144
|
+
className="mt-1 w-full shrink-0 gap-1 text-xs sm:mt-0 sm:w-auto"
|
|
145
|
+
>
|
|
142
146
|
{t('activate')}
|
|
143
147
|
<ChevronRight className="h-3 w-3" />
|
|
144
148
|
</Button>
|
|
@@ -119,24 +119,26 @@ function TimelineContent({ events }: { events: ActivityEvent[] }) {
|
|
|
119
119
|
let lastDate = '';
|
|
120
120
|
|
|
121
121
|
return (
|
|
122
|
-
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
122
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
123
123
|
<CardHeader className="shrink-0 pb-3">
|
|
124
124
|
<div className="flex items-center justify-between">
|
|
125
125
|
<div>
|
|
126
|
-
<CardTitle className="text-
|
|
126
|
+
<CardTitle className="text-sm font-semibold sm:text-base">
|
|
127
127
|
{t('title')}
|
|
128
128
|
</CardTitle>
|
|
129
|
-
<CardDescription>
|
|
129
|
+
<CardDescription className="text-xs sm:text-sm">
|
|
130
|
+
{t('description')}
|
|
131
|
+
</CardDescription>
|
|
130
132
|
</div>
|
|
131
|
-
<Badge variant="secondary">
|
|
133
|
+
<Badge variant="secondary" className="text-[10px] sm:text-xs">
|
|
132
134
|
{t('events', { count: events.length })}
|
|
133
135
|
</Badge>
|
|
134
136
|
</div>
|
|
135
137
|
</CardHeader>
|
|
136
138
|
<CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
|
|
137
|
-
<ScrollArea className="h-full pr-3">
|
|
139
|
+
<ScrollArea className="h-full pr-2 sm:pr-3">
|
|
138
140
|
<div className="relative flex flex-col pb-2">
|
|
139
|
-
<div className="absolute bottom-0 left-[
|
|
141
|
+
<div className="absolute bottom-0 left-[13px] top-0 w-px bg-border/60 sm:left-[15px]" />
|
|
140
142
|
{events.map((event, index) => {
|
|
141
143
|
const type = detectType(event.action);
|
|
142
144
|
const config =
|
|
@@ -155,23 +157,25 @@ function TimelineContent({ events }: { events: ActivityEvent[] }) {
|
|
|
155
157
|
<div key={event.id}>
|
|
156
158
|
{showDate && (
|
|
157
159
|
<div className="relative z-10 mb-1 mt-4 first:mt-0">
|
|
158
|
-
<span className="ml-
|
|
160
|
+
<span className="ml-9 inline-block rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground sm:ml-11 sm:px-2 sm:text-[11px]">
|
|
159
161
|
{dateLabel}
|
|
160
162
|
</span>
|
|
161
163
|
</div>
|
|
162
164
|
)}
|
|
163
165
|
<div className="group relative flex items-start gap-3 py-1.5">
|
|
164
166
|
<div
|
|
165
|
-
className={`relative z-10 mt-0.5 flex h-
|
|
167
|
+
className={`relative z-10 mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg sm:h-8 sm:w-8 ${config.bg}`}
|
|
166
168
|
>
|
|
167
|
-
<Icon
|
|
169
|
+
<Icon
|
|
170
|
+
className={`h-3 w-3 sm:h-3.5 sm:w-3.5 ${config.color}`}
|
|
171
|
+
/>
|
|
168
172
|
</div>
|
|
169
|
-
<div className="flex min-w-0 flex-1 flex-col gap-0.5 rounded-lg px-
|
|
173
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5 rounded-lg px-1.5 py-1.5 transition-colors group-hover:bg-muted/40 sm:px-2">
|
|
170
174
|
<div className="flex items-center justify-between gap-2">
|
|
171
|
-
<span className="min-w-0 break-
|
|
172
|
-
{event.action}
|
|
173
|
-
</span>
|
|
174
|
-
<span className="shrink-0 text-[
|
|
175
|
+
<span className="min-w-0 wrap-break-word text-xs font-medium leading-snug text-foreground sm:text-sm">
|
|
176
|
+
{event.action}
|
|
177
|
+
</span>
|
|
178
|
+
<span className="shrink-0 text-[10px] tabular-nums text-muted-foreground sm:text-[11px]">
|
|
175
179
|
{formatTime(event.created_at)}
|
|
176
180
|
</span>
|
|
177
181
|
</div>
|
|
@@ -40,6 +40,7 @@ const chartConfig = {
|
|
|
40
40
|
const emailStats = [
|
|
41
41
|
{
|
|
42
42
|
label: 'Recebidos',
|
|
43
|
+
shortLabel: 'Rec.',
|
|
43
44
|
value: '35',
|
|
44
45
|
icon: Mail,
|
|
45
46
|
color: 'text-blue-600 dark:text-blue-400',
|
|
@@ -47,6 +48,7 @@ const emailStats = [
|
|
|
47
48
|
},
|
|
48
49
|
{
|
|
49
50
|
label: 'Lidos',
|
|
51
|
+
shortLabel: 'Lid.',
|
|
50
52
|
value: '26',
|
|
51
53
|
icon: MailCheck,
|
|
52
54
|
color: 'text-emerald-600 dark:text-emerald-400',
|
|
@@ -54,6 +56,7 @@ const emailStats = [
|
|
|
54
56
|
},
|
|
55
57
|
{
|
|
56
58
|
label: 'Nao lidos',
|
|
59
|
+
shortLabel: 'N. lid.',
|
|
57
60
|
value: '9',
|
|
58
61
|
icon: MailWarning,
|
|
59
62
|
color: 'text-amber-600 dark:text-amber-400',
|
|
@@ -61,6 +64,7 @@ const emailStats = [
|
|
|
61
64
|
},
|
|
62
65
|
{
|
|
63
66
|
label: 'Com erro',
|
|
67
|
+
shortLabel: 'Erro',
|
|
64
68
|
value: '2',
|
|
65
69
|
icon: MailX,
|
|
66
70
|
color: 'text-red-600 dark:text-red-400',
|
|
@@ -85,63 +89,66 @@ export default function EmailNotifications({
|
|
|
85
89
|
widgetName={widget?.name ?? 'email-notifications'}
|
|
86
90
|
onRemove={onRemove}
|
|
87
91
|
>
|
|
88
|
-
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
92
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
89
93
|
<CardHeader className="shrink-0 pb-2">
|
|
90
94
|
<div className="flex items-center gap-2">
|
|
91
|
-
<Mail className="h-
|
|
95
|
+
<Mail className="h-4 w-4 text-rose-600 dark:text-rose-400 sm:h-5 sm:w-5" />
|
|
92
96
|
<div>
|
|
93
|
-
<CardTitle className="text-
|
|
97
|
+
<CardTitle className="text-sm font-semibold sm:text-base">
|
|
94
98
|
Notificacoes por E-mail
|
|
95
99
|
</CardTitle>
|
|
96
|
-
<CardDescription>
|
|
100
|
+
<CardDescription className="text-xs sm:text-sm">
|
|
97
101
|
E-mails do sistema nos ultimos 14 dias
|
|
98
102
|
</CardDescription>
|
|
99
103
|
</div>
|
|
100
104
|
</div>
|
|
101
105
|
</CardHeader>
|
|
102
|
-
<CardContent className="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden pt-0">
|
|
103
|
-
<div className="grid grid-cols-
|
|
104
|
-
{emailStats.map((stat) => {
|
|
105
|
-
const Icon = stat.icon;
|
|
106
|
-
return (
|
|
107
|
-
<div
|
|
108
|
-
key={stat.label}
|
|
109
|
-
className="flex min-w-0 flex-col items-center gap-1 rounded-lg border p-2
|
|
110
|
-
>
|
|
106
|
+
<CardContent className="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden pt-0">
|
|
107
|
+
<div className="grid grid-cols-2 gap-1.5 sm:gap-2 md:grid-cols-4">
|
|
108
|
+
{emailStats.map((stat) => {
|
|
109
|
+
const Icon = stat.icon;
|
|
110
|
+
return (
|
|
111
|
+
<div
|
|
112
|
+
key={stat.label}
|
|
113
|
+
className="flex min-w-0 flex-col items-center gap-1 rounded-lg border p-2 text-center transition-colors hover:bg-muted/30 sm:p-2.5"
|
|
114
|
+
>
|
|
111
115
|
<div
|
|
112
|
-
className={`flex h-
|
|
116
|
+
className={`flex h-6 w-6 items-center justify-center rounded-md sm:h-7 sm:w-7 ${stat.bg}`}
|
|
113
117
|
>
|
|
114
|
-
<Icon
|
|
118
|
+
<Icon
|
|
119
|
+
className={`h-3 w-3 sm:h-3.5 sm:w-3.5 ${stat.color}`}
|
|
120
|
+
/>
|
|
115
121
|
</div>
|
|
116
|
-
<span className="text-
|
|
122
|
+
<span className="text-base font-bold text-foreground sm:text-lg">
|
|
117
123
|
{stat.value}
|
|
118
124
|
</span>
|
|
119
|
-
<span className="text-[
|
|
120
|
-
{stat.
|
|
125
|
+
<span className="truncate text-[9px] text-muted-foreground sm:text-[10px]">
|
|
126
|
+
<span className="sm:hidden">{stat.shortLabel}</span>
|
|
127
|
+
<span className="hidden sm:inline">{stat.label}</span>
|
|
121
128
|
</span>
|
|
122
129
|
</div>
|
|
123
130
|
);
|
|
124
131
|
})}
|
|
125
132
|
</div>
|
|
126
133
|
|
|
127
|
-
<ChartContainer
|
|
128
|
-
config={chartConfig}
|
|
129
|
-
className="min-h-[
|
|
130
|
-
>
|
|
131
|
-
<AreaChart data={data}>
|
|
134
|
+
<ChartContainer
|
|
135
|
+
config={chartConfig}
|
|
136
|
+
className="min-h-[170px] w-full flex-1 overflow-hidden sm:min-h-[220px]"
|
|
137
|
+
>
|
|
138
|
+
<AreaChart data={data}>
|
|
132
139
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
133
|
-
<XAxis
|
|
134
|
-
dataKey="date"
|
|
135
|
-
tickLine={false}
|
|
136
|
-
axisLine={false}
|
|
137
|
-
fontSize={
|
|
138
|
-
tickMargin={
|
|
139
|
-
minTickGap={
|
|
140
|
-
/>
|
|
140
|
+
<XAxis
|
|
141
|
+
dataKey="date"
|
|
142
|
+
tickLine={false}
|
|
143
|
+
axisLine={false}
|
|
144
|
+
fontSize={10}
|
|
145
|
+
tickMargin={4}
|
|
146
|
+
minTickGap={20}
|
|
147
|
+
/>
|
|
141
148
|
<YAxis
|
|
142
149
|
tickLine={false}
|
|
143
150
|
axisLine={false}
|
|
144
|
-
fontSize={
|
|
151
|
+
fontSize={10}
|
|
145
152
|
tickMargin={4}
|
|
146
153
|
allowDecimals={false}
|
|
147
154
|
/>
|
|
@@ -28,37 +28,40 @@ function LoginChart({ data }: { data: LoginDay[] }) {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
31
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
32
32
|
<CardHeader className="shrink-0 pb-2">
|
|
33
33
|
<div className="flex items-center gap-2">
|
|
34
|
-
<LogIn className="h-
|
|
34
|
+
<LogIn className="h-4 w-4 text-blue-600 sm:h-5 sm:w-5" />
|
|
35
35
|
<div>
|
|
36
|
-
<CardTitle className="text-
|
|
36
|
+
<CardTitle className="text-sm font-semibold sm:text-base">
|
|
37
37
|
{t('title')}
|
|
38
38
|
</CardTitle>
|
|
39
|
-
<CardDescription>
|
|
39
|
+
<CardDescription className="text-xs sm:text-sm">
|
|
40
|
+
{t('description')}
|
|
41
|
+
</CardDescription>
|
|
40
42
|
</div>
|
|
41
43
|
</div>
|
|
42
44
|
</CardHeader>
|
|
43
|
-
<CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
|
|
44
|
-
<ChartContainer
|
|
45
|
-
config={chartConfig}
|
|
46
|
-
className="h-full min-h-[
|
|
47
|
-
>
|
|
48
|
-
<BarChart data={data as any} barGap={2}>
|
|
45
|
+
<CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
|
|
46
|
+
<ChartContainer
|
|
47
|
+
config={chartConfig}
|
|
48
|
+
className="h-full min-h-[140px] w-full flex-1 overflow-hidden sm:min-h-40"
|
|
49
|
+
>
|
|
50
|
+
<BarChart data={data as any} barGap={2}>
|
|
49
51
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
50
|
-
<XAxis
|
|
51
|
-
dataKey="day"
|
|
52
|
-
tickLine={false}
|
|
53
|
-
axisLine={false}
|
|
54
|
-
fontSize={
|
|
55
|
-
tickMargin={
|
|
56
|
-
minTickGap={
|
|
57
|
-
|
|
52
|
+
<XAxis
|
|
53
|
+
dataKey="day"
|
|
54
|
+
tickLine={false}
|
|
55
|
+
axisLine={false}
|
|
56
|
+
fontSize={10}
|
|
57
|
+
tickMargin={4}
|
|
58
|
+
minTickGap={20}
|
|
59
|
+
interval="preserveStartEnd"
|
|
60
|
+
/>
|
|
58
61
|
<YAxis
|
|
59
62
|
tickLine={false}
|
|
60
63
|
axisLine={false}
|
|
61
|
-
fontSize={
|
|
64
|
+
fontSize={10}
|
|
62
65
|
tickMargin={4}
|
|
63
66
|
allowDecimals={false}
|
|
64
67
|
/>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
2
|
+
import { LayoutList } from 'lucide-react';
|
|
3
|
+
import { useTranslations } from 'next-intl';
|
|
4
|
+
import StatCard from '../stats';
|
|
5
|
+
import { WidgetWrapper } from '../widget-wrapper';
|
|
6
|
+
|
|
7
|
+
interface MenusCardProps {
|
|
8
|
+
widget?: any;
|
|
9
|
+
onRemove?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SystemStatsData {
|
|
13
|
+
cards?: {
|
|
14
|
+
menus?: {
|
|
15
|
+
value: number;
|
|
16
|
+
change: number | null;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function MenusCard({ widget, onRemove }: MenusCardProps) {
|
|
22
|
+
const t = useTranslations('core.Dashboard');
|
|
23
|
+
|
|
24
|
+
const { data, isLoading, isAccessDenied, isError } =
|
|
25
|
+
useWidgetData<SystemStatsData>({
|
|
26
|
+
endpoint: '/dashboard-core/stats/overview/system',
|
|
27
|
+
queryKey: 'dashboard-stats-system',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const value = data?.cards?.menus?.value?.toLocaleString('pt-BR') || '0';
|
|
31
|
+
const change = data?.cards?.menus?.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('menus')}
|
|
41
|
+
onRemove={onRemove}
|
|
42
|
+
>
|
|
43
|
+
<StatCard
|
|
44
|
+
title={t('menus')}
|
|
45
|
+
value={value}
|
|
46
|
+
change={
|
|
47
|
+
change !== null && change !== undefined
|
|
48
|
+
? `${change > 0 ? '+' : ''}${change}%`
|
|
49
|
+
: undefined
|
|
50
|
+
}
|
|
51
|
+
changeType={changeType}
|
|
52
|
+
icon={<LayoutList className="h-6 w-6 text-green-500" />}
|
|
53
|
+
iconBg="bg-green-500/10"
|
|
54
|
+
delay={50}
|
|
55
|
+
/>
|
|
56
|
+
</WidgetWrapper>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
2
|
+
import { Route } from 'lucide-react';
|
|
3
|
+
import { useTranslations } from 'next-intl';
|
|
4
|
+
import StatCard from '../stats';
|
|
5
|
+
import { WidgetWrapper } from '../widget-wrapper';
|
|
6
|
+
|
|
7
|
+
interface RoutesCardProps {
|
|
8
|
+
widget?: any;
|
|
9
|
+
onRemove?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SystemStatsData {
|
|
13
|
+
cards?: {
|
|
14
|
+
routes?: {
|
|
15
|
+
value: number;
|
|
16
|
+
change: number | null;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function RoutesCard({ widget, onRemove }: RoutesCardProps) {
|
|
22
|
+
const t = useTranslations('core.Dashboard');
|
|
23
|
+
|
|
24
|
+
const { data, isLoading, isAccessDenied, isError } =
|
|
25
|
+
useWidgetData<SystemStatsData>({
|
|
26
|
+
endpoint: '/dashboard-core/stats/overview/system',
|
|
27
|
+
queryKey: 'dashboard-stats-system',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const value = data?.cards?.routes?.value?.toLocaleString('pt-BR') || '0';
|
|
31
|
+
const change = data?.cards?.routes?.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('routes')}
|
|
41
|
+
onRemove={onRemove}
|
|
42
|
+
>
|
|
43
|
+
<StatCard
|
|
44
|
+
title={t('routes')}
|
|
45
|
+
value={value}
|
|
46
|
+
change={
|
|
47
|
+
change !== null && change !== undefined
|
|
48
|
+
? `${change > 0 ? '+' : ''}${change}%`
|
|
49
|
+
: undefined
|
|
50
|
+
}
|
|
51
|
+
changeType={changeType}
|
|
52
|
+
icon={<Route className="h-6 w-6 text-orange-500" />}
|
|
53
|
+
iconBg="bg-orange-500/10"
|
|
54
|
+
delay={50}
|
|
55
|
+
/>
|
|
56
|
+
</WidgetWrapper>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -34,24 +34,24 @@ export default function StatAccessLevel({
|
|
|
34
34
|
widgetName={widget?.name ?? 'stat-access-level'}
|
|
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-
|
|
40
|
-
<Zap className="h-
|
|
41
|
-
</div>
|
|
42
|
-
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[
|
|
44
|
-
{t('accessLevel')}
|
|
45
|
-
</span>
|
|
46
|
-
<span className="truncate text-
|
|
47
|
-
{data ? t('accessLevelValue', { level: data }) : '—'}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="
|
|
50
|
-
{t('accessLevelSubtitle')}
|
|
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-amber-50 dark:bg-amber-950/40 sm:h-9 sm:w-9 md:h-11 md:w-11">
|
|
40
|
+
<Zap className="h-3.5 w-3.5 text-amber-600 dark:text-amber-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('accessLevel')}
|
|
45
|
+
</span>
|
|
46
|
+
<span className="truncate text-base font-bold tracking-tight text-foreground sm:text-lg md:text-2xl">
|
|
47
|
+
{data ? t('accessLevelValue', { level: data }) : '—'}
|
|
48
|
+
</span>
|
|
49
|
+
<span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
|
|
50
|
+
{t('accessLevelSubtitle')}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
55
|
</WidgetWrapper>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -34,24 +34,24 @@ export default function StatActionsToday({
|
|
|
34
34
|
widgetName={widget?.name ?? 'stat-actions-today'}
|
|
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-
|
|
40
|
-
<MousePointerClick className="h-
|
|
41
|
-
</div>
|
|
42
|
-
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[
|
|
44
|
-
{t('actionsToday')}
|
|
45
|
-
</span>
|
|
46
|
-
<span className="truncate text-
|
|
47
|
-
{data ?? '—'}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="
|
|
50
|
-
{t('actionsTodaySubtitle')}
|
|
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-indigo-50 dark:bg-indigo-950/40 sm:h-9 sm:w-9 md:h-11 md:w-11">
|
|
40
|
+
<MousePointerClick className="h-3.5 w-3.5 text-indigo-600 dark:text-indigo-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('actionsToday')}
|
|
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('actionsTodaySubtitle')}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
55
|
</WidgetWrapper>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -34,24 +34,24 @@ export default function StatConsecutiveDays({
|
|
|
34
34
|
widgetName={widget?.name ?? 'stat-consecutive-days'}
|
|
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-
|
|
40
|
-
<CalendarDays className="h-
|
|
41
|
-
</div>
|
|
42
|
-
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[
|
|
44
|
-
{t('consecutiveDays')}
|
|
45
|
-
</span>
|
|
46
|
-
<span className="truncate text-
|
|
47
|
-
{data ?? '—'}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="
|
|
50
|
-
{t('consecutiveDaysSubtitle')}
|
|
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-emerald-50 dark:bg-emerald-950/40 sm:h-9 sm:w-9 md:h-11 md:w-11">
|
|
40
|
+
<CalendarDays className="h-3.5 w-3.5 text-emerald-600 dark:text-emerald-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('consecutiveDays')}
|
|
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('consecutiveDaysSubtitle')}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
55
|
</WidgetWrapper>
|
|
56
56
|
);
|
|
57
57
|
}
|