@hed-hog/core 0.0.298 → 0.0.299

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 (139) hide show
  1. package/dist/dashboard/dashboard/dashboard.controller.d.ts +3 -0
  2. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  3. package/dist/dashboard/dashboard/dashboard.service.d.ts +3 -0
  4. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +12 -0
  6. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
  7. package/dist/dashboard/dashboard-component/dashboard-component.controller.js +22 -0
  8. package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
  9. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +15 -0
  10. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
  11. package/dist/dashboard/dashboard-component/dashboard-component.service.js +110 -3
  12. package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
  13. package/dist/dashboard/dashboard-component/dto/create.dto.d.ts +1 -0
  14. package/dist/dashboard/dashboard-component/dto/create.dto.d.ts.map +1 -1
  15. package/dist/dashboard/dashboard-component/dto/create.dto.js +5 -0
  16. package/dist/dashboard/dashboard-component/dto/create.dto.js.map +1 -1
  17. package/dist/dashboard/dashboard-component/dto/update.dto.d.ts +1 -0
  18. package/dist/dashboard/dashboard-component/dto/update.dto.d.ts.map +1 -1
  19. package/dist/dashboard/dashboard-component/dto/update.dto.js +5 -0
  20. package/dist/dashboard/dashboard-component/dto/update.dto.js.map +1 -1
  21. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -0
  22. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts.map +1 -1
  23. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -0
  24. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
  25. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +7 -1
  26. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  27. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +7 -1
  28. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  29. package/dist/dashboard/dashboard-core/dashboard-core.service.js +88 -4
  30. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  31. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +1 -0
  32. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
  33. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +1 -0
  34. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
  35. package/hedhog/data/dashboard_item.yaml +1 -1
  36. package/hedhog/data/route.yaml +12 -0
  37. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +141 -24
  38. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +3 -0
  39. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +136 -23
  40. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +266 -85
  41. package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +11 -0
  42. package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +192 -0
  43. package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +236 -0
  44. package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +108 -0
  45. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +66 -0
  46. package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +122 -0
  47. package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +63 -0
  48. package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +73 -0
  49. package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +73 -0
  50. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +123 -0
  51. package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +118 -0
  52. package/hedhog/frontend/messages/en.json +3 -0
  53. package/hedhog/frontend/messages/pt.json +3 -0
  54. package/hedhog/frontend/public/dashboard-previews/.gitkeep +12 -0
  55. package/hedhog/frontend/public/dashboard-previews/account-security.png +0 -0
  56. package/hedhog/frontend/public/dashboard-previews/active-users-card.png +0 -0
  57. package/hedhog/frontend/public/dashboard-previews/activity-timeline.png +0 -0
  58. package/hedhog/frontend/public/dashboard-previews/cash-balance-kpi.png +0 -0
  59. package/hedhog/frontend/public/dashboard-previews/cash-flow-chart.png +0 -0
  60. package/hedhog/frontend/public/dashboard-previews/default-kpi.png +0 -0
  61. package/hedhog/frontend/public/dashboard-previews/email-notifications.png +0 -0
  62. package/hedhog/frontend/public/dashboard-previews/financial-alerts.png +0 -0
  63. package/hedhog/frontend/public/dashboard-previews/login-history-chart.png +0 -0
  64. package/hedhog/frontend/public/dashboard-previews/mail-sent-card.png +0 -0
  65. package/hedhog/frontend/public/dashboard-previews/mail-sent-chart.png +0 -0
  66. package/hedhog/frontend/public/dashboard-previews/menus-card.png +0 -0
  67. package/hedhog/frontend/public/dashboard-previews/payable-30d-kpi.png +0 -0
  68. package/hedhog/frontend/public/dashboard-previews/permissions-card.png +0 -0
  69. package/hedhog/frontend/public/dashboard-previews/permissions-chart.png +0 -0
  70. package/hedhog/frontend/public/dashboard-previews/profile-card.png +0 -0
  71. package/hedhog/frontend/public/dashboard-previews/receivable-30d-kpi.png +0 -0
  72. package/hedhog/frontend/public/dashboard-previews/routes-card.png +0 -0
  73. package/hedhog/frontend/public/dashboard-previews/session-activity-chart.png +0 -0
  74. package/hedhog/frontend/public/dashboard-previews/sessions-today-card.png +0 -0
  75. package/hedhog/frontend/public/dashboard-previews/stat-access-level.png +0 -0
  76. package/hedhog/frontend/public/dashboard-previews/stat-actions-today.png +0 -0
  77. package/hedhog/frontend/public/dashboard-previews/stat-consecutive-days.png +0 -0
  78. package/hedhog/frontend/public/dashboard-previews/stat-online-time.png +0 -0
  79. package/hedhog/frontend/public/dashboard-previews/upcoming-payable.png +0 -0
  80. package/hedhog/frontend/public/dashboard-previews/upcoming-receivable.png +0 -0
  81. package/hedhog/frontend/public/dashboard-previews/user-growth-chart.png +0 -0
  82. package/hedhog/frontend/public/dashboard-previews/user-roles.png +0 -0
  83. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/account-security.tsx.ejs +33 -29
  84. package/hedhog/frontend/widgets/active-users-card.tsx.ejs +58 -0
  85. package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +223 -0
  86. package/hedhog/frontend/widgets/email-notifications.tsx.ejs +226 -0
  87. package/hedhog/frontend/widgets/locale-config.tsx.ejs +168 -0
  88. package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +115 -0
  89. package/hedhog/frontend/widgets/mail-config.tsx.ejs +199 -0
  90. package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +58 -0
  91. package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +149 -0
  92. package/hedhog/frontend/widgets/menus-card.tsx.ejs +58 -0
  93. package/hedhog/frontend/widgets/oauth-config.tsx.ejs +175 -0
  94. package/hedhog/frontend/widgets/permissions-card.tsx.ejs +61 -0
  95. package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +156 -0
  96. package/hedhog/frontend/widgets/profile-card.tsx.ejs +186 -0
  97. package/hedhog/frontend/widgets/routes-card.tsx.ejs +58 -0
  98. package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +183 -0
  99. package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +62 -0
  100. package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +57 -0
  101. package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +57 -0
  102. package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +57 -0
  103. package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +57 -0
  104. package/hedhog/frontend/widgets/storage-config.tsx.ejs +196 -0
  105. package/hedhog/frontend/widgets/theme-config.tsx.ejs +213 -0
  106. package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +210 -0
  107. package/hedhog/frontend/widgets/user-roles.tsx.ejs +132 -0
  108. package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-sessions.tsx.ejs +1 -1
  109. package/hedhog/table/dashboard_component.yaml +7 -0
  110. package/package.json +4 -4
  111. package/src/dashboard/dashboard-component/dashboard-component.controller.ts +36 -12
  112. package/src/dashboard/dashboard-component/dashboard-component.service.ts +150 -3
  113. package/src/dashboard/dashboard-component/dto/create.dto.ts +4 -0
  114. package/src/dashboard/dashboard-component/dto/update.dto.ts +4 -0
  115. package/src/dashboard/dashboard-core/dashboard-core.service.ts +108 -5
  116. /package/hedhog/frontend/app/dashboard/components/widgets/{active-users-card.tsx.ejs → core.active-users-card.tsx.ejs} +0 -0
  117. /package/hedhog/frontend/app/dashboard/components/widgets/{activity-timeline.tsx.ejs → core.activity-timeline.tsx.ejs} +0 -0
  118. /package/hedhog/frontend/app/dashboard/components/widgets/{email-notifications.tsx.ejs → core.email-notifications.tsx.ejs} +0 -0
  119. /package/hedhog/frontend/app/dashboard/components/widgets/{locale-config.tsx.ejs → core.locale-config.tsx.ejs} +0 -0
  120. /package/hedhog/frontend/app/dashboard/components/widgets/{login-history-chart.tsx.ejs → core.login-history-chart.tsx.ejs} +0 -0
  121. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-config.tsx.ejs → core.mail-config.tsx.ejs} +0 -0
  122. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-card.tsx.ejs → core.mail-sent-card.tsx.ejs} +0 -0
  123. /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-chart.tsx.ejs → core.mail-sent-chart.tsx.ejs} +0 -0
  124. /package/hedhog/frontend/app/dashboard/components/widgets/{menus-card.tsx.ejs → core.menus-card.tsx.ejs} +0 -0
  125. /package/hedhog/frontend/app/dashboard/components/widgets/{oauth-config.tsx.ejs → core.oauth-config.tsx.ejs} +0 -0
  126. /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-card.tsx.ejs → core.permissions-card.tsx.ejs} +0 -0
  127. /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-chart.tsx.ejs → core.permissions-chart.tsx.ejs} +0 -0
  128. /package/hedhog/frontend/app/dashboard/components/widgets/{profile-card.tsx.ejs → core.profile-card.tsx.ejs} +0 -0
  129. /package/hedhog/frontend/app/dashboard/components/widgets/{routes-card.tsx.ejs → core.routes-card.tsx.ejs} +0 -0
  130. /package/hedhog/frontend/app/dashboard/components/widgets/{session-activity-chart.tsx.ejs → core.session-activity-chart.tsx.ejs} +0 -0
  131. /package/hedhog/frontend/app/dashboard/components/widgets/{sessions-today-card.tsx.ejs → core.sessions-today-card.tsx.ejs} +0 -0
  132. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-access-level.tsx.ejs → core.stat-access-level.tsx.ejs} +0 -0
  133. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-actions-today.tsx.ejs → core.stat-actions-today.tsx.ejs} +0 -0
  134. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-consecutive-days.tsx.ejs → core.stat-consecutive-days.tsx.ejs} +0 -0
  135. /package/hedhog/frontend/app/dashboard/components/widgets/{stat-online-time.tsx.ejs → core.stat-online-time.tsx.ejs} +0 -0
  136. /package/hedhog/frontend/app/dashboard/components/widgets/{storage-config.tsx.ejs → core.storage-config.tsx.ejs} +0 -0
  137. /package/hedhog/frontend/app/dashboard/components/widgets/{theme-config.tsx.ejs → core.theme-config.tsx.ejs} +0 -0
  138. /package/hedhog/frontend/app/dashboard/components/widgets/{user-growth-chart.tsx.ejs → core.user-growth-chart.tsx.ejs} +0 -0
  139. /package/hedhog/frontend/app/dashboard/components/widgets/{user-roles.tsx.ejs → core.user-roles.tsx.ejs} +0 -0
