@hed-hog/core 0.0.278 → 0.0.285

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +60 -0
  2. package/dist/auth/auth.controller.d.ts +3 -3
  3. package/dist/auth/auth.service.d.ts +8 -8
  4. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +12 -0
  5. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  6. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +9 -0
  7. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  8. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +12 -0
  9. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  10. package/dist/dashboard/dashboard-core/dashboard-core.service.js +25 -0
  11. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  12. package/dist/file/file.controller.d.ts +2 -2
  13. package/dist/file/file.service.d.ts +4 -4
  14. package/dist/role/guards/role.guard.d.ts.map +1 -1
  15. package/dist/role/guards/role.guard.js +1 -1
  16. package/dist/role/guards/role.guard.js.map +1 -1
  17. package/dist/session/session.controller.d.ts +1 -1
  18. package/dist/session/session.service.d.ts +3 -3
  19. package/dist/user/user.controller.d.ts +2 -2
  20. package/dist/user/user.service.d.ts +6 -6
  21. package/hedhog/data/dashboard_component.yaml +95 -77
  22. package/hedhog/data/dashboard_component_role.yaml +91 -79
  23. package/hedhog/data/dashboard_item.yaml +121 -101
  24. package/hedhog/data/route.yaml +8 -0
  25. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +69 -62
  26. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +23 -12
  27. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +80 -5
  28. package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +33 -29
  29. package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +18 -14
  30. package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +39 -32
  31. package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +22 -19
  32. package/hedhog/frontend/app/dashboard/components/widgets/menus-card.tsx.ejs +58 -0
  33. package/hedhog/frontend/app/dashboard/components/widgets/routes-card.tsx.ejs +58 -0
  34. package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +18 -18
  35. package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +18 -18
  36. package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +18 -18
  37. package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +18 -18
  38. package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +15 -11
  39. package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +39 -37
  40. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +20 -4
  41. package/hedhog/frontend/app/mail/log/page.tsx.ejs +36 -47
  42. package/hedhog/frontend/app/mail/template/page.tsx.ejs +176 -126
  43. package/hedhog/frontend/app/menu/page.tsx.ejs +45 -39
  44. package/hedhog/frontend/app/roles/page.tsx.ejs +45 -46
  45. package/hedhog/frontend/app/users/page.tsx.ejs +70 -73
  46. package/hedhog/frontend/messages/en.json +15 -2
  47. package/hedhog/frontend/messages/pt.json +15 -2
  48. package/package.json +4 -4
  49. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
  50. package/src/dashboard/dashboard-core/dashboard-core.service.ts +34 -0
  51. package/src/role/guards/role.guard.ts +9 -8
@@ -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-5 flex flex-col items-center gap-3 rounded-xl bg-muted/50 p-4 sm:p-5">
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 className={`text-5xl font-bold tracking-tight ${scoreColor}`}>
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-lg text-muted-foreground">/100</span>
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-xs text-muted-foreground">
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-center gap-3 rounded-lg p-3 transition-colors hover:bg-muted/50"
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-9 w-9 shrink-0 items-center justify-center rounded-lg ${
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-words text-sm font-medium text-foreground">
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-xs text-muted-foreground">
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-2 w-full shrink-0 gap-1 text-xs sm:mt-0 sm:w-auto"
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-base font-semibold">
126
+ <CardTitle className="text-sm font-semibold sm:text-base">
127
127
  {t('title')}
128
128
  </CardTitle>
129
- <CardDescription>{t('description')}</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-[15px] top-0 w-px bg-border/60" />
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-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">
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-8 w-8 shrink-0 items-center justify-center rounded-lg ${config.bg}`}
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 className={`h-3.5 w-3.5 ${config.color}`} />
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-2 py-1.5 transition-colors group-hover:bg-muted/40">
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-words 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
+ <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-5 w-5 text-rose-600 dark:text-rose-400" />
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-base font-semibold">
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-1 gap-2 md:grid-cols-2 xl:grid-cols-4">
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.5 text-center transition-colors hover:bg-muted/30"
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-7 w-7 items-center justify-center rounded-md ${stat.bg}`}
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 className={`h-3.5 w-3.5 ${stat.color}`} />
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-lg font-bold text-foreground">
122
+ <span className="text-base font-bold text-foreground sm:text-lg">
117
123
  {stat.value}
118
124
  </span>
119
- <span className="text-[10px] text-muted-foreground">
120
- {stat.label}
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-[220px] w-full flex-1 overflow-hidden"
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={11}
138
- tickMargin={8}
139
- minTickGap={24}
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={11}
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-5 w-5 text-blue-600" />
34
+ <LogIn className="h-4 w-4 text-blue-600 sm:h-5 sm:w-5" />
35
35
  <div>
36
- <CardTitle className="text-base font-semibold">
36
+ <CardTitle className="text-sm font-semibold sm:text-base">
37
37
  {t('title')}
38
38
  </CardTitle>
39
- <CardDescription>{t('description')}</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-[160px] w-full flex-1 overflow-hidden"
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={12}
55
- tickMargin={8}
56
- minTickGap={16}
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={12}
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-9 w-9 shrink-0 items-center justify-center rounded-xl bg-amber-50 dark:bg-amber-950/40 md:h-11 md:w-11">
40
- <Zap className="h-4 w-4 text-amber-600 dark:text-amber-400 md:h-5 md:w-5" />
41
- </div>
42
- <div className="flex min-w-0 flex-col">
43
- <span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
44
- {t('accessLevel')}
45
- </span>
46
- <span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
47
- {data ? t('accessLevelValue', { level: data }) : '—'}
48
- </span>
49
- <span className="hidden text-[11px] text-muted-foreground sm:block">
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-9 w-9 shrink-0 items-center justify-center rounded-xl bg-indigo-50 dark:bg-indigo-950/40 md:h-11 md:w-11">
40
- <MousePointerClick className="h-4 w-4 text-indigo-600 dark:text-indigo-400 md:h-5 md:w-5" />
41
- </div>
42
- <div className="flex min-w-0 flex-col">
43
- <span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
44
- {t('actionsToday')}
45
- </span>
46
- <span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
47
- {data ?? '—'}
48
- </span>
49
- <span className="hidden text-[11px] text-muted-foreground sm:block">
50
- {t('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-9 w-9 shrink-0 items-center justify-center rounded-xl bg-emerald-50 dark:bg-emerald-950/40 md:h-11 md:w-11">
40
- <CalendarDays className="h-4 w-4 text-emerald-600 dark:text-emerald-400 md:h-5 md:w-5" />
41
- </div>
42
- <div className="flex min-w-0 flex-col">
43
- <span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
44
- {t('consecutiveDays')}
45
- </span>
46
- <span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
47
- {data ?? '—'}
48
- </span>
49
- <span className="hidden text-[11px] text-muted-foreground sm:block">
50
- {t('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
  }