@hed-hog/core 0.0.278 → 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.
Files changed (36) hide show
  1. package/README.md +60 -0
  2. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +12 -0
  3. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  4. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +9 -0
  5. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  6. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +12 -0
  7. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  8. package/dist/dashboard/dashboard-core/dashboard-core.service.js +25 -0
  9. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  10. package/hedhog/data/dashboard_component.yaml +95 -77
  11. package/hedhog/data/dashboard_component_role.yaml +91 -79
  12. package/hedhog/data/dashboard_item.yaml +121 -101
  13. package/hedhog/data/route.yaml +8 -0
  14. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +17 -17
  15. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +23 -12
  16. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +80 -5
  17. package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +33 -29
  18. package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +18 -14
  19. package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +39 -32
  20. package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +22 -19
  21. package/hedhog/frontend/app/dashboard/components/widgets/menus-card.tsx.ejs +58 -0
  22. package/hedhog/frontend/app/dashboard/components/widgets/routes-card.tsx.ejs +58 -0
  23. package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +18 -18
  24. package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +18 -18
  25. package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +18 -18
  26. package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +18 -18
  27. package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +15 -11
  28. package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +39 -37
  29. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +20 -4
  30. package/hedhog/frontend/app/mail/log/page.tsx.ejs +5 -11
  31. package/hedhog/frontend/app/users/page.tsx.ejs +9 -9
  32. package/hedhog/frontend/messages/en.json +10 -2
  33. package/hedhog/frontend/messages/pt.json +10 -2
  34. package/package.json +3 -3
  35. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
  36. package/src/dashboard/dashboard-core/dashboard-core.service.ts +34 -0
@@ -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
  }
@@ -34,24 +34,24 @@ export default function StatOnlineTime({
34
34
  widgetName={widget?.name ?? 'stat-online-time'}
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-blue-50 dark:bg-blue-950/40 md:h-11 md:w-11">
40
- <Clock className="h-4 w-4 text-blue-600 dark:text-blue-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('onlineTime')}
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('onlineTimeSubtitle')}
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-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
+ </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('onlineTime')}
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('onlineTimeSubtitle')}
51
+ </span>
52
+ </div>
53
+ </CardContent>
54
+ </Card>
55
55
  </WidgetWrapper>
56
56
  );
57
57
  }
@@ -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 min-h-0 flex-col overflow-hidden">
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-5 w-5 text-amber-600 dark:text-amber-400" />
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-base font-semibold">
60
+ <CardTitle className="text-sm font-semibold sm:text-base">
61
61
  {t('title')}
62
62
  </CardTitle>
63
- <CardDescription>{t('description')}</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 min-h-0 flex-1 overflow-auto pt-0">
68
- <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
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-3 rounded-xl border p-3 transition-all duration-200 hover:shadow-sm ${style.border} ${style.bg}`}
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-9 w-9 shrink-0 items-center justify-center rounded-lg ${style.iconBg}`}
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 className={`h-4 w-4 ${style.iconColor}`} />
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-sm font-medium text-foreground">
86
+ <span className="truncate text-[13px] font-medium text-foreground sm:text-sm">
83
87
  {role.name}
84
88
  </span>
85
- <span className="text-xs text-muted-foreground">
89
+ <span className="truncate text-[11px] text-muted-foreground sm:text-xs">
86
90
  {role.slug}
87
91
  </span>
88
92
  </div>
@@ -86,31 +86,33 @@ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
86
86
  const router = useRouter();
87
87
 
