@hed-hog/core 0.0.276 → 0.0.279
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 +8 -1
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +7 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +10 -1
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +34 -8
- package/dist/auth/auth.service.js.map +1 -1
- 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/profile/profile.service.js +1 -1
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/role/guards/role.guard.d.ts +1 -0
- package/dist/role/guards/role.guard.d.ts.map +1 -1
- package/dist/role/guards/role.guard.js +18 -0
- package/dist/role/guards/role.guard.js.map +1 -1
- package/dist/session/session.service.js +1 -1
- package/dist/session/session.service.js.map +1 -1
- package/dist/user/dto/reset-password.dto.d.ts +4 -0
- package/dist/user/dto/reset-password.dto.d.ts.map +1 -0
- package/dist/user/dto/reset-password.dto.js +26 -0
- package/dist/user/dto/reset-password.dto.js.map +1 -0
- package/dist/user/user.controller.d.ts +5 -0
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +13 -0
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +6 -0
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +65 -0
- package/dist/user/user.service.js.map +1 -1
- package/hedhog/data/dashboard_component.yaml +74 -12
- package/hedhog/data/dashboard_component_role.yaml +223 -145
- package/hedhog/data/dashboard_item.yaml +42 -22
- package/hedhog/data/dashboard_role.yaml +18 -12
- package/hedhog/data/menu.yaml +6 -0
- package/hedhog/data/route.yaml +65 -1
- package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +2 -1
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +17 -17
- 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 +17 -13
- package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +16 -12
- package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +27 -16
- package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +13 -9
- package/hedhog/frontend/app/dashboard/components/widgets/menus-card.tsx.ejs +58 -0
- package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +62 -58
- 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 +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +15 -11
- package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +18 -15
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +20 -4
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +29 -14
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +5 -11
- package/hedhog/frontend/app/users/page.tsx.ejs +331 -10
- package/hedhog/frontend/messages/en.json +29 -3
- package/hedhog/frontend/messages/pt.json +29 -3
- package/package.json +4 -4
- package/src/auth/auth.controller.ts +21 -20
- package/src/auth/auth.service.ts +63 -15
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +34 -0
- package/src/profile/profile.service.ts +1 -1
- package/src/role/guards/role.guard.ts +36 -7
- package/src/session/session.service.ts +2 -2
- package/src/user/dto/reset-password.dto.ts +11 -0
- package/src/user/user.controller.ts +24 -14
- package/src/user/user.service.ts +84 -0
|
@@ -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,59 +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 flex-col">
|
|
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-1 overflow-
|
|
103
|
-
<div className="
|
|
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">
|
|
104
108
|
{emailStats.map((stat) => {
|
|
105
109
|
const Icon = stat.icon;
|
|
106
110
|
return (
|
|
107
111
|
<div
|
|
108
112
|
key={stat.label}
|
|
109
|
-
className="flex flex-col items-center gap-1 rounded-lg border p-2
|
|
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"
|
|
110
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
|
|
134
|
+
<ChartContainer
|
|
135
|
+
config={chartConfig}
|
|
136
|
+
className="min-h-[170px] w-full flex-1 overflow-hidden sm:min-h-[220px]"
|
|
137
|
+
>
|
|
128
138
|
<AreaChart data={data}>
|
|
129
139
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
130
140
|
<XAxis
|
|
131
141
|
dataKey="date"
|
|
132
142
|
tickLine={false}
|
|
133
143
|
axisLine={false}
|
|
134
|
-
fontSize={
|
|
135
|
-
tickMargin={
|
|
144
|
+
fontSize={10}
|
|
145
|
+
tickMargin={4}
|
|
146
|
+
minTickGap={20}
|
|
136
147
|
/>
|
|
137
148
|
<YAxis
|
|
138
149
|
tickLine={false}
|
|
139
150
|
axisLine={false}
|
|
140
|
-
fontSize={
|
|
151
|
+
fontSize={10}
|
|
141
152
|
tickMargin={4}
|
|
142
153
|
allowDecimals={false}
|
|
143
154
|
/>
|
|
@@ -28,22 +28,24 @@ function LoginChart({ data }: { data: LoginDay[] }) {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<Card className="flex h-full flex-col">
|
|
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 flex-1 flex-col pt-0">
|
|
45
|
+
<CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
|
|
44
46
|
<ChartContainer
|
|
45
47
|
config={chartConfig}
|
|
46
|
-
className="h-full min-h-[
|
|
48
|
+
className="h-full min-h-[140px] w-full flex-1 overflow-hidden sm:min-h-40"
|
|
47
49
|
>
|
|
48
50
|
<BarChart data={data as any} barGap={2}>
|
|
49
51
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
@@ -51,13 +53,15 @@ function LoginChart({ data }: { data: LoginDay[] }) {
|
|
|
51
53
|
dataKey="day"
|
|
52
54
|
tickLine={false}
|
|
53
55
|
axisLine={false}
|
|
54
|
-
fontSize={
|
|
55
|
-
tickMargin={
|
|
56
|
+
fontSize={10}
|
|
57
|
+
tickMargin={4}
|
|
58
|
+
minTickGap={20}
|
|
59
|
+
interval="preserveStartEnd"
|
|
56
60
|
/>
|
|
57
61
|
<YAxis
|
|
58
62
|
tickLine={false}
|
|
59
63
|
axisLine={false}
|
|
60
|
-
fontSize={
|
|
64
|
+
fontSize={10}
|
|
61
65
|
tickMargin={4}
|
|
62
66
|
allowDecimals={false}
|
|
63
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
|
+
}
|
|
@@ -37,20 +37,24 @@ function CustomTooltip({
|
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
interface PermissionsChartProps {
|
|
41
|
-
widget?:
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
interface PermissionsChartProps {
|
|
41
|
+
widget?: {
|
|
42
|
+
name?: string;
|
|
43
|
+
} | null;
|
|
44
|
+
onRemove?: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PermissionDistributionItem {
|
|
48
|
+
name: string;
|
|
49
|
+
value: number;
|
|
50
|
+
color: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface UserStatsData {
|
|
54
|
+
charts?: {
|
|
55
|
+
permissionDistribution?: PermissionDistributionItem[];
|
|
56
|
+
};
|
|
57
|
+
}
|
|
54
58
|
|
|
55
59
|
export default function PermissionsChart({
|
|
56
60
|
widget,
|
|
@@ -66,10 +70,10 @@ export default function PermissionsChart({
|
|
|
66
70
|
} = useWidgetData<UserStatsData>({
|
|
67
71
|
endpoint: '/dashboard-core/stats/overview/users',
|
|
68
72
|
queryKey: 'dashboard-stats-users',
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const data = statsData?.charts?.permissionDistribution || [];
|
|
72
|
-
const total = data.reduce((sum
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const data = statsData?.charts?.permissionDistribution || [];
|
|
76
|
+
const total = data.reduce((sum, item) => sum + item.value, 0);
|
|
73
77
|
|
|
74
78
|
return (
|
|
75
79
|
<WidgetWrapper
|
|
@@ -86,19 +90,19 @@ export default function PermissionsChart({
|
|
|
86
90
|
>
|
|
87
91
|
<IconGripVertical className="text-muted-foreground/50 size-4 shrink-0" />
|
|
88
92
|
</div>
|
|
89
|
-
<CardHeader className="pb-2 pt-4 pl-10">
|
|
90
|
-
<CardTitle className="text-base font-semibold">
|
|
91
|
-
{t('permissionsDistributionTitle')}
|
|
92
|
-
</CardTitle>
|
|
93
|
+
<CardHeader className="pb-2 pt-4 pl-10">
|
|
94
|
+
<CardTitle className="text-base font-semibold">
|
|
95
|
+
{t('permissionsDistributionTitle')}
|
|
96
|
+
</CardTitle>
|
|
93
97
|
<CardDescription>
|
|
94
98
|
{t('permissionsDistributionDescription')}
|
|
95
99
|
</CardDescription>
|
|
96
|
-
</CardHeader>
|
|
97
|
-
<CardContent className="flex-1 pt-0">
|
|
98
|
-
<div className="flex
|
|
99
|
-
<div className="relative h-[280px] w-[280px] shrink-0">
|
|
100
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
101
|
-
<PieChart>
|
|
100
|
+
</CardHeader>
|
|
101
|
+
<CardContent className="flex-1 pt-0">
|
|
102
|
+
<div className="flex h-full items-center justify-between gap-4 overflow-hidden">
|
|
103
|
+
<div className="relative h-[280px] w-[280px] shrink-0">
|
|
104
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
105
|
+
<PieChart>
|
|
102
106
|
<Pie
|
|
103
107
|
data={data}
|
|
104
108
|
cx="50%"
|
|
@@ -106,14 +110,14 @@ export default function PermissionsChart({
|
|
|
106
110
|
innerRadius={70}
|
|
107
111
|
outerRadius={110}
|
|
108
112
|
paddingAngle={4}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{data.map((entry
|
|
114
|
-
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
115
|
-
))}
|
|
116
|
-
</Pie>
|
|
113
|
+
dataKey="value"
|
|
114
|
+
animationDuration={1200}
|
|
115
|
+
strokeWidth={0}
|
|
116
|
+
>
|
|
117
|
+
{data.map((entry, index: number) => (
|
|
118
|
+
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
119
|
+
))}
|
|
120
|
+
</Pie>
|
|
117
121
|
<Tooltip content={<CustomTooltip />} />
|
|
118
122
|
</PieChart>
|
|
119
123
|
</ResponsiveContainer>
|
|
@@ -121,28 +125,28 @@ export default function PermissionsChart({
|
|
|
121
125
|
<span className="text-2xl font-bold text-foreground">
|
|
122
126
|
{total}
|
|
123
127
|
</span>
|
|
124
|
-
<span className="text-[10px] text-muted-foreground">
|
|
125
|
-
{t('total')}
|
|
126
|
-
</span>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
<div className="grid grid-cols-2 gap-
|
|
130
|
-
{data.map((item
|
|
131
|
-
<div key={item.name} className="flex items-
|
|
132
|
-
<div
|
|
133
|
-
className="h-
|
|
134
|
-
style={{ backgroundColor: item.color }}
|
|
135
|
-
/>
|
|
136
|
-
<div className="flex flex-col">
|
|
137
|
-
<span className="text-
|
|
138
|
-
{item.name}
|
|
139
|
-
</span>
|
|
140
|
-
<span className="text-
|
|
141
|
-
{item.value} ({Math.round((item.value / total) * 100)}%)
|
|
142
|
-
</span>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
))}
|
|
128
|
+
<span className="text-[10px] text-muted-foreground">
|
|
129
|
+
{t('total')}
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="grid max-h-[280px] min-w-0 flex-1 grid-cols-2 content-start gap-x-4 gap-y-2 overflow-y-auto pr-2">
|
|
134
|
+
{data.map((item) => (
|
|
135
|
+
<div key={item.name} className="flex min-w-0 items-start gap-2.5">
|
|
136
|
+
<div
|
|
137
|
+
className="mt-1 h-2.5 w-2.5 shrink-0 rounded-sm"
|
|
138
|
+
style={{ backgroundColor: item.color }}
|
|
139
|
+
/>
|
|
140
|
+
<div className="flex min-w-0 flex-col">
|
|
141
|
+
<span className="break-words text-xs font-medium leading-tight text-foreground">
|
|
142
|
+
{item.name}
|
|
143
|
+
</span>
|
|
144
|
+
<span className="text-[11px] text-muted-foreground">
|
|
145
|
+
{item.value} ({total ? Math.round((item.value / total) * 100) : 0}%)
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
))}
|
|
146
150
|
</div>
|
|
147
151
|
</div>
|
|
148
152
|
</CardContent>
|
|
@@ -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
|
+
}
|
|
@@ -35,18 +35,18 @@ export default function StatAccessLevel({
|
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
37
|
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<Zap className="h-5 w-5 text-amber-600 dark:text-amber-400" />
|
|
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
41
|
</div>
|
|
42
42
|
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[
|
|
43
|
+
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground sm:text-[11px]">
|
|
44
44
|
{t('accessLevel')}
|
|
45
45
|
</span>
|
|
46
|
-
<span className="text-
|
|
46
|
+
<span className="truncate text-base font-bold tracking-tight text-foreground sm:text-lg md:text-2xl">
|
|
47
47
|
{data ? t('accessLevelValue', { level: data }) : '—'}
|
|
48
48
|
</span>
|
|
49
|
-
<span className="text-[
|
|
49
|
+
<span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
|
|
50
50
|
{t('accessLevelSubtitle')}
|
|
51
51
|
</span>
|
|
52
52
|
</div>
|
|
@@ -35,18 +35,18 @@ export default function StatActionsToday({
|
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
37
|
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<MousePointerClick className="h-5 w-5 text-indigo-600 dark:text-indigo-400" />
|
|
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
41
|
</div>
|
|
42
42
|
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[
|
|
43
|
+
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground sm:text-[11px]">
|
|
44
44
|
{t('actionsToday')}
|
|
45
45
|
</span>
|
|
46
|
-
<span className="text-
|
|
46
|
+
<span className="truncate text-lg font-bold tracking-tight text-foreground sm:text-xl md:text-2xl">
|
|
47
47
|
{data ?? '—'}
|
|
48
48
|
</span>
|
|
49
|
-
<span className="text-[
|
|
49
|
+
<span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
|
|
50
50
|
{t('actionsTodaySubtitle')}
|
|
51
51
|
</span>
|
|
52
52
|
</div>
|
|
@@ -35,18 +35,18 @@ export default function StatConsecutiveDays({
|
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
37
|
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<CalendarDays className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
|
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
41
|
</div>
|
|
42
42
|
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[
|
|
43
|
+
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground sm:text-[11px]">
|
|
44
44
|
{t('consecutiveDays')}
|
|
45
45
|
</span>
|
|
46
|
-
<span className="text-
|
|
46
|
+
<span className="truncate text-lg font-bold tracking-tight text-foreground sm:text-xl md:text-2xl">
|
|
47
47
|
{data ?? '—'}
|
|
48
48
|
</span>
|
|
49
|
-
<span className="text-[
|
|
49
|
+
<span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
|
|
50
50
|
{t('consecutiveDaysSubtitle')}
|
|
51
51
|
</span>
|
|
52
52
|
</div>
|
|
@@ -35,18 +35,18 @@ export default function StatOnlineTime({
|
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
37
|
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<Clock className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
|
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
41
|
</div>
|
|
42
42
|
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[
|
|
43
|
+
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground sm:text-[11px]">
|
|
44
44
|
{t('onlineTime')}
|
|
45
45
|
</span>
|
|
46
|
-
<span className="text-
|
|
46
|
+
<span className="truncate text-lg font-bold tracking-tight text-foreground sm:text-xl md:text-2xl">
|
|
47
47
|
{data ?? '—'}
|
|
48
48
|
</span>
|
|
49
|
-
<span className="text-[
|
|
49
|
+
<span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
|
|
50
50
|
{t('onlineTimeSubtitle')}
|
|
51
51
|
</span>
|
|
52
52
|
</div>
|
|
@@ -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 flex-col">
|
|
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-
|
|
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-
|
|
60
|
+
<CardTitle className="text-sm font-semibold sm:text-base">
|
|
61
61
|
{t('title')}
|
|
62
62
|
</CardTitle>
|
|
63
|
-
<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-1 overflow-auto pt-0">
|
|
68
|
-
<div className="grid grid-cols-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
86
|
+
<span className="truncate text-[13px] font-medium text-foreground sm:text-sm">
|
|
83
87
|
{role.name}
|
|
84
88
|
</span>
|
|
85
|
-
<span className="text-
|
|
89
|
+
<span className="truncate text-[11px] text-muted-foreground sm:text-xs">
|
|
86
90
|
{role.slug}
|
|
87
91
|
</span>
|
|
88
92
|
</div>
|