feature/transportes-permisos

This commit is contained in:
2026-05-05 16:58:44 -06:00
parent 2ba043eec5
commit c96184da90
14 changed files with 275 additions and 140 deletions

View File

@@ -10,7 +10,7 @@ export function createColumns(
onSuccess: () => void,
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
): ColumnDef<Driver>[] {
return [
const cols: ColumnDef<Driver>[] = [
{
accessorKey: 'transporter_key',
header: 'Clave Transportista',
@@ -40,8 +40,10 @@ export function createColumns(
accessorKey: 'last_name',
header: 'Apellido',
cell: ({ row }) => row.original.last_name || '-'
},
{
}
];
if (permissions.canEdit || permissions.canDelete) {
cols.push({
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
@@ -52,6 +54,7 @@ export function createColumns(
canDelete: permissions.canDelete
});
}
}
];
});
}
return cols;
}

View File

@@ -90,7 +90,7 @@
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>

View File

@@ -10,7 +10,7 @@ export function createColumns(
onSuccess: () => void,
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
): ColumnDef<Trailer>[] {
return [
const cols: ColumnDef<Trailer>[] = [
{
accessorKey: 'trailer_number',
header: 'Número de Trailer',
@@ -52,8 +52,10 @@ export function createColumns(
cell: ({ row }) => {
return row.original.country || '-';
}
},
{
}
];
if (permissions.canEdit || permissions.canDelete) {
cols.push({
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
@@ -64,6 +66,7 @@ export function createColumns(
canDelete: permissions.canDelete
});
}
}
];
});
}
return cols;
}

View File

@@ -85,7 +85,7 @@
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>

View File

@@ -88,7 +88,7 @@
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>

View File

@@ -10,7 +10,7 @@ export function createColumns(
onSuccess: () => void,
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
): ColumnDef<Transporter>[] {
return [
const cols: ColumnDef<Transporter>[] = [
{
accessorKey: 'transporter_key',
header: 'Clave',
@@ -52,8 +52,10 @@ export function createColumns(
cell: ({ row }) => {
return row.original.transport_code || '-';
}
},
{
}
];
if (permissions.canEdit || permissions.canDelete) {
cols.push({
id: 'actions',
header: 'Acciones',
cell: ({ row }) => {
@@ -64,6 +66,7 @@ export function createColumns(
canDelete: permissions.canDelete
});
}
}
];
});
}
return cols;
}

View File

@@ -11,7 +11,7 @@ export function createColumns(
onSuccess: () => void,
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
): ColumnDef<Vehicle>[] {
return [
const cols: ColumnDef<Vehicle>[] = [
{
accessorKey: 'vehicle_key',
header: tv('col_key'),
@@ -54,8 +54,10 @@ export function createColumns(
if (!row.original.transport_type) return '-';
return `${row.original.transport_type} - ${tvTransportType(row.original.transport_type, row.original.transport_type)}`;
}
},
{
}
];
if (permissions.canEdit || permissions.canDelete) {
cols.push({
id: 'actions',
header: tv('col_actions'),
cell: ({ row }) => {
@@ -66,6 +68,7 @@ export function createColumns(
canDelete: permissions.canDelete
});
}
}
];
});
}
return cols;
}

View File

@@ -86,7 +86,7 @@
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">{tv('menu_open')}</span>

View File

@@ -350,18 +350,22 @@ export function getSidebarData(): SidebarData {
{
title: m["sidebar.transports.transporters"](),
url: "/dashboard/general_catalogs/transporters",
permission: 'transporters.view',
},
{
title: m["sidebar.transports.drivers"](),
url: "/dashboard/general_catalogs/drivers",
permission: 'drivers.view',
},
{
title: m["sidebar.transports.trailers"](),
url: "/dashboard/general_catalogs/trailers",
permission: 'trailers.view',
},
{
title: m["sidebar.transports.vehicles"](),
url: "/dashboard/general_catalogs/vehicles",
permission: 'vehicles.view',
},
],
},

View File

@@ -0,0 +1,19 @@
/**
* Permisos de catálogos de transporte — mantener alineado con
* `permissions_transports` en backend/api/v1/modules/core/permissions/seed_v2.py
* (gen_crud transporters, drivers, trailers, vehicles → módulo `transports`).
*/
import type { User } from '$lib/auth';
import { userHasPermission } from '$lib/auth';
export type TransportCatalogResource = 'transporters' | 'drivers' | 'trailers' | 'vehicles';
export type TransportCatalogAction = 'view' | 'create' | 'edit' | 'delete';
export function userHasTransportAction(
user: User | null,
resource: TransportCatalogResource,
action: TransportCatalogAction
): boolean {
return userHasPermission(user, `${resource}.${action}`);
}

View File

