@hed-hog/core 0.0.361 → 0.0.362

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/access-log/access-log.controller.d.ts +1 -1
  2. package/dist/access-log/access-log.service.d.ts +1 -1
  3. package/dist/access-log/access-log.service.d.ts.map +1 -1
  4. package/dist/access-log/access-log.service.js +43 -1
  5. package/dist/access-log/access-log.service.js.map +1 -1
  6. package/dist/auth/auth.controller.d.ts +1 -0
  7. package/dist/auth/auth.controller.d.ts.map +1 -1
  8. package/dist/auth/auth.controller.js +6 -2
  9. package/dist/auth/auth.controller.js.map +1 -1
  10. package/dist/auth/auth.service.d.ts +1 -0
  11. package/dist/auth/auth.service.d.ts.map +1 -1
  12. package/dist/auth/auth.service.js +15 -5
  13. package/dist/auth/auth.service.js.map +1 -1
  14. package/dist/auth/guards/auth.guard.d.ts +1 -0
  15. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  16. package/dist/auth/guards/auth.guard.js +11 -4
  17. package/dist/auth/guards/auth.guard.js.map +1 -1
  18. package/dist/file/file.module.d.ts.map +1 -1
  19. package/dist/file/file.module.js +3 -2
  20. package/dist/file/file.module.js.map +1 -1
  21. package/dist/file/file.service.d.ts +71 -1
  22. package/dist/file/file.service.d.ts.map +1 -1
  23. package/dist/file/file.service.js +180 -32
  24. package/dist/file/file.service.js.map +1 -1
  25. package/dist/file/image-optimization.service.d.ts +20 -0
  26. package/dist/file/image-optimization.service.d.ts.map +1 -0
  27. package/dist/file/image-optimization.service.js +126 -0
  28. package/dist/file/image-optimization.service.js.map +1 -0
  29. package/dist/file/provider/local.provider.d.ts +1 -1
  30. package/dist/file/provider/local.provider.d.ts.map +1 -1
  31. package/dist/file/provider/local.provider.js +2 -9
  32. package/dist/file/provider/local.provider.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/mcp/tools/core-users.mcp-tools.d.ts +1 -1
  38. package/dist/mcp/tools/core-users.mcp-tools.d.ts.map +1 -1
  39. package/dist/mcp/tools/core-users.mcp-tools.js +2 -2
  40. package/dist/mcp/tools/core-users.mcp-tools.js.map +1 -1
  41. package/dist/profile/profile.service.d.ts.map +1 -1
  42. package/dist/profile/profile.service.js +44 -23
  43. package/dist/profile/profile.service.js.map +1 -1
  44. package/dist/session/session.service.d.ts.map +1 -1
  45. package/dist/session/session.service.js +7 -2
  46. package/dist/session/session.service.js.map +1 -1
  47. package/dist/token/token.service.d.ts +1 -0
  48. package/dist/token/token.service.d.ts.map +1 -1
  49. package/dist/token/token.service.js +23 -6
  50. package/dist/token/token.service.js.map +1 -1
  51. package/dist/user/user.controller.d.ts +1 -1
  52. package/dist/user/user.controller.d.ts.map +1 -1
  53. package/dist/user/user.controller.js +4 -3
  54. package/dist/user/user.controller.js.map +1 -1
  55. package/dist/user/user.service.d.ts +1 -1
  56. package/dist/user/user.service.d.ts.map +1 -1
  57. package/dist/user/user.service.js +6 -6
  58. package/dist/user/user.service.js.map +1 -1
  59. package/hedhog/data/route.yaml +2 -0
  60. package/hedhog/data/setting_group.yaml +117 -0
  61. package/hedhog/data/setting_subgroup.yaml +8 -0
  62. package/hedhog/frontend/app/access-log/page.tsx.ejs +170 -11
  63. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +1 -1
  64. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +1 -1
  65. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +112 -5
  66. package/hedhog/frontend/app/users/page.tsx.ejs +7 -7
  67. package/hedhog/frontend/app/users/permissions.tsx.ejs +137 -83
  68. package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +28 -26
  69. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +207 -75
  70. package/hedhog/frontend/messages/en.json +9 -2
  71. package/hedhog/frontend/messages/pt.json +9 -2
  72. package/package.json +11 -6
  73. package/src/access-log/access-log.service.ts +19 -1
  74. package/src/auth/auth.controller.ts +5 -0
  75. package/src/auth/auth.service.ts +14 -4
  76. package/src/auth/guards/auth.guard.ts +10 -3
  77. package/src/file/file.module.ts +3 -2
  78. package/src/file/file.service.ts +361 -47
  79. package/src/file/image-optimization.service.ts +123 -0
  80. package/src/file/provider/local.provider.ts +2 -9
  81. package/src/index.ts +1 -0
  82. package/src/mcp/tools/core-users.mcp-tools.ts +267 -267
  83. package/src/profile/profile.service.ts +40 -22
  84. package/src/session/session.service.ts +10 -2
  85. package/src/token/token.service.ts +26 -6
  86. package/src/user/user.controller.ts +5 -2
  87. package/src/user/user.service.ts +11 -8
  88. package/dist/menu/menu.service.spec.d.ts +0 -1
  89. package/dist/menu/menu.service.spec.d.ts.map +0 -1
  90. package/dist/menu/menu.service.spec.js +0 -208
  91. package/dist/menu/menu.service.spec.js.map +0 -1
  92. package/dist/route/route.service.spec.d.ts +0 -1
  93. package/dist/route/route.service.spec.d.ts.map +0 -1
  94. package/dist/route/route.service.spec.js +0 -262
  95. package/dist/route/route.service.spec.js.map +0 -1
  96. package/dist/screen/screen.service.spec.d.ts +0 -1
  97. package/dist/screen/screen.service.spec.d.ts.map +0 -1
  98. package/dist/screen/screen.service.spec.js +0 -259
  99. package/dist/screen/screen.service.spec.js.map +0 -1
  100. package/dist/user/user.service.spec.d.ts +0 -1
  101. package/dist/user/user.service.spec.d.ts.map +0 -1
  102. package/dist/user/user.service.spec.js +0 -261
  103. package/dist/user/user.service.spec.js.map +0 -1