88
88
  return (
89
- <Card className="flex h-full min-h-0 flex-col overflow-hidden">
90
- <CardHeader className="shrink-0 pb-3">
91
- <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
92
- <div className="flex min-w-0 items-center gap-2">
93
- <Globe className="h-5 w-5 text-blue-600" />
94
- <div className="min-w-0">
95
- <CardTitle className="text-base font-semibold">
96
- {t('title')}
97
- </CardTitle>
98
- <CardDescription>{t('description')}</CardDescription>
99
- </div>
100
- </div>
101
- <Button
102
- variant="outline"
103
- size="sm"
104
- onClick={() => router.push('/core/account/sessions')}
105
- className="w-full shrink-0 sm:w-auto"
106
- >
107
- <Info className="h-3.5 w-3.5" />
108
- {t('moreInfo')}
109
- </Button>
110
- </div>
111
- </CardHeader>
112
- <CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
113
- <div className="flex flex-col gap-2">
89
+ <Card className="flex h-full min-h-0 flex-col overflow-hidden">
90
+ <CardHeader className="shrink-0 pb-3">
91
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
92
+ <div className="flex min-w-0 items-center gap-2">
93
+ <Globe className="h-4 w-4 text-blue-600 sm:h-5 sm:w-5" />
94
+ <div className="min-w-0">
95
+ <CardTitle className="text-sm font-semibold sm:text-base">
96
+ {t('title')}
97
+ </CardTitle>
98
+ <CardDescription className="text-xs sm:text-sm">
99
+ {t('description')}
100
+ </CardDescription>
101
+ </div>
102
+ </div>
103
+ <Button
104
+ variant="outline"
105
+ size="sm"
106
+ onClick={() => router.push('/core/account/sessions')}
107
+ className="h-8 w-full shrink-0 gap-1.5 px-2.5 text-xs sm:h-9 sm:w-auto sm:text-sm"
108
+ >
109
+ <Info className="h-3.5 w-3.5" />
110
+ {t('moreInfo')}
111
+ </Button>
112
+ </div>
113
+ </CardHeader>
114
+ <CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
115
+ <div className="flex flex-col gap-2">
114
116
  {sessions.map((session, index) => {
115
117
  const ua = session.user_agent ?? '';
116
118
  const deviceType = detectDeviceType(ua);
@@ -135,42 +137,42 @@ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
135
137
  return (
136
138
  <div
137
139
  key={session.id}
138
- className={`group flex items-center gap-3 rounded-xl border p-3.5 transition-all duration-200 hover:shadow-sm ${
140
+ className={`group flex items-center gap-2.5 rounded-xl border p-3 transition-all duration-200 hover:shadow-sm sm:gap-3 sm:p-3.5 ${
139
141
  isCurrent
140
142
  ? 'border-emerald-200 bg-emerald-50/50 dark:border-emerald-800 dark:bg-emerald-950/30'
141
143
  : 'bg-card hover:bg-muted/30'
142
144
  }`}
143
145
  >
144
146
  <div
145
- className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${
147
+ className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-10 sm:w-10 ${
146
148
  isCurrent
147
149
  ? 'bg-emerald-100 dark:bg-emerald-900/50'
148
150
  : 'bg-muted'
149
151
  }`}
150
152
  >
151
153
  <DeviceIcon
152
- className={`h-5 w-5 ${
154
+ className={`h-4 w-4 sm:h-5 sm:w-5 ${
153
155
  isCurrent
154
156
  ? 'text-emerald-600 dark:text-emerald-400'
155
157
  : 'text-muted-foreground'
156
158
  }`}
157
159
  />
158
160
  </div>
159
- <div className="flex min-w-0 flex-1 flex-col gap-0.5">
160
- <div className="flex flex-wrap items-center gap-2">
161
- <span className="min-w-0 break-words text-sm font-medium text-foreground">
162
- {device}
163
- </span>
161
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
162
+ <div className="flex flex-wrap items-center gap-2">
163
+ <span className="min-w-0 wrap-break-word text-[13px] font-medium text-foreground sm:text-sm">
164
+ {device}
165
+ </span>
164
166
  {isCurrent && (
165
167
  <Badge className="bg-emerald-100 text-[10px] text-emerald-700 hover:bg-emerald-100 dark:bg-emerald-900/50 dark:text-emerald-400 dark:hover:bg-emerald-900/50">
166
168
  {t('thisSession')}
167
169
  </Badge>
168
170
  )}
169
171
  </div>
170
- <span className="break-words text-xs text-muted-foreground">
171
- {browser} &middot; {ip}
172
- </span>
173
- <div className="flex flex-wrap items-center gap-3 text-[11px] text-muted-foreground/70">
172
+ <span className="wrap-break-word text-[11px] text-muted-foreground sm:text-xs">
173
+ {browser} &middot; {ip}
174
+ </span>
175
+ <div className="flex flex-wrap items-center gap-2 text-[10px] text-muted-foreground/70 sm:gap-3 sm:text-[11px]">
174
176
  <span className="flex items-center gap-1">
175
177
  <Clock className="h-3 w-3" />
176
178
  {relativeTime}
@@ -34,8 +34,12 @@
34
34
  opacity: 0.45;
35
35
  }
36
36
 
37
- .dashboard-grid .react-grid-item:hover > .react-resizable-handle.react-resizable-handle-se,
38
- .dashboard-grid .react-grid-item.resizing > .react-resizable-handle.react-resizable-handle-se {
37
+ .dashboard-grid
38
+ .react-grid-item:hover
39
+ > .react-resizable-handle.react-resizable-handle-se,
40
+ .dashboard-grid
41
+ .react-grid-item.resizing
42
+ > .react-resizable-handle.react-resizable-handle-se {
39
43
  opacity: 1;
40
44
  }
41
45
 
@@ -43,11 +47,15 @@
43
47
  display: none;
44
48
  }
45
49
 
46
- .dashboard-grid .react-grid-item > .react-resizable-handle:not(.react-resizable-handle-se) {
50
+ .dashboard-grid
51
+ .react-grid-item
52
+ > .react-resizable-handle:not(.react-resizable-handle-se) {
47
53
  display: none;
48
54
  }
49
55
 
50
- .dashboard-grid .react-grid-item > .react-resizable-handle.react-resizable-handle-se {
56
+ .dashboard-grid
57
+ .react-grid-item
58
+ > .react-resizable-handle.react-resizable-handle-se {
51
59
  bottom: 0;
52
60
  right: 0;
53
61
  cursor: se-resize;
@@ -102,3 +110,11 @@
102
110
  .dashboard-widget > [data-slot='card'] > [data-slot='card-header'] {
103
111
  padding-top: 0;
104
112
  }
113
+
114
+ @media (max-width: 639px) {
115
+ .dashboard-widget > [data-slot='card'] {
116
+ gap: 0.625rem;
117
+ padding-top: 0.625rem;
118
+ padding-bottom: 0.625rem;
119
+ }
120
+ }
@@ -51,10 +51,9 @@ export default function MailLogPage() {
51
51
  const [pageSize, setPageSize] = useState(10);
52
52
  const { request, getSettingValue } = useApp();
53
53
 
54
- const {
55
- data: { data, total },
56
- refetch: refetchLogs,
57
- } = useQuery<PaginationResult<MailSent>>({
54
+ const { data: logsResult, refetch: refetchLogs } = useQuery<
55
+ PaginationResult<MailSent>
56
+ >({
58
57
  queryKey: ['mail-sent', debouncedSearch, page, pageSize],
59
58
  queryFn: async () => {
60
59
  const response = await request({
@@ -67,13 +66,8 @@ export default function MailLogPage() {
67
66
  });
68
67
  return response.data as PaginationResult<MailSent>;
69
68
  },
70
- initialData: {
71
- data: [],
72
- total: 0,
73
- page: 1,
74
- pageSize: 10,
75
- },
76
- });
69
+ });
70
+ const { data = [], total = 0 } = logsResult ?? {};
77
71
 
78
72
  useEffect(() => {
79
73
  if (data) {
@@ -679,17 +679,17 @@ export default function UserPage() {
679
679
  />
680
680
  </div>
681
681
 
682
- <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
683
- <DialogContent className="sm:max-w-lg">
684
- <DialogHeader>
685
- <DialogTitle>{t('dialogAddUserTitle')}</DialogTitle>
686
- <DialogDescription>{t('description')}</DialogDescription>
687
- </DialogHeader>
682
+ <Sheet open={isDialogOpen} onOpenChange={setIsDialogOpen}>
683
+ <SheetContent className="w-full sm:max-w-lg overflow-y-auto gap-0">
684
+ <SheetHeader>
685
+ <SheetTitle>{t('dialogAddUserTitle')}</SheetTitle>
686
+ <SheetDescription>{t('description')}</SheetDescription>
687
+ </SheetHeader>
688
688
  <div className="w-full border-t pt-1 mt-1" />
689
689
  <Form {...form}>
690
690
  <form
691
691
  onSubmit={form.handleSubmit(onSubmit)}
692
- className="space-y-4"
692
+ className="px-4 gap-4 w-full flex flex-col pt-2"
693
693
  >
694
694
  <FormField
695
695
  control={form.control}
@@ -772,8 +772,8 @@ export default function UserPage() {
772
772
  </Button>
773
773
  </form>
774
774
  </Form>
775
- </DialogContent>
776
- </Dialog>
775
+ </SheetContent>
776
+ </Sheet>
777
777
 
778
778
  {editingUser && (
779
779
  <Sheet
@@ -1044,10 +1044,12 @@
1044
1044
  "extraLarge": "Extra Large",
1045
1045
  "clearSize": "Clear Size",
1046
1046
  "heading": "Heading",
1047
+ "alignment": "Alignment",
1047
1048
  "alignLeft": "Align Left",
1048
1049
  "alignCenter": "Center",
1049
1050
  "alignRight": "Align Right",
1050
1051
  "justify": "Justify",
1052
+ "lists": "Lists",
1051
1053
  "bulletList": "Bullet List",
1052
1054
  "numberedList": "Numbered List",
1053
1055
  "addLink": "Add Link",
@@ -1061,6 +1063,7 @@
1061
1063
  "clearFormatting": "Clear Formatting",
1062
1064
  "undo": "Undo",
1063
1065
  "redo": "Redo",
1066
+ "more": "More",
1064
1067
  "advancedMode": "Advanced Mode (HTML)",
1065
1068
  "advancedModeTitle": "Advanced Mode - HTML Editor",
1066
1069
  "advancedModeDescription": "Edit HTML directly with syntax highlighting and automatic indentation",
@@ -1160,8 +1163,11 @@
1160
1163
  },
1161
1164
  "ForbiddenDialog": {
1162
1165
  "title": "Access Denied",
1163
- "defaultMessage": "You do not have permission to access this resource.",
1164
- "understood": "Understood"
1166
+ "defaultMessage": "You do not have access to request this resource.",
1167
+ "understood": "Understood",
1168
+ "statusCode": "Status",
1169
+ "method": "Method",
1170
+ "url": "URL"
1165
1171
  },
1166
1172
  "ForbiddenPage": {
1167
1173
  "message": "Forbidden: You don't have permission to access this page.",
@@ -1174,6 +1180,8 @@
1174
1180
  "sessionsToday": "Sessions Today",
1175
1181
  "emailsSent": "E-mails Sent",
1176
1182
  "permissions": "Permissions",
1183
+ "menus": "Menus",
1184
+ "routes": "Routes",
1177
1185
  "userGrowthTitle": "User Growth",
1178
1186
  "userGrowthDescription": "Monthly evolution of users and sessions",
1179
1187
  "users": "Users",
@@ -1049,10 +1049,12 @@
1049
1049
  "extraLarge": "Extra Grande",
1050
1050
  "clearSize": "Limpar Tamanho",
1051
1051
  "heading": "Título",
1052
+ "alignment": "Alinhamento",
1052
1053
  "alignLeft": "Alinhar à Esquerda",
1053
1054
  "alignCenter": "Centralizar",
1054
1055
  "alignRight": "Alinhar à Direita",
1055
1056
  "justify": "Justificar",
1057
+ "lists": "Listas",
1056
1058
  "bulletList": "Lista com Marcadores",
1057
1059
  "numberedList": "Lista Numerada",
1058
1060
  "addLink": "Adicionar Link",
@@ -1066,6 +1068,7 @@
1066
1068
  "clearFormatting": "Limpar Formatação",
1067
1069
  "undo": "Desfazer",
1068
1070
  "redo": "Refazer",
1071
+ "more": "Mais",
1069
1072
  "advancedMode": "Modo Avançado (HTML)",
1070
1073
  "advancedModeTitle": "Modo Avançado - Editor HTML",
1071
1074
  "advancedModeDescription": "Edite o HTML diretamente com syntax highlighting e indentação automática",
@@ -1215,8 +1218,11 @@
1215
1218
  },
1216
1219
  "ForbiddenDialog": {
1217
1220
  "title": "Acesso Negado",
1218
- "defaultMessage": "Você não tem permissão para acessar este recurso.",
1219
- "understood": "Entendi"
1221
+ "defaultMessage": "Você não tem acesso para solicitar este recurso.",
1222
+ "understood": "Entendi",
1223
+ "statusCode": "Status",
1224
+ "method": "Método",
1225
+ "url": "URL"
1220
1226
  },
1221
1227
  "ForbiddenPage": {
1222
1228
  "message": "Proibido: Você não tem permissão para acessar esta página.",
@@ -1229,6 +1235,8 @@
1229
1235
  "sessionsToday": "Sessões Hoje",
1230
1236
  "emailsSent": "E-mails Enviados",
1231
1237
  "permissions": "Permissões",
1238
+ "menus": "Menus",
1239
+ "routes": "Rotas",
1232
1240
  "userGrowthTitle": "Crescimento de Usuários",
1233
1241
  "userGrowthDescription": "Evolução mensal de usuários e sessões",
1234
1242
  "users": "Usuários",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/core",
3
- "version": "0.0.278",
3
+ "version": "0.0.279",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -32,9 +32,9 @@
32
32
  "uuid": "^11.1.0",
33
33
  "@hed-hog/api-pagination": "0.0.6",
34
34
  "@hed-hog/api-types": "0.0.1",
35
- "@hed-hog/api-prisma": "0.0.5",
36
- "@hed-hog/api-mail": "0.0.8",
37
35
  "@hed-hog/api-locale": "0.0.13",
36
+ "@hed-hog/api-mail": "0.0.8",
37
+ "@hed-hog/api-prisma": "0.0.5",
38
38
  "@hed-hog/api": "0.0.4"
39
39
  },
40
40
  "exports": {
@@ -23,6 +23,11 @@ export class DashboardCoreController {
23
23
  return this.dashboardCoreService.getMailStatistics();
24
24
  }
25
25
 
26
+ @Get('stats/overview/system')
27
+ getSystemStatistics() {
28
+ return this.dashboardCoreService.getSystemStatistics();
29
+ }
30
+
26
31
  @Get('widgets/me')
27
32
  getWidgetsData(@User() user, @Locale() locale: string) {
28
33
  return this.dashboardCoreService.getWidgetsData(user.id, locale);
@@ -297,6 +297,40 @@ export class DashboardCoreService {
297
297
  };
298
298
  }
299
299
 
300
+ async getSystemStatistics() {
301
+ const now = new Date();
302
+ const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
303
+ const lastMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);
304
+ const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59);
305
+
306
+ const [
307
+ menusCount,
308
+ routesCount,
309
+ menusCurrentMonth,
310
+ menusLastMonth,
311
+ ] = await Promise.all([
312
+ this.prismaService.menu.count(),
313
+ this.prismaService.route.count(),
314
+ this.prismaService.menu.count({ where: { created_at: { gte: currentMonthStart } } }),
315
+ this.prismaService.menu.count({ where: { created_at: { gte: lastMonthStart, lte: lastMonthEnd } } }),
316
+ ]);
317
+
318
+ const menusChange = this.calculateChange(menusCurrentMonth, menusLastMonth);
319
+
320
+ return {
321
+ cards: {
322
+ menus: {
323
+ value: menusCount,
324
+ change: menusChange,
325
+ },
326
+ routes: {
327
+ value: routesCount,
328
+ change: null,
329
+ },
330
+ },
331
+ };
332
+ }
333
+
300
334
  async getUserLayout(userId: number, slug: string, localeCode: string) {
301
335
  const dashboard = await this.prismaService.dashboard.findFirst({ where: { slug } });
302
336