@hed-hog/core 0.0.237 → 0.0.238

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 (78) hide show
  1. package/dist/dashboard/dashboard/dashboard.controller.d.ts +102 -0
  2. package/dist/dashboard/dashboard/dashboard.controller.d.ts.map +1 -1
  3. package/dist/dashboard/dashboard/dashboard.service.d.ts +102 -0
  4. package/dist/dashboard/dashboard/dashboard.service.d.ts.map +1 -1
  5. package/dist/dashboard/dashboard/dashboard.service.js +26 -0
  6. package/dist/dashboard/dashboard/dashboard.service.js.map +1 -1
  7. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts.map +1 -1
  8. package/dist/dashboard/dashboard-component/dashboard-component.service.js +39 -0
  9. package/dist/dashboard/dashboard-component/dashboard-component.service.js.map +1 -1
  10. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +7 -1
  11. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts.map +1 -1
  12. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.js +11 -0
  13. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.js.map +1 -1
  14. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +7 -1
  15. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts.map +1 -1
  16. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.js +31 -0
  17. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.js.map +1 -1
  18. package/dist/dashboard/dashboard-component-role/dto/create-batch.dto.d.ts +5 -0
  19. package/dist/dashboard/dashboard-component-role/dto/create-batch.dto.d.ts.map +1 -0
  20. package/dist/dashboard/dashboard-component-role/dto/create-batch.dto.js +27 -0
  21. package/dist/dashboard/dashboard-component-role/dto/create-batch.dto.js.map +1 -0
  22. package/dist/dashboard/dashboard-component-role/dto/index.d.ts +1 -0
  23. package/dist/dashboard/dashboard-component-role/dto/index.d.ts.map +1 -1
  24. package/dist/dashboard/dashboard-component-role/dto/index.js +1 -0
  25. package/dist/dashboard/dashboard-component-role/dto/index.js.map +1 -1
  26. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +7 -1
  27. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts.map +1 -1
  28. package/dist/dashboard/dashboard-role/dashboard-role.controller.js +11 -0
  29. package/dist/dashboard/dashboard-role/dashboard-role.controller.js.map +1 -1
  30. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +7 -1
  31. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts.map +1 -1
  32. package/dist/dashboard/dashboard-role/dashboard-role.service.js +31 -0
  33. package/dist/dashboard/dashboard-role/dashboard-role.service.js.map +1 -1
  34. package/dist/dashboard/dashboard-role/dto/create-batch.dto.d.ts +5 -0
  35. package/dist/dashboard/dashboard-role/dto/create-batch.dto.d.ts.map +1 -0
  36. package/dist/dashboard/dashboard-role/dto/create-batch.dto.js +27 -0
  37. package/dist/dashboard/dashboard-role/dto/create-batch.dto.js.map +1 -0
  38. package/dist/dashboard/dashboard-role/dto/index.d.ts +1 -0
  39. package/dist/dashboard/dashboard-role/dto/index.d.ts.map +1 -1
  40. package/dist/dashboard/dashboard-role/dto/index.js +1 -0
  41. package/dist/dashboard/dashboard-role/dto/index.js.map +1 -1
  42. package/dist/file/file.controller.d.ts +4 -1
  43. package/dist/file/file.controller.d.ts.map +1 -1
  44. package/dist/file/file.controller.js +47 -8
  45. package/dist/file/file.controller.js.map +1 -1
  46. package/dist/file/file.service.d.ts +1 -0
  47. package/dist/file/file.service.d.ts.map +1 -1
  48. package/dist/file/file.service.js +7 -0
  49. package/dist/file/file.service.js.map +1 -1
  50. package/dist/user/user.controller.d.ts +1 -0
  51. package/dist/user/user.controller.d.ts.map +1 -1
  52. package/dist/user/user.controller.js +13 -0
  53. package/dist/user/user.controller.js.map +1 -1
  54. package/dist/user/user.service.d.ts +1 -0
  55. package/dist/user/user.service.d.ts.map +1 -1
  56. package/dist/user/user.service.js +20 -0
  57. package/dist/user/user.service.js.map +1 -1
  58. package/hedhog/data/route.yaml +24 -0
  59. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +21 -3
  60. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +344 -5
  61. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +358 -2
  62. package/hedhog/frontend/messages/en.json +19 -0
  63. package/hedhog/frontend/messages/pt.json +19 -0
  64. package/package.json +4 -4
  65. package/src/dashboard/dashboard/dashboard.service.ts +26 -0
  66. package/src/dashboard/dashboard-component/dashboard-component.service.ts +39 -0
  67. package/src/dashboard/dashboard-component-role/dashboard-component-role.controller.ts +12 -1
  68. package/src/dashboard/dashboard-component-role/dashboard-component-role.service.ts +44 -1
  69. package/src/dashboard/dashboard-component-role/dto/create-batch.dto.ts +11 -0
  70. package/src/dashboard/dashboard-component-role/dto/index.ts +2 -0
  71. package/src/dashboard/dashboard-role/dashboard-role.controller.ts +22 -11
  72. package/src/dashboard/dashboard-role/dashboard-role.service.ts +48 -5
  73. package/src/dashboard/dashboard-role/dto/create-batch.dto.ts +11 -0
  74. package/src/dashboard/dashboard-role/dto/index.ts +2 -0
  75. package/src/file/file.controller.ts +50 -7
  76. package/src/file/file.service.ts +10 -0
  77. package/src/user/user.controller.ts +12 -1
  78. package/src/user/user.service.ts +32 -5