@@ -87,6 +87,7 @@ function getLocalizedIntegrationName(
87
87
  );
88
88
  }
89
89
 
90
+ const MAIL_INTEGRATION_PROFILE_SETTING_SLUG = 'mail-integration-profile-id';
90
91
  const DAILY_REPORT_EMAIL_INTEGRATION_SETTING_SLUG =
91
92
  'operations.daily-report.email-integration-profile-id';
92
93
  const DAILY_REPORT_WHATSAPP_INTEGRATION_SETTING_SLUG =
@@ -635,7 +636,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
635
636
  setting.slug
636
637
  );
637
638
 
638
- if (setting.slug === 'file-storage-profile-id') {
639
+ if (
640
+ setting.slug === 'file-storage-profile-id' ||
641
+ setting.slug === 'lms-bulk-upload-storage-profile-id'
642
+ ) {
639
643
  const loadStorageTypeId = async () => {
640
644
  const typesResponse = await request<IntegrationTypeOption[]>({
641
645
  url: '/integration-profile/types',
@@ -972,10 +976,113 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
972
976
  );
973
977
  }
974
978
 
975
- if (
976
- setting.slug !== 'mail-integration-profile-id' &&
977
- !dailyReportIntegrationTypeSlug
978
- ) {
979
+ if (setting.slug === MAIL_INTEGRATION_PROFILE_SETTING_SLUG) {
980
+ return (
981
+ <>
982
+ <div className="flex w-full min-w-0 items-center gap-2 md:max-w-72">
983
+ <EntityPicker<IntegrationProfileOption & Record<string, unknown>>
984
+ placeholder={t('selectMailProfile')}
985
+ emptyStateDescription={t('mailProfileEmptyState')}
986
+ value={parsedValue ? Number(parsedValue) : null}
987
+ onChange={(id, option) => {
988
+ setStorageProfileSelectedLabel(option?.name ?? '');
989
+ handleChangeValue(id != null ? String(id) : '', true);
990
+ }}
991
+ valueType="number"
992
+ initialSelectedLabel={storageProfileSelectedLabel}
993
+ showCreateButton={false}
994
+ clearable={false}
995
+ loadOptions={async ({ page, pageSize, search }) => {
996
+ const qs = new URLSearchParams({
997
+ page: String(page),
998
+ pageSize: String(pageSize),
999
+ typeSlug: 'email',
1000
+ });
1001
+ if (search) qs.set('search', search);
1002
+ const response = await request<
1003
+ ApiPaginated<
1004
+ IntegrationProfileOption & Record<string, unknown>
1005
+ >
1006
+ >({
1007
+ url: `/integration-profile?${qs}`,
1008
+ method: 'GET',
1009
+ });
1010
+ const paged = response.data;
1011
+ return {
1012
+ items: paged?.data ?? [],
1013
+ hasMore: (paged?.page ?? 0) < (paged?.lastPage ?? 0),
1014
+ };
1015
+ }}
1016
+ getOptionValue={(opt) => opt.id}
1017
+ getOptionLabel={(opt) => String(opt.name ?? '')}
1018
+ getOptionDescription={(opt) => {
1019
+ const providerSlug = String(
1020
+ opt.integration_provider?.slug ?? ''
1021
+ );
1022
+ const providerName = getLocalizedIntegrationName(
1023
+ opt.integration_provider?.integration_provider_locale,
1024
+ currentLocaleCode
1025
+ );
1026
+ return [providerName || providerSlug, opt.slug]
1027
+ .filter(Boolean)
1028
+ .join(' • ');
1029
+ }}
1030
+ className={cn('flex-1 min-w-0', className)}
1031
+ />
1032
+
1033
+ <Button
1034
+ type="button"
1035
+ variant="outline"
1036
+ size="icon"
1037
+ className="h-10 w-10 shrink-0"
1038
+ onClick={() => {
1039
+ setStorageProfileEditingId(
1040
+ parsedValue ? Number(parsedValue) : null
1041
+ );
1042
+ setIsStorageProfileSheetOpen(true);
1043
+ }}
1044
+ disabled={!parsedValue}
1045
+ aria-label="Editar perfil de email"
1046
+ title="Editar perfil de email"
1047
+ >
1048
+ <Pencil className="h-4 w-4" />
1049
+ </Button>
1050
+
1051
+ <Button
1052
+ type="button"
1053
+ variant="outline"
1054
+ size="icon"
1055
+ className="h-10 w-10 shrink-0"
1056
+ onClick={() => {
1057
+ setStorageProfileEditingId(null);
1058
+ setIsStorageProfileSheetOpen(true);
1059
+ }}
1060
+ aria-label="Criar perfil de email"
1061
+ title="Criar perfil de email"
1062
+ >
1063
+ <Plus className="h-4 w-4" />
1064
+ </Button>
1065
+ </div>
1066
+
1067
+ <IntegrationProfileSheet
1068
+ open={isStorageProfileSheetOpen}
1069
+ onOpenChange={(nextOpen) => {
1070
+ setIsStorageProfileSheetOpen(nextOpen);
1071
+ if (!nextOpen) setStorageProfileEditingId(null);
1072
+ }}
1073
+ profileId={storageProfileEditingId}
1074
+ initialTypeSlug="email"
1075
+ lockedTypeSlug="email"
1076
+ onSaved={(profile: IntegrationProfileSheetSavedProfile) => {
1077
+ setStorageProfileSelectedLabel(profile.name ?? '');
1078
+ handleChangeValue(String(profile.id), true);
1079
+ }}
1080
+ />
1081
+ </>
1082
+ );
1083
+ }
1084
+
1085
+ if (!dailyReportIntegrationTypeSlug) {
979
1086
  return (
980
1087
  <Input
981
1088
  type="text"
@@ -17,6 +17,12 @@ import {
17
17
  CardHeader,
18
18
  CardTitle,
19
19
  } from '@/components/ui/card';
20
+ import {
21
+ DropdownMenu,
22
+ DropdownMenuContent,
23
+ DropdownMenuItem,
24
+ DropdownMenuTrigger,
25
+ } from '@/components/ui/dropdown-menu';
20
26
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
21
27
  import { Skeleton } from '@/components/ui/skeleton';
22
28
  import {
@@ -53,12 +59,6 @@ import {
53
59
  import { useTranslations } from 'next-intl';
54
60
  import { useEffect, useMemo, useState } from 'react';
55
61
  import { UserSheet } from './user-sheet';
56
- import {
57
- DropdownMenu,
58
- DropdownMenuContent,
59
- DropdownMenuItem,
60
- DropdownMenuTrigger,
61
- } from '@/components/ui/dropdown-menu';
62
62
 
63
63
  const USER_VIEW_STORAGE_KEY = 'core-user-view-mode';
64
64
  type UserViewMode = 'table' | 'cards';
@@ -366,7 +366,7 @@ export default function UserPage() {
366
366
  viewMode === 'cards' ? (
367
367
  <div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
368
368
  {Array.from({ length: 6 }).map((_, i) => (
369
- <Card key={i} className="overflow-hidden py-0">
369
+ <Card key={i} className="overflow-hidden">
370
370
  <CardContent className="space-y-3 p-4">
371
371
  <div className="flex items-center gap-3">
372
372
  <Skeleton className="h-12 w-12 rounded-full" />
@@ -1,11 +1,23 @@
1
1
  'use client';
2
2
 
3
3
  import { EmptyState } from '@/components/entity-list';
4
+ import { Badge } from '@/components/ui/badge';
4
5
  import { Button } from '@/components/ui/button';
5
- import { Card, CardContent } from '@/components/ui/card';
6
6
  import { Input } from '@/components/ui/input';
7
- import { Label } from '@/components/ui/label';
8
7
  import { Switch } from '@/components/ui/switch';
8
+ import {
9
+ Table,
10
+ TableBody,
11
+ TableCell,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ } from '@/components/ui/table';
16
+ import {
17
+ Tooltip,
18
+ TooltipContent,
19
+ TooltipTrigger,
20
+ } from '@/components/ui/tooltip';
9
21
  import { Role } from '@hed-hog/api-types';
10
22
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
11
23
  import { CheckCheck, Eye, Loader2, Search, ShieldCheck, X } from 'lucide-react';
@@ -167,36 +179,50 @@ export function PermissionsSection({
167
179
  />
168
180
  </div>
169
181
  {hasUnassigned && (
170
- <Button
171
- variant="outline"
172
- size="sm"
173
- className="shrink-0 gap-1.5"
174
- onClick={handleAssignAll}
175
- disabled={isDisabled}
176
- >
177
- {isBulkLoading ? (
178
- <Loader2 className="h-4 w-4 animate-spin" />
179
- ) : (
180
- <CheckCheck className="h-4 w-4" />
181
- )}
182
- {t('addAllRoles')}
183
- </Button>
182
+ <Tooltip>
183
+ <TooltipTrigger asChild>
184
+ <Button
185
+ variant="outline"
186
+ size="icon"
187
+ className="h-8 w-8 shrink-0"
188
+ onClick={handleAssignAll}
189
+ disabled={isDisabled}
190
+ aria-label={t('addAllRoles')}
191
+ >
192
+ {isBulkLoading ? (
193
+ <Loader2 className="h-4 w-4 animate-spin" />
194
+ ) : (
195
+ <CheckCheck className="h-4 w-4" />
196
+ )}
197
+ </Button>
198
+ </TooltipTrigger>
199
+ <TooltipContent>
200
+ <p>{t('addAllRoles')}</p>
201
+ </TooltipContent>
202
+ </Tooltip>
184
203
  )}
185
204
  {hasAssigned && (
186
- <Button
187
- variant="outline"
188
- size="sm"
189
- className="shrink-0 gap-1.5"
190
- onClick={handleRemoveAll}
191
- disabled={isDisabled}
192
- >
193
- {isBulkLoading ? (
194
- <Loader2 className="h-4 w-4 animate-spin" />
195
- ) : (
196
- <X className="h-4 w-4" />
197
- )}
198
- {t('removeAllRoles')}
199
- </Button>
205
+ <Tooltip>
206
+ <TooltipTrigger asChild>
207
+ <Button
208
+ variant="outline"
209
+ size="icon"
210
+ className="h-8 w-8 shrink-0"
211
+ onClick={handleRemoveAll}
212
+ disabled={isDisabled}
213
+ aria-label={t('removeAllRoles')}
214
+ >
215
+ {isBulkLoading ? (
216
+ <Loader2 className="h-4 w-4 animate-spin" />
217
+ ) : (
218
+ <X className="h-4 w-4" />
219
+ )}
220
+ </Button>
221
+ </TooltipTrigger>
222
+ <TooltipContent>
223
+ <p>{t('removeAllRoles')}</p>
224
+ </TooltipContent>
225
+ </Tooltip>
200
226
  )}
201
227
  <Button
202
228
  variant="outline"
@@ -215,61 +241,89 @@ export function PermissionsSection({
215
241
  <p className="text-sm text-muted-foreground">{t('noRolesMatch')}</p>
216
242
  </div>
217
243
  ) : (
218
- <div className="space-y-2">
219
- {filteredRoles.map((role, index) => {
220
- const isToggling = togglingRoleId === role.id;
244
+ <div className="overflow-hidden rounded-lg border border-border/70 bg-background">
245
+ <Table className="table-fixed">
246
+ <TableHeader className="bg-muted/20">
247
+ <TableRow className="hover:bg-transparent">
248
+ <TableHead className="h-8 px-2 text-[11px] font-medium text-muted-foreground">
249
+ Cargo
250
+ </TableHead>
251
+ <TableHead className="h-8 w-37.5 px-2 text-[11px] font-medium text-muted-foreground">
252
+ Slug
253
+ </TableHead>
254
+ <TableHead className="h-8 w-19.5 px-2 text-right text-[11px] font-medium text-muted-foreground">
255
+ Acesso
256
+ </TableHead>
257
+ </TableRow>
258
+ </TableHeader>
259
+ <TableBody>
260
+ {filteredRoles.map((role, index) => {
261
+ const isToggling = togglingRoleId === role.id;
221
262
 
222
- return (
223
- <Card
224
- key={role.id ?? `role-${index}`}
225
- className={`transition-all py-4 ${
226
- role.isAssigned
227
- ? 'border-primary bg-primary/5'
228
- : 'border-border hover:border-primary/50'
229
- }`}
230
- >
231
- <CardContent className="flex items-center justify-between px-4">
232
- <div className="flex items-center gap-2 flex-1">
233
- <div
234
- className={`rounded-md p-2 ${
235
- role.isAssigned ? 'bg-primary/10' : 'bg-muted'
236
- }`}
237
- >
238
- <ShieldCheck
239
- className={`h-5 w-5 ${
240
- role.isAssigned
241
- ? 'text-primary'
242
- : 'text-muted-foreground'
243
- }`}
244
- />
245
- </div>
246
- <div className="flex-1">
247
- <Label
248
- htmlFor={`role-${role.id}`}
249
- className="text-sm font-medium cursor-pointer"
250
- >
251
- {role.name} ({role.slug})
252
- </Label>
253
- {role.description && (
254
- <p className="text-xs text-muted-foreground">
255
- {role.description}
256
- </p>
257
- )}
258
- </div>
259
- </div>
260
- <Switch
261
- id={`role-${role.id}`}
262
- checked={role.isAssigned}
263
- disabled={isToggling || isDisabled}
264
- onCheckedChange={() =>
265
- handleToggleRole(role.id!, role.isAssigned)
263
+ return (
264
+ <TableRow
265
+ key={role.id ?? `role-${index}`}
266
+ className={
267
+ role.isAssigned
268
+ ? 'bg-primary/5 hover:bg-primary/5'
269
+ : 'hover:bg-muted/20'
266
270
  }
267
- className="ml-4"
268
- />
269
- </CardContent>
270
- </Card>
271
- );
272
- })}
271
+ >
272
+ <TableCell className="px-2 py-1.5 align-middle">
273
+ <div className="flex min-w-0 items-start gap-1.5">
274
+ <div
275
+ className={`mt-0.5 rounded-md p-0.5 ${
276
+ role.isAssigned ? 'bg-primary/10' : 'bg-muted/70'
277
+ }`}
278
+ >
279
+ <ShieldCheck
280
+ className={`h-3.5 w-3.5 ${
281
+ role.isAssigned
282
+ ? 'text-primary'
283
+ : 'text-muted-foreground'
284
+ }`}
285
+ />
286
+ </div>
287
+ <div className="min-w-0 flex-1">
288
+ <div className="truncate text-[13px] font-semibold leading-4 text-foreground">
289
+ {role.name}
290
+ </div>
291
+ {role.description && (
292
+ <p className="truncate text-[11px] leading-3 text-muted-foreground">
293
+ {role.description}
294
+ </p>
295
+ )}
296
+ </div>
297
+ </div>
298
+ </TableCell>
299
+ <TableCell className="px-2 py-1.5 align-middle">
300
+ {role.slug ? (
301
+ <Badge
302
+ variant="outline"
303
+ className="h-4 rounded-sm border-border/70 bg-muted/40 px-1 font-mono text-[9px] font-medium lowercase leading-none tracking-[0.02em] text-muted-foreground"
304
+ >
305
+ {role.slug}
306
+ </Badge>
307
+ ) : null}
308
+ </TableCell>
309
+ <TableCell className="px-2 py-1.5 align-middle">
310
+ <div className="flex justify-end">
311
+ <Switch
312
+ id={`role-${role.id}`}
313
+ checked={role.isAssigned}
314
+ disabled={isToggling || isDisabled}
315
+ onCheckedChange={() =>
316
+ handleToggleRole(role.id!, role.isAssigned)
317
+ }
318
+ className="scale-90 origin-center"
319
+ />
320
+ </div>
321
+ </TableCell>
322
+ </TableRow>
323
+ );
324
+ })}
325
+ </TableBody>
326
+ </Table>
273
327
  </div>
274
328
  )}
275
329
 
@@ -203,32 +203,34 @@ export function UserAccessSheet({
203
203
  <div className="w-full border-t" />
204
204
 
205
205
  <Tabs defaultValue="menu" className="px-6 pt-4">
206
- <TabsList className="w-full mb-4">
207
- <TabsTrigger
208
- value="menu"
209
- className="flex-1 flex items-center gap-2"
210
- >
211
- <Menu className="h-4 w-4" />
212
- {t('accessTabMenu')}
213
- {!loadingMenus && (
214
- <Badge variant="secondary" className="ml-1 text-xs">
215
- {menus.length}
216
- </Badge>
217
- )}
218
- </TabsTrigger>
219
- <TabsTrigger
220
- value="routes"
221
- className="flex-1 flex items-center gap-2"
222
- >
223
- <Globe className="h-4 w-4" />
224
- {t('accessTabRoutes')}
225
- {!loadingRoutes && (
226
- <Badge variant="secondary" className="ml-1 text-xs">
227
- {routes.length}
228
- </Badge>
229
- )}
230
- </TabsTrigger>
231
- </TabsList>
206
+ <div className="sticky top-0 z-20 -mx-6 bg-background/95 px-6 backdrop-blur supports-backdrop-filter:bg-background/80">
207
+ <TabsList className="mb-4 w-full">
208
+ <TabsTrigger
209
+ value="menu"
210
+ className="flex-1 flex items-center gap-2"
211
+ >
212
+ <Menu className="h-4 w-4" />
213
+ {t('accessTabMenu')}
214
+ {!loadingMenus && (
215
+ <Badge variant="secondary" className="ml-1 text-xs">
216
+ {menus.length}
217
+ </Badge>
218
+ )}
219
+ </TabsTrigger>
220
+ <TabsTrigger
221
+ value="routes"
222
+ className="flex-1 flex items-center gap-2"
223
+ >
224
+ <Globe className="h-4 w-4" />
225
+ {t('accessTabRoutes')}
226
+ {!loadingRoutes && (
227
+ <Badge variant="secondary" className="ml-1 text-xs">
228
+ {routes.length}
229
+ </Badge>
230
+ )}
231
+ </TabsTrigger>
232
+ </TabsList>
233
+ </div>
232
234
 
233
235
  <TabsContent value="menu" className="mt-0">
234
236
  {loadingMenus ? (