@hed-hog/core 0.0.275 → 0.0.278
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/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +7 -8
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/auth/auth.controller.d.ts +8 -1
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +7 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +10 -1
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +34 -8
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/profile/profile.service.js +1 -1
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/role/guards/role.guard.d.ts +1 -0
- package/dist/role/guards/role.guard.d.ts.map +1 -1
- package/dist/role/guards/role.guard.js +18 -0
- package/dist/role/guards/role.guard.js.map +1 -1
- package/dist/session/session.service.js +1 -1
- package/dist/session/session.service.js.map +1 -1
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +1 -2
- package/dist/task/task.service.js.map +1 -1
- package/dist/user/dto/reset-password.dto.d.ts +4 -0
- package/dist/user/dto/reset-password.dto.d.ts.map +1 -0
- package/dist/user/dto/reset-password.dto.js +26 -0
- package/dist/user/dto/reset-password.dto.js.map +1 -0
- package/dist/user/user.controller.d.ts +5 -0
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +13 -0
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +6 -0
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +65 -0
- package/dist/user/user.service.js.map +1 -1
- package/hedhog/data/dashboard_component.yaml +77 -33
- package/hedhog/data/dashboard_component_role.yaml +132 -66
- package/hedhog/data/dashboard_item.yaml +100 -100
- package/hedhog/data/dashboard_role.yaml +18 -12
- package/hedhog/data/menu.yaml +6 -0
- package/hedhog/data/route.yaml +57 -1
- package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +2 -1
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +10 -10
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +20 -20
- package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +24 -24
- package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +4 -4
- package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +23 -19
- package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +15 -14
- package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +62 -58
- package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +18 -18
- package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +3 -3
- package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +34 -33
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +92 -92
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +29 -14
- package/hedhog/frontend/app/users/page.tsx.ejs +322 -1
- package/hedhog/frontend/messages/en.json +19 -1
- package/hedhog/frontend/messages/pt.json +19 -1
- package/hedhog/table/mail.yaml +15 -15
- package/hedhog/table/mail_sent.yaml +21 -21
- package/hedhog/table/mail_var.yaml +12 -12
- package/package.json +4 -4
- package/src/ai/ai.service.ts +3 -4
- package/src/auth/auth.controller.ts +21 -20
- package/src/auth/auth.service.ts +63 -15
- package/src/profile/profile.service.ts +1 -1
- package/src/role/guards/role.guard.ts +36 -7
- package/src/session/session.service.ts +2 -2
- package/src/setting/setting.service.ts +5 -5
- package/src/task/task.service.ts +5 -6
- package/src/user/dto/reset-password.dto.ts +11 -0
- package/src/user/user.controller.ts +24 -14
- package/src/user/user.service.ts +84 -0
|
@@ -85,7 +85,7 @@ export default function EmailNotifications({
|
|
|
85
85
|
widgetName={widget?.name ?? 'email-notifications'}
|
|
86
86
|
onRemove={onRemove}
|
|
87
87
|
>
|
|
88
|
-
<Card className="flex h-full flex-col">
|
|
88
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
89
89
|
<CardHeader className="shrink-0 pb-2">
|
|
90
90
|
<div className="flex items-center gap-2">
|
|
91
91
|
<Mail className="h-5 w-5 text-rose-600 dark:text-rose-400" />
|
|
@@ -99,15 +99,15 @@ export default function EmailNotifications({
|
|
|
99
99
|
</div>
|
|
100
100
|
</div>
|
|
101
101
|
</CardHeader>
|
|
102
|
-
<CardContent className="flex-1 overflow-
|
|
103
|
-
<div className="
|
|
104
|
-
{emailStats.map((stat) => {
|
|
105
|
-
const Icon = stat.icon;
|
|
106
|
-
return (
|
|
107
|
-
<div
|
|
108
|
-
key={stat.label}
|
|
109
|
-
className="flex flex-col items-center gap-1 rounded-lg border p-2.5 transition-colors hover:bg-muted/30"
|
|
110
|
-
>
|
|
102
|
+
<CardContent className="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden pt-0">
|
|
103
|
+
<div className="grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-4">
|
|
104
|
+
{emailStats.map((stat) => {
|
|
105
|
+
const Icon = stat.icon;
|
|
106
|
+
return (
|
|
107
|
+
<div
|
|
108
|
+
key={stat.label}
|
|
109
|
+
className="flex min-w-0 flex-col items-center gap-1 rounded-lg border p-2.5 text-center transition-colors hover:bg-muted/30"
|
|
110
|
+
>
|
|
111
111
|
<div
|
|
112
112
|
className={`flex h-7 w-7 items-center justify-center rounded-md ${stat.bg}`}
|
|
113
113
|
>
|
|
@@ -124,16 +124,20 @@ export default function EmailNotifications({
|
|
|
124
124
|
})}
|
|
125
125
|
</div>
|
|
126
126
|
|
|
127
|
-
<ChartContainer
|
|
128
|
-
|
|
127
|
+
<ChartContainer
|
|
128
|
+
config={chartConfig}
|
|
129
|
+
className="min-h-[220px] w-full flex-1 overflow-hidden"
|
|
130
|
+
>
|
|
131
|
+
<AreaChart data={data}>
|
|
129
132
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
130
|
-
<XAxis
|
|
131
|
-
dataKey="date"
|
|
132
|
-
tickLine={false}
|
|
133
|
-
axisLine={false}
|
|
134
|
-
fontSize={11}
|
|
135
|
-
tickMargin={8}
|
|
136
|
-
|
|
133
|
+
<XAxis
|
|
134
|
+
dataKey="date"
|
|
135
|
+
tickLine={false}
|
|
136
|
+
axisLine={false}
|
|
137
|
+
fontSize={11}
|
|
138
|
+
tickMargin={8}
|
|
139
|
+
minTickGap={24}
|
|
140
|
+
/>
|
|
137
141
|
<YAxis
|
|
138
142
|
tickLine={false}
|
|
139
143
|
axisLine={false}
|
|
@@ -28,7 +28,7 @@ function LoginChart({ data }: { data: LoginDay[] }) {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<Card className="flex h-full flex-col">
|
|
31
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
32
32
|
<CardHeader className="shrink-0 pb-2">
|
|
33
33
|
<div className="flex items-center gap-2">
|
|
34
34
|
<LogIn className="h-5 w-5 text-blue-600" />
|
|
@@ -40,20 +40,21 @@ function LoginChart({ data }: { data: LoginDay[] }) {
|
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
42
42
|
</CardHeader>
|
|
43
|
-
<CardContent className="flex flex-1 flex-col pt-0">
|
|
44
|
-
<ChartContainer
|
|
45
|
-
config={chartConfig}
|
|
46
|
-
className="h-full min-h-[
|
|
47
|
-
>
|
|
48
|
-
<BarChart data={data as any} barGap={2}>
|
|
43
|
+
<CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
|
|
44
|
+
<ChartContainer
|
|
45
|
+
config={chartConfig}
|
|
46
|
+
className="h-full min-h-[160px] w-full flex-1 overflow-hidden"
|
|
47
|
+
>
|
|
48
|
+
<BarChart data={data as any} barGap={2}>
|
|
49
49
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
50
|
-
<XAxis
|
|
51
|
-
dataKey="day"
|
|
52
|
-
tickLine={false}
|
|
53
|
-
axisLine={false}
|
|
54
|
-
fontSize={12}
|
|
55
|
-
tickMargin={8}
|
|
56
|
-
|
|
50
|
+
<XAxis
|
|
51
|
+
dataKey="day"
|
|
52
|
+
tickLine={false}
|
|
53
|
+
axisLine={false}
|
|
54
|
+
fontSize={12}
|
|
55
|
+
tickMargin={8}
|
|
56
|
+
minTickGap={16}
|
|
57
|
+
/>
|
|
57
58
|
<YAxis
|
|
58
59
|
tickLine={false}
|
|
59
60
|
axisLine={false}
|
|
@@ -37,20 +37,24 @@ function CustomTooltip({
|
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
interface PermissionsChartProps {
|
|
41
|
-
widget?:
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
interface PermissionsChartProps {
|
|
41
|
+
widget?: {
|
|
42
|
+
name?: string;
|
|
43
|
+
} | null;
|
|
44
|
+
onRemove?: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PermissionDistributionItem {
|
|
48
|
+
name: string;
|
|
49
|
+
value: number;
|
|
50
|
+
color: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface UserStatsData {
|
|
54
|
+
charts?: {
|
|
55
|
+
permissionDistribution?: PermissionDistributionItem[];
|
|
56
|
+
};
|
|
57
|
+
}
|
|
54
58
|
|
|
55
59
|
export default function PermissionsChart({
|
|
56
60
|
widget,
|
|
@@ -66,10 +70,10 @@ export default function PermissionsChart({
|
|
|
66
70
|
} = useWidgetData<UserStatsData>({
|
|
67
71
|
endpoint: '/dashboard-core/stats/overview/users',
|
|
68
72
|
queryKey: 'dashboard-stats-users',
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const data = statsData?.charts?.permissionDistribution || [];
|
|
72
|
-
const total = data.reduce((sum
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const data = statsData?.charts?.permissionDistribution || [];
|
|
76
|
+
const total = data.reduce((sum, item) => sum + item.value, 0);
|
|
73
77
|
|
|
74
78
|
return (
|
|
75
79
|
<WidgetWrapper
|
|
@@ -86,19 +90,19 @@ export default function PermissionsChart({
|
|
|
86
90
|
>
|
|
87
91
|
<IconGripVertical className="text-muted-foreground/50 size-4 shrink-0" />
|
|
88
92
|
</div>
|
|
89
|
-
<CardHeader className="pb-2 pt-4 pl-10">
|
|
90
|
-
<CardTitle className="text-base font-semibold">
|
|
91
|
-
{t('permissionsDistributionTitle')}
|
|
92
|
-
</CardTitle>
|
|
93
|
+
<CardHeader className="pb-2 pt-4 pl-10">
|
|
94
|
+
<CardTitle className="text-base font-semibold">
|
|
95
|
+
{t('permissionsDistributionTitle')}
|
|
96
|
+
</CardTitle>
|
|
93
97
|
<CardDescription>
|
|
94
98
|
{t('permissionsDistributionDescription')}
|
|
95
99
|
</CardDescription>
|
|
96
|
-
</CardHeader>
|
|
97
|
-
<CardContent className="flex-1 pt-0">
|
|
98
|
-
<div className="flex
|
|
99
|
-
<div className="relative h-[280px] w-[280px] shrink-0">
|
|
100
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
101
|
-
<PieChart>
|
|
100
|
+
</CardHeader>
|
|
101
|
+
<CardContent className="flex-1 pt-0">
|
|
102
|
+
<div className="flex h-full items-center justify-between gap-4 overflow-hidden">
|
|
103
|
+
<div className="relative h-[280px] w-[280px] shrink-0">
|
|
104
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
105
|
+
<PieChart>
|
|
102
106
|
<Pie
|
|
103
107
|
data={data}
|
|
104
108
|
cx="50%"
|
|
@@ -106,14 +110,14 @@ export default function PermissionsChart({
|
|
|
106
110
|
innerRadius={70}
|
|
107
111
|
outerRadius={110}
|
|
108
112
|
paddingAngle={4}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{data.map((entry
|
|
114
|
-
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
115
|
-
))}
|
|
116
|
-
</Pie>
|
|
113
|
+
dataKey="value"
|
|
114
|
+
animationDuration={1200}
|
|
115
|
+
strokeWidth={0}
|
|
116
|
+
>
|
|
117
|
+
{data.map((entry, index: number) => (
|
|
118
|
+
<Cell key={`cell-${index}`} fill={entry.color} />
|
|
119
|
+
))}
|
|
120
|
+
</Pie>
|
|
117
121
|
<Tooltip content={<CustomTooltip />} />
|
|
118
122
|
</PieChart>
|
|
119
123
|
</ResponsiveContainer>
|
|
@@ -121,28 +125,28 @@ export default function PermissionsChart({
|
|
|
121
125
|
<span className="text-2xl font-bold text-foreground">
|
|
122
126
|
{total}
|
|
123
127
|
</span>
|
|
124
|
-
<span className="text-[10px] text-muted-foreground">
|
|
125
|
-
{t('total')}
|
|
126
|
-
</span>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
<div className="grid grid-cols-2 gap-
|
|
130
|
-
{data.map((item
|
|
131
|
-
<div key={item.name} className="flex items-
|
|
132
|
-
<div
|
|
133
|
-
className="h-
|
|
134
|
-
style={{ backgroundColor: item.color }}
|
|
135
|
-
/>
|
|
136
|
-
<div className="flex flex-col">
|
|
137
|
-
<span className="text-
|
|
138
|
-
{item.name}
|
|
139
|
-
</span>
|
|
140
|
-
<span className="text-
|
|
141
|
-
{item.value} ({Math.round((item.value / total) * 100)}%)
|
|
142
|
-
</span>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
))}
|
|
128
|
+
<span className="text-[10px] text-muted-foreground">
|
|
129
|
+
{t('total')}
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="grid max-h-[280px] min-w-0 flex-1 grid-cols-2 content-start gap-x-4 gap-y-2 overflow-y-auto pr-2">
|
|
134
|
+
{data.map((item) => (
|
|
135
|
+
<div key={item.name} className="flex min-w-0 items-start gap-2.5">
|
|
136
|
+
<div
|
|
137
|
+
className="mt-1 h-2.5 w-2.5 shrink-0 rounded-sm"
|
|
138
|
+
style={{ backgroundColor: item.color }}
|
|
139
|
+
/>
|
|
140
|
+
<div className="flex min-w-0 flex-col">
|
|
141
|
+
<span className="break-words text-xs font-medium leading-tight text-foreground">
|
|
142
|
+
{item.name}
|
|
143
|
+
</span>
|
|
144
|
+
<span className="text-[11px] text-muted-foreground">
|
|
145
|
+
{item.value} ({total ? Math.round((item.value / total) * 100) : 0}%)
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
))}
|
|
146
150
|
</div>
|
|
147
151
|
</div>
|
|
148
152
|
</CardContent>
|
|
@@ -34,24 +34,24 @@ export default function StatAccessLevel({
|
|
|
34
34
|
widgetName={widget?.name ?? 'stat-access-level'}
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
|
-
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<Zap className="h-
|
|
41
|
-
</div>
|
|
42
|
-
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
-
{t('accessLevel')}
|
|
45
|
-
</span>
|
|
46
|
-
<span className="text-
|
|
47
|
-
{data ? t('accessLevelValue', { level: data }) : '—'}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="text-[11px] text-muted-foreground">
|
|
50
|
-
{t('accessLevelSubtitle')}
|
|
51
|
-
</span>
|
|
52
|
-
</div>
|
|
53
|
-
</CardContent>
|
|
54
|
-
</Card>
|
|
37
|
+
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
+
<CardContent className="flex h-full items-center gap-3 p-3 md:gap-4 md:p-4">
|
|
39
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-amber-50 dark:bg-amber-950/40 md:h-11 md:w-11">
|
|
40
|
+
<Zap className="h-4 w-4 text-amber-600 dark:text-amber-400 md:h-5 md:w-5" />
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex min-w-0 flex-col">
|
|
43
|
+
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
+
{t('accessLevel')}
|
|
45
|
+
</span>
|
|
46
|
+
<span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
|
|
47
|
+
{data ? t('accessLevelValue', { level: data }) : '—'}
|
|
48
|
+
</span>
|
|
49
|
+
<span className="hidden text-[11px] text-muted-foreground sm:block">
|
|
50
|
+
{t('accessLevelSubtitle')}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
55
|
</WidgetWrapper>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -34,24 +34,24 @@ export default function StatActionsToday({
|
|
|
34
34
|
widgetName={widget?.name ?? 'stat-actions-today'}
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
|
-
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<MousePointerClick className="h-
|
|
41
|
-
</div>
|
|
42
|
-
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
-
{t('actionsToday')}
|
|
45
|
-
</span>
|
|
46
|
-
<span className="text-
|
|
47
|
-
{data ?? '—'}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="text-[11px] text-muted-foreground">
|
|
50
|
-
{t('actionsTodaySubtitle')}
|
|
51
|
-
</span>
|
|
52
|
-
</div>
|
|
53
|
-
</CardContent>
|
|
54
|
-
</Card>
|
|
37
|
+
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
+
<CardContent className="flex h-full items-center gap-3 p-3 md:gap-4 md:p-4">
|
|
39
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-indigo-50 dark:bg-indigo-950/40 md:h-11 md:w-11">
|
|
40
|
+
<MousePointerClick className="h-4 w-4 text-indigo-600 dark:text-indigo-400 md:h-5 md:w-5" />
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex min-w-0 flex-col">
|
|
43
|
+
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
+
{t('actionsToday')}
|
|
45
|
+
</span>
|
|
46
|
+
<span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
|
|
47
|
+
{data ?? '—'}
|
|
48
|
+
</span>
|
|
49
|
+
<span className="hidden text-[11px] text-muted-foreground sm:block">
|
|
50
|
+
{t('actionsTodaySubtitle')}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
55
|
</WidgetWrapper>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -34,24 +34,24 @@ export default function StatConsecutiveDays({
|
|
|
34
34
|
widgetName={widget?.name ?? 'stat-consecutive-days'}
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
|
-
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<CalendarDays className="h-
|
|
41
|
-
</div>
|
|
42
|
-
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
-
{t('consecutiveDays')}
|
|
45
|
-
</span>
|
|
46
|
-
<span className="text-
|
|
47
|
-
{data ?? '—'}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="text-[11px] text-muted-foreground">
|
|
50
|
-
{t('consecutiveDaysSubtitle')}
|
|
51
|
-
</span>
|
|
52
|
-
</div>
|
|
53
|
-
</CardContent>
|
|
54
|
-
</Card>
|
|
37
|
+
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
+
<CardContent className="flex h-full items-center gap-3 p-3 md:gap-4 md:p-4">
|
|
39
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-emerald-50 dark:bg-emerald-950/40 md:h-11 md:w-11">
|
|
40
|
+
<CalendarDays className="h-4 w-4 text-emerald-600 dark:text-emerald-400 md:h-5 md:w-5" />
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex min-w-0 flex-col">
|
|
43
|
+
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
+
{t('consecutiveDays')}
|
|
45
|
+
</span>
|
|
46
|
+
<span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
|
|
47
|
+
{data ?? '—'}
|
|
48
|
+
</span>
|
|
49
|
+
<span className="hidden text-[11px] text-muted-foreground sm:block">
|
|
50
|
+
{t('consecutiveDaysSubtitle')}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
55
|
</WidgetWrapper>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -34,24 +34,24 @@ export default function StatOnlineTime({
|
|
|
34
34
|
widgetName={widget?.name ?? 'stat-online-time'}
|
|
35
35
|
onRemove={onRemove}
|
|
36
36
|
>
|
|
37
|
-
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
-
<CardContent className="flex h-full items-center gap-4 p-4">
|
|
39
|
-
<div className="flex h-
|
|
40
|
-
<Clock className="h-
|
|
41
|
-
</div>
|
|
42
|
-
<div className="flex min-w-0 flex-col">
|
|
43
|
-
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
-
{t('onlineTime')}
|
|
45
|
-
</span>
|
|
46
|
-
<span className="text-
|
|
47
|
-
{data ?? '—'}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="text-[11px] text-muted-foreground">
|
|
50
|
-
{t('onlineTimeSubtitle')}
|
|
51
|
-
</span>
|
|
52
|
-
</div>
|
|
53
|
-
</CardContent>
|
|
54
|
-
</Card>
|
|
37
|
+
<Card className="h-full overflow-hidden transition-all duration-300 hover:shadow-md">
|
|
38
|
+
<CardContent className="flex h-full items-center gap-3 p-3 md:gap-4 md:p-4">
|
|
39
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-blue-50 dark:bg-blue-950/40 md:h-11 md:w-11">
|
|
40
|
+
<Clock className="h-4 w-4 text-blue-600 dark:text-blue-400 md:h-5 md:w-5" />
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex min-w-0 flex-col">
|
|
43
|
+
<span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
44
|
+
{t('onlineTime')}
|
|
45
|
+
</span>
|
|
46
|
+
<span className="truncate text-xl font-bold tracking-tight text-foreground md:text-2xl">
|
|
47
|
+
{data ?? '—'}
|
|
48
|
+
</span>
|
|
49
|
+
<span className="hidden text-[11px] text-muted-foreground sm:block">
|
|
50
|
+
{t('onlineTimeSubtitle')}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
55
|
</WidgetWrapper>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -52,7 +52,7 @@ function RolesContent({ roles }: { roles: RoleData[] }) {
|
|
|
52
52
|
const t = useTranslations('core.DashboardPage.userRoles');
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
|
-
<Card className="flex h-full flex-col">
|
|
55
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
56
56
|
<CardHeader className="shrink-0 pb-3">
|
|
57
57
|
<div className="flex items-center gap-2">
|
|
58
58
|
<Crown className="h-5 w-5 text-amber-600 dark:text-amber-400" />
|
|
@@ -64,8 +64,8 @@ function RolesContent({ roles }: { roles: RoleData[] }) {
|
|
|
64
64
|
</div>
|
|
65
65
|
</div>
|
|
66
66
|
</CardHeader>
|
|
67
|
-
<CardContent className="flex-1 overflow-auto pt-0">
|
|
68
|
-
<div className="grid grid-cols-
|
|
67
|
+
<CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
|
|
68
|
+
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
|
|
69
69
|
{roles.map((role, index) => {
|
|
70
70
|
const style = levelStyles[index % levelStyles.length]!;
|
|
71
71
|
return (
|
|
@@ -86,30 +86,31 @@ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
|
|
|
86
86
|
const router = useRouter();
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
|
-
<Card className="flex h-full flex-col">
|
|
90
|
-
<CardHeader className="shrink-0 pb-3">
|
|
91
|
-
<div className="flex items-
|
|
92
|
-
<div className="flex items-center gap-2">
|
|
93
|
-
<Globe className="h-5 w-5 text-blue-600" />
|
|
94
|
-
<div>
|
|
95
|
-
<CardTitle className="text-base font-semibold">
|
|
96
|
-
{t('title')}
|
|
97
|
-
</CardTitle>
|
|
98
|
-
<CardDescription>{t('description')}</CardDescription>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
<Button
|
|
102
|
-
variant="outline"
|
|
103
|
-
size="sm"
|
|
104
|
-
onClick={() => router.push('/core/account/sessions')}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
89
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
90
|
+
<CardHeader className="shrink-0 pb-3">
|
|
91
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
92
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
93
|
+
<Globe className="h-5 w-5 text-blue-600" />
|
|
94
|
+
<div className="min-w-0">
|
|
95
|
+
<CardTitle className="text-base font-semibold">
|
|
96
|
+
{t('title')}
|
|
97
|
+
</CardTitle>
|
|
98
|
+
<CardDescription>{t('description')}</CardDescription>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<Button
|
|
102
|
+
variant="outline"
|
|
103
|
+
size="sm"
|
|
104
|
+
onClick={() => router.push('/core/account/sessions')}
|
|
105
|
+
className="w-full shrink-0 sm:w-auto"
|
|
106
|
+
>
|
|
107
|
+
<Info className="h-3.5 w-3.5" />
|
|
108
|
+
{t('moreInfo')}
|
|
109
|
+
</Button>
|
|
110
|
+
</div>
|
|
111
|
+
</CardHeader>
|
|
112
|
+
<CardContent className="flex min-h-0 flex-1 overflow-auto pt-0">
|
|
113
|
+
<div className="flex flex-col gap-2">
|
|
113
114
|
{sessions.map((session, index) => {
|
|
114
115
|
const ua = session.user_agent ?? '';
|
|
115
116
|
const deviceType = detectDeviceType(ua);
|
|
@@ -155,21 +156,21 @@ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
|
|
|
155
156
|
}`}
|
|
156
157
|
/>
|
|
157
158
|
</div>
|
|
158
|
-
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
159
|
-
<div className="flex items-center gap-2">
|
|
160
|
-
<span className="text-sm font-medium text-foreground">
|
|
161
|
-
{device}
|
|
162
|
-
</span>
|
|
159
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
160
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
161
|
+
<span className="min-w-0 break-words text-sm font-medium text-foreground">
|
|
162
|
+
{device}
|
|
163
|
+
</span>
|
|
163
164
|
{isCurrent && (
|
|
164
165
|
<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">
|
|
165
166
|
{t('thisSession')}
|
|
166
167
|
</Badge>
|
|
167
168
|
)}
|
|
168
169
|
</div>
|
|
169
|
-
<span className="text-xs text-muted-foreground">
|
|
170
|
-
{browser} · {ip}
|
|
171
|
-
</span>
|
|
172
|
-
<div className="flex items-center gap-3 text-[11px] text-muted-foreground/70">
|
|
170
|
+
<span className="break-words text-xs text-muted-foreground">
|
|
171
|
+
{browser} · {ip}
|
|
172
|
+
</span>
|
|
173
|
+
<div className="flex flex-wrap items-center gap-3 text-[11px] text-muted-foreground/70">
|
|
173
174
|
<span className="flex items-center gap-1">
|
|
174
175
|
<Clock className="h-3 w-3" />
|
|
175
176
|
{relativeTime}
|