@@ -0,0 +1,236 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@/components/ui/card';
12
+ import {
13
+ DropdownMenu,
14
+ DropdownMenuContent,
15
+ DropdownMenuItem,
16
+ DropdownMenuTrigger,
17
+ } from '@/components/ui/dropdown-menu';
18
+ import { useWidgetData } from '@/hooks/use-widget-data';
19
+ import type { AllWidgetsData, SessionData } from '@/types/widget-data';
20
+ import {
21
+ Clock,
22
+ Globe,
23
+ Info,
24
+ LogOut,
25
+ Monitor,
26
+ MoreHorizontal,
27
+ Smartphone,
28
+ Tablet,
29
+ } from 'lucide-react';
30
+ import { useTranslations } from 'next-intl';
31
+ import { useRouter } from 'next/navigation';
32
+ import { WidgetWrapper } from '../widget-wrapper';
33
+
34
+ function detectDeviceType(ua: string): 'desktop' | 'mobile' | 'tablet' {
35
+ const u = ua.toLowerCase();
36
+ if (u.includes('ipad') || u.includes('tablet')) return 'tablet';
37
+ if (u.includes('mobile') || u.includes('iphone') || u.includes('android'))
38
+ return 'mobile';
39
+ return 'desktop';
40
+ }
41
+
42
+ function detectBrowser(ua: string): string {
43
+ if (/edg\//i.test(ua)) return 'Edge';
44
+ if (/chrome\//i.test(ua)) return 'Chrome';
45
+ if (/firefox\//i.test(ua)) return 'Firefox';
46
+ if (/safari\//i.test(ua)) return 'Safari';
47
+ if (/msie|trident/i.test(ua)) return 'IE';
48
+ return 'Browser';
49
+ }
50
+
51
+ function detectDevice(ua: string): string {
52
+ if (/ipad/i.test(ua)) return 'iPad';
53
+ if (/iphone/i.test(ua)) return 'iPhone';
54
+ if (/android/i.test(ua)) return 'Android';
55
+ if (/macintosh|mac os/i.test(ua)) return 'Mac';
56
+ if (/windows/i.test(ua)) return 'Windows';
57
+ if (/linux/i.test(ua)) return 'Linux';
58
+ return 'Dispositivo';
59
+ }
60
+
61
+ function formatRelative(
62
+ dateStr: string,
63
+ tActiveNow: string,
64
+ tMinsAgo: (n: number) => string,
65
+ tHoursAgo: (n: number) => string,
66
+ tDaysAgo: (n: number) => string
67
+ ): string {
68
+ const diff = Date.now() - new Date(dateStr).getTime();
69
+ const mins = Math.floor(diff / 60000);
70
+ if (mins < 1) return tActiveNow;
71
+ if (mins < 60) return tMinsAgo(mins);
72
+ const hours = Math.floor(mins / 60);
73
+ if (hours < 24) return tHoursAgo(hours);
74
+ const days = Math.floor(hours / 24);
75
+ return tDaysAgo(days);
76
+ }
77
+
78
+ const deviceIcons = {
79
+ desktop: Monitor,
80
+ mobile: Smartphone,
81
+ tablet: Tablet,
82
+ };
83
+
84
+ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
85
+ const t = useTranslations('core.DashboardPage.userSessions');
86
+ const router = useRouter();
87
+
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-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="grid w-full items-start grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-2 sm:gap-3">
116
+ {sessions.map((session, index) => {
117
+ const ua = session.user_agent ?? '';
118
+ const deviceType = detectDeviceType(ua);
119
+ const browser = detectBrowser(ua);
120
+ const device = detectDevice(ua);
121
+ const isCurrent = index === 0;
122
+ const DeviceIcon = deviceIcons[deviceType];
123
+ const ip = session.ip_address
124
+ ? session.ip_address.replace(/(\d+\.\d+\.\d+\.)\d+/, '$1***')
125
+ : '—';
126
+ const relativeTime = formatRelative(
127
+ session.created_at,
128
+ t('activeNow'),
129
+ (n) => t('minutesAgo', { count: n }),
130
+ (n) => t('hoursAgo', { count: n }),
131
+ (n) =>
132
+ n === 1
133
+ ? t('daysAgo', { count: n })
134
+ : t('daysAgoPlural', { count: n })
135
+ );
136
+
137
+ return (
138
+ <div
139
+ key={session.id}
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 ${
141
+ isCurrent
142
+ ? 'border-emerald-200 bg-emerald-50/50 dark:border-emerald-800 dark:bg-emerald-950/30'
143
+ : 'bg-card hover:bg-muted/30'
144
+ }`}
145
+ >
146
+ <div
147
+ className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-10 sm:w-10 ${
148
+ isCurrent
149
+ ? 'bg-emerald-100 dark:bg-emerald-900/50'
150
+ : 'bg-muted'
151
+ }`}
152
+ >
153
+ <DeviceIcon
154
+ className={`h-4 w-4 sm:h-5 sm:w-5 ${
155
+ isCurrent
156
+ ? 'text-emerald-600 dark:text-emerald-400'
157
+ : 'text-muted-foreground'
158
+ }`}
159
+ />
160
+ </div>
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>
166
+ {isCurrent && (
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">
168
+ {t('thisSession')}
169
+ </Badge>
170
+ )}
171
+ </div>
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]">
176
+ <span className="flex items-center gap-1">
177
+ <Clock className="h-3 w-3" />
178
+ {relativeTime}
179
+ </span>
180
+ </div>
181
+ </div>
182
+ {!isCurrent && (
183
+ <DropdownMenu>
184
+ <DropdownMenuTrigger asChild>
185
+ <Button
186
+ variant="ghost"
187
+ size="icon"
188
+ className="h-8 w-8 shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
189
+ >
190
+ <MoreHorizontal className="h-4 w-4" />
191
+ </Button>
192
+ </DropdownMenuTrigger>
193
+ <DropdownMenuContent align="end">
194
+ <DropdownMenuItem className="text-destructive">
195
+ <LogOut className="mr-2 h-4 w-4" />
196
+ {t('terminateSession')}
197
+ </DropdownMenuItem>
198
+ </DropdownMenuContent>
199
+ </DropdownMenu>
200
+ )}
201
+ </div>
202
+ );
203
+ })}
204
+ </div>
205
+ </CardContent>
206
+ </Card>
207
+ );
208
+ }
209
+
210
+ interface UserSessionsProps {
211
+ widget?: { name?: string };
212
+ onRemove?: () => void;
213
+ }
214
+
215
+ export default function UserSessions({ widget, onRemove }: UserSessionsProps) {
216
+ const { data, isLoading, isError, isAccessDenied } = useWidgetData<
217
+ AllWidgetsData,
218
+ SessionData[]
219
+ >({
220
+ endpoint: '/dashboard-core/widgets/me',
221
+ queryKey: 'widget-me',
222
+ select: (d) => d.userSessions,
223
+ });
224
+
225
+ return (
226
+ <WidgetWrapper
227
+ isLoading={isLoading}
228
+ isError={isError}
229
+ isAccessDenied={isAccessDenied}
230
+ widgetName={widget?.name ?? 'user-sessions'}
231
+ onRemove={onRemove}
232
+ >
233
+ {data && <SessionsContent sessions={data} />}
234
+ </WidgetWrapper>
235
+ );
236
+ }
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { useWidgetData } from '@/hooks/use-widget-data';
6
+ import { AlertTriangle } from 'lucide-react';
7
+ import { useLocale, useTranslations } from 'next-intl';
8
+ import { WidgetWrapper } from '../widget-wrapper';
9
+
10
+ interface FinanceData {
11
+ titulosPagar?: Array<{
12
+ status: string;
13
+ parcelas: Array<{ status: string }>;
14
+ }>;
15
+ extratos?: Array<{
16
+ statusConciliacao: string;
17
+ }>;
18
+ periodoAberto?: {
19
+ inicio?: string | null;
20
+ } | null;
21
+ }
22
+
23
+ interface AlertsProps {
24
+ widget?: { name?: string };
25
+ onRemove?: () => void;
26
+ }
27
+
28
+ export default function Alerts({ widget, onRemove }: AlertsProps) {
29
+ const t = useTranslations('finance.DashboardPage');
30
+ const locale = useLocale();
31
+
32
+ const { data, isLoading, isAccessDenied, isError } =
33
+ useWidgetData<FinanceData>({
34
+ endpoint: '/finance/data',
35
+ queryKey: 'finance-alerts',
36
+ });
37
+
38
+ const approvedPayables = (data?.titulosPagar || []).filter(
39
+ (titulo) => titulo.status !== 'rascunho' && titulo.status !== 'cancelado'
40
+ );
41
+
42
+ const overdue = approvedPayables.filter((titulo) =>
43
+ titulo.parcelas.some((p) => p.status === 'vencido')
44
+ ).length;
45
+
46
+ const pendingReconciliation = (data?.extratos || []).filter(
47
+ (e) => e.statusConciliacao === 'pendente'
48
+ ).length;
49
+
50
+ const periodBase = data?.periodoAberto?.inicio
51
+ ? new Date(data.periodoAberto.inicio)
52
+ : new Date();
53
+ const month = new Intl.DateTimeFormat(locale, { month: 'long' }).format(
54
+ periodBase
55
+ );
56
+ const currentPeriod = `${month.charAt(0).toUpperCase()}${month.slice(1)}/${periodBase.getFullYear()}`;
57
+
58
+ return (
59
+ <WidgetWrapper
60
+ isLoading={isLoading}
61
+ isAccessDenied={isAccessDenied}
62
+ isError={isError}
63
+ widgetName={widget?.name || t('alerts.title')}
64
+ onRemove={onRemove}
65
+ >
66
+ <Card className="h-full">
67
+ <CardHeader>
68
+ <CardTitle className="flex items-center gap-2 text-base">
69
+ <AlertTriangle className="h-4 w-4 text-yellow-500" />
70
+ {t('alerts.title')}
71
+ </CardTitle>
72
+ </CardHeader>
73
+ <CardContent>
74
+ <div className="space-y-3">
75
+ {overdue > 0 && (
76
+ <div className="flex items-center justify-between rounded-lg bg-red-50 p-3">
77
+ <span className="text-sm">{t('alerts.overdueTitles')}</span>
78
+ <Badge variant="destructive">{overdue}</Badge>
79
+ </div>
80
+ )}
81
+ {pendingReconciliation > 0 && (
82
+ <div className="flex items-center justify-between rounded-lg bg-yellow-50 p-3">
83
+ <span className="text-sm">
84
+ {t('alerts.pendingReconciliation')}
85
+ </span>
86
+ <Badge
87
+ variant="outline"
88
+ className="border-yellow-500 text-yellow-700"
89
+ >
90
+ {pendingReconciliation}
91
+ </Badge>
92
+ </div>
93
+ )}
94
+ <div className="flex items-center justify-between rounded-lg bg-blue-50 p-3">
95
+ <span className="text-sm">{t('alerts.openPeriod')}</span>
96
+ <Badge
97
+ variant="outline"
98
+ className="border-blue-500 text-blue-700"
99
+ >
100
+ {currentPeriod}
101
+ </Badge>
102
+ </div>
103
+ </div>
104
+ </CardContent>
105
+ </Card>
106
+ </WidgetWrapper>
107
+ );
108
+ }
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+
3
+ import { Card, CardContent } from '@/components/ui/card';
4
+ import { useWidgetData } from '@/hooks/use-widget-data';
5
+ import { Wallet } from 'lucide-react';
6
+ import { useTranslations } from 'next-intl';
7
+ import { WidgetWrapper } from '../widget-wrapper';
8
+
9
+ interface CashBalanceKpiProps {
10
+ widget?: { name?: string };
11
+ onRemove?: () => void;
12
+ }
13
+
14
+ interface FinanceData {
15
+ kpis?: {
16
+ saldoCaixa: number;
17
+ };
18
+ }
19
+
20
+ export default function CashBalanceKpi({
21
+ widget,
22
+ onRemove,
23
+ }: CashBalanceKpiProps) {
24
+ const t = useTranslations('finance.DashboardPage');
25
+
26
+ const { data, isLoading, isAccessDenied, isError } =
27
+ useWidgetData<FinanceData>({
28
+ endpoint: '/finance/data',
29
+ queryKey: 'finance-kpi-cash-balance',
30
+ });
31
+
32
+ const value = data?.kpis?.saldoCaixa ?? 0;
33
+ const formatted = new Intl.NumberFormat('pt-BR', {
34
+ style: 'currency',
35
+ currency: 'BRL',
36
+ }).format(value);
37
+
38
+ return (
39
+ <WidgetWrapper
40
+ isLoading={isLoading}
41
+ isAccessDenied={isAccessDenied}
42
+ isError={isError}
43
+ widgetName={widget?.name || t('kpis.cashBalance.title')}
44
+ onRemove={onRemove}
45
+ >
46
+ <Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
47
+ <CardContent className="flex h-full items-center gap-3 p-4">
48
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-emerald-50">
49
+ <Wallet className="h-5 w-5 text-emerald-600" />
50
+ </div>
51
+ <div className="flex min-w-0 flex-col">
52
+ <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
53
+ {t('kpis.cashBalance.title')}
54
+ </span>
55
+ <span className="truncate text-lg font-bold tracking-tight text-foreground">
56
+ {formatted}
57
+ </span>
58
+ <span className="text-[10px] text-muted-foreground">
59
+ {t('kpis.cashBalance.description')}
60
+ </span>
61
+ </div>
62
+ </CardContent>
63
+ </Card>
64
+ </WidgetWrapper>
65
+ );
66
+ }
@@ -0,0 +1,122 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from '@/components/ui/card';
10
+ import { useWidgetData } from '@/hooks/use-widget-data';
11
+ import { useTranslations } from 'next-intl';
12
+ import {
13
+ CartesianGrid,
14
+ Legend,
15
+ Line,
16
+ LineChart,
17
+ ResponsiveContainer,
18
+ Tooltip,
19
+ XAxis,
20
+ YAxis,
21
+ } from 'recharts';
22
+ import { WidgetWrapper } from '../widget-wrapper';
23
+
24
+ interface CashFlowPoint {
25
+ data: string;
26
+ saldoPrevisto: number;
27
+ saldoRealizado: number;
28
+ }
29
+
30
+ interface FinanceData {
31
+ fluxoCaixaPrevisto?: CashFlowPoint[];
32
+ }
33
+
34
+ interface CashFlowChartProps {
35
+ widget?: { name?: string };
36
+ onRemove?: () => void;
37
+ }
38
+
39
+ export default function CashFlowChart({
40
+ widget,
41
+ onRemove,
42
+ }: CashFlowChartProps) {
43
+ const t = useTranslations('finance.DashboardPage');
44
+
45
+ const { data, isLoading, isAccessDenied, isError } =
46
+ useWidgetData<FinanceData>({
47
+ endpoint: '/finance/data',
48
+ queryKey: 'finance-cash-flow-chart',
49
+ });
50
+
51
+ const chartData = (data?.fluxoCaixaPrevisto || []).map((item) => ({
52
+ ...item,
53
+ data: new Date(item.data).toLocaleDateString('pt-BR', {
54
+ day: '2-digit',
55
+ month: '2-digit',
56
+ }),
57
+ }));
58
+
59
+ return (
60
+ <WidgetWrapper
61
+ isLoading={isLoading}
62
+ isAccessDenied={isAccessDenied}
63
+ isError={isError}
64
+ widgetName={widget?.name || t('cashFlow.title')}
65
+ onRemove={onRemove}
66
+ >
67
+ <Card className="h-full flex flex-col">
68
+ <CardHeader className="pb-2">
69
+ <CardTitle className="text-base">{t('cashFlow.title')}</CardTitle>
70
+ <CardDescription>{t('cashFlow.description')}</CardDescription>
71
+ </CardHeader>
72
+ <CardContent className="flex-1 pt-0">
73
+ <div className="h-[280px] w-full">
74
+ <ResponsiveContainer width="100%" height="100%">
75
+ <LineChart data={chartData}>
76
+ <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
77
+ <XAxis
78
+ dataKey="data"
79
+ tick={{ fontSize: 12 }}
80
+ tickLine={false}
81
+ axisLine={false}
82
+ />
83
+ <YAxis
84
+ tick={{ fontSize: 12 }}
85
+ tickLine={false}
86
+ axisLine={false}
87
+ tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
88
+ />
89
+ <Tooltip
90
+ formatter={(value: number) =>
91
+ new Intl.NumberFormat('pt-BR', {
92
+ style: 'currency',
93
+ currency: 'BRL',
94
+ }).format(value)
95
+ }
96
+ />
97
+ <Legend />
98
+ <Line
99
+ type="monotone"
100
+ dataKey="saldoPrevisto"
101
+ name={t('chart.predicted')}
102
+ stroke="hsl(var(--primary))"
103
+ strokeWidth={2}
104
+ dot={false}
105
+ />
106
+ <Line
107
+ type="monotone"
108
+ dataKey="saldoRealizado"
109
+ name={t('chart.actual')}
110
+ stroke="hsl(var(--chart-2))"
111
+ strokeWidth={2}
112
+ dot={false}
113
+ connectNulls
114
+ />
115
+ </LineChart>
116
+ </ResponsiveContainer>
117
+ </div>
118
+ </CardContent>
119
+ </Card>
120
+ </WidgetWrapper>
121
+ );
122
+ }
@@ -0,0 +1,63 @@
1
+ 'use client';
2
+
3
+ import { Card, CardContent } from '@/components/ui/card';
4
+ import { useWidgetData } from '@/hooks/use-widget-data';
5
+ import { AlertTriangle } from 'lucide-react';
6
+ import { useTranslations } from 'next-intl';
7
+ import { WidgetWrapper } from '../widget-wrapper';
8
+
9
+ interface DefaultKpiProps {
10
+ widget?: { name?: string };
11
+ onRemove?: () => void;
12
+ }
13
+
14
+ interface FinanceData {
15
+ kpis?: {
16
+ inadimplencia: number;
17
+ };
18
+ }
19
+
20
+ export default function DefaultKpi({ widget, onRemove }: DefaultKpiProps) {
21
+ const t = useTranslations('finance.DashboardPage');
22
+
23
+ const { data, isLoading, isAccessDenied, isError } =
24
+ useWidgetData<FinanceData>({
25
+ endpoint: '/finance/data',
26
+ queryKey: 'finance-kpi-default',
27
+ });
28
+
29
+ const value = data?.kpis?.inadimplencia ?? 0;
30
+ const formatted = new Intl.NumberFormat('pt-BR', {
31
+ style: 'currency',
32
+ currency: 'BRL',
33
+ }).format(value);
34
+
35
+ return (
36
+ <WidgetWrapper
37
+ isLoading={isLoading}
38
+ isAccessDenied={isAccessDenied}
39
+ isError={isError}
40
+ widgetName={widget?.name || t('kpis.default.title')}
41
+ onRemove={onRemove}
42
+ >
43
+ <Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
44
+ <CardContent className="flex h-full items-center gap-3 p-4">
45
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-amber-50">
46
+ <AlertTriangle className="h-5 w-5 text-amber-600" />
47
+ </div>
48
+ <div className="flex min-w-0 flex-col">
49
+ <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
50
+ {t('kpis.default.title')}
51
+ </span>
52
+ <span className="truncate text-lg font-bold tracking-tight text-foreground">
53
+ {formatted}
54
+ </span>
55
+ <span className="text-[10px] text-muted-foreground">
56
+ {t('kpis.default.description')}
57
+ </span>
58
+ </div>
59
+ </CardContent>
60
+ </Card>
61
+ </WidgetWrapper>
62
+ );
63
+ }
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+
3
+ import { Card, CardContent } from '@/components/ui/card';
4
+ import { useWidgetData } from '@/hooks/use-widget-data';
5
+ import { TrendingDown } from 'lucide-react';
6
+ import { useTranslations } from 'next-intl';
7
+ import { WidgetWrapper } from '../widget-wrapper';
8
+
9
+ interface Payable30dKpiProps {
10
+ widget?: { name?: string };
11
+ onRemove?: () => void;
12
+ }
13
+
14
+ interface FinanceData {
15
+ kpis?: {
16
+ aPagar30dias: number;
17
+ aPagar7dias: number;
18
+ };
19
+ }
20
+
21
+ export default function Payable30dKpi({
22
+ widget,
23
+ onRemove,
24
+ }: Payable30dKpiProps) {
25
+ const t = useTranslations('finance.DashboardPage');
26
+
27
+ const { data, isLoading, isAccessDenied, isError } =
28
+ useWidgetData<FinanceData>({
29
+ endpoint: '/finance/data',
30
+ queryKey: 'finance-kpi-payable-30d',
31
+ });
32
+
33
+ const value = data?.kpis?.aPagar30dias ?? 0;
34
+ const sevenDays = data?.kpis?.aPagar7dias ?? 0;
35
+ const formatted = new Intl.NumberFormat('pt-BR', {
36
+ style: 'currency',
37
+ currency: 'BRL',
38
+ }).format(value);
39
+
40
+ return (
41
+ <WidgetWrapper
42
+ isLoading={isLoading}
43
+ isAccessDenied={isAccessDenied}
44
+ isError={isError}
45
+ widgetName={widget?.name || t('kpis.payable30.title')}
46
+ onRemove={onRemove}
47
+ >
48
+ <Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
49
+ <CardContent className="flex h-full items-center gap-3 p-4">
50
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-red-50">
51
+ <TrendingDown className="h-5 w-5 text-red-600" />
52
+ </div>
53
+ <div className="flex min-w-0 flex-col">
54
+ <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
55
+ {t('kpis.payable30.title')}
56
+ </span>
57
+ <span className="truncate text-lg font-bold tracking-tight text-foreground">
58
+ {formatted}
59
+ </span>
60
+ <span className="text-[10px] text-muted-foreground">
61
+ {t('kpis.payable30.sevenDays', {
62
+ value: new Intl.NumberFormat('pt-BR', {
63
+ style: 'currency',
64
+ currency: 'BRL',
65
+ }).format(sevenDays),
66
+ })}
67
+ </span>
68
+ </div>
69
+ </CardContent>
70
+ </Card>
71
+ </WidgetWrapper>
72
+ );
73
+ }