@@ -446,12 +446,24 @@
446
446
  role:
447
447
  - where:
448
448
  slug: admin
449
+ - url: /file/open/:token
450
+ method: GET
451
+ relations:
452
+ role:
453
+ - where:
454
+ slug: admin
449
455
  - url: /file/download/:token
450
456
  method: GET
451
457
  relations:
452
458
  role:
453
459
  - where:
454
460
  slug: admin
461
+ - url: /file/open/:id
462
+ method: PUT
463
+ relations:
464
+ role:
465
+ - where:
466
+ slug: admin
455
467
  - url: /file/download/:id
456
468
  method: PUT
457
469
  relations:
@@ -766,6 +778,12 @@
766
778
  role:
767
779
  - where:
768
780
  slug: admin
781
+ - url: /dashboard-role/batch
782
+ method: POST
783
+ relations:
784
+ role:
785
+ - where:
786
+ slug: admin
769
787
  - url: /dashboard-role/:id
770
788
  method: DELETE
771
789
  relations:
@@ -790,6 +808,12 @@
790
808
  role:
791
809
  - where:
792
810
  slug: admin
811
+ - url: /dashboard-component-role/batch
812
+ method: POST
813
+ relations:
814
+ role:
815
+ - where:
816
+ slug: admin
793
817
  - url: /dashboard-component-role/:id
794
818
  method: DELETE
795
819
  relations:
@@ -298,10 +298,28 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
298
298
  },
299
299
  });
300
300
 
301
+ const fileId = response.data?.id;
302
+
303
+ if (!fileId) {
304
+ throw new Error('File ID not returned');
305
+ }
306
+
307
+ const openResponse: any = await request({
308
+ url: `/file/open/${fileId}`,
309
+ method: 'PUT',
310
+ });
311
+
312
+ const openUrl = openResponse?.data?.url;
313
+
314
+ if (!openUrl) {
315
+ throw new Error('Temporary open URL not returned');
316
+ }
317
+
301
318
  const fileUrl =
302
- String(process.env.NEXT_PUBLIC_API_BASE_URL) +
303
- '/file/open/' +
304
- response.data?.id;
319
+ typeof openUrl === 'string' && openUrl.startsWith('http')
320
+ ? openUrl
321
+ : `${String(process.env.NEXT_PUBLIC_API_BASE_URL || '')}${openUrl}`;
322
+
305
323
  handleChangeValue(formatValue(fileUrl));
306
324
  showToastHandler('success', t('fileUploaded'));
307
325
  } catch (error) {
@@ -10,6 +10,7 @@ import {
10
10
  AlertDialogHeader,
11
11
  AlertDialogTitle,
12
12
  } from '@/components/ui/alert-dialog';
13
+ import { Badge } from '@/components/ui/badge';
13
14
  import { Button } from '@/components/ui/button';
14
15
  import { Checkbox } from '@/components/ui/checkbox';
15
16
  import {
@@ -30,6 +31,14 @@ import {
30
31
  SelectTrigger,
31
32
  SelectValue,
32
33
  } from '@/components/ui/select';
34
+ import {
35
+ Sheet,
36
+ SheetContent,
37
+ SheetDescription,
38
+ SheetFooter,
39
+ SheetHeader,
40
+ SheetTitle,
41
+ } from '@/components/ui/sheet';
33
42
  import {
34
43
  Table,
35
44
  TableBody,
@@ -38,6 +47,11 @@ import {
38
47
  TableHeader,
39
48
  TableRow,
40
49
  } from '@/components/ui/table';
50
+ import {
51
+ Tooltip,
52
+ TooltipContent,
53
+ TooltipTrigger,
54
+ } from '@/components/ui/tooltip';
41
55
  import { useDebounce } from '@/hooks/use-debounce';
42
56
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
43
57
  import * as TablerIcons from '@tabler/icons-react';
@@ -50,6 +64,7 @@ import {
50
64
  IconGlobe,
51
65
  IconPlus,
52
66
  IconTrash,
67
+ IconX,
53
68
  } from '@tabler/icons-react';
54
69
  import { useTranslations } from 'next-intl';
55
70
  import { useState } from 'react';
@@ -74,6 +89,25 @@ interface Component {
74
89
  name: string;
75
90
  description?: string;
76
91
  }>;
92
+ dashboard_component_role?: Array<{
93
+ role_id?: number;
94
+ role: {
95
+ id?: number;
96
+ role_id?: number;
97
+ slug: string;
98
+ role_locale?: Array<{
99
+ locale: { code: string };
100
+ name: string;
101
+ }>;
102
+ };
103
+ }>;
104
+ }
105
+
106
+ interface RoleOption {
107
+ id?: number;
108
+ role_id?: number;
109
+ slug: string;
110
+ name?: string;
77
111
  }
78
112
 
79
113
  export function ComponentsTab() {
@@ -87,9 +121,20 @@ export function ComponentsTab() {
87
121
  const [componentToDelete, setComponentToDelete] = useState<number | null>(
88
122
  null
89
123
  );
124
+ const [removeRoleDialogOpen, setRemoveRoleDialogOpen] = useState(false);
125
+ const [roleToRemove, setRoleToRemove] = useState<{
126
+ componentId: number;
127
+ roleId: number;
128
+ roleName: string;
129
+ } | null>(null);
130
+ const [roleSheetOpen, setRoleSheetOpen] = useState(false);
131
+ const [componentForRoleSheet, setComponentForRoleSheet] =
132
+ useState<Component | null>(null);
133
+ const [selectedRoleIds, setSelectedRoleIds] = useState<number[]>([]);
90
134
  const [page, setPage] = useState(1);
91
135
  const [pageSize, setPageSize] = useState(10);
92
136
  const [searchQuery, setSearchQuery] = useState('');
137
+ const [roleSearchQuery, setRoleSearchQuery] = useState('');
93
138
  const debouncedSearch = useDebounce(searchQuery, 500);
94
139
 
95
140
  const renderIcon = (iconName?: string) => {
@@ -145,7 +190,23 @@ export function ComponentsTab() {
145
190
  },
146
191
  });
147
192
 
193
+ const { data: rolesData, isLoading: isLoadingRoles } = useQuery<{
194
+ data: RoleOption[];
195
+ total: number;
196
+ }>({
197
+ queryKey: ['roles-for-component-sheet', currentLocaleCode],
198
+ queryFn: async () => {
199
+ const response = await request<{ data: RoleOption[]; total: number }>({
200
+ url: '/role?page=1&pageSize=1000',
201
+ method: 'GET',
202
+ });
203
+
204
+ return response.data;
205
+ },
206
+ });
207
+
148
208
  const components = paginatedData?.data ?? [];
209
+ const availableRoles = rolesData?.data ?? [];
149
210
  const total = paginatedData?.total ?? 0;
150
211
  const totalPages = Math.ceil(total / pageSize);
151
212
 
@@ -291,6 +352,81 @@ export function ComponentsTab() {
291
352
  }
292
353
  };
293
354
 
355
+ const handleOpenRoleSheet = (component: Component) => {
356
+ setComponentForRoleSheet(component);
357
+ setSelectedRoleIds([]);
358
+ setRoleSheetOpen(true);
359
+ };
360
+
361
+ const handleRoleSelection = (roleId: number, checked: boolean) => {
362
+ setSelectedRoleIds((prev) => {
363
+ if (checked) {
364
+ if (prev.includes(roleId)) return prev;
365
+ return [...prev, roleId];
366
+ }
367
+
368
+ return prev.filter((id) => id !== roleId);
369
+ });
370
+ };
371
+
372
+ const handleAddRolesToComponent = async () => {
373
+ if (!componentForRoleSheet) return;
374
+
375
+ if (selectedRoleIds.length === 0) {
376
+ toast.error(t('selectRoleRequired'));
377
+ return;
378
+ }
379
+
380
+ try {
381
+ await request({
382
+ url: '/dashboard-component-role/batch',
383
+ method: 'POST',
384
+ data: {
385
+ component_id: componentForRoleSheet.id,
386
+ role_ids: selectedRoleIds,
387
+ },
388
+ });
389
+
390
+ toast.success(t('roleAdded'));
391
+ setRoleSheetOpen(false);
392
+ setComponentForRoleSheet(null);
393
+ setSelectedRoleIds([]);
394
+ refetch();
395
+ } catch (error: any) {
396
+ console.error('Erro ao adicionar permissões ao componente:', error);
397
+ toast.error(t('errorAddingRole'));
398
+ }
399
+ };
400
+
401
+ const handleRoleRemoveClick = (
402
+ componentId: number,
403
+ roleId: number,
404
+ roleName: string
405
+ ) => {
406
+ setRoleToRemove({ componentId, roleId, roleName });
407
+ setRemoveRoleDialogOpen(true);
408
+ };
409
+
410
+ const handleRoleRemoveConfirm = async () => {
411
+ if (!roleToRemove) return;
412
+
413
+ try {
414
+ await request({
415
+ url: `/dashboard-component-role/component/${roleToRemove.componentId}/role/${roleToRemove.roleId}`,
416
+ method: 'DELETE',
417
+ });
418
+
419
+ toast.success(t('roleDeleted'));
420
+ refetch();
421
+ } catch (error) {
422
+ console.error('Erro ao remover permissão do componente:', error);
423
+ toast.error(t('errorDeletingRole'));
424
+ } finally {
425
+ setRemoveRoleDialogOpen(false);
426
+ setRoleToRemove(null);
427
+ }
428
+ };
429
+
294
430
  return (
295
431
  <div className="space-y-4">
296
432
  <div className="flex justify-between items-center">
@@ -581,16 +717,16 @@ export function ComponentsTab() {
581
717
  <TableRow>
582
718
  <TableHead>{t('slug')}</TableHead>
583
719
  <TableHead>{t('name')}</TableHead>
584
- <TableHead>{t('descriptionLabel')}</TableHead>
585
720
  <TableHead>{t('size')}</TableHead>
586
721
  <TableHead>{t('resizable')}</TableHead>
722
+ <TableHead>{t('role')}</TableHead>
587
723
  <TableHead className="w-[100px]">{t('actions')}</TableHead>
588
724
  </TableRow>
589
725
  </TableHeader>
590
726
  <TableBody>
591
727
  {isLoading ? (
592
728
  <TableRow>
593
- <TableCell colSpan={7} className="text-center">
729
+ <TableCell colSpan={6} className="text-center">
594
730
  {t('loading')}
595
731
  </TableCell>
596
732
  </TableRow>
@@ -605,19 +741,111 @@ export function ComponentsTab() {
605
741
  (l) => l.locale.code === currentLocaleCode
606
742
  )?.description;
607
743
 
744
+ const roleEntries = (
745
+ component.dashboard_component_role?.map((componentRole) => {
746
+ const roleId =
747
+ componentRole.role_id ??
748
+ componentRole.role.role_id ??
749
+ componentRole.role.id;
750
+
751
+ if (!roleId) return null;
752
+
753
+ const localeRoleName = componentRole.role.role_locale?.find(
754
+ (roleLocale) =>
755
+ roleLocale.locale.code === currentLocaleCode
756
+ )?.name;
757
+
758
+ return {
759
+ id: roleId,
760
+ name: localeRoleName || componentRole.role.slug,
761
+ };
762
+ }) || []
763
+ ).filter(
764
+ (
765
+ roleEntry,
766
+ index,
767
+ array
768
+ ): roleEntry is { id: number; name: string } =>
769
+ !!roleEntry &&
770
+ array.findIndex((item) => item?.id === roleEntry.id) ===
771
+ index
772
+ );
773
+
608
774
  return (
609
775
  <TableRow key={component.id}>
610
776
  <TableCell className="font-mono">
611
777
  {component.slug}
612
778
  </TableCell>
613
- <TableCell>{name}</TableCell>
614
- <TableCell>{description}</TableCell>
779
+ <TableCell>
780
+ <div className="flex flex-col">
781
+ <span>{name}</span>
782
+ {description && (
783
+ <span className="text-muted-foreground text-xs">
784
+ {description}
785
+ </span>
786
+ )}
787
+ </div>
788
+ </TableCell>
615
789
  <TableCell>
616
790
  {component.width}x{component.height}
617
791
  </TableCell>
618
792
  <TableCell>
619
793
  {component.is_resizable ? t('yes') : t('no')}
620
794
  </TableCell>
795
+ <TableCell>
796
+ <div className="flex flex-wrap items-center gap-1">
797
+ {roleEntries.length > 0 ? (
798
+ roleEntries.map((roleEntry) => (
799
+ <Badge
800
+ key={`${component.id}-${roleEntry.id}`}
801
+ variant="outline"
802
+ className="gap-1 pr-1"
803
+ >
804
+ <span>{roleEntry.name}</span>
805
+ <Tooltip>
806
+ <TooltipTrigger asChild>
807
+ <button
808
+ type="button"
809
+ className="rounded-sm p-0.5 hover:bg-muted"
810
+ onClick={() =>
811
+ handleRoleRemoveClick(
812
+ component.id,
813
+ roleEntry.id,
814
+ roleEntry.name
815
+ )
816
+ }
817
+ aria-label={`${t('delete')} ${roleEntry.name}`}
818
+ >
819
+ <IconX className="size-3" />
820
+ </button>
821
+ </TooltipTrigger>
822
+ <TooltipContent>
823
+ {`${t('delete')} ${roleEntry.name}`}
824
+ </TooltipContent>
825
+ </Tooltip>
826
+ </Badge>
827
+ ))
828
+ ) : (
829
+ <span className="text-muted-foreground text-xs mr-1">
830
+ {t('noRoles')}
831
+ </span>
832
+ )}
833
+ <Tooltip>
834
+ <TooltipTrigger asChild>
835
+ <Button
836
+ type="button"
837
+ size="icon"
838
+ variant="outline"
839
+ className="size-6"
840
+ onClick={() => handleOpenRoleSheet(component)}
841
+ >
842
+ <IconPlus className="size-3" />
843
+ </Button>
844
+ </TooltipTrigger>
845
+ <TooltipContent>{t('addRole')}</TooltipContent>
846
+ </Tooltip>
847
+ </div>
848
+ </TableCell>
621
849
  <TableCell>
622
850
  <div className="flex gap-2">
623
851
  <Button
@@ -641,7 +869,7 @@ export function ComponentsTab() {
641
869
  })
642
870
  ) : (
643
871
  <TableRow>
644
- <TableCell colSpan={7} className="text-center">
872
+ <TableCell colSpan={6} className="text-center">
645
873
  {t('noComponents')}
646
874
  </TableCell>
647
875
  </TableRow>
@@ -748,6 +976,117 @@ export function ComponentsTab() {
748
976
  </AlertDialogFooter>
749
977
  </AlertDialogContent>
750
978
  </AlertDialog>
979
+
980
+ <Dialog
981
+ open={removeRoleDialogOpen}
982
+ onOpenChange={setRemoveRoleDialogOpen}
983
+ >
984
+ <DialogContent className="max-w-md">
985
+ <DialogHeader>
986
+ <DialogTitle>{t('confirmDelete')}</DialogTitle>
987
+ <DialogDescription>
988
+ {roleToRemove
989
+ ? `${t('deleteDescription')} (${roleToRemove.roleName})`
990
+ : t('deleteDescription')}
991
+ </DialogDescription>
992
+ </DialogHeader>
993
+ <DialogFooter>
994
+ <Button
995
+ variant="outline"
996
+ onClick={() => setRemoveRoleDialogOpen(false)}
997
+ >
998
+ {t('cancel')}
999
+ </Button>
1000
+ <Button variant="destructive" onClick={handleRoleRemoveConfirm}>
1001
+ {t('delete')}
1002
+ </Button>
1003
+ </DialogFooter>
1004
+ </DialogContent>
1005
+ </Dialog>
1006
+
1007
+ <Sheet
1008
+ open={roleSheetOpen}
1009
+ onOpenChange={(openValue) => {
1010
+ setRoleSheetOpen(openValue);
1011
+ if (!openValue) {
1012
+ setComponentForRoleSheet(null);
1013
+ setSelectedRoleIds([]);
1014
+ }
1015
+ }}
1016
+ >
1017
+ <SheetContent side="right" className="sm:max-w-lg overflow-y-auto">
1018
+ <SheetHeader>
1019
+ <SheetTitle>{t('addRole')}</SheetTitle>
1020
+ <SheetDescription>
1021
+ {componentForRoleSheet
1022
+ ? `${t('component')}: ${componentForRoleSheet.slug}`
1023
+ : t('manageComponentRoles')}
1024
+ </SheetDescription>
1025
+ </SheetHeader>
1026
+
1027
+ <div className="space-y-3 py-4">
1028
+ {isLoadingRoles ? (
1029
+ <p className="text-sm text-muted-foreground">{t('loading')}</p>
1030
+ ) : availableRoles.length > 0 ? (
1031
+ availableRoles.map((role) => {
1032
+ const roleId = role.role_id ?? role.id;
1033
+ if (!roleId) return null;
1034
+
1035
+ const roleName = role.name || role.slug;
1036
+ const alreadyLinked =
1037
+ componentForRoleSheet?.dashboard_component_role?.some(
1038
+ (componentRole) =>
1039
+ (componentRole.role_id ??
1040
+ componentRole.role.role_id ??
1041
+ componentRole.role.id) === roleId
1042
+ ) ?? false;
1043
+
1044
+ const isSelected = selectedRoleIds.includes(roleId);
1045
+
1046
+ return (
1047
+ <div
1048
+ key={`available-role-${roleId}`}
1049
+ className={`flex items-center gap-3 rounded-md border p-3 transition-colors ${
1050
+ alreadyLinked
1051
+ ? 'cursor-not-allowed opacity-70'
1052
+ : 'cursor-pointer hover:bg-muted/40'
1053
+ } ${isSelected ? 'border-primary bg-primary/5' : ''}`}
1054
+ onClick={() => {
1055
+ if (alreadyLinked) return;
1056
+ handleRoleSelection(roleId, !isSelected);
1057
+ }}
1058
+ >
1059
+ <div onClick={(event) => event.stopPropagation()}>
1060
+ <Checkbox
1061
+ checked={alreadyLinked || isSelected}
1062
+ onCheckedChange={(checked) =>
1063
+ handleRoleSelection(roleId, checked === true)
1064
+ }
1065
+ disabled={alreadyLinked}
1066
+ />
1067
+ </div>
1068
+ <div className="min-w-0">
1069
+ <p className="text-sm font-medium truncate">{roleName}</p>
1070
+ <p className="text-xs text-muted-foreground truncate">
1071
+ {role.slug}
1072
+ </p>
1073
+ </div>
1074
+ </div>
1075
+ );
1076
+ })
1077
+ ) : (
1078
+ <p className="text-sm text-muted-foreground">{t('noRoles')}</p>
1079
+ )}
1080
+ </div>
1081
+
1082
+ <SheetFooter>
1083
+ <Button variant="outline" onClick={() => setRoleSheetOpen(false)}>
1084
+ {t('cancel')}
1085
+ </Button>
1086
+ <Button onClick={handleAddRolesToComponent}>{t('save')}</Button>
1087
+ </SheetFooter>
1088
+ </SheetContent>
1089
+ </Sheet>
751
1090
  </div>
752
1091
  );
753
1092
  }