fix: solución de bloqueos y estandarización de permisos
This commit is contained in:
158
frontend/src/lib/components/dashboard/common/error-state.svelte
Normal file
158
frontend/src/lib/components/dashboard/common/error-state.svelte
Normal file
@@ -0,0 +1,158 @@
|
||||
<script lang="ts">
|
||||
import { ShieldAlert, AlertTriangle, RefreshCw, ArrowLeft, Home } from 'lucide-svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let {
|
||||
status = 0,
|
||||
error = '',
|
||||
onRetry = () => { if (typeof window !== 'undefined') window.location.reload(); },
|
||||
onBack = () => { if (typeof window !== 'undefined') window.history.back(); }
|
||||
}: {
|
||||
status?: number,
|
||||
error?: string,
|
||||
onRetry?: () => void,
|
||||
onBack?: () => void
|
||||
} = $props();
|
||||
|
||||
// Determinar si es un error de permisos (403)
|
||||
let isForbidden = $derived(status === 403 || error.toLowerCase().includes('permission') || error.toLowerCase().includes('acceso denegado') || error.includes('403'));
|
||||
|
||||
// Determinar si es un error de servidor (500)
|
||||
let isServerError = $derived(status >= 500 || (error && (error.toLowerCase().includes('server error') || error.toLowerCase().includes('error interno'))));
|
||||
|
||||
// Extraer el código del permiso si viene en el error
|
||||
let permissionCode = $derived.by(() => {
|
||||
if (!isForbidden) return null;
|
||||
// Buscar patrones como "cat_example.view" o "Missing required permissions: cat_example.view"
|
||||
const missing = error.match(/Missing required permissions?:\s*([a-z0-9_.]+)/i);
|
||||
if (missing) return missing[1];
|
||||
const legacy = error.match(/([a-z0-9_.]+\.[a-z0-9_.]+)/i);
|
||||
if (legacy) return legacy[0];
|
||||
const simple = error.match(/(cat_|settings_)[a-z0-9_.]+/i);
|
||||
return simple ? simple[0] : null;
|
||||
});
|
||||
|
||||
let title = $derived(isForbidden ? 'Acceso Restringido' : isServerError ? 'Error del Servidor' : 'Algo salió mal');
|
||||
|
||||
let displayError = $derived.by(() => {
|
||||
if (isForbidden) {
|
||||
return 'No tienes los permisos necesarios para acceder a esta sección de la plataforma.';
|
||||
}
|
||||
if (isServerError) {
|
||||
return 'Estamos experimentando dificultades técnicas en nuestros servidores.';
|
||||
}
|
||||
return error || 'Ocurrió un error inesperado al intentar procesar tu solicitud.';
|
||||
});
|
||||
|
||||
const activeCompany = $derived(companyStore.activeCompany);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-center p-6 min-h-[450px] w-full"
|
||||
in:fade={{ duration: 400 }}
|
||||
>
|
||||
<Card.Root class="max-w-md w-full border-2 bg-card/60 backdrop-blur-md shadow-2xl overflow-hidden {isForbidden ? 'border-dashed' : 'border-destructive/20'}">
|
||||
<!-- Barra de progreso decorativa -->
|
||||
<div class="h-1.5 w-full bg-gradient-to-r {isForbidden ? 'from-primary/80 via-primary/40 to-primary/80' : 'from-destructive/80 via-destructive/40 to-destructive/80'} animate-gradient-x"></div>
|
||||
|
||||
<Card.Header class="flex flex-col items-center gap-5 pt-10 text-center">
|
||||
<div class="relative">
|
||||
<div
|
||||
class="relative"
|
||||
in:fly={{ y: 20, duration: 600, delay: 100 }}
|
||||
>
|
||||
<div class="absolute -inset-6 {isForbidden ? 'bg-primary/15' : 'bg-destructive/15'} rounded-full blur-2xl animate-pulse"></div>
|
||||
<div class="relative bg-background p-5 rounded-full border shadow-xl">
|
||||
{#if isForbidden}
|
||||
<ShieldAlert class="h-14 w-14 text-primary" />
|
||||
{:else}
|
||||
<AlertTriangle class="h-14 w-14 text-destructive" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3" in:fade={{ delay: 300 }}>
|
||||
<Card.Title class="text-3xl font-black tracking-tight {isForbidden ? 'text-foreground' : 'text-destructive'}">
|
||||
{title}
|
||||
</Card.Title>
|
||||
<Card.Description class="text-base text-muted-foreground px-6 leading-relaxed">
|
||||
{displayError}
|
||||
</Card.Description>
|
||||
</div>
|
||||
</Card.Header>
|
||||
|
||||
<Card.Content class="flex flex-col items-center gap-8 pb-10 pt-4">
|
||||
<div
|
||||
class="flex flex-col items-center gap-7 w-full"
|
||||
in:fade={{ delay: 500 }}
|
||||
>
|
||||
{#if isForbidden && permissionCode}
|
||||
<div class="flex flex-col items-center gap-2.5">
|
||||
<span class="text-[10px] uppercase font-bold tracking-[0.1em] text-muted-foreground/80">Identificador de Permiso</span>
|
||||
<Badge variant="outline" class="font-mono text-xs bg-muted/70 border-primary/30 text-primary px-4 py-1.5 shadow-sm">
|
||||
{permissionCode}
|
||||
</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isServerError && error && !isForbidden}
|
||||
<div class="w-full px-6">
|
||||
<div class="rounded-lg bg-destructive/5 border border-destructive/10 p-4">
|
||||
<p class="text-[11px] font-mono text-destructive/70 break-all text-center leading-tight">
|
||||
{error.length > 150 ? error.substring(0, 150) + '...' : error}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-3 w-full px-6">
|
||||
{#if isServerError}
|
||||
<Button variant="default" class="w-full sm:w-auto min-w-[140px] gap-2 shadow-lg hover:scale-105 transition-transform" onclick={onRetry}>
|
||||
<RefreshCw class="h-4 w-4" />
|
||||
Reintentar
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<Button variant="outline" class="w-full sm:w-auto min-w-[140px] gap-2 shadow-sm" onclick={onBack}>
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
Regresar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<a href="/dashboard" class="text-xs text-muted-foreground hover:text-primary transition-colors flex items-center gap-1.5">
|
||||
<Home class="h-3 w-3" />
|
||||
Ir al Inicio del Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer class="bg-muted/40 border-t py-5 justify-center">
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<p class="text-[10px] text-muted-foreground/80 text-center max-w-[280px]">
|
||||
Si consideras que esto es un error o el problema persiste, contacta al soporte técnico.
|
||||
</p>
|
||||
{#if activeCompany}
|
||||
<p class="text-[9px] text-muted-foreground/40 font-mono">
|
||||
CID: {activeCompany.id} | TS: {new Date().toISOString()}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes gradient-x {
|
||||
0%, 100% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
}
|
||||
.animate-gradient-x {
|
||||
background-size: 200% 200%;
|
||||
animation: gradient-x 5s ease infinite;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import { ShieldAlert, ArrowLeft, RefreshCw } from 'lucide-svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
let {
|
||||
error = '',
|
||||
onRetry = () => {},
|
||||
onBack = () => history.back()
|
||||
} = $props();
|
||||
|
||||
// Extraer el código del permiso si viene en el error (ej: "Missing required permissions: cat_packages.view")
|
||||
let permissionCode = $derived.by(() => {
|
||||
const missing = error.match(/Missing required permissions?:\s*([a-z0-9_.]+)/i);
|
||||
if (missing) return missing[1];
|
||||
const legacy = error.match(/(cat_|settings_)[a-z0-9_.]+/i);
|
||||
return legacy ? legacy[0] : null;
|
||||
});
|
||||
|
||||
let displayError = $derived(
|
||||
error.toLowerCase().includes('permission') || error.toLowerCase().includes('acceso denegado') || error.includes('403')
|
||||
? 'No tienes los permisos necesarios para acceder a esta información.'
|
||||
: error || 'Ocurrió un error inesperado al cargar los datos.'
|
||||
);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-center p-8 min-h-[400px] w-full"
|
||||
in:fade={{ duration: 300 }}
|
||||
>
|
||||
<Card.Root class="max-w-md w-full border-dashed border-2 bg-card/50 backdrop-blur-sm shadow-xl overflow-hidden">
|
||||
<div class="h-1.5 w-full bg-gradient-to-r from-primary via-destructive/50 to-primary/30 animate-gradient-x"></div>
|
||||
|
||||
<Card.Header class="flex flex-col items-center gap-4 pt-8 text-center">
|
||||
<div class="relative">
|
||||
<div
|
||||
class="relative"
|
||||
in:fly={{ y: 20, duration: 500, delay: 200 }}
|
||||
>
|
||||
<div class="absolute -inset-4 bg-primary/10 rounded-full blur-xl animate-pulse"></div>
|
||||
<div class="relative bg-background p-4 rounded-full border shadow-inner">
|
||||
<ShieldAlert class="h-12 w-12 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div in:fade={{ delay: 400 }}>
|
||||
<div class="space-y-2">
|
||||
<Card.Title class="text-2xl font-bold tracking-tight">Acceso Restringido</Card.Title>
|
||||
<Card.Description class="text-sm text-muted-foreground px-4">
|
||||
{displayError}
|
||||
</Card.Description>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
|
||||
<Card.Content class="flex flex-col items-center gap-6 pb-8 pt-2">
|
||||
<div
|
||||
class="flex flex-col items-center gap-6 w-full"
|
||||
in:fade={{ delay: 600 }}
|
||||
>
|
||||
{#if permissionCode}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<span class="text-[10px] uppercase tracking-wider text-muted-foreground font-semibold">Identificador de Permiso</span>
|
||||
<Badge variant="outline" class="font-mono text-[11px] bg-muted/50 border-primary/20 text-primary px-3 py-1">
|
||||
{permissionCode}
|
||||
</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center justify-center w-full px-6 text-center">
|
||||
<Button variant="outline" class="w-full max-w-[200px] gap-2 shadow-sm" onclick={() => onBack()}>
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
Regresar al Dashboard
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer class="bg-muted/30 border-t py-4 justify-center">
|
||||
<p class="text-[11px] text-muted-foreground text-center">
|
||||
Si consideras que esto es un error, contacta al administrador del sistema.
|
||||
</p>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes gradient-x {
|
||||
0%, 100% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
}
|
||||
.animate-gradient-x {
|
||||
background-size: 200% 200%;
|
||||
animation: gradient-x 5s ease infinite;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,10 @@ import type { ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/excha
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ExchangeRate>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<ExchangeRate>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'date',
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: ExchangeRate;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -74,19 +78,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,25 +3,25 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ClassificationConcept>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<ClassificationConcept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'classification',
|
||||
header: 'Clasificación',
|
||||
cell: ({ row }) => row.original.classification || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: ClassificationConcept;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -62,19 +66,28 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Separator />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if !canEdit && !canDelete}
|
||||
<DropdownMenu.Item disabled>Sin permisos</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Company>[] {
|
||||
export function createColumns(onSuccess?: () => void, permissions?: { canEdit: boolean, canDelete: boolean }): ColumnDef<Company>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Concept>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Concept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -31,7 +34,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Concept>[] {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Concept;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -62,19 +66,23 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerConcept>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<CustomsBrokerConcept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'broker_key',
|
||||
@@ -31,7 +34,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerCo
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: CustomsBrokerConcept;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -19,7 +23,7 @@
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar el concepto "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
if (!confirm(`¿Estás seguro de eliminar el concepto "${item.concept}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,19 +66,28 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Separator />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if !canEdit && !canDelete}
|
||||
<DropdownMenu.Item disabled>Sin permisos</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
@@ -82,4 +95,5 @@
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={onSuccess}
|
||||
companyId={companyStore.activeCompany?.id ?? 0}
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,10 @@ function formatDate(date?: string | null): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Doda>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<Doda>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
@@ -126,7 +129,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Doda>[] {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Doda;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -61,18 +65,22 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => goto(`/dashboard/general_catalogs/doda/edit/${item.id}`)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => goto(`/dashboard/general_catalogs/doda/edit/${item.id}`)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { ElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Head } from '$lib/components/ui/table';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ElectronicNotice>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<ElectronicNotice>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'notice_number',
|
||||
@@ -33,11 +34,13 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ElectronicNotic
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Headers: 'Acciones',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: ElectronicNotice;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -60,18 +64,22 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -6,11 +6,15 @@ import CatalogDataTableActions from './catalog-data-table-actions.svelte';
|
||||
export function createCatalogColumns({
|
||||
onInsertItems,
|
||||
onEdit,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
onInsertItems: (equivalency: Equivalency) => void;
|
||||
onEdit: (equivalency: Equivalency) => void;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
}): ColumnDef<Equivalency>[] {
|
||||
return [
|
||||
{
|
||||
@@ -25,13 +29,15 @@ export function createCatalogColumns({
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: 'Acciones',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(CatalogDataTableActions, {
|
||||
item: row.original,
|
||||
onInsertItems,
|
||||
onEdit,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
item,
|
||||
onInsertItems,
|
||||
onEdit,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Equivalency;
|
||||
onInsertItems: (equivalency: Equivalency) => void;
|
||||
onEdit: (equivalency: Equivalency) => void;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -54,18 +58,22 @@
|
||||
<DropdownMenu.Item onclick={() => onInsertItems(item)}>
|
||||
<span>Insertar items</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => onEdit(item)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Borrar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => onEdit(item)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Borrar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { EquivalencyItem } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<EquivalencyItem>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<EquivalencyItem>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'original_field',
|
||||
@@ -21,11 +24,13 @@ export function createColumns(onSuccess?: () => void): ColumnDef<EquivalencyItem
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: 'Acciones',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: EquivalencyItem;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -65,18 +69,22 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { ErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ErrorCatalog>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<ErrorCatalog>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -24,7 +27,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ErrorCatalog>[]
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: ErrorCatalog;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -71,19 +75,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Header } from '$lib/components/ui/alert-dialog';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Identifier>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Identifier>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -28,11 +31,13 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Identifier>[] {
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: 'Acciones',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Identifier;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -50,7 +54,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -60,18 +64,22 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { INPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<INPC>[] {
|
||||
export function createColumns(onSuccess?: () => void, permissions?: { canEdit: boolean, canDelete: boolean }): ColumnDef<INPC>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'year',
|
||||
|
||||
@@ -3,27 +3,30 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Legend>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
cell: ({ row }) => row.original.code?.toString() || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
export const createColumns = (
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Legend>[] => [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
cell: ({ row }) => row.original.code?.toString() || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Legend;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -63,19 +67,23 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Prevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Header } from '$lib/components/ui/alert-dialog';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Prevalidator>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<Prevalidator>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -28,11 +29,13 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Prevalidator>[]
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: 'Acciones',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Prevalidator;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -60,18 +64,22 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -7,11 +7,24 @@
|
||||
import { getSectors, type Sector } from '$lib/api/dashboard/general_catalogs/sectors';
|
||||
import { Loader2, Search } from 'lucide-svelte';
|
||||
import Badge from '$lib/components/ui/badge/badge.svelte';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
import { currentUser, userHasPermission } from '$lib/auth';
|
||||
|
||||
let { title = 'Sectores' }: { title?: string } = $props();
|
||||
|
||||
let sectors = $state<Sector[]>([]);
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let status = $state<number>(200);
|
||||
|
||||
// Permisos
|
||||
const canView = $derived(userHasPermission($currentUser, 'cat_sectors.view') || userHasPermission($currentUser, 'frac_sectors.view'));
|
||||
const canCreate = $derived(userHasPermission($currentUser, 'cat_sectors.create'));
|
||||
const canEdit = $derived(userHasPermission($currentUser, 'cat_sectors.edit'));
|
||||
const canDelete = $derived(userHasPermission($currentUser, 'cat_sectors.delete'));
|
||||
const isError = $derived(!canView || status >= 400 || error);
|
||||
|
||||
let searchTerm = $state('');
|
||||
let page = $state(1);
|
||||
let pageSize = 50;
|
||||
@@ -19,11 +32,15 @@
|
||||
let total = $state(0);
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let sentinel = $state<HTMLDivElement | null>(null);
|
||||
|
||||
async function loadSectors(reset = false) {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId || !canView) return;
|
||||
if (loading || (!hasMore && !reset)) return;
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
if (reset) {
|
||||
page = 1;
|
||||
@@ -34,21 +51,28 @@
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await getSectors(page, pageSize, searchTerm || undefined);
|
||||
const response = await getSectors(page, pageSize, companyId, searchTerm || undefined);
|
||||
|
||||
const newItems = response.items || [];
|
||||
if (reset) {
|
||||
sectors = newItems;
|
||||
if (response.data) {
|
||||
status = 200;
|
||||
const newItems = response.data.items || [];
|
||||
if (reset) {
|
||||
sectors = newItems;
|
||||
} else {
|
||||
sectors = [...sectors, ...newItems];
|
||||
}
|
||||
|
||||
total = response.data.total;
|
||||
hasMore = newItems.length === pageSize && sectors.length < total;
|
||||
} else {
|
||||
sectors = [...sectors, ...newItems];
|
||||
error = response.error || 'Failed to load';
|
||||
status = response.status || 500;
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
total = response.total;
|
||||
// Safer end-of-data detection
|
||||
hasMore = newItems.length === pageSize && sectors.length < total;
|
||||
} catch (error) {
|
||||
console.error('Error loading sectors:', error);
|
||||
toast.error('Error al cargar sectores');
|
||||
} catch (err: any) {
|
||||
console.error('Error loading sectors:', err);
|
||||
error = err.message || 'Error al cargar sectores';
|
||||
status = err.status || 500;
|
||||
hasMore = false;
|
||||
} finally {
|
||||
loading = false;
|
||||
@@ -88,9 +112,12 @@
|
||||
if (sentinel) observer.observe(sentinel);
|
||||
}
|
||||
|
||||
// Initial load
|
||||
// Initial load and reload when company changes
|
||||
$effect(() => {
|
||||
untrack(() => loadSectors(true));
|
||||
const id = companyStore.activeCompany?.id;
|
||||
if (id) {
|
||||
untrack(() => loadSectors(true));
|
||||
}
|
||||
});
|
||||
|
||||
// Setup observer only when sentinel is available
|
||||
@@ -104,87 +131,106 @@
|
||||
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Gestiona los sectores de la tarifa.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Sectores</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar por clave o descripción..."
|
||||
class="h-9 w-44 bg-card pl-8 lg:w-64"
|
||||
bind:value={searchTerm}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
{#if isError}
|
||||
<ErrorState
|
||||
status={!canView ? 403 : status}
|
||||
error={!canView ? 'No tienes permiso para ver este catálogo' : (error || '')}
|
||||
onRetry={() => loadSectors(true)}
|
||||
/>
|
||||
{:else}
|
||||
|
||||
{#if error}
|
||||
<div class="flex-none rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-end gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar por clave o descripción..."
|
||||
class="h-9 w-44 bg-card pl-8 lg:w-64"
|
||||
bind:value={searchTerm}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-0 flex-1 overflow-auto">
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Clave</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Descripción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">Estatus</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if loading && page === 1}
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0 overflow-hidden flex-1 flex flex-col">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden border-none text-nowrap">
|
||||
<div class="min-h-0 flex-1 overflow-auto">
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Cell colspan={3} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Clave</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Descripción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">Estatus</Table.Head>
|
||||
</Table.Row>
|
||||
{:else if sectors.length === 0}
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Cell colspan={3} class="h-24 text-center">
|
||||
No se encontraron sectores.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each sectors as sector}
|
||||
<Table.Row inTabOrder={false} class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{sector.key}</Table.Cell>
|
||||
<Table.Cell>{sector.description}</Table.Cell>
|
||||
<Table.Cell class="text-right">
|
||||
{#if sector.authorized}
|
||||
<Badge variant="default">Autorizado</Badge>
|
||||
{:else}
|
||||
<Badge variant="secondary">No Autorizado</Badge>
|
||||
{/if}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if loading && page === 1}
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Cell colspan={3} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading && page > 1}
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Cell colspan={3} class="h-12 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
{:else if sectors.length === 0}
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Cell colspan={3} class="h-24 text-center">
|
||||
No se encontraron sectores.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each sectors as sector}
|
||||
<Table.Row inTabOrder={false} class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{sector.key}</Table.Cell>
|
||||
<Table.Cell>{sector.description}</Table.Cell>
|
||||
<Table.Cell class="text-right">
|
||||
{#if sector.authorized}
|
||||
<Badge variant="default">Autorizado</Badge>
|
||||
{:else}
|
||||
<Badge variant="secondary">No Autorizado</Badge>
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading && page > 1}
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Cell colspan={3} class="h-12 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {sectors.length} de {total} registros</div>
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {sectors.length} de {total} registros</div>
|
||||
|
||||
<!-- Infinite Scroll Sentinel -->
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
<!-- Infinite Scroll Sentinel -->
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { Signature } from '$lib/api/dashboard/a76/general_catalogs/signatur
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Signature>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<Signature>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -25,7 +28,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Signature>[] {
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Signature;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -67,19 +71,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { UnitConversion } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Header } from '$lib/components/ui/alert-dialog';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitConversion>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<UnitConversion>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'from_unit_code',
|
||||
@@ -24,7 +25,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitConversion>
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
conversion: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
conversion,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
conversion: UnitConversion;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -58,19 +62,23 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureACE>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<UnitOfMeasureACE>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -21,7 +24,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAC
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: UnitOfMeasureACE;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -59,19 +63,23 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { UnitOfMeasureAmerican } from "$lib/api/dashboard/a76/general_catal
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAmerican>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<UnitOfMeasureAmerican>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -16,11 +19,14 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAm
|
||||
{
|
||||
id: "actions",
|
||||
header: "Acciones",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
interface Props {
|
||||
unit: UnitOfMeasureAmerican;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
}
|
||||
|
||||
let { unit, onSuccess }: Props = $props();
|
||||
let { unit, onSuccess, canEdit = true, canDelete = true }: Props = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
@@ -42,15 +44,19 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => (dialogOpen = true)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete}>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (dialogOpen = true)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive focus:text-destructive">
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { UnitOfMeasureCustoms } from "$lib/api/dashboard/a76/general_catalo
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCustoms>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<UnitOfMeasureCustoms>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -20,11 +23,14 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCu
|
||||
{
|
||||
id: "actions",
|
||||
header: "Acciones",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalo
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureGeneral>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<UnitOfMeasureGeneral>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -21,7 +24,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureGe
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { UnitOfMeasureOMA } from "$lib/api/dashboard/a76/general_catalogs/u
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureOMA>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<UnitOfMeasureOMA>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -16,11 +19,14 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureOM
|
||||
{
|
||||
id: "actions",
|
||||
header: "Acciones",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
const response = await materialTypesApi.list(1, 100, 'ACTIVO FIJO');
|
||||
const response = await materialTypesApi.list(companyId, 1, 100, 'ACTIVO FIJO');
|
||||
if (response.data) {
|
||||
materialTypes = response.data.items;
|
||||
}
|
||||
|
||||
@@ -5,15 +5,24 @@
|
||||
deleteCanadianFraction,
|
||||
type CanadianFraction
|
||||
} from '$lib/api/dashboard/general_catalogs/canadian';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/components/ui/table';
|
||||
import { Search, Loader2, Plus, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import CanadianFractionDialog from './CanadianFractionDialog.svelte';
|
||||
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { currentUser, userHasPermission } from '$lib/auth';
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
|
||||
let fractions = $state<CanadianFraction[]>([]);
|
||||
let loading = $state(false);
|
||||
@@ -22,11 +31,13 @@
|
||||
let totalItems = $state(0);
|
||||
let totalPages = $state(0);
|
||||
let pageSize = 50;
|
||||
let error = $state<string | null>(null);
|
||||
let status = $state<number>(200);
|
||||
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let scrollContainer: HTMLDivElement;
|
||||
let sentinel = $state<HTMLDivElement | null>(null);
|
||||
let scrollContainer = $state<HTMLDivElement | null>(null);
|
||||
|
||||
// Infinite scroll state
|
||||
let hasMore = $state(true);
|
||||
@@ -35,6 +46,16 @@
|
||||
let dialogOpen = $state(false);
|
||||
let editingFraction = $state<CanadianFraction | null>(null);
|
||||
let deletingFractionId = $state<number | null>(null);
|
||||
let showDeleteConfirm = $state(false);
|
||||
let fractionToDelete = $state<CanadianFraction | null>(null);
|
||||
|
||||
// Permissions
|
||||
const canView = $derived(userHasPermission($currentUser, 'frac_canadian.view'));
|
||||
const canCreate = $derived(userHasPermission($currentUser, 'frac_canadian.create'));
|
||||
const canEdit = $derived(userHasPermission($currentUser, 'frac_canadian.edit'));
|
||||
const canDelete = $derived(userHasPermission($currentUser, 'frac_canadian.delete'));
|
||||
|
||||
const isError = $derived(!canView || status >= 400 || error);
|
||||
|
||||
async function loadFractions(reset = false) {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
@@ -42,6 +63,7 @@
|
||||
if (loading) return;
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
if (reset) {
|
||||
page = 1;
|
||||
@@ -71,19 +93,16 @@
|
||||
|
||||
// Safer end-of-data detection
|
||||
hasMore = newItems.length === pageSize && fractions.length < totalItems;
|
||||
} catch (error) {
|
||||
console.error('Error loading Canadian fractions:', error);
|
||||
toast.error('Error al cargar fracciones canadienses');
|
||||
} catch (err: any) {
|
||||
console.error('Error loading Canadian fractions:', err);
|
||||
error = err.message || 'Error al cargar fracciones canadienses';
|
||||
status = err.status || 500;
|
||||
hasMore = false;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
loadFractions(true);
|
||||
}
|
||||
|
||||
function handleSearchInput() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
@@ -94,7 +113,7 @@
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
clearTimeout(searchTimeout);
|
||||
handleSearch();
|
||||
loadFractions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,22 +127,29 @@
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDelete(fraction: CanadianFraction) {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
function confirmDelete(fraction: CanadianFraction) {
|
||||
fractionToDelete = fraction;
|
||||
showDeleteConfirm = true;
|
||||
}
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la fracción ${fraction.fraction}?`)) return;
|
||||
async function handleDelete() {
|
||||
if (!fractionToDelete || !companyStore.activeCompany?.id) return;
|
||||
|
||||
try {
|
||||
deletingFractionId = fraction.id;
|
||||
await deleteCanadianFraction(companyId, fraction.id);
|
||||
deletingFractionId = fractionToDelete.id;
|
||||
await deleteCanadianFraction(companyStore.activeCompany.id, fractionToDelete.id);
|
||||
toast.success('Fracción eliminada correctamente');
|
||||
loadFractions(true);
|
||||
} catch (error) {
|
||||
console.error('Error deleting Canadian fraction:', error);
|
||||
toast.error('Error al eliminar la fracción');
|
||||
} catch (err: any) {
|
||||
console.error('Error deleting Canadian fraction:', err);
|
||||
const msg = err.status === 403
|
||||
? 'No tienes permiso para eliminar este registro'
|
||||
: 'Error al eliminar la fracción';
|
||||
toast.error(msg);
|
||||
} finally {
|
||||
deletingFractionId = null;
|
||||
showDeleteConfirm = false;
|
||||
fractionToDelete = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +172,6 @@
|
||||
if (sentinel) observer.observe(sentinel);
|
||||
}
|
||||
|
||||
// Removed onMount as we use $effect for company changes which covers initial load
|
||||
|
||||
$effect(() => {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (companyId) {
|
||||
@@ -164,103 +188,151 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden">
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Fracciones Canadienses</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar por fracción o descripción..."
|
||||
class="h-9 w-44 bg-card pl-9 lg:w-64"
|
||||
bind:value={searchQuery}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
<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 gap-4">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-2xl font-bold tracking-tight">Listado de Fracciones Canadienses</h1>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Gestiona las fracciones arancelarias de la tarifa canadiense.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
{#if !isError && canCreate}
|
||||
<Button class="h-9" onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if isError}
|
||||
<ErrorState
|
||||
status={!canView ? 403 : status}
|
||||
error={!canView ? 'No tienes permiso para ver este catálogo' : (error || '')}
|
||||
onRetry={() => loadFractions(true)}
|
||||
/>
|
||||
{:else}
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col overflow-hidden shadow-sm">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-end gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar..."
|
||||
class="h-9 w-44 bg-card pl-9 lg:w-64"
|
||||
bind:value={searchQuery}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button class="h-9" onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-[360px] max-h-[calc(100svh-340px)] flex-1 overflow-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row>
|
||||
<Table.Head class="catalog-table-head-cell">Fracción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Descripción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">País</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Unidad</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">ADV</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Acciones</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={6} class="h-24 text-center">No se encontraron resultados</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<Table.Row class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{fraction.fraction}</Table.Cell>
|
||||
<Table.Cell>{fraction.description || '-'}</Table.Cell>
|
||||
<TableCell>{fraction.country_code}</TableCell>
|
||||
<TableCell>{fraction.unit_of_measure || '-'}</TableCell>
|
||||
<TableCell class="text-right">{fraction.ad_valorem ?? '-'}</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" onclick={() => handleEdit(fraction)}>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => handleDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={6} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0 overflow-hidden flex-1 flex flex-col">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden border-none">
|
||||
<div class="flex-1 overflow-auto" bind:this={scrollContainer}>
|
||||
<Table>
|
||||
<TableHeader class="catalog-table-header sticky top-0 bg-background z-10">
|
||||
<TableRow>
|
||||
<TableHead class="catalog-table-head-cell">Fracción</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Descripción</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">País</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Unidad</TableHead>
|
||||
<TableHead class="catalog-table-head-cell text-right">ADV</TableHead>
|
||||
{#if canEdit || canDelete}
|
||||
<TableHead class="catalog-table-head-cell w-[100px]">Acciones</TableHead>
|
||||
{/if}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
<TableRow>
|
||||
<TableCell colspan={canEdit || canDelete ? 6 : 5} class="h-24 text-center">No se encontraron resultados</TableCell>
|
||||
</TableRow>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<TableRow class="catalog-table-row">
|
||||
<TableCell class="font-medium">{fraction.fraction}</TableCell>
|
||||
<TableCell class="max-w-md truncate" title={fraction.description}>{fraction.description || '-'}</TableCell>
|
||||
<TableCell>{fraction.country_code}</TableCell>
|
||||
<TableCell>{fraction.unit_of_measure || '-'}</TableCell>
|
||||
<TableCell class="text-right">{fraction.ad_valorem ?? '-'}</TableCell>
|
||||
{#if canEdit || canDelete}
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if canEdit}
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" onclick={() => handleEdit(fraction)}>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => confirmDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading}
|
||||
<TableRow>
|
||||
<TableCell colspan={canEdit || canDelete ? 6 : 5} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {fractions.length} de {totalItems} registros</div>
|
||||
|
||||
<CanadianFractionDialog
|
||||
bind:open={dialogOpen}
|
||||
fraction={editingFraction}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
<div class="flex-none text-sm text-muted-foreground mt-2">Mostrando {fractions.length} de {totalItems} registros</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<AlertDialog.Root bind:open={showDeleteConfirm}>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
<AlertDialog.Title>¿Estás seguro?</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
Esta acción no se puede deshacer. Se eliminará la fracción arancelaria canadiense permanentemente.
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
<AlertDialog.Footer>
|
||||
<AlertDialog.Cancel>Cancelar</AlertDialog.Cancel>
|
||||
<AlertDialog.Action
|
||||
class="text-destructive-foreground bg-destructive hover:bg-destructive/90"
|
||||
onclick={handleDelete}
|
||||
>
|
||||
Eliminar
|
||||
</AlertDialog.Action>
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
|
||||
<CanadianFractionDialog
|
||||
bind:open={dialogOpen}
|
||||
fraction={editingFraction}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
@@ -12,14 +12,24 @@
|
||||
import { Search, Loader2, Plus, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import HistoricalFractionDialog from './HistoricalFractionDialog.svelte';
|
||||
import { currentUser, userHasPermission } from '$lib/auth';
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { title = 'Fracciones históricas' }: { title?: string } = $props();
|
||||
let { title = 'Fracciones históricas' }: { title?: string } = $props();
|
||||
|
||||
let fractions = $state<HistoricalFraction[]>([]);
|
||||
let loading = $state(false);
|
||||
let historicalFraction = $state('');
|
||||
// Permisos
|
||||
const canView = $derived(userHasPermission($currentUser, 'frac_historical.view'));
|
||||
const canCreate = $derived(userHasPermission($currentUser, 'frac_historical.create'));
|
||||
const canEdit = $derived(userHasPermission($currentUser, 'frac_historical.edit'));
|
||||
const canDelete = $derived(userHasPermission($currentUser, 'frac_historical.delete'));
|
||||
|
||||
let fractions = $state<HistoricalFraction[]>([]);
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let status = $state<number>(200);
|
||||
let historicalFraction = $state('');
|
||||
let page = $state(1);
|
||||
let totalItems = $state(0);
|
||||
let totalPages = $state(0);
|
||||
@@ -27,8 +37,8 @@ let pageSize = 50;
|
||||
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let scrollContainer: HTMLDivElement;
|
||||
let sentinel = $state<HTMLDivElement | null>(null);
|
||||
let scrollContainer = $state<HTMLDivElement | null>(null);
|
||||
|
||||
// Infinite scroll state
|
||||
let hasMore = $state(true);
|
||||
@@ -172,7 +182,6 @@ let scrollContainer: HTMLDivElement;
|
||||
|
||||
|
||||
<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">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
@@ -180,88 +189,101 @@ let scrollContainer: HTMLDivElement;
|
||||
Gestiona las fracciones históricas de la tarifa.
|
||||
</p>
|
||||
</div>
|
||||
<Button class="h-9" onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
{#if canView && canCreate}
|
||||
<Button class="h-9" onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Card.Root class="flex min-h-0 flex-1 flex-col border bg-background">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Fracciones Históricas</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar fracción..."
|
||||
class="h-9 w-40 bg-card pl-9 lg:w-56"
|
||||
bind:value={historicalFraction}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
{#if !canView}
|
||||
<ErrorState status={403} error="No tienes permiso para ver este catálogo" onRetry={() => loadFractions(true)} />
|
||||
{:else}
|
||||
|
||||
<Card.Root class="flex min-h-0 flex-1 flex-col border bg-background">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-end gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar fracción..."
|
||||
class="h-9 w-40 bg-card pl-9 lg:w-56"
|
||||
bind:value={historicalFraction}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-[360px] max-h-[calc(100svh-340px)] flex-1 overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row>
|
||||
<Table.Head class="catalog-table-head-cell">Fracción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Tipo</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">UM</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">País</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Fecha Pub.</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Fecha Fin</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">IGI</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">IGE</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Acciones</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-[360px] max-h-[calc(100svh-340px)] flex-1 overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={9} class="h-24 text-center">No se encontraron resultados</Table.Cell>
|
||||
<Table.Head class="catalog-table-head-cell">Fracción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Tipo</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">UM</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">País</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Fecha Pub.</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Fecha Fin</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">IGI</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">IGE</Table.Head>
|
||||
{#if canEdit || canDelete}
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Acciones</Table.Head>
|
||||
{/if}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<Table.Row class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{fraction.historical_fraction}</Table.Cell>
|
||||
<Table.Cell>{fraction.fraction_type || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.unit_of_measure_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.country || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.publication_date ? new Date(fraction.publication_date).toLocaleDateString() : '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.end_date ? new Date(fraction.end_date).toLocaleDateString() : '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.import_tax_rate ?? '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.export_tax_rate ?? '-'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" onclick={() => handleEdit(fraction)}>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => handleDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={canEdit || canDelete ? 9 : 8} class="h-24 text-center">No se encontraron resultados</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<Table.Row class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{fraction.historical_fraction}</Table.Cell>
|
||||
<Table.Cell>{fraction.fraction_type || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.unit_of_measure_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.country || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.publication_date ? new Date(fraction.publication_date).toLocaleDateString() : '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.end_date ? new Date(fraction.end_date).toLocaleDateString() : '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.import_tax_rate ?? '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.export_tax_rate ?? '-'}</Table.Cell>
|
||||
{#if canEdit || canDelete}
|
||||
<Table.Cell>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if canEdit}
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" onclick={() => handleEdit(fraction)}>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => handleDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={9} class="h-24 text-center">
|
||||
@@ -286,4 +308,5 @@ let scrollContainer: HTMLDivElement;
|
||||
fraction={editingFraction}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -21,17 +21,21 @@
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import TariffFractionFormDialog from './TariffFractionFormDialog.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { currentUser, userHasPermission } from '$lib/auth';
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
|
||||
let {
|
||||
title = 'Fracciones Arancelarias',
|
||||
catalog = 'mex', // 'mex' or 'usa'
|
||||
levelFilter = null, // null or number
|
||||
readOnly = false
|
||||
readOnly = false,
|
||||
basePerm: customBasePerm = null
|
||||
}: {
|
||||
title?: string;
|
||||
catalog?: string;
|
||||
levelFilter?: number | null;
|
||||
readOnly?: boolean;
|
||||
basePerm?: string | null;
|
||||
} = $props();
|
||||
|
||||
let fractions = $state<TariffFraction[]>([]);
|
||||
@@ -39,11 +43,31 @@
|
||||
let currentPage = $state(1);
|
||||
let pageSize = 50;
|
||||
let isLoading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let status = $state<number>(200);
|
||||
|
||||
// Permisos
|
||||
const permMap: Record<string, string> = {
|
||||
'mex': 'frac_sitar',
|
||||
'usa': 'frac_sitar_us',
|
||||
'american': 'frac_american',
|
||||
'canadian': 'frac_canadian',
|
||||
'historical': 'frac_historical'
|
||||
};
|
||||
const basePerm = $derived(customBasePerm || permMap[catalog] || 'frac_sitar');
|
||||
|
||||
const canView = $derived(userHasPermission($currentUser, `${basePerm}.view`));
|
||||
const canCreate = $derived(userHasPermission($currentUser, `${basePerm}.create`));
|
||||
const canEdit = $derived(userHasPermission($currentUser, `${basePerm}.edit`));
|
||||
const canDelete = $derived(userHasPermission($currentUser, `${basePerm}.delete`));
|
||||
|
||||
const isError = $derived(!canView || status >= 400 || error);
|
||||
|
||||
let search = $state('');
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let scrollContainer: HTMLDivElement;
|
||||
let sentinel = $state<HTMLDivElement | null>(null);
|
||||
let scrollContainer = $state<HTMLDivElement | null>(null);
|
||||
|
||||
// Infinite scroll state
|
||||
let hasMore = $state(true);
|
||||
@@ -62,6 +86,7 @@
|
||||
if (isLoading) return;
|
||||
|
||||
isLoading = true;
|
||||
error = null;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 1;
|
||||
@@ -76,25 +101,31 @@
|
||||
filters.catalog = catalog;
|
||||
|
||||
const response = await getTariffFractions(currentPage, pageSize, companyId, filters);
|
||||
const payload = (response.data || response) as any;
|
||||
|
||||
if (response.data) {
|
||||
const newItems = response.data.items || [];
|
||||
if (payload?.items) {
|
||||
const newItems = payload.items || [];
|
||||
if (reset) {
|
||||
fractions = newItems;
|
||||
} else {
|
||||
fractions = [...fractions, ...newItems];
|
||||
}
|
||||
totalFractions = response.data.total;
|
||||
totalFractions = payload.total || 0;
|
||||
|
||||
// Safer end-of-data detection
|
||||
hasMore = newItems.length === pageSize && fractions.length < totalFractions;
|
||||
} else {
|
||||
if (reset) fractions = [];
|
||||
hasMore = false;
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
status = (response as any).status || 500;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading fractions:', error);
|
||||
toast.error('Error al cargar las fracciones');
|
||||
} catch (err: any) {
|
||||
console.error('Error loading fractions:', err);
|
||||
error = err.message || 'Error al cargar las fracciones';
|
||||
status = err.status || 500;
|
||||
hasMore = false;
|
||||
} finally {
|
||||
isLoading = false;
|
||||
@@ -149,20 +180,20 @@
|
||||
if (!fractionToDelete || !companyStore.activeCompany?.id) return;
|
||||
|
||||
try {
|
||||
// Note: Delete might allow deleting items from source API if allowed,
|
||||
// or just local overrides. Assuming Service handles logic.
|
||||
await deleteTariffFraction(fractionToDelete.id, companyStore.activeCompany.id, catalog);
|
||||
toast.success('Fracción eliminada correctamente');
|
||||
loadFractions(true);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Error deleting fraction:', error);
|
||||
toast.error('Error al eliminar la fracción. Puede que esté en uso.');
|
||||
const msg = error.status === 403
|
||||
? 'No tienes permiso para eliminar este registro'
|
||||
: 'Error al eliminar la fracción. Puede que esté en uso.';
|
||||
toast.error(msg);
|
||||
} finally {
|
||||
showDeleteConfirm = false;
|
||||
fractionToDelete = null;
|
||||
}
|
||||
}
|
||||
// Removed onMount as we use $effect for company changes which covers initial load
|
||||
|
||||
// Reload when company changes
|
||||
$effect(() => {
|
||||
@@ -182,117 +213,142 @@
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
{#if !readOnly}
|
||||
<Button class="h-9" onclick={openCreateDialog}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
{/if}
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Gestiona las fracciones arancelarias de la tarifa.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
{#if !isError && !readOnly && canCreate}
|
||||
<Button class="h-9" onclick={openCreateDialog}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Fracciones</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Buscar..." class="h-9 w-40 bg-card pl-8 lg:w-56" bind:value={search} oninput={handleSearchInput} />
|
||||
{#if isError}
|
||||
<ErrorState
|
||||
status={!canView ? 403 : status}
|
||||
error={!canView ? 'No tienes permiso para ver este catálogo' : (error || '')}
|
||||
onRetry={() => loadFractions(true)}
|
||||
/>
|
||||
{:else}
|
||||
|
||||
{#if error}
|
||||
<div class="flex-none rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-end gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Buscar..." class="h-9 w-40 bg-card pl-8 lg:w-56" bind:value={search} oninput={handleSearchInput} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-[360px] max-h-[calc(100svh-340px)] flex-1 overflow-auto" bind:this={scrollContainer}>
|
||||
<Table>
|
||||
<TableHeader class="catalog-table-header">
|
||||
<TableRow>
|
||||
<TableHead class="catalog-table-head-cell">Clave</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Fracción</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Descripción</TableHead>
|
||||
{#if catalog === 'mex'}
|
||||
<TableHead class="catalog-table-head-cell">NICO</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">U.M.T</TableHead>
|
||||
{:else}
|
||||
<TableHead class="catalog-table-head-cell">Unidad</TableHead>
|
||||
{/if}
|
||||
<TableHead class="catalog-table-head-cell">Adv. Impo</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Adv. Expo</TableHead>
|
||||
{#if !readOnly}
|
||||
<TableHead class="catalog-table-head-cell w-[100px]">Acciones</TableHead>
|
||||
{/if}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if fractions.length === 0 && !isLoading}
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0 overflow-hidden flex-1 flex flex-col">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden border-none">
|
||||
<div class="flex-1 overflow-auto" bind:this={scrollContainer}>
|
||||
<Table>
|
||||
<TableHeader class="catalog-table-header sticky top-0 bg-background z-10">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
No se encontraron resultados
|
||||
</TableCell>
|
||||
<TableHead class="catalog-table-head-cell">Clave</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Fracción</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Descripción</TableHead>
|
||||
{#if catalog === 'mex'}
|
||||
<TableHead class="catalog-table-head-cell">NICO</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">U.M.T</TableHead>
|
||||
{:else if catalog === 'usa' || catalog === 'american' || catalog === 'canadian'}
|
||||
<TableHead class="catalog-table-head-cell">Unidad</TableHead>
|
||||
{/if}
|
||||
<TableHead class="catalog-table-head-cell">Adv. Impo</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Adv. Expo</TableHead>
|
||||
{#if !readOnly && (canEdit || canDelete)}
|
||||
<TableHead class="catalog-table-head-cell w-[100px]">Acciones</TableHead>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<TableRow class="catalog-table-row">
|
||||
<TableCell class="font-mono">{fraction.um_code || fraction.code}</TableCell>
|
||||
<TableCell class="font-medium">{fraction.fraction}</TableCell>
|
||||
<TableCell class="max-w-md truncate" title={fraction.description}>
|
||||
{fraction.description}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if fractions.length === 0 && !isLoading}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
No se encontraron resultados
|
||||
</TableCell>
|
||||
{#if catalog === 'mex'}
|
||||
<TableCell>{fraction.nico || '-'}</TableCell>
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
{:else}
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
{/if}
|
||||
<TableCell>{fraction.adv_impo || '-'}</TableCell>
|
||||
<TableCell>{fraction.adv_expo || '-'}</TableCell>
|
||||
{#if !readOnly}
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" onclick={() => openEditDialog(fraction)}>
|
||||
<Edit class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="text-destructive hover:text-destructive"
|
||||
onclick={() => confirmDelete(fraction)}
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if isLoading}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
{:else}
|
||||
{#each fractions as fraction (fraction.id)}
|
||||
<TableRow class="catalog-table-row">
|
||||
<TableCell class="font-mono">{fraction.um_code || fraction.code}</TableCell>
|
||||
<TableCell class="font-medium">{fraction.fraction}</TableCell>
|
||||
<TableCell class="max-w-md truncate" title={fraction.description}>
|
||||
{fraction.description}
|
||||
</TableCell>
|
||||
{#if catalog === 'mex'}
|
||||
<TableCell>{fraction.nico || '-'}</TableCell>
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
{:else if catalog === 'usa' || catalog === 'american' || catalog === 'canadian'}
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
{/if}
|
||||
<TableCell>{fraction.adv_impo || '-'}</TableCell>
|
||||
<TableCell>{fraction.adv_expo || '-'}</TableCell>
|
||||
{#if (!readOnly && (canEdit || canDelete))}
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if canEdit}
|
||||
<Button variant="ghost" size="icon" onclick={() => openEditDialog(fraction)}>
|
||||
<Edit class="h-4 w-4" />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="text-destructive hover:text-destructive"
|
||||
onclick={() => confirmDelete(fraction)}
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if isLoading}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {fractions.length} de {totalFractions} registros</div>
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {fractions.length} de {totalFractions} registros</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<AlertDialog.Root bind:open={showDeleteConfirm}>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import * as Table from "$lib/components/ui/table";
|
||||
import { Search, Loader2, Globe } from "lucide-svelte";
|
||||
import { countriesApi, type Country } from "$lib/api/dashboard/reference_data/countries";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
// --- PROPS ---
|
||||
@@ -96,7 +97,15 @@
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await countriesApi.list(page, pageSize, searchTerm);
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
toast.error("No hay una empresa activa seleccionada");
|
||||
loading = false;
|
||||
loadingMore = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await countriesApi.list(companyId, page, pageSize, searchTerm);
|
||||
|
||||
if (response.error) {
|
||||
toast.error(`Error: ${response.error}`);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { Search, Loader2, Layers, Tag, Box } from 'lucide-svelte';
|
||||
// Importamos la interfaz corregida
|
||||
import { materialTypesApi, type MaterialType } from '$lib/api/dashboard/a76/material-types';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
// --- PROPS ---
|
||||
let {
|
||||
@@ -34,9 +35,12 @@
|
||||
});
|
||||
|
||||
async function loadMaterials() {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
const res = await materialTypesApi.list(1, 100);
|
||||
const res = await materialTypesApi.list(companyId, 1, 100);
|
||||
|
||||
const responseData = (res as any).data || res;
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Loader2, Search } from 'lucide-svelte';
|
||||
import { countriesApi } from '$lib/api/dashboard/reference_data/countries';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
open = $bindable(),
|
||||
@@ -22,27 +25,27 @@
|
||||
loading = true;
|
||||
error = '';
|
||||
try {
|
||||
const response = await fetch('/api-sveltekit/countries', {
|
||||
credentials: 'include'
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (Array.isArray(data)) {
|
||||
countries = data;
|
||||
} else if (data.items && Array.isArray(data.items)) {
|
||||
countries = data.items;
|
||||
} else {
|
||||
console.error('Unexpected data format:', data);
|
||||
countries = [];
|
||||
}
|
||||
filteredCountries = countries;
|
||||
} else {
|
||||
error = `Error: ${response.status} - ${response.statusText}`;
|
||||
console.error('Error response:', await response.text());
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'No hay una empresa activa seleccionada';
|
||||
return;
|
||||
}
|
||||
|
||||
// Para este diálogo, cargamos una cantidad grande o implementamos paginación si fuera necesario
|
||||
// Por ahora seguimos el patrón original de cargar "todos" (limite 100 en backend)
|
||||
const response = await countriesApi.list(companyId, 1, 100);
|
||||
|
||||
if (response.data) {
|
||||
countries = response.data.items || [];
|
||||
filteredCountries = countries;
|
||||
} else if (response.error) {
|
||||
error = `Error: ${response.error}`;
|
||||
toast.error(error);
|
||||
}
|
||||
} catch (err) {
|
||||
error = 'Error loading countries';
|
||||
console.error('Error loading countries:', err);
|
||||
toast.error(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Package>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Package>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'key',
|
||||
@@ -63,7 +66,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Package>[] {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Package;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -79,7 +83,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -90,24 +94,30 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={handleDialogSuccess}
|
||||
/>
|
||||
{#if canEdit}
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={handleDialogSuccess}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Port>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Port>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'port_code',
|
||||
@@ -40,7 +43,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Port>[] {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Port;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let editDialogOpen = $state(false);
|
||||
@@ -19,7 +23,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -29,26 +33,34 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => editDialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => deleteDialogOpen = true} class="text-destructive">
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => editDialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => deleteDialogOpen = true} class="text-destructive">
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={editDialogOpen}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
{#if canEdit}
|
||||
<CreateEditDialog
|
||||
bind:open={editDialogOpen}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<DeleteDialog
|
||||
bind:open={deleteDialogOpen}
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
{#if canDelete}
|
||||
<DeleteDialog
|
||||
bind:open={deleteDialogOpen}
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -10,7 +10,10 @@ export type CodePedimentoRegimen = {
|
||||
type_code: string | null;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CodePedimentoRegimen>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<CodePedimentoRegimen>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "pedimento_code",
|
||||
@@ -67,7 +70,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<CodePedimentoRe
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CodePedimentoRegimen } from "./columns.js";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import EllipsisIcon from '@lucide/svelte/icons/ellipsis';
|
||||
import Copy from '@lucide/svelte/icons/copy';
|
||||
import Eye from '@lucide/svelte/icons/eye';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
||||
import type { CodePedimentoRegimen } from './columns.js';
|
||||
import DetailsDialog from './details-dialog.svelte';
|
||||
|
||||
let {
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: CodePedimentoRegimen;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -37,13 +45,38 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar ID
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item
|
||||
onclick={() => alert('Módulo de edición no disponible para Catálogos Públicos')}
|
||||
>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item
|
||||
onclick={() => alert('Módulo de eliminación no disponible para Catálogos Públicos')}
|
||||
class="text-destructive"
|
||||
>
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
|
||||
@@ -8,7 +8,10 @@ export type Container = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Container>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Container>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -40,11 +43,15 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Container>[] {
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Mantener compatibilidad hacia atrás
|
||||
export const columns = createColumns();
|
||||
// Legacy export removed
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { Container } from "./columns.js";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import EllipsisIcon from '@lucide/svelte/icons/ellipsis';
|
||||
import Copy from '@lucide/svelte/icons/copy';
|
||||
import Eye from '@lucide/svelte/icons/eye';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
||||
import type { Container } from './columns.js';
|
||||
import DetailsDialog from './details-dialog.svelte';
|
||||
|
||||
let {
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Container;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,10 +26,6 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -37,13 +41,38 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
Copiar ID
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Código
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item
|
||||
onclick={() => alert('Módulo de edición no disponible para Catálogos Públicos')}
|
||||
>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item
|
||||
onclick={() => alert('Módulo de eliminación no disponible para Catálogos Públicos')}
|
||||
class="text-destructive"
|
||||
>
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
|
||||
@@ -11,7 +11,10 @@ export type Country = {
|
||||
description_en: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Country>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Country>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "m3_key",
|
||||
@@ -84,11 +87,15 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Country>[] {
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Mantener compatibilidad hacia atrás
|
||||
export const columns = createColumns();
|
||||
// Legacy export removed
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { Country } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Country;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.m3_key.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
Copiar ID
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave M3
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ export type CurrencyType = {
|
||||
country_description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CurrencyType>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<CurrencyType>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -54,11 +57,15 @@ export function createColumns(onSuccess?: () => void): ColumnDef<CurrencyType>[]
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Mantener compatibilidad hacia atrás
|
||||
export const columns = createColumns();
|
||||
// Legacy export removed
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CurrencyType } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: CurrencyType;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Código
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ export type CustomsSection = {
|
||||
section_name: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CustomsSection>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<CustomsSection>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "customs_code",
|
||||
@@ -40,11 +43,15 @@ export function createColumns(onSuccess?: () => void): ColumnDef<CustomsSection>
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Mantener compatibilidad hacia atrás
|
||||
export const columns = createColumns();
|
||||
// Legacy export removed
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CustomsSection } from "./columns.js";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: CustomsSection;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.customs_code.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,13 +45,42 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Código
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (showEditDialog = true)}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (showDeleteDialog = true)} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
|
||||
{#if canEdit}
|
||||
<CreateEditDialog bind:open={showEditDialog} {item} {onSuccess} />
|
||||
{/if}
|
||||
|
||||
{#if canDelete}
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
{/if}
|
||||
|
||||
@@ -9,7 +9,10 @@ export type CustomsWarehouse = {
|
||||
fiscalized_warehouse: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CustomsWarehouse>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<CustomsWarehouse>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -54,7 +57,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<CustomsWarehous
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CustomsWarehouse } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: CustomsWarehouse;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -25,7 +33,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +45,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ export type Incoterm = {
|
||||
description_en: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Incoterm>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<Incoterm>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -54,7 +57,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Incoterm>[] {
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { Incoterm } from "./columns.js";
|
||||
@@ -7,16 +10,20 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Incoterm;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
navigator.clipboard.writeText(item.code);
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
@@ -25,7 +32,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +44,31 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
Copiar Código
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ export type InvoiceType = {
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<InvoiceType>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<InvoiceType>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -68,7 +71,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<InvoiceType>[]
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { InvoiceType } from "./columns.js";
|
||||
@@ -7,16 +11,20 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: InvoiceType;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
navigator.clipboard.writeText(item.key);
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
@@ -25,7 +33,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +45,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ export type MaterialType = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<MaterialType>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<MaterialType>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -54,7 +57,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<MaterialType>[]
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { MaterialType } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: MaterialType;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ export type PaymentMethod = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<PaymentMethod>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<PaymentMethod>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -40,7 +43,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<PaymentMethod>[
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { PaymentMethod } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: PaymentMethod;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ export type PedimentoCode = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<PedimentoCode>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<PedimentoCode>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -40,7 +43,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<PedimentoCode>[
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { PedimentoCode } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: PedimentoCode;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Código
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ export type PedimentoRegimen = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<PedimentoRegimen>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<PedimentoRegimen>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -40,11 +43,15 @@ export function createColumns(onSuccess?: () => void): ColumnDef<PedimentoRegime
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Mantener compatibilidad hacia atrás
|
||||
export const columns = createColumns();
|
||||
// Legacy export removed
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { PedimentoRegimen } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: PedimentoRegimen;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Código
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@ export type Sector = {
|
||||
tenant_id: number;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Sector>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Sector>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -60,7 +63,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Sector>[] {
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,43 +1,35 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { Sector } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Sector;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -49,18 +41,34 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -10,7 +10,10 @@ export type State = {
|
||||
ame_key?: string | null;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<State>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<State>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "m3_key",
|
||||
@@ -74,7 +77,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<State>[] {
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,43 +1,35 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { State } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: State;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.m3_key.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -49,18 +41,34 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
Copiar Clave M3
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -8,7 +8,10 @@ export type TransportMode = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<TransportMode>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<TransportMode>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -40,11 +43,15 @@ export function createColumns(onSuccess?: () => void): ColumnDef<TransportMode>[
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Mantener compatibilidad hacia atrás
|
||||
export const columns = createColumns();
|
||||
// Legacy export removed
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { TransportMode } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: TransportMode;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => {/* Edición no disponible por ahora para catálogos públicos en UI */ alert('Módulo de edición no disponible para Catálogos Públicos')}}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => {/* Eliminación no disponible por ahora para catálogos públicos en UI */ alert('Módulo de eliminación no disponible para Catálogos Públicos')}} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ export type TransportType = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<TransportType>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<TransportType>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "transport_code",
|
||||
@@ -40,7 +43,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<TransportType>[
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { TransportType } from "./columns.js";
|
||||
@@ -7,10 +11,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: TransportType;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
@@ -18,14 +26,10 @@
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.transport_code.toString());
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +41,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Código
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => (showDetailsDialog = true)}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ export type ValuationMethod = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ValuationMethod>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
{ canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<ValuationMethod>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "key",
|
||||
@@ -40,7 +43,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ValuationMethod
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { item: row.original, onSuccess });
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import Copy from "@lucide/svelte/icons/copy";
|
||||
import Eye from "@lucide/svelte/icons/eye";
|
||||
import Pencil from "@lucide/svelte/icons/pencil";
|
||||
import Trash2 from "@lucide/svelte/icons/trash-2";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { ValuationMethod } from "./columns.js";
|
||||
@@ -7,16 +11,20 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: ValuationMethod;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
navigator.clipboard.writeText(item.key);
|
||||
}
|
||||
|
||||
function handleViewDetails() {
|
||||
@@ -25,7 +33,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -37,11 +45,32 @@
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleCopyId}>
|
||||
<Copy class="mr-2 size-4" />
|
||||
Copiar Clave
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>
|
||||
<Eye class="mr-2 size-4" />
|
||||
Ver detalles
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
|
||||
{#if canEdit || canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de edición no disponible para Catálogos Públicos'))}>
|
||||
<Pencil class="mr-2 size-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Item onclick={() => (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive">
|
||||
<Trash2 class="mr-2 size-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Group>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Seal>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Seal>[] {
|
||||
return [
|
||||
|
||||
{
|
||||
@@ -14,12 +17,12 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Seal>[] {
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
meta: {
|
||||
class: 'w-[100px] text-right'
|
||||
},
|
||||
meta: {},
|
||||
cell: ({ row }) => renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Seal;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -75,19 +79,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Driver>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Driver>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'transporter_key',
|
||||
@@ -44,7 +47,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Driver>[] {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Driver;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -86,7 +90,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -97,19 +101,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Trailer>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Trailer>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'trailer_number',
|
||||
@@ -56,7 +59,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Trailer>[] {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Trailer;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -81,7 +85,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -92,19 +96,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Transporter;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -84,7 +88,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -95,19 +99,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Transporter>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Transporter>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'transporter_key',
|
||||
@@ -56,7 +59,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Transporter>[]
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Vehicle>[] {
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true }
|
||||
): ColumnDef<Vehicle>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'vehicle_key',
|
||||
@@ -56,7 +59,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Vehicle>[] {
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit: permissions.canEdit,
|
||||
canDelete: permissions.canDelete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Vehicle;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -81,7 +85,7 @@
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
@@ -92,19 +96,23 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import { Title } from '../ui/alert';
|
||||
export interface NavItem {
|
||||
title: string;
|
||||
url: string;
|
||||
permission?: string;
|
||||
}
|
||||
|
||||
export interface NavMainItem {
|
||||
@@ -31,6 +32,7 @@ export interface NavMainItem {
|
||||
url: string;
|
||||
icon: any;
|
||||
isActive?: boolean;
|
||||
permission?: string;
|
||||
items?: NavItem[];
|
||||
}
|
||||
|
||||
@@ -108,6 +110,7 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.reference_data.currency_types"](),
|
||||
url: "/dashboard/reference_data/currency_types",
|
||||
permission: "ref_currency_types.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.reference_data.customs_sections"](),
|
||||
@@ -245,14 +248,17 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.general_catalogs.conversions"](),
|
||||
url: "/dashboard/general_catalogs/unit_conversions",
|
||||
permission: "cat_unit_conversions.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.equivalences"](),
|
||||
url: "/dashboard/general_catalogs/equivalencies",
|
||||
permission: "cat_equivalencies.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.exchange_rates"](),
|
||||
url: "/dashboard/general_catalogs/exchange-rate",
|
||||
permission: "cat_exchange_rates.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.currency_types"](),
|
||||
@@ -261,6 +267,7 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.general_catalogs.multi_currency"](),
|
||||
url: "/dashboard/general_catalogs/multi_currency_types",
|
||||
permission: "cat_multi_currency_types.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.invoice_types"](),
|
||||
@@ -411,23 +418,28 @@ export function getSidebarData(): SidebarData {
|
||||
items: [
|
||||
{
|
||||
title: m["sidebar.import_invoices.temporary"](),
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=TEM"
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=TEM",
|
||||
permission: "invoice.imp.tem.view"
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.definitive"](),
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=DEF",
|
||||
permission: "invoice.imp.def.view"
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.mexican_purchases"](),
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=MEX",
|
||||
permission: "invoice.imp.cm.view"
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.regime_change"](),
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=CR",
|
||||
permission: "invoice.imp.cr.view"
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.repair"](),
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=REP",
|
||||
permission: "invoice.imp.rep.view"
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -439,10 +451,12 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.export_invoices.exportation"](),
|
||||
url: "/dashboard/invoices?operation_type=exp",
|
||||
permission: "invoice.exp.view"
|
||||
},
|
||||
{
|
||||
title: m["sidebar.export_invoices.repair"](),
|
||||
url: "/dashboard/invoices?operation_type=exp&invoice_type=REPAR",
|
||||
permission: "invoice.exp.rep.view"
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
||||
import { useSidebar } from '$lib/components/ui/sidebar/context.svelte.js';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
import { authStore, userHasPermission } from '$lib/auth';
|
||||
|
||||
let {
|
||||
items
|
||||
@@ -13,13 +14,37 @@
|
||||
url: string;
|
||||
icon?: any;
|
||||
isActive?: boolean;
|
||||
permission?: string;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
permission?: string;
|
||||
}[];
|
||||
}[];
|
||||
} = $props();
|
||||
|
||||
// Filtrar items según permisos (si el item tiene la propiedad 'permission')
|
||||
const filteredItems = $derived(
|
||||
items
|
||||
.map((item) => ({
|
||||
...item,
|
||||
items: item.items?.filter((subItem) => {
|
||||
if (subItem.permission && !userHasPermission($authStore.user, subItem.permission)) return false;
|
||||
return true;
|
||||
})
|
||||
}))
|
||||
.filter((item) => {
|
||||
// 1. Filtrar por permiso explícito del item principal
|
||||
if (item.permission && !userHasPermission($authStore.user, item.permission)) return false;
|
||||
|
||||
// 2. Ocultar categorías (url="#") que se quedaron sin sub-items visibles
|
||||
if (item.url === '#' && item.items && item.items.length === 0) return false;
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
const sidebar = useSidebar();
|
||||
|
||||
// Estado del Sistema Híbrido controlado por hover estricto (sin timers)
|
||||
@@ -79,7 +104,7 @@
|
||||
<Sidebar.Group>
|
||||
<Sidebar.GroupLabel>Anexo-76</Sidebar.GroupLabel>
|
||||
<Sidebar.Menu id="dashboard-sidebar-nav" aria-label="Navegación principal">
|
||||
{#each items as item (item.title)}
|
||||
{#each filteredItems as item (item.title)}
|
||||
{#if item.items && item.items.length > 0}
|
||||
{#if sidebar.state === 'collapsed'}
|
||||
<!-- Sidebar Colapsado: Dropdown controlado por eventos estrictos -->
|
||||
|
||||
Reference in New Issue
Block a user