@hed-hog/core 0.0.297 → 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.
- package/dist/auth/auth.controller.d.ts +10 -10
- package/dist/auth/auth.service.d.ts +10 -10
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +3 -0
- package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard/dashboard.service.d.ts +3 -0
- package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +12 -0
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js +22 -0
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +15 -0
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.js +110 -3
- package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
- package/dist/dashboard/dashboard-component/dto/create.dto.d.ts +1 -0
- package/dist/dashboard/dashboard-component/dto/create.dto.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dto/create.dto.js +5 -0
- package/dist/dashboard/dashboard-component/dto/create.dto.js.map +1 -1
- package/dist/dashboard/dashboard-component/dto/update.dto.d.ts +1 -0
- package/dist/dashboard/dashboard-component/dto/update.dto.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dto/update.dto.js +5 -0
- package/dist/dashboard/dashboard-component/dto/update.dto.js.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -0
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +21 -1
- 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.module.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.module.js +6 -1
- package/dist/dashboard/dashboard-core/dashboard-core.module.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +180 -2
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +619 -9
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +1 -0
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +1 -0
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +16 -0
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +7 -1
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +38 -1
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts +1 -0
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +38 -29
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +2 -1
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/user/constants/user.constants.d.ts +1 -0
- package/dist/user/constants/user.constants.d.ts.map +1 -1
- package/dist/user/constants/user.constants.js +2 -1
- package/dist/user/constants/user.constants.js.map +1 -1
- package/dist/user/user.controller.d.ts +10 -10
- package/dist/user/user.service.d.ts +30 -30
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +2 -1
- package/dist/user/user.service.js.map +1 -1
- package/hedhog/data/dashboard_item.yaml +10 -10
- package/hedhog/data/route.yaml +20 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +212 -34
- package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +3 -0
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +136 -23
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +266 -85
- package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +11 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +192 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +226 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +168 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +199 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +175 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +186 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +196 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +213 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +132 -0
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +236 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +108 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +66 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +122 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +63 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +73 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +73 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +123 -0
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +118 -0
- package/hedhog/frontend/messages/en.json +93 -0
- package/hedhog/frontend/messages/pt.json +93 -0
- package/hedhog/frontend/public/dashboard-previews/.gitkeep +12 -0
- package/hedhog/frontend/public/dashboard-previews/account-security.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/active-users-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/activity-timeline.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/cash-balance-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/cash-flow-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/default-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/email-notifications.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/financial-alerts.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/login-history-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/mail-sent-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/mail-sent-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/menus-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/payable-30d-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/permissions-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/permissions-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/profile-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/receivable-30d-kpi.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/routes-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/session-activity-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/sessions-today-card.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-access-level.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-actions-today.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-consecutive-days.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/stat-online-time.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/upcoming-payable.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/upcoming-receivable.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/user-growth-chart.png +0 -0
- package/hedhog/frontend/public/dashboard-previews/user-roles.png +0 -0
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/account-security.tsx.ejs +33 -29
- package/hedhog/frontend/widgets/active-users-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +223 -0
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/email-notifications.tsx.ejs +85 -61
- package/hedhog/frontend/widgets/locale-config.tsx.ejs +168 -0
- package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +115 -0
- package/hedhog/frontend/widgets/mail-config.tsx.ejs +199 -0
- package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +149 -0
- package/hedhog/frontend/widgets/menus-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/oauth-config.tsx.ejs +175 -0
- package/hedhog/frontend/widgets/permissions-card.tsx.ejs +61 -0
- package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +156 -0
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/profile-card.tsx.ejs +3 -3
- package/hedhog/frontend/widgets/routes-card.tsx.ejs +58 -0
- package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +183 -0
- package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +62 -0
- package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +57 -0
- package/hedhog/frontend/widgets/storage-config.tsx.ejs +196 -0
- package/hedhog/frontend/widgets/theme-config.tsx.ejs +213 -0
- package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +210 -0
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-roles.tsx.ejs +12 -14
- package/hedhog/frontend/{app/dashboard/components/widgets → widgets}/user-sessions.tsx.ejs +1 -1
- package/hedhog/table/dashboard_component.yaml +7 -0
- package/hedhog/table/mail_sent_user.yaml +75 -0
- package/package.json +4 -4
- package/src/dashboard/dashboard-component/dashboard-component.controller.ts +36 -12
- package/src/dashboard/dashboard-component/dashboard-component.service.ts +150 -3
- package/src/dashboard/dashboard-component/dto/create.dto.ts +4 -0
- package/src/dashboard/dashboard-component/dto/update.dto.ts +4 -0
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
- package/src/dashboard/dashboard-core/dashboard-core.module.ts +6 -1
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +874 -8
- package/src/file/file.controller.ts +37 -13
- package/src/file/file.service.ts +47 -5
- package/src/file/provider/s3.provider.ts +39 -29
- package/src/oauth/oauth.service.ts +8 -7
- package/src/user/constants/user.constants.ts +1 -0
- package/src/user/user.service.ts +2 -1
- package/hedhog/frontend/app/dashboard/components/widgets/locale-config.tsx.ejs +0 -309
- package/hedhog/frontend/app/dashboard/components/widgets/mail-config.tsx.ejs +0 -445
- package/hedhog/frontend/app/dashboard/components/widgets/oauth-config.tsx.ejs +0 -296
- package/hedhog/frontend/app/dashboard/components/widgets/storage-config.tsx.ejs +0 -340
- package/hedhog/frontend/app/dashboard/components/widgets/theme-config.tsx.ejs +0 -275
- /package/hedhog/frontend/app/dashboard/components/widgets/{active-users-card.tsx.ejs → core.active-users-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{activity-timeline.tsx.ejs → core.activity-timeline.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{login-history-chart.tsx.ejs → core.login-history-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-card.tsx.ejs → core.mail-sent-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{mail-sent-chart.tsx.ejs → core.mail-sent-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{menus-card.tsx.ejs → core.menus-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-card.tsx.ejs → core.permissions-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{permissions-chart.tsx.ejs → core.permissions-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{routes-card.tsx.ejs → core.routes-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{session-activity-chart.tsx.ejs → core.session-activity-chart.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{sessions-today-card.tsx.ejs → core.sessions-today-card.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-access-level.tsx.ejs → core.stat-access-level.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-actions-today.tsx.ejs → core.stat-actions-today.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-consecutive-days.tsx.ejs → core.stat-consecutive-days.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{stat-online-time.tsx.ejs → core.stat-online-time.tsx.ejs} +0 -0
- /package/hedhog/frontend/app/dashboard/components/widgets/{user-growth-chart.tsx.ejs → core.user-growth-chart.tsx.ejs} +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Badge } from '@/components/ui/badge';
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from '@/components/ui/card';
|
|
11
|
+
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
12
|
+
import type {
|
|
13
|
+
DashboardCoreConfigOverviewData,
|
|
14
|
+
ThemeConfigWidgetData,
|
|
15
|
+
} from '@/types/widget-data';
|
|
16
|
+
import { Palette } from 'lucide-react';
|
|
17
|
+
import { useTranslations } from 'next-intl';
|
|
18
|
+
import { WidgetWrapper } from '../widget-wrapper';
|
|
19
|
+
|
|
20
|
+
const emptyPaletteMode = {
|
|
21
|
+
primary: null,
|
|
22
|
+
primaryForeground: null,
|
|
23
|
+
secondary: null,
|
|
24
|
+
secondaryForeground: null,
|
|
25
|
+
accent: null,
|
|
26
|
+
accentForeground: null,
|
|
27
|
+
muted: null,
|
|
28
|
+
mutedForeground: null,
|
|
29
|
+
background: null,
|
|
30
|
+
backgroundForeground: null,
|
|
31
|
+
card: null,
|
|
32
|
+
cardForeground: null,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const defaultThemeConfigData: ThemeConfigWidgetData = {
|
|
36
|
+
status: {
|
|
37
|
+
isConfigured: false,
|
|
38
|
+
configuredTokenCount: 0,
|
|
39
|
+
},
|
|
40
|
+
branding: {
|
|
41
|
+
systemName: null,
|
|
42
|
+
systemSlogan: null,
|
|
43
|
+
iconUrl: null,
|
|
44
|
+
imageUrl: null,
|
|
45
|
+
},
|
|
46
|
+
presentation: {
|
|
47
|
+
mode: null,
|
|
48
|
+
font: null,
|
|
49
|
+
textSize: null,
|
|
50
|
+
radius: null,
|
|
51
|
+
},
|
|
52
|
+
palette: {
|
|
53
|
+
light: emptyPaletteMode,
|
|
54
|
+
dark: emptyPaletteMode,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
interface ThemeConfigProps {
|
|
59
|
+
widget?: { name?: string };
|
|
60
|
+
onRemove?: () => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default function ThemeConfig({ widget, onRemove }: ThemeConfigProps) {
|
|
64
|
+
const t = useTranslations('core.DashboardPage.themeConfig');
|
|
65
|
+
|
|
66
|
+
const { data, isLoading, isError, isAccessDenied } = useWidgetData<
|
|
67
|
+
DashboardCoreConfigOverviewData,
|
|
68
|
+
ThemeConfigWidgetData
|
|
69
|
+
>({
|
|
70
|
+
endpoint: '/dashboard-core/config/overview',
|
|
71
|
+
queryKey: 'dashboard-core-config-overview',
|
|
72
|
+
select: (d) => d.themeConfig,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const themeData = data ?? defaultThemeConfigData;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<WidgetWrapper
|
|
79
|
+
isLoading={isLoading}
|
|
80
|
+
isError={isError}
|
|
81
|
+
isAccessDenied={isAccessDenied}
|
|
82
|
+
widgetName={widget?.name ?? t('title')}
|
|
83
|
+
onRemove={onRemove}
|
|
84
|
+
>
|
|
85
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
86
|
+
<CardHeader className="shrink-0">
|
|
87
|
+
<div className="flex items-center justify-between gap-3">
|
|
88
|
+
<div className="flex items-center gap-3">
|
|
89
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-rose-50">
|
|
90
|
+
<Palette className="h-5 w-5 text-rose-600" />
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
<CardTitle className="text-base">{t('title')}</CardTitle>
|
|
94
|
+
<CardDescription>{t('description')}</CardDescription>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<Badge
|
|
98
|
+
variant="secondary"
|
|
99
|
+
className={
|
|
100
|
+
themeData.status.isConfigured
|
|
101
|
+
? 'bg-emerald-50 text-emerald-700'
|
|
102
|
+
: 'bg-amber-50 text-amber-700'
|
|
103
|
+
}
|
|
104
|
+
>
|
|
105
|
+
{themeData.status.isConfigured ? t('configured') : t('pending')}
|
|
106
|
+
</Badge>
|
|
107
|
+
</div>
|
|
108
|
+
</CardHeader>
|
|
109
|
+
<CardContent className="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden pt-0">
|
|
110
|
+
<div className="rounded-lg border bg-muted/30 p-4">
|
|
111
|
+
<div className="flex items-center gap-3 rounded-lg border bg-background p-3">
|
|
112
|
+
<div
|
|
113
|
+
className="flex h-10 w-10 items-center justify-center rounded-lg text-sm font-bold text-white"
|
|
114
|
+
style={{
|
|
115
|
+
backgroundColor: themeData.palette.light.primary || '#64748b',
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{(themeData.branding.systemName || 'S').charAt(0).toUpperCase()}
|
|
119
|
+
</div>
|
|
120
|
+
<div className="min-w-0">
|
|
121
|
+
<p className="truncate text-sm font-semibold">
|
|
122
|
+
{themeData.branding.systemName || t('notSet')}
|
|
123
|
+
</p>
|
|
124
|
+
<p className="truncate text-xs text-muted-foreground">
|
|
125
|
+
{themeData.branding.systemSlogan || t('notSet')}
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
<Badge variant="outline" className="ml-auto text-[10px]">
|
|
129
|
+
{themeData.presentation.mode || t('notSet')}
|
|
130
|
+
</Badge>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
|
135
|
+
<div className="rounded-lg border p-3 text-center">
|
|
136
|
+
<p className="text-[11px] text-muted-foreground">{t('tokens')}</p>
|
|
137
|
+
<p className="mt-1 text-lg font-semibold">
|
|
138
|
+
{themeData.status.configuredTokenCount}
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="rounded-lg border p-3 text-center">
|
|
142
|
+
<p className="text-[11px] text-muted-foreground">{t('font')}</p>
|
|
143
|
+
<p className="mt-1 truncate text-sm font-medium">
|
|
144
|
+
{themeData.presentation.font || t('notSet')}
|
|
145
|
+
</p>
|
|
146
|
+
</div>
|
|
147
|
+
<div className="rounded-lg border p-3 text-center">
|
|
148
|
+
<p className="text-[11px] text-muted-foreground">
|
|
149
|
+
{t('textSize')}
|
|
150
|
+
</p>
|
|
151
|
+
<p className="mt-1 truncate text-sm font-medium">
|
|
152
|
+
{themeData.presentation.textSize || t('notSet')}
|
|
153
|
+
</p>
|
|
154
|
+
</div>
|
|
155
|
+
<div className="rounded-lg border p-3 text-center">
|
|
156
|
+
<p className="text-[11px] text-muted-foreground">{t('radius')}</p>
|
|
157
|
+
<p className="mt-1 truncate text-sm font-medium">
|
|
158
|
+
{themeData.presentation.radius || t('notSet')}
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
164
|
+
<div className="rounded-lg border p-3">
|
|
165
|
+
<div className="mb-2 flex items-center justify-between">
|
|
166
|
+
<p className="text-xs text-muted-foreground">
|
|
167
|
+
{t('lightPalette')}
|
|
168
|
+
</p>
|
|
169
|
+
<Badge variant="outline" className="text-[10px]">
|
|
170
|
+
{themeData.palette.light.primary || t('notSet')}
|
|
171
|
+
</Badge>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="flex items-center gap-2">
|
|
174
|
+
<span
|
|
175
|
+
className="h-5 w-5 rounded-full border"
|
|
176
|
+
style={{
|
|
177
|
+
backgroundColor:
|
|
178
|
+
themeData.palette.light.primary || 'transparent',
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
<span className="text-xs text-muted-foreground">
|
|
182
|
+
{t('primary')}
|
|
183
|
+
</span>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<div className="rounded-lg border p-3">
|
|
187
|
+
<div className="mb-2 flex items-center justify-between">
|
|
188
|
+
<p className="text-xs text-muted-foreground">
|
|
189
|
+
{t('darkPalette')}
|
|
190
|
+
</p>
|
|
191
|
+
<Badge variant="outline" className="text-[10px]">
|
|
192
|
+
{themeData.palette.dark.primary || t('notSet')}
|
|
193
|
+
</Badge>
|
|
194
|
+
</div>
|
|
195
|
+
<div className="flex items-center gap-2">
|
|
196
|
+
<span
|
|
197
|
+
className="h-5 w-5 rounded-full border"
|
|
198
|
+
style={{
|
|
199
|
+
backgroundColor:
|
|
200
|
+
themeData.palette.dark.primary || 'transparent',
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
203
|
+
<span className="text-xs text-muted-foreground">
|
|
204
|
+
{t('primary')}
|
|
205
|
+
</span>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</CardContent>
|
|
210
|
+
</Card>
|
|
211
|
+
</WidgetWrapper>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Badge } from '@/components/ui/badge';
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from '@/components/ui/card';
|
|
11
|
+
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
12
|
+
import type { AllWidgetsData, RoleData } from '@/types/widget-data';
|
|
13
|
+
import { Crown, ShieldCheck } from 'lucide-react';
|
|
14
|
+
import { useTranslations } from 'next-intl';
|
|
15
|
+
import { WidgetWrapper } from '../widget-wrapper';
|
|
16
|
+
|
|
17
|
+
const levelStyles = [
|
|
18
|
+
{
|
|
19
|
+
border: 'border-blue-200 dark:border-blue-800',
|
|
20
|
+
bg: 'bg-blue-50/50 dark:bg-blue-950/30',
|
|
21
|
+
iconBg: 'bg-blue-100 dark:bg-blue-900/50',
|
|
22
|
+
iconColor: 'text-blue-600 dark:text-blue-400',
|
|
23
|
+
badge: 'bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-400',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
border: 'border-indigo-200 dark:border-indigo-800',
|
|
27
|
+
bg: 'bg-indigo-50/30 dark:bg-indigo-950/20',
|
|
28
|
+
iconBg: 'bg-indigo-100 dark:bg-indigo-900/50',
|
|
29
|
+
iconColor: 'text-indigo-600 dark:text-indigo-400',
|
|
30
|
+
badge:
|
|
31
|
+
'bg-indigo-50 text-indigo-700 dark:bg-indigo-950/40 dark:text-indigo-400',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
border: 'border-emerald-200 dark:border-emerald-800',
|
|
35
|
+
bg: 'bg-emerald-50/30 dark:bg-emerald-950/20',
|
|
36
|
+
iconBg: 'bg-emerald-100 dark:bg-emerald-900/50',
|
|
37
|
+
iconColor: 'text-emerald-600 dark:text-emerald-400',
|
|
38
|
+
badge:
|
|
39
|
+
'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-400',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
border: 'border-amber-200 dark:border-amber-800',
|
|
43
|
+
bg: 'bg-amber-50/30 dark:bg-amber-950/20',
|
|
44
|
+
iconBg: 'bg-amber-100 dark:bg-amber-900/50',
|
|
45
|
+
iconColor: 'text-amber-600 dark:text-amber-400',
|
|
46
|
+
badge:
|
|
47
|
+
'bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-400',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function RolesContent({ roles }: { roles: RoleData[] }) {
|
|
52
|
+
const t = useTranslations('core.DashboardPage.userRoles');
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
56
|
+
<CardHeader className="shrink-0 space-y-1 pb-2">
|
|
57
|
+
<div className="flex items-center gap-1.5">
|
|
58
|
+
<Crown className="h-4 w-4 text-amber-600 dark:text-amber-400" />
|
|
59
|
+
<div>
|
|
60
|
+
<CardTitle className="text-sm font-semibold">
|
|
61
|
+
{t('title')}
|
|
62
|
+
</CardTitle>
|
|
63
|
+
<CardDescription className="text-xs leading-tight">
|
|
64
|
+
{t('description')}
|
|
65
|
+
</CardDescription>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</CardHeader>
|
|
69
|
+
<CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
|
|
70
|
+
<div className="grid content-start items-start grid-cols-1 gap-1.5 md:grid-cols-2">
|
|
71
|
+
{roles.map((role, index) => {
|
|
72
|
+
const style = levelStyles[index % levelStyles.length]!;
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
key={role.id}
|
|
76
|
+
className={`flex h-fit self-start items-center gap-2 rounded-lg border p-2 transition-all duration-200 hover:shadow-sm ${style.border} ${style.bg}`}
|
|
77
|
+
>
|
|
78
|
+
<div
|
|
79
|
+
className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-md ${style.iconBg}`}
|
|
80
|
+
>
|
|
81
|
+
<ShieldCheck className={`h-3.5 w-3.5 ${style.iconColor}`} />
|
|
82
|
+
</div>
|
|
83
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
84
|
+
<span className="truncate text-xs font-medium text-foreground sm:text-[13px]">
|
|
85
|
+
{role.name}
|
|
86
|
+
</span>
|
|
87
|
+
<span className="truncate text-[10px] text-muted-foreground sm:text-[11px]">
|
|
88
|
+
{role.slug}
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
<Badge
|
|
92
|
+
variant="secondary"
|
|
93
|
+
className={`shrink-0 px-1.5 py-0 text-[10px] ${style.badge}`}
|
|
94
|
+
>
|
|
95
|
+
{t('active')}
|
|
96
|
+
</Badge>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
</div>
|
|
101
|
+
</CardContent>
|
|
102
|
+
</Card>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface UserRolesProps {
|
|
107
|
+
widget?: { name?: string };
|
|
108
|
+
onRemove?: () => void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default function UserRoles({ widget, onRemove }: UserRolesProps) {
|
|
112
|
+
const { data, isLoading, isError, isAccessDenied } = useWidgetData<
|
|
113
|
+
AllWidgetsData,
|
|
114
|
+
RoleData[]
|
|
115
|
+
>({
|
|
116
|
+
endpoint: '/dashboard-core/widgets/me',
|
|
117
|
+
queryKey: 'widget-me',
|
|
118
|
+
select: (d) => d.userRoles,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<WidgetWrapper
|
|
123
|
+
isLoading={isLoading}
|
|
124
|
+
isError={isError}
|
|
125
|
+
isAccessDenied={isAccessDenied}
|
|
126
|
+
widgetName={widget?.name ?? 'user-roles'}
|
|
127
|
+
onRemove={onRemove}
|
|
128
|
+
>
|
|
129
|
+
{data && <RolesContent roles={data} />}
|
|
130
|
+
</WidgetWrapper>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -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} · {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
|
+
}
|