@hed-hog/core 0.0.185 → 0.0.190
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/hedhog/frontend/app/account/2fa/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/accounts/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +356 -0
- package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +379 -0
- package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +184 -0
- package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +144 -0
- package/hedhog/frontend/app/account/components/email-request-dialog.tsx.ejs +96 -0
- package/hedhog/frontend/app/account/components/mfa-add-buttons.tsx.ejs +43 -0
- package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +115 -0
- package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +236 -0
- package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +209 -0
- package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +192 -0
- package/hedhog/frontend/app/account/components/regenerate-codes-dialog.tsx.ejs +372 -0
- package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +337 -0
- package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +393 -0
- package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +332 -0
- package/hedhog/frontend/app/account/email/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/hooks/use-mfa-methods.ts.ejs +27 -0
- package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +461 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +105 -0
- package/hedhog/frontend/app/account/lib/mfa-utils.tsx.ejs +37 -0
- package/hedhog/frontend/app/account/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/password/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/profile/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/account/sessions/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +490 -0
- package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +62 -0
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +316 -0
- package/hedhog/frontend/app/configurations/page.tsx.ejs +35 -0
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +351 -0
- package/hedhog/frontend/app/dashboard/[slug]/page.tsx.ejs +11 -0
- package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +62 -0
- package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +45 -0
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +196 -0
- package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +63 -0
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +516 -0
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +753 -0
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +516 -0
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +489 -0
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +621 -0
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -0
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +312 -0
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +1177 -0
- package/hedhog/frontend/app/preferences/page.tsx.ejs +448 -0
- package/hedhog/frontend/app/roles/menus.tsx.ejs +504 -0
- package/hedhog/frontend/app/roles/page.tsx.ejs +814 -0
- package/hedhog/frontend/app/roles/routes.tsx.ejs +397 -0
- package/hedhog/frontend/app/roles/users.tsx.ejs +306 -0
- package/hedhog/frontend/app/users/active-session.tsx.ejs +159 -0
- package/hedhog/frontend/app/users/identifiers.tsx.ejs +279 -0
- package/hedhog/frontend/app/users/page.tsx.ejs +1257 -0
- package/hedhog/frontend/app/users/permissions.tsx.ejs +155 -0
- package/hedhog/frontend/messages/en.json +1080 -0
- package/hedhog/frontend/messages/pt.json +1135 -0
- package/package.json +4 -4
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Accordion,
|
|
5
|
+
AccordionContent,
|
|
6
|
+
AccordionItem,
|
|
7
|
+
AccordionTrigger,
|
|
8
|
+
} from '@/components/ui/accordion';
|
|
9
|
+
import { Button } from '@/components/ui/button';
|
|
10
|
+
import { Label } from '@/components/ui/label';
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
} from '@/components/ui/select';
|
|
18
|
+
import { Switch } from '@/components/ui/switch';
|
|
19
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
20
|
+
import * as TablerIcons from '@tabler/icons-react';
|
|
21
|
+
import {
|
|
22
|
+
ChevronLeft,
|
|
23
|
+
ChevronRight,
|
|
24
|
+
ChevronsLeft,
|
|
25
|
+
ChevronsRight,
|
|
26
|
+
Loader2,
|
|
27
|
+
Menu as MenuIcon,
|
|
28
|
+
} from 'lucide-react';
|
|
29
|
+
import { useTranslations } from 'next-intl';
|
|
30
|
+
import { JSX, useEffect, useState } from 'react';
|
|
31
|
+
import { toast } from 'sonner';
|
|
32
|
+
|
|
33
|
+
type Menu = {
|
|
34
|
+
id: number;
|
|
35
|
+
slug: string;
|
|
36
|
+
url: string;
|
|
37
|
+
icon?: string;
|
|
38
|
+
menu_id?: number | null;
|
|
39
|
+
menu_locale?: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
}>;
|
|
42
|
+
role_menu?: Array<{
|
|
43
|
+
menu_id: number;
|
|
44
|
+
role_id: number;
|
|
45
|
+
}>;
|
|
46
|
+
children?: Menu[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type RoleMenusSectionProps = {
|
|
50
|
+
roleId: number;
|
|
51
|
+
onMenuChange?: () => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function RoleMenusSection({
|
|
55
|
+
roleId,
|
|
56
|
+
onMenuChange,
|
|
57
|
+
}: RoleMenusSectionProps) {
|
|
58
|
+
const t = useTranslations('core.RolePage');
|
|
59
|
+
const { request, currentLocaleCode } = useApp();
|
|
60
|
+
const [togglingMenuId, setTogglingMenuId] = useState<number | null>(null);
|
|
61
|
+
const [page, setPage] = useState(1);
|
|
62
|
+
const [pageSize, setPageSize] = useState(10);
|
|
63
|
+
const [expandedMenus, setExpandedMenus] = useState<string[]>([]);
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
data: assignedMenusData,
|
|
67
|
+
isLoading: isLoadingAssigned,
|
|
68
|
+
refetch: refetchAssignedMenus,
|
|
69
|
+
} = useQuery<{ data: Menu[] }>({
|
|
70
|
+
queryKey: ['role-menus-assigned', roleId, currentLocaleCode],
|
|
71
|
+
queryFn: async () => {
|
|
72
|
+
const response = await request<{ data: Menu[] }>({
|
|
73
|
+
url: `/role/${roleId}/menu?pageSize=10000`,
|
|
74
|
+
method: 'GET',
|
|
75
|
+
});
|
|
76
|
+
return response.data;
|
|
77
|
+
},
|
|
78
|
+
enabled: !!roleId,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const {
|
|
82
|
+
data: menusData,
|
|
83
|
+
isLoading: isLoadingMenus,
|
|
84
|
+
refetch: refetchMenus,
|
|
85
|
+
} = useQuery<{ data: Menu[]; total: number; lastPage: number }>({
|
|
86
|
+
queryKey: [
|
|
87
|
+
'role-menus-paginated',
|
|
88
|
+
roleId,
|
|
89
|
+
currentLocaleCode,
|
|
90
|
+
page,
|
|
91
|
+
pageSize,
|
|
92
|
+
],
|
|
93
|
+
queryFn: async () => {
|
|
94
|
+
const response = await request<{
|
|
95
|
+
data: Menu[];
|
|
96
|
+
total: number;
|
|
97
|
+
lastPage: number;
|
|
98
|
+
}>({
|
|
99
|
+
url: `/role/${roleId}/menu?page=${page}&pageSize=${pageSize}`,
|
|
100
|
+
method: 'GET',
|
|
101
|
+
});
|
|
102
|
+
return response.data;
|
|
103
|
+
},
|
|
104
|
+
enabled: !!roleId,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const menus = menusData?.data || [];
|
|
108
|
+
const totalPages = menusData?.lastPage || 1;
|
|
109
|
+
const totalMenus = menusData?.total || 0;
|
|
110
|
+
|
|
111
|
+
const handleToggleMenu = async (
|
|
112
|
+
menuId: number,
|
|
113
|
+
isAssigned: boolean,
|
|
114
|
+
includeChildren: boolean = true
|
|
115
|
+
) => {
|
|
116
|
+
setTogglingMenuId(menuId);
|
|
117
|
+
try {
|
|
118
|
+
const currentMenuIds =
|
|
119
|
+
assignedMenusData?.data
|
|
120
|
+
?.filter((m: Menu) => m.role_menu && m.role_menu.length > 0)
|
|
121
|
+
.map((m: Menu) => m.id) || [];
|
|
122
|
+
|
|
123
|
+
const menuIdsToToggle = [menuId];
|
|
124
|
+
const findMenuWithChildren = (
|
|
125
|
+
menusList: Menu[],
|
|
126
|
+
targetId: number
|
|
127
|
+
): Menu | undefined => {
|
|
128
|
+
for (const menu of menusList) {
|
|
129
|
+
if (menu.id === targetId) return menu;
|
|
130
|
+
if (menu.children && menu.children.length > 0) {
|
|
131
|
+
const found = findMenuWithChildren(menu.children, targetId);
|
|
132
|
+
if (found) return found;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const findParentMenu = (targetMenuId: number): Menu | undefined => {
|
|
139
|
+
const menu = menus.find((m) => m.id === targetMenuId);
|
|
140
|
+
if (menu?.menu_id) {
|
|
141
|
+
return menus.find((m) => m.id === menu.menu_id);
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (includeChildren) {
|
|
147
|
+
const menu = findMenuWithChildren(hierarchicalMenus, menuId);
|
|
148
|
+
if (menu?.children && menu.children.length > 0) {
|
|
149
|
+
const getAllChildrenIds = (children: Menu[]): number[] => {
|
|
150
|
+
return children.flatMap((child) => [
|
|
151
|
+
child.id,
|
|
152
|
+
...(child.children ? getAllChildrenIds(child.children) : []),
|
|
153
|
+
]);
|
|
154
|
+
};
|
|
155
|
+
menuIdsToToggle.push(...getAllChildrenIds(menu.children));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let newMenuIds: number[];
|
|
160
|
+
if (isAssigned) {
|
|
161
|
+
newMenuIds = currentMenuIds.filter(
|
|
162
|
+
(id: number) => !menuIdsToToggle.includes(id)
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
newMenuIds = [...new Set([...currentMenuIds, ...menuIdsToToggle])];
|
|
166
|
+
const getAllParentIds = (childMenuId: number): number[] => {
|
|
167
|
+
const parentIds: number[] = [];
|
|
168
|
+
const parent = findParentMenu(childMenuId);
|
|
169
|
+
if (parent) {
|
|
170
|
+
parentIds.push(parent.id);
|
|
171
|
+
parentIds.push(...getAllParentIds(parent.id));
|
|
172
|
+
}
|
|
173
|
+
return parentIds;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const parentIds = getAllParentIds(menuId);
|
|
177
|
+
if (parentIds.length > 0) {
|
|
178
|
+
newMenuIds = [...new Set([...newMenuIds, ...parentIds])];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await request({
|
|
183
|
+
url: `/role/${roleId}/menu`,
|
|
184
|
+
method: 'PATCH',
|
|
185
|
+
data: { ids: newMenuIds },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
toast.success(isAssigned ? t('menuRemoved') : t('menuAssigned'));
|
|
189
|
+
await refetchAssignedMenus();
|
|
190
|
+
await refetchMenus();
|
|
191
|
+
onMenuChange?.();
|
|
192
|
+
} catch (error) {
|
|
193
|
+
toast.error(
|
|
194
|
+
isAssigned ? t('errorRemovingMenu') : t('errorAssigningMenu')
|
|
195
|
+
);
|
|
196
|
+
} finally {
|
|
197
|
+
setTogglingMenuId(null);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const isMenuAssigned = (menu: Menu) => {
|
|
202
|
+
const assignedMenu = assignedMenusData?.data?.find(
|
|
203
|
+
(m: Menu) => m.id === menu.id
|
|
204
|
+
);
|
|
205
|
+
return !!(assignedMenu?.role_menu && assignedMenu.role_menu.length > 0);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const getMenuName = (menu: Menu) => {
|
|
209
|
+
return menu.menu_locale?.[0]?.name || menu.slug;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const renderIcon = (iconName?: string) => {
|
|
213
|
+
if (!iconName) {
|
|
214
|
+
return <MenuIcon className="h-5 w-5" />;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const toPascalCase = (str: string) =>
|
|
218
|
+
str.replace(/(^\w|-\w)/g, (match) =>
|
|
219
|
+
match.replace('-', '').toUpperCase()
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const pascalName = toPascalCase(String(iconName));
|
|
223
|
+
let IconComponent = (TablerIcons as any)[`Icon${pascalName}`];
|
|
224
|
+
if (!IconComponent) {
|
|
225
|
+
IconComponent = (TablerIcons as any)[`Icon${pascalName}Filled`];
|
|
226
|
+
}
|
|
227
|
+
if (!IconComponent) {
|
|
228
|
+
IconComponent = (TablerIcons as any)[`Icon${pascalName}Circle`];
|
|
229
|
+
}
|
|
230
|
+
if (IconComponent) {
|
|
231
|
+
return <IconComponent className="h-5 w-5" />;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return <MenuIcon className="h-5 w-5" />;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const organizeMenuHierarchy = (menusList: Menu[]): Menu[] => {
|
|
238
|
+
const menuMap = new Map<number, Menu>();
|
|
239
|
+
const rootMenus: Menu[] = [];
|
|
240
|
+
|
|
241
|
+
menusList.forEach((menu) => {
|
|
242
|
+
menuMap.set(menu.id, { ...menu, children: [] });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
menuMap.forEach((menu) => {
|
|
246
|
+
if (menu.menu_id && menuMap.has(menu.menu_id)) {
|
|
247
|
+
const parent = menuMap.get(menu.menu_id)!;
|
|
248
|
+
if (!parent.children) parent.children = [];
|
|
249
|
+
parent.children.push(menu);
|
|
250
|
+
} else {
|
|
251
|
+
rootMenus.push(menu);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return rootMenus;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const hierarchicalMenus = organizeMenuHierarchy(menus);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (expandedMenus.length === 0 && hierarchicalMenus.length > 0) {
|
|
261
|
+
const allParentIds = hierarchicalMenus
|
|
262
|
+
.filter((m) => m.children && m.children.length > 0)
|
|
263
|
+
.map((m) => `menu-${m.id}`);
|
|
264
|
+
setExpandedMenus(allParentIds);
|
|
265
|
+
}
|
|
266
|
+
}, [hierarchicalMenus.length]);
|
|
267
|
+
|
|
268
|
+
const renderChildMenu = (menu: Menu): JSX.Element => {
|
|
269
|
+
const isAssigned = isMenuAssigned(menu);
|
|
270
|
+
const isToggling = togglingMenuId === menu.id;
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div
|
|
274
|
+
key={menu.id}
|
|
275
|
+
className={`flex items-center justify-between p-3 rounded-md border transition-all ${
|
|
276
|
+
isAssigned
|
|
277
|
+
? 'border-primary/50 bg-primary/5'
|
|
278
|
+
: 'border-border hover:border-primary/30'
|
|
279
|
+
}`}
|
|
280
|
+
>
|
|
281
|
+
<div className="flex items-center gap-3 flex-1">
|
|
282
|
+
<div
|
|
283
|
+
className={`rounded-md p-2 ${
|
|
284
|
+
isAssigned ? 'bg-primary/10' : 'bg-muted'
|
|
285
|
+
}`}
|
|
286
|
+
>
|
|
287
|
+
<div
|
|
288
|
+
className={`${
|
|
289
|
+
isAssigned ? 'text-primary' : 'text-muted-foreground'
|
|
290
|
+
}`}
|
|
291
|
+
>
|
|
292
|
+
{renderIcon(menu.icon)}
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
<div className="flex-1">
|
|
296
|
+
<Label
|
|
297
|
+
htmlFor={`menu-${menu.id}`}
|
|
298
|
+
className="text-sm font-medium cursor-pointer"
|
|
299
|
+
>
|
|
300
|
+
{getMenuName(menu)}
|
|
301
|
+
</Label>
|
|
302
|
+
<p className="text-xs text-muted-foreground mt-0.5">{menu.url}</p>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
<Switch
|
|
306
|
+
id={`menu-${menu.id}`}
|
|
307
|
+
checked={isAssigned}
|
|
308
|
+
disabled={isToggling}
|
|
309
|
+
onCheckedChange={() => handleToggleMenu(menu.id, isAssigned, false)}
|
|
310
|
+
className="ml-4"
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
if (isLoadingMenus || isLoadingAssigned) {
|
|
317
|
+
return (
|
|
318
|
+
<div className="flex items-center justify-center py-8">
|
|
319
|
+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
320
|
+
<span className="ml-2 text-sm text-muted-foreground">
|
|
321
|
+
{t('loadingMenus')}
|
|
322
|
+
</span>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!menus || menus.length === 0) {
|
|
328
|
+
return (
|
|
329
|
+
<div className="border border-dashed rounded-lg p-8 flex flex-col items-center justify-center">
|
|
330
|
+
<MenuIcon className="h-12 w-12 text-muted-foreground mb-3" />
|
|
331
|
+
<p className="text-sm font-medium text-center">
|
|
332
|
+
{t('noMenusAvailable')}
|
|
333
|
+
</p>
|
|
334
|
+
</div>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div className="space-y-3">
|
|
340
|
+
<div>
|
|
341
|
+
<h4 className="text-sm font-semibold flex items-center gap-2">
|
|
342
|
+
<MenuIcon className="h-4 w-4" />
|
|
343
|
+
{t('menusTitle')}
|
|
344
|
+
</h4>
|
|
345
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
346
|
+
{t('menusDescription')}
|
|
347
|
+
</p>
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
<Accordion
|
|
351
|
+
type="multiple"
|
|
352
|
+
value={expandedMenus}
|
|
353
|
+
onValueChange={setExpandedMenus}
|
|
354
|
+
className="space-y-2"
|
|
355
|
+
>
|
|
356
|
+
{hierarchicalMenus.map((menu) => {
|
|
357
|
+
const isAssigned = isMenuAssigned(menu);
|
|
358
|
+
const isToggling = togglingMenuId === menu.id;
|
|
359
|
+
const hasChildren = menu.children && menu.children.length > 0;
|
|
360
|
+
|
|
361
|
+
if (!hasChildren) {
|
|
362
|
+
return renderChildMenu(menu);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<AccordionItem
|
|
367
|
+
key={menu.id}
|
|
368
|
+
value={`menu-${menu.id}`}
|
|
369
|
+
className={`border rounded-lg transition-all ${
|
|
370
|
+
isAssigned ? 'border-primary/50 bg-primary/5' : ''
|
|
371
|
+
}`}
|
|
372
|
+
>
|
|
373
|
+
<div className="flex items-center justify-between p-4">
|
|
374
|
+
<div className="flex items-center gap-3 flex-1">
|
|
375
|
+
<AccordionTrigger className="hover:no-underline p-0" />
|
|
376
|
+
<div
|
|
377
|
+
className={`rounded-md p-2 ${
|
|
378
|
+
isAssigned ? 'bg-primary/10' : 'bg-muted'
|
|
379
|
+
}`}
|
|
380
|
+
>
|
|
381
|
+
<div
|
|
382
|
+
className={`${
|
|
383
|
+
isAssigned ? 'text-primary' : 'text-muted-foreground'
|
|
384
|
+
}`}
|
|
385
|
+
>
|
|
386
|
+
{renderIcon(menu.icon)}
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
<div className="flex-1">
|
|
390
|
+
<Label
|
|
391
|
+
htmlFor={`menu-${menu.id}`}
|
|
392
|
+
className="text-sm font-medium cursor-pointer"
|
|
393
|
+
>
|
|
394
|
+
{getMenuName(menu)}
|
|
395
|
+
</Label>
|
|
396
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
397
|
+
{menu.url}
|
|
398
|
+
</p>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
<Switch
|
|
402
|
+
id={`menu-${menu.id}`}
|
|
403
|
+
checked={isAssigned}
|
|
404
|
+
disabled={isToggling}
|
|
405
|
+
onCheckedChange={() =>
|
|
406
|
+
handleToggleMenu(menu.id, isAssigned, true)
|
|
407
|
+
}
|
|
408
|
+
className="ml-4"
|
|
409
|
+
/>
|
|
410
|
+
</div>
|
|
411
|
+
<AccordionContent className="px-4 pb-4 pt-2 space-y-2 ml-8">
|
|
412
|
+
{menu.children?.map((child) => renderChildMenu(child))}
|
|
413
|
+
</AccordionContent>
|
|
414
|
+
</AccordionItem>
|
|
415
|
+
);
|
|
416
|
+
})}
|
|
417
|
+
</Accordion>
|
|
418
|
+
|
|
419
|
+
{/* Paginação */}
|
|
420
|
+
<div className="flex items-center justify-between px-2 pt-4">
|
|
421
|
+
<div className="hidden flex-1 text-sm text-muted-foreground lg:flex">
|
|
422
|
+
{t('showing')} {menus.length} {t('of')} {totalMenus} {t('menus')}
|
|
423
|
+
</div>
|
|
424
|
+
<div className="flex w-full items-center gap-8 lg:w-fit">
|
|
425
|
+
<div className="hidden items-center gap-2 lg:flex">
|
|
426
|
+
<Label
|
|
427
|
+
htmlFor="rows-per-page-menus"
|
|
428
|
+
className="text-sm font-medium"
|
|
429
|
+
>
|
|
430
|
+
{t('rowsPerPage')}
|
|
431
|
+
</Label>
|
|
432
|
+
<Select
|
|
433
|
+
value={`${pageSize}`}
|
|
434
|
+
onValueChange={(value) => {
|
|
435
|
+
setPageSize(Number(value));
|
|
436
|
+
setPage(1);
|
|
437
|
+
}}
|
|
438
|
+
>
|
|
439
|
+
<SelectTrigger
|
|
440
|
+
size="sm"
|
|
441
|
+
className="w-20"
|
|
442
|
+
id="rows-per-page-menus"
|
|
443
|
+
>
|
|
444
|
+
<SelectValue placeholder={pageSize} />
|
|
445
|
+
</SelectTrigger>
|
|
446
|
+
<SelectContent side="top">
|
|
447
|
+
{[10, 20, 30, 40, 50].map((size) => (
|
|
448
|
+
<SelectItem key={size} value={`${size}`}>
|
|
449
|
+
{size}
|
|
450
|
+
</SelectItem>
|
|
451
|
+
))}
|
|
452
|
+
</SelectContent>
|
|
453
|
+
</Select>
|
|
454
|
+
</div>
|
|
455
|
+
<div className="flex w-fit items-center justify-center text-sm font-medium">
|
|
456
|
+
{t('page')} {page} {t('of')} {totalPages}
|
|
457
|
+
</div>
|
|
458
|
+
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
|
459
|
+
<Button
|
|
460
|
+
variant="outline"
|
|
461
|
+
className="hidden size-8 lg:flex"
|
|
462
|
+
size="icon"
|
|
463
|
+
onClick={() => setPage(1)}
|
|
464
|
+
disabled={page === 1}
|
|
465
|
+
>
|
|
466
|
+
<span className="sr-only">{t('goToFirstPage')}</span>
|
|
467
|
+
<ChevronsLeft className="size-4" />
|
|
468
|
+
</Button>
|
|
469
|
+
<Button
|
|
470
|
+
variant="outline"
|
|
471
|
+
className="size-8"
|
|
472
|
+
size="icon"
|
|
473
|
+
onClick={() => setPage(page - 1)}
|
|
474
|
+
disabled={page === 1}
|
|
475
|
+
>
|
|
476
|
+
<span className="sr-only">{t('goToPreviousPage')}</span>
|
|
477
|
+
<ChevronLeft className="size-4" />
|
|
478
|
+
</Button>
|
|
479
|
+
<Button
|
|
480
|
+
variant="outline"
|
|
481
|
+
className="size-8"
|
|
482
|
+
size="icon"
|
|
483
|
+
onClick={() => setPage(page + 1)}
|
|
484
|
+
disabled={page >= totalPages}
|
|
485
|
+
>
|
|
486
|
+
<span className="sr-only">{t('goToNextPage')}</span>
|
|
487
|
+
<ChevronRight className="size-4" />
|
|
488
|
+
</Button>
|
|
489
|
+
<Button
|
|
490
|
+
variant="outline"
|
|
491
|
+
className="hidden size-8 lg:flex"
|
|
492
|
+
size="icon"
|
|
493
|
+
onClick={() => setPage(totalPages)}
|
|
494
|
+
disabled={page >= totalPages}
|
|
495
|
+
>
|
|
496
|
+
<span className="sr-only">{t('goToLastPage')}</span>
|
|
497
|
+
<ChevronsRight className="size-4" />
|
|
498
|
+
</Button>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
);
|
|
504
|
+
}
|