@hed-hog/core 0.0.299 → 0.0.300
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/dashboard/dashboard/dashboard.controller.d.ts +6 -0
- package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -0
- package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +2 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js +6 -3
- package/dist/dashboard/dashboard-component/dashboard-component.controller.js.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +7 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-component/dashboard-component.service.js +76 -33
- package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +65 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +111 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +69 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +526 -19
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -0
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -0
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +2 -0
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +2 -0
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
- package/hedhog/data/dashboard.yaml +12 -6
- package/hedhog/data/dashboard_component_role.yaml +66 -0
- package/hedhog/data/dashboard_role.yaml +2 -8
- package/hedhog/data/route.yaml +72 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +333 -128
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +277 -53
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +179 -231
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +64 -18
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +1389 -0
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +37 -0
- package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +8 -8
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +3 -3
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +3 -25
- package/hedhog/frontend/messages/en.json +112 -2
- package/hedhog/frontend/messages/pt.json +111 -1
- package/hedhog/frontend/widgets/account-security.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/active-users-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/activity-timeline.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/email-notifications.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/locale-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/login-history-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/mail-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/mail-sent-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/mail-sent-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/menus-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/oauth-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/permissions-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/permissions-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/profile-card.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/routes-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/session-activity-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/sessions-today-card.tsx.ejs +2 -2
- package/hedhog/frontend/widgets/stat-access-level.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-actions-today.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-consecutive-days.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/stat-online-time.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/storage-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/theme-config.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-growth-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-roles.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/user-sessions.tsx.ejs +1 -1
- package/hedhog/table/dashboard.yaml +6 -0
- package/package.json +5 -5
- package/src/dashboard/dashboard-component/dashboard-component.controller.ts +15 -2
- package/src/dashboard/dashboard-component/dashboard-component.service.ts +107 -43
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +112 -1
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +674 -19
- package/hedhog/frontend/app/dashboard/components/widgets/core..gitkeep.ejs +0 -11
- package/hedhog/frontend/app/dashboard/components/widgets/core.account-security.tsx.ejs +0 -192
- package/hedhog/frontend/app/dashboard/components/widgets/core.active-users-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.activity-timeline.tsx.ejs +0 -223
- package/hedhog/frontend/app/dashboard/components/widgets/core.email-notifications.tsx.ejs +0 -226
- package/hedhog/frontend/app/dashboard/components/widgets/core.locale-config.tsx.ejs +0 -168
- package/hedhog/frontend/app/dashboard/components/widgets/core.login-history-chart.tsx.ejs +0 -115
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-config.tsx.ejs +0 -199
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.mail-sent-chart.tsx.ejs +0 -149
- package/hedhog/frontend/app/dashboard/components/widgets/core.menus-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.oauth-config.tsx.ejs +0 -175
- package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-card.tsx.ejs +0 -61
- package/hedhog/frontend/app/dashboard/components/widgets/core.permissions-chart.tsx.ejs +0 -156
- package/hedhog/frontend/app/dashboard/components/widgets/core.profile-card.tsx.ejs +0 -186
- package/hedhog/frontend/app/dashboard/components/widgets/core.routes-card.tsx.ejs +0 -58
- package/hedhog/frontend/app/dashboard/components/widgets/core.session-activity-chart.tsx.ejs +0 -183
- package/hedhog/frontend/app/dashboard/components/widgets/core.sessions-today-card.tsx.ejs +0 -62
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-access-level.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-actions-today.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-consecutive-days.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.stat-online-time.tsx.ejs +0 -57
- package/hedhog/frontend/app/dashboard/components/widgets/core.storage-config.tsx.ejs +0 -196
- package/hedhog/frontend/app/dashboard/components/widgets/core.theme-config.tsx.ejs +0 -213
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-growth-chart.tsx.ejs +0 -210
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-roles.tsx.ejs +0 -132
- package/hedhog/frontend/app/dashboard/components/widgets/core.user-sessions.tsx.ejs +0 -236
- package/hedhog/frontend/app/dashboard/components/widgets/finance.alerts.tsx.ejs +0 -108
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-balance-kpi.tsx.ejs +0 -66
- package/hedhog/frontend/app/dashboard/components/widgets/finance.cash-flow-chart.tsx.ejs +0 -122
- package/hedhog/frontend/app/dashboard/components/widgets/finance.default-kpi.tsx.ejs +0 -63
- package/hedhog/frontend/app/dashboard/components/widgets/finance.payable-30d-kpi.tsx.ejs +0 -73
- package/hedhog/frontend/app/dashboard/components/widgets/finance.receivable-30d-kpi.tsx.ejs +0 -73
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-payable.tsx.ejs +0 -123
- package/hedhog/frontend/app/dashboard/components/widgets/finance.upcoming-receivable.tsx.ejs +0 -118
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Reserved for widget templates contributed directly by this library.
|
|
2
|
-
|
|
3
|
-
Expected naming convention:
|
|
4
|
-
- core widgets: <slug>.tsx.ejs (legacy-compatible)
|
|
5
|
-
- cross-library-safe widgets: <library>.<slug>.tsx.ejs
|
|
6
|
-
|
|
7
|
-
The Hedhog CLI should copy these templates to:
|
|
8
|
-
apps/admin/src/app/(app)/(libraries)/core/dashboard/components/widgets/
|
|
9
|
-
|
|
10
|
-
Optional static previews can follow the same namespaced slug and be copied to:
|
|
11
|
-
apps/admin/public/libraries/core/dashboard-previews/
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import {
|
|
3
|
-
Card,
|
|
4
|
-
CardContent,
|
|
5
|
-
CardDescription,
|
|
6
|
-
CardHeader,
|
|
7
|
-
CardTitle,
|
|
8
|
-
} from '@/components/ui/card';
|
|
9
|
-
import { Progress } from '@/components/ui/progress';
|
|
10
|
-
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
11
|
-
import type { AllWidgetsData } from '@/types/widget-data';
|
|
12
|
-
import {
|
|
13
|
-
AlertTriangle,
|
|
14
|
-
CheckCircle2,
|
|
15
|
-
ChevronRight,
|
|
16
|
-
Lock,
|
|
17
|
-
Mail,
|
|
18
|
-
ShieldCheck,
|
|
19
|
-
Smartphone,
|
|
20
|
-
} from 'lucide-react';
|
|
21
|
-
import { useTranslations } from 'next-intl';
|
|
22
|
-
import { useEffect, useState } from 'react';
|
|
23
|
-
|
|
24
|
-
import type { AccountSecurityData } from '@/types/widget-data';
|
|
25
|
-
import { useRouter } from 'next/navigation';
|
|
26
|
-
import { WidgetWrapper } from '../widget-wrapper';
|
|
27
|
-
|
|
28
|
-
const ICON_MAP: Record<string, React.ElementType> = {
|
|
29
|
-
password: Lock,
|
|
30
|
-
'2fa': Smartphone,
|
|
31
|
-
email: Mail,
|
|
32
|
-
sessions: AlertTriangle,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const CHECK_ROUTE_MAP: Record<string, string> = {
|
|
36
|
-
password: '/core/account/password',
|
|
37
|
-
'2fa': '/core/account/2fa',
|
|
38
|
-
email: '/core/account/email',
|
|
39
|
-
sessions: '/core/account/sessions',
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
43
|
-
const t = useTranslations('core.DashboardPage.accountSecurity');
|
|
44
|
-
const router = useRouter();
|
|
45
|
-
const [score, setScore] = useState(0);
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
const timer = setTimeout(() => setScore(data.score), 300);
|
|
49
|
-
return () => clearTimeout(timer);
|
|
50
|
-
}, [data.score]);
|
|
51
|
-
|
|
52
|
-
const scoreColor =
|
|
53
|
-
score >= 80
|
|
54
|
-
? 'text-emerald-600'
|
|
55
|
-
: score >= 50
|
|
56
|
-
? 'text-amber-600'
|
|
57
|
-
: 'text-red-600';
|
|
58
|
-
|
|
59
|
-
const progressColor =
|
|
60
|
-
score >= 80
|
|
61
|
-
? 'hsl(160, 84%, 39%)'
|
|
62
|
-
: score >= 50
|
|
63
|
-
? 'hsl(38, 92%, 50%)'
|
|
64
|
-
: 'hsl(0, 84%, 60%)';
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
68
|
-
<CardHeader className="shrink-0 px-4 pb-2 pt-4 sm:px-5">
|
|
69
|
-
<div className="flex items-center gap-2">
|
|
70
|
-
<ShieldCheck className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
|
71
|
-
<div>
|
|
72
|
-
<CardTitle className="text-base font-semibold">
|
|
73
|
-
{t('title')}
|
|
74
|
-
</CardTitle>
|
|
75
|
-
<CardDescription className="text-xs sm:text-sm">
|
|
76
|
-
{t('description')}
|
|
77
|
-
</CardDescription>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</CardHeader>
|
|
81
|
-
<CardContent className="flex min-h-0 flex-1 flex-col overflow-auto px-4 pb-3 pt-0 sm:px-5 sm:pb-4">
|
|
82
|
-
<div className="mb-2 flex flex-col items-center gap-1.5 rounded-xl bg-muted/50 p-2 sm:mb-3 sm:gap-2 sm:p-3">
|
|
83
|
-
<div className="flex items-baseline gap-1">
|
|
84
|
-
<span
|
|
85
|
-
className={`text-2xl font-bold tracking-tight sm:text-3xl ${scoreColor}`}
|
|
86
|
-
>
|
|
87
|
-
{score}
|
|
88
|
-
</span>
|
|
89
|
-
<span className="text-xs text-muted-foreground sm:text-sm">
|
|
90
|
-
/100
|
|
91
|
-
</span>
|
|
92
|
-
</div>
|
|
93
|
-
<Progress
|
|
94
|
-
value={score}
|
|
95
|
-
className="h-1.5 w-full max-w-70"
|
|
96
|
-
style={
|
|
97
|
-
{
|
|
98
|
-
'--progress-foreground': progressColor,
|
|
99
|
-
} as any
|
|
100
|
-
}
|
|
101
|
-
/>
|
|
102
|
-
<p className="text-[10px] text-muted-foreground sm:text-[11px]">
|
|
103
|
-
{score >= 80 ? t('wellProtected') : t('recommendProtections')}
|
|
104
|
-
</p>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<div className="flex flex-col gap-1">
|
|
108
|
-
{data.checks.map((item) => {
|
|
109
|
-
const Icon = ICON_MAP[item.id] ?? ShieldCheck;
|
|
110
|
-
return (
|
|
111
|
-
<button
|
|
112
|
-
type="button"
|
|
113
|
-
key={item.id}
|
|
114
|
-
onClick={() =>
|
|
115
|
-
router.push(CHECK_ROUTE_MAP[item.id] ?? '/core/account')
|
|
116
|
-
}
|
|
117
|
-
className="group flex w-full cursor-pointer flex-wrap items-start gap-2 rounded-lg p-2 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:items-center sm:gap-2.5 sm:p-2.5"
|
|
118
|
-
>
|
|
119
|
-
<div
|
|
120
|
-
className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-lg sm:h-8 sm:w-8 ${
|
|
121
|
-
item.enabled
|
|
122
|
-
? 'bg-emerald-50 dark:bg-emerald-950/40'
|
|
123
|
-
: 'bg-muted'
|
|
124
|
-
}`}
|
|
125
|
-
>
|
|
126
|
-
<Icon
|
|
127
|
-
className={`h-3.5 w-3.5 ${
|
|
128
|
-
item.enabled
|
|
129
|
-
? 'text-emerald-600 dark:text-emerald-400'
|
|
130
|
-
: 'text-muted-foreground'
|
|
131
|
-
}`}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
135
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
136
|
-
<span className="wrap-break-word text-[12px] font-medium text-foreground sm:text-[13px]">
|
|
137
|
-
{t(`labels.${item.labelKey}` as any) || item.labelKey}
|
|
138
|
-
</span>
|
|
139
|
-
{item.enabled ? (
|
|
140
|
-
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
|
|
141
|
-
) : (
|
|
142
|
-
<AlertTriangle className="h-3.5 w-3.5 text-amber-500" />
|
|
143
|
-
)}
|
|
144
|
-
</div>
|
|
145
|
-
<span className="text-[10px] text-muted-foreground sm:text-[11px]">
|
|
146
|
-
{t(`descriptions.${item.descriptionKey}` as any) ||
|
|
147
|
-
item.descriptionKey}
|
|
148
|
-
</span>
|
|
149
|
-
</div>
|
|
150
|
-
<div className="mt-0.5 flex w-full shrink-0 items-center justify-end gap-1 text-[11px] font-medium text-foreground sm:mt-0 sm:w-auto sm:text-xs">
|
|
151
|
-
{!item.enabled && <span>{t('activate')}</span>}
|
|
152
|
-
<ChevronRight className="h-3 w-3" />
|
|
153
|
-
</div>
|
|
154
|
-
</button>
|
|
155
|
-
);
|
|
156
|
-
})}
|
|
157
|
-
</div>
|
|
158
|
-
</CardContent>
|
|
159
|
-
</Card>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
interface AccountSecurityProps {
|
|
164
|
-
widget?: { name?: string };
|
|
165
|
-
onRemove?: () => void;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export default function AccountSecurity({
|
|
169
|
-
widget,
|
|
170
|
-
onRemove,
|
|
171
|
-
}: AccountSecurityProps) {
|
|
172
|
-
const { data, isLoading, isError, isAccessDenied } = useWidgetData<
|
|
173
|
-
AllWidgetsData,
|
|
174
|
-
AccountSecurityData
|
|
175
|
-
>({
|
|
176
|
-
endpoint: '/dashboard-core/widgets/me',
|
|
177
|
-
queryKey: 'widget-me',
|
|
178
|
-
select: (d) => d.accountSecurity,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return (
|
|
182
|
-
<WidgetWrapper
|
|
183
|
-
isLoading={isLoading}
|
|
184
|
-
isError={isError}
|
|
185
|
-
isAccessDenied={isAccessDenied}
|
|
186
|
-
widgetName={widget?.name ?? 'account-security'}
|
|
187
|
-
onRemove={onRemove}
|
|
188
|
-
>
|
|
189
|
-
{data && <AccountSecurityContent data={data} />}
|
|
190
|
-
</WidgetWrapper>
|
|
191
|
-
);
|
|
192
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
2
|
-
import { Users } from 'lucide-react';
|
|
3
|
-
import { useTranslations } from 'next-intl';
|
|
4
|
-
import StatCard from '../stats';
|
|
5
|
-
import { WidgetWrapper } from '../widget-wrapper';
|
|
6
|
-
|
|
7
|
-
interface ActiveUsersProps {
|
|
8
|
-
widget?: any;
|
|
9
|
-
onRemove?: () => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface UserStatsData {
|
|
13
|
-
cards?: {
|
|
14
|
-
activeUsers?: {
|
|
15
|
-
value: number;
|
|
16
|
-
change: number | null;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default function ActiveUsers({ widget, onRemove }: ActiveUsersProps) {
|
|
22
|
-
const t = useTranslations('core.Dashboard');
|
|
23
|
-
|
|
24
|
-
const { data, isLoading, isAccessDenied, isError } =
|
|
25
|
-
useWidgetData<UserStatsData>({
|
|
26
|
-
endpoint: '/dashboard-core/stats/overview/users',
|
|
27
|
-
queryKey: 'dashboard-stats-users',
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const value = data?.cards?.activeUsers?.value?.toLocaleString('pt-BR') || '0';
|
|
31
|
-
const change = data?.cards?.activeUsers?.change;
|
|
32
|
-
const changeType =
|
|
33
|
-
change !== null && change !== undefined && change >= 0 ? 'up' : 'down';
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<WidgetWrapper
|
|
37
|
-
isLoading={isLoading}
|
|
38
|
-
isAccessDenied={isAccessDenied}
|
|
39
|
-
isError={isError}
|
|
40
|
-
widgetName={widget?.name || t('activeUsers')}
|
|
41
|
-
onRemove={onRemove}
|
|
42
|
-
>
|
|
43
|
-
<StatCard
|
|
44
|
-
title={t('activeUsers')}
|
|
45
|
-
value={value}
|
|
46
|
-
change={
|
|
47
|
-
change !== null && change !== undefined
|
|
48
|
-
? `${change > 0 ? '+' : ''}${change}%`
|
|
49
|
-
: undefined
|
|
50
|
-
}
|
|
51
|
-
changeType={changeType}
|
|
52
|
-
icon={<Users className="h-6 w-6 text-blue-500" />}
|
|
53
|
-
iconBg="bg-blue-500/10"
|
|
54
|
-
delay={50}
|
|
55
|
-
/>
|
|
56
|
-
</WidgetWrapper>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
@@ -1,223 +0,0 @@
|
|
|
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 { ScrollArea } from '@/components/ui/scroll-area';
|
|
12
|
-
import { useWidgetData } from '@/hooks/use-widget-data';
|
|
13
|
-
import type { ActivityEvent, AllWidgetsData } from '@/types/widget-data';
|
|
14
|
-
import {
|
|
15
|
-
FileEdit,
|
|
16
|
-
LogIn,
|
|
17
|
-
LogOut,
|
|
18
|
-
Mail,
|
|
19
|
-
Settings,
|
|
20
|
-
ShieldAlert,
|
|
21
|
-
UserCheck,
|
|
22
|
-
} from 'lucide-react';
|
|
23
|
-
import { useTranslations } from 'next-intl';
|
|
24
|
-
import { WidgetWrapper } from '../widget-wrapper';
|
|
25
|
-
|
|
26
|
-
const typeIconMap: Record<string, React.ElementType> = {
|
|
27
|
-
login: LogIn,
|
|
28
|
-
logout: LogOut,
|
|
29
|
-
security: ShieldAlert,
|
|
30
|
-
settings: Settings,
|
|
31
|
-
edit: FileEdit,
|
|
32
|
-
permission: UserCheck,
|
|
33
|
-
email: Mail,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const typeColorMap: Record<string, { color: string; bg: string }> = {
|
|
37
|
-
login: {
|
|
38
|
-
color: 'text-blue-600 dark:text-blue-400',
|
|
39
|
-
bg: 'bg-blue-50 dark:bg-blue-950/40',
|
|
40
|
-
},
|
|
41
|
-
logout: { color: 'text-muted-foreground', bg: 'bg-muted' },
|
|
42
|
-
security: {
|
|
43
|
-
color: 'text-amber-600 dark:text-amber-400',
|
|
44
|
-
bg: 'bg-amber-50 dark:bg-amber-950/40',
|
|
45
|
-
},
|
|
46
|
-
settings: { color: 'text-foreground', bg: 'bg-muted' },
|
|
47
|
-
edit: {
|
|
48
|
-
color: 'text-indigo-600 dark:text-indigo-400',
|
|
49
|
-
bg: 'bg-indigo-50 dark:bg-indigo-950/40',
|
|
50
|
-
},
|
|
51
|
-
permission: {
|
|
52
|
-
color: 'text-emerald-600 dark:text-emerald-400',
|
|
53
|
-
bg: 'bg-emerald-50 dark:bg-emerald-950/40',
|
|
54
|
-
},
|
|
55
|
-
email: {
|
|
56
|
-
color: 'text-rose-600 dark:text-rose-400',
|
|
57
|
-
bg: 'bg-rose-50 dark:bg-rose-950/40',
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
function detectType(action: string): string {
|
|
62
|
-
const a = action.toLowerCase();
|
|
63
|
-
if (a.includes('login') || a.includes('entrou') || a.includes('acesso'))
|
|
64
|
-
return 'login';
|
|
65
|
-
if (a.includes('logout') || a.includes('saiu') || a.includes('encerr'))
|
|
66
|
-
return 'logout';
|
|
67
|
-
if (
|
|
68
|
-
a.includes('senha') ||
|
|
69
|
-
a.includes('2fa') ||
|
|
70
|
-
a.includes('mfa') ||
|
|
71
|
-
a.includes('segur')
|
|
72
|
-
)
|
|
73
|
-
return 'security';
|
|
74
|
-
if (a.includes('config') || a.includes('idioma') || a.includes('preferên'))
|
|
75
|
-
return 'settings';
|
|
76
|
-
if (
|
|
77
|
-
a.includes('edit') ||
|
|
78
|
-
a.includes('atualiz') ||
|
|
79
|
-
a.includes('alter') ||
|
|
80
|
-
a.includes('perfil')
|
|
81
|
-
)
|
|
82
|
-
return 'edit';
|
|
83
|
-
if (a.includes('permiss') || a.includes('acesso') || a.includes('role'))
|
|
84
|
-
return 'permission';
|
|
85
|
-
if (a.includes('email') || a.includes('e-mail') || a.includes('notif'))
|
|
86
|
-
return 'email';
|
|
87
|
-
return 'settings';
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function formatDate(
|
|
91
|
-
dateStr: string,
|
|
92
|
-
tToday: string,
|
|
93
|
-
tYesterday: string
|
|
94
|
-
): string {
|
|
95
|
-
const date = new Date(dateStr);
|
|
96
|
-
const now = new Date();
|
|
97
|
-
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
98
|
-
const yesterday = new Date(today);
|
|
99
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
100
|
-
const eventDay = new Date(
|
|
101
|
-
date.getFullYear(),
|
|
102
|
-
date.getMonth(),
|
|
103
|
-
date.getDate()
|
|
104
|
-
);
|
|
105
|
-
if (eventDay.getTime() === today.getTime()) return tToday;
|
|
106
|
-
if (eventDay.getTime() === yesterday.getTime()) return tYesterday;
|
|
107
|
-
return date.toLocaleDateString(undefined, { day: '2-digit', month: 'short' });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function formatTime(dateStr: string): string {
|
|
111
|
-
return new Date(dateStr).toLocaleTimeString('pt-BR', {
|
|
112
|
-
hour: '2-digit',
|
|
113
|
-
minute: '2-digit',
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function TimelineContent({ events }: { events: ActivityEvent[] }) {
|
|
118
|
-
const t = useTranslations('core.DashboardPage.activityTimeline');
|
|
119
|
-
let lastDate = '';
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
123
|
-
<CardHeader className="shrink-0 pb-3">
|
|
124
|
-
<div className="flex items-center justify-between">
|
|
125
|
-
<div>
|
|
126
|
-
<CardTitle className="text-sm font-semibold sm:text-base">
|
|
127
|
-
{t('title')}
|
|
128
|
-
</CardTitle>
|
|
129
|
-
<CardDescription className="text-xs sm:text-sm">
|
|
130
|
-
{t('description')}
|
|
131
|
-
</CardDescription>
|
|
132
|
-
</div>
|
|
133
|
-
<Badge variant="secondary" className="text-[10px] sm:text-xs">
|
|
134
|
-
{t('events', { count: events.length })}
|
|
135
|
-
</Badge>
|
|
136
|
-
</div>
|
|
137
|
-
</CardHeader>
|
|
138
|
-
<CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
|
|
139
|
-
<ScrollArea className="h-full pr-2 sm:pr-3">
|
|
140
|
-
<div className="relative flex flex-col pb-2">
|
|
141
|
-
<div className="absolute bottom-0 left-[13px] top-0 w-px bg-border/60 sm:left-[15px]" />
|
|
142
|
-
{events.map((event, index) => {
|
|
143
|
-
const type = detectType(event.action);
|
|
144
|
-
const config =
|
|
145
|
-
typeColorMap[type] ??
|
|
146
|
-
(typeColorMap.settings as { color: string; bg: string });
|
|
147
|
-
const Icon = (typeIconMap[type] ?? Settings) as React.ElementType;
|
|
148
|
-
const dateLabel = formatDate(
|
|
149
|
-
event.created_at,
|
|
150
|
-
t('today'),
|
|
151
|
-
t('yesterday')
|
|
152
|
-
);
|
|
153
|
-
const showDate = dateLabel !== lastDate;
|
|
154
|
-
lastDate = dateLabel;
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<div key={event.id}>
|
|
158
|
-
{showDate && (
|
|
159
|
-
<div className="relative z-10 mb-1 mt-4 first:mt-0">
|
|
160
|
-
<span className="ml-9 inline-block rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground sm:ml-11 sm:px-2 sm:text-[11px]">
|
|
161
|
-
{dateLabel}
|
|
162
|
-
</span>
|
|
163
|
-
</div>
|
|
164
|
-
)}
|
|
165
|
-
<div className="group relative flex items-start gap-3 py-1.5">
|
|
166
|
-
<div
|
|
167
|
-
className={`relative z-10 mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg sm:h-8 sm:w-8 ${config.bg}`}
|
|
168
|
-
>
|
|
169
|
-
<Icon
|
|
170
|
-
className={`h-3 w-3 sm:h-3.5 sm:w-3.5 ${config.color}`}
|
|
171
|
-
/>
|
|
172
|
-
</div>
|
|
173
|
-
<div className="flex min-w-0 flex-1 flex-col gap-0.5 rounded-lg px-1.5 py-1.5 transition-colors group-hover:bg-muted/40 sm:px-2">
|
|
174
|
-
<div className="flex items-center justify-between gap-2">
|
|
175
|
-
<span className="min-w-0 wrap-break-word text-xs font-medium leading-snug text-foreground sm:text-sm">
|
|
176
|
-
{event.action}
|
|
177
|
-
</span>
|
|
178
|
-
<span className="shrink-0 text-[10px] tabular-nums text-muted-foreground sm:text-[11px]">
|
|
179
|
-
{formatTime(event.created_at)}
|
|
180
|
-
</span>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
);
|
|
186
|
-
})}
|
|
187
|
-
</div>
|
|
188
|
-
</ScrollArea>
|
|
189
|
-
</CardContent>
|
|
190
|
-
</Card>
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
interface ActivityTimelineProps {
|
|
195
|
-
widget?: { name?: string };
|
|
196
|
-
onRemove?: () => void;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export default function ActivityTimeline({
|
|
200
|
-
widget,
|
|
201
|
-
onRemove,
|
|
202
|
-
}: ActivityTimelineProps) {
|
|
203
|
-
const { data, isLoading, isError, isAccessDenied } = useWidgetData<
|
|
204
|
-
AllWidgetsData,
|
|
205
|
-
ActivityEvent[]
|
|
206
|
-
>({
|
|
207
|
-
endpoint: '/dashboard-core/widgets/me',
|
|
208
|
-
queryKey: 'widget-me',
|
|
209
|
-
select: (d) => d.activityTimeline,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
return (
|
|
213
|
-
<WidgetWrapper
|
|
214
|
-
isLoading={isLoading}
|
|
215
|
-
isError={isError}
|
|
216
|
-
isAccessDenied={isAccessDenied}
|
|
217
|
-
widgetName={widget?.name ?? 'activity-timeline'}
|
|
218
|
-
onRemove={onRemove}
|
|
219
|
-
>
|
|
220
|
-
{data && <TimelineContent events={data} />}
|
|
221
|
-
</WidgetWrapper>
|
|
222
|
-
);
|
|
223
|
-
}
|