@@ -12,6 +12,15 @@
import { companyStore } from '$lib/stores/company.svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosCatalogoSimple } from '$lib/config/shortcuts/dashboard/general_catalogs/common/factory';
import { get } from 'svelte/store';
import { currentUser } from '$lib/auth';
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
import { userHasTransportAction } from '$lib/permissions/transport-permissions';
const canView = $derived(userHasTransportAction($currentUser, 'drivers', 'view'));
const canCreate = $derived(userHasTransportAction($currentUser, 'drivers', 'create'));
const canEdit = $derived(userHasTransportAction($currentUser, 'drivers', 'edit'));
const canDelete = $derived(userHasTransportAction($currentUser, 'drivers', 'delete'));
let data = $state<Driver[]>([]);
let totalItems = $state(0);
@@ -88,6 +97,7 @@
}
function handleRowDoubleClick(row: Driver) {
if (!userHasTransportAction(get(currentUser), 'drivers', 'edit')) return;
editingItem = row;
createDialogOpen = true;
}
@@ -150,6 +160,8 @@
useShortcuts('Conductores', [
...obtenerAtajosCatalogoSimple({
manejarNuevo: () => {
if (!userHasTransportAction(get(currentUser), 'drivers', 'create')) return;
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: loadData
@@ -164,16 +176,23 @@
}
]);
const columns = createColumns(loadData);
const columns = $derived(createColumns(loadData, { canEdit, canDelete }));
</script>
{#if !canView}
<ErrorState status={403} />
{:else}
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold tracking-tight">Conductores</h1>
<p class="text-muted-foreground">Gestión del catálogo de conductores</p>
</div>
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Conductor</Button></div>
<div class="flex items-center gap-3">
{#if canCreate}
<Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Conductor</Button>
{/if}
</div>
</div>
<Card.Root class="border bg-background flex flex-col">
@@ -185,35 +204,41 @@
<div class="h-20"></div>
<!-- Footer fijo con botones de acción -->
<div
id="drivers-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
{#if canEdit || canDelete}
<div
id="drivers-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
{#if canEdit}
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
{/if}
{#if canDelete}
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
{/if}
</div>
</div>
</div>
</div>
{/if}
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
</div>
{/if}

View File

@@ -16,6 +16,15 @@
import { browser } from '$app/environment';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosCatalogoSimple } from '$lib/config/shortcuts/dashboard/general_catalogs/common/factory';
import { get } from 'svelte/store';
import { currentUser } from '$lib/auth';
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
import { userHasTransportAction } from '$lib/permissions/transport-permissions';
const canView = $derived(userHasTransportAction($currentUser, 'trailers', 'view'));
const canCreate = $derived(userHasTransportAction($currentUser, 'trailers', 'create'));
const canEdit = $derived(userHasTransportAction($currentUser, 'trailers', 'edit'));
const canDelete = $derived(userHasTransportAction($currentUser, 'trailers', 'delete'));
// --- ESTADO ---
let data = $state<Trailer[]>([]);
@@ -91,6 +100,7 @@
}
function handleRowDoubleClick(row: Trailer) {
if (!userHasTransportAction(get(currentUser), 'trailers', 'edit')) return;
editingItem = row;
createDialogOpen = true;
}
@@ -169,6 +179,8 @@
useShortcuts('Trailers', [
...obtenerAtajosCatalogoSimple({
manejarNuevo: () => {
if (!userHasTransportAction(get(currentUser), 'trailers', 'create')) return;
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: loadData
@@ -183,16 +195,23 @@
}
]);
const columns = createColumns(loadData);
const columns = $derived(createColumns(loadData, { canEdit, canDelete }));
</script>
{#if !canView}
<ErrorState status={403} />
{:else}
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold tracking-tight">Trailers</h1>
<p class="text-muted-foreground">Gestión del catálogo de trailers de la compañía</p>
</div>
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Trailer</Button></div>
<div class="flex items-center gap-3">
{#if canCreate}
<Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Trailer</Button>
{/if}
</div>
</div>
<Card.Root class="border bg-background flex flex-col">
@@ -204,35 +223,41 @@
<div class="h-20"></div>
<!-- Footer fijo con botones de acción -->
<div
id="trailers-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
{#if canEdit || canDelete}
<div
id="trailers-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
{#if canEdit}
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
{/if}
{#if canDelete}
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
{/if}
</div>
</div>
</div>
</div>
{/if}
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
</div>
{/if}

View File

@@ -16,6 +16,15 @@
import { browser } from '$app/environment';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosCatalogoSimple } from '$lib/config/shortcuts/dashboard/general_catalogs/common/factory';
import { get } from 'svelte/store';
import { currentUser } from '$lib/auth';
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
import { userHasTransportAction } from '$lib/permissions/transport-permissions';
const canView = $derived(userHasTransportAction($currentUser, 'transporters', 'view'));
const canCreate = $derived(userHasTransportAction($currentUser, 'transporters', 'create'));
const canEdit = $derived(userHasTransportAction($currentUser, 'transporters', 'edit'));
const canDelete = $derived(userHasTransportAction($currentUser, 'transporters', 'delete'));
// --- ESTADO ---
let data = $state<Transporter[]>([]);
@@ -91,6 +100,7 @@
}
function handleRowDoubleClick(row: Transporter) {
if (!userHasTransportAction(get(currentUser), 'transporters', 'edit')) return;
editingItem = row;
createDialogOpen = true;
}
@@ -169,6 +179,8 @@
useShortcuts('Transportistas', [
...obtenerAtajosCatalogoSimple({
manejarNuevo: () => {
if (!userHasTransportAction(get(currentUser), 'transporters', 'create')) return;
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: loadData
@@ -183,16 +195,23 @@
}
]);
const columns = createColumns(loadData);
const columns = $derived(createColumns(loadData, { canEdit, canDelete }));
</script>
{#if !canView}
<ErrorState status={403} />
{:else}
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold tracking-tight">Transportistas</h1>
<p class="text-muted-foreground">Gestión del catálogo de líneas transportistas</p>
</div>
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Transportista</Button></div>
<div class="flex items-center gap-3">
{#if canCreate}
<Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Transportista</Button>
{/if}
</div>
</div>
<Card.Root class="border bg-background flex flex-col">
@@ -204,35 +223,41 @@
<div class="h-20"></div>
<!-- Footer fijo con botones de acción -->
<div
id="transporters-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
{#if canEdit || canDelete}
<div
id="transporters-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
{#if canEdit}
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
Editar
</Button>
{/if}
{#if canDelete}
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
Eliminar
</Button>
{/if}
</div>
</div>
</div>
</div>
{/if}
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
</div>
{/if}

View File

@@ -17,6 +17,15 @@
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosCatalogoSimple } from '$lib/config/shortcuts/dashboard/general_catalogs/common/factory';
import { tv } from '$lib/i18n/vehicles';
import { get } from 'svelte/store';
import { currentUser } from '$lib/auth';
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
import { userHasTransportAction } from '$lib/permissions/transport-permissions';
const canView = $derived(userHasTransportAction($currentUser, 'vehicles', 'view'));
const canCreate = $derived(userHasTransportAction($currentUser, 'vehicles', 'create'));
const canEdit = $derived(userHasTransportAction($currentUser, 'vehicles', 'edit'));
const canDelete = $derived(userHasTransportAction($currentUser, 'vehicles', 'delete'));
// --- ESTADO ---
let data = $state<Vehicle[]>([]);
@@ -92,6 +101,7 @@
}
function handleRowDoubleClick(row: Vehicle) {
if (!userHasTransportAction(get(currentUser), 'vehicles', 'edit')) return;
editingItem = row;
createDialogOpen = true;
}
@@ -170,6 +180,8 @@
useShortcuts(tv('shortcuts_scope'), [
...obtenerAtajosCatalogoSimple({
manejarNuevo: () => {
if (!userHasTransportAction(get(currentUser), 'vehicles', 'create')) return;
editingItem = null;
createDialogOpen = true;
},
manejarActualizar: loadData
@@ -184,9 +196,12 @@
}
]);
const columns = createColumns(loadData);
const columns = $derived(createColumns(loadData, { canEdit, canDelete }));
</script>
{#if !canView}
<ErrorState status={403} />
{:else}
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
<div class="flex items-center justify-between">
<div>
@@ -195,7 +210,11 @@
{tv('page_subtitle')}
</p>
</div>
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />{tv('new_vehicle')}</Button></div>
<div class="flex items-center gap-3">
{#if canCreate}
<Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />{tv('new_vehicle')}</Button>
{/if}
</div>
</div>
<Card.Root class="border bg-background flex flex-col">
@@ -207,35 +226,41 @@
<div class="h-20"></div>
<!-- Footer fijo con botones de acción -->
<div
id="vehicles-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
{tv('edit')}
</Button>
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
{tv('delete')}
</Button>
{#if canEdit || canDelete}
<div
id="vehicles-list-footer"
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
>
<div class="mx-auto max-w-[1400px] px-4 py-4">
<div class="flex justify-end gap-2">
{#if canEdit}
<Button
variant="outline"
size="sm"
onclick={handleEditSelected}
disabled={selectedIds.length !== 1}
>
<Pencil size={16} class="mr-2" />
{tv('edit')}
</Button>
{/if}
{#if canDelete}
<Button
variant="outline"
size="sm"
onclick={handleDeleteSelected}
disabled={selectedIds.length === 0}
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 size={16} class="mr-2" />
{tv('delete')}
</Button>
{/if}
</div>
</div>
</div>
</div>
{/if}
<CreateEditDialog bind:open={createDialogOpen} item={editingItem} onSuccess={handleDialogSuccess} />
</div>
{/if}