feature/transportes-permisos
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
19
frontend/src/lib/permissions/transport-permissions.ts
Normal file
19
frontend/src/lib/permissions/transport-permissions.ts
Normal 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}`);
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user