feat: replace SVG icons with Lucide icons in reference data pages

- Updated the "Nueva Sección" button in customs_sections, customs_warehouses, incoterms, invoice_types, material_types, payment_methods, pedimento_codes, pedimento_regimens, sectors, states, transport_modes, and transport_types pages to use the Plus icon from Lucide.
- Updated the "Actualizar" button in the same pages to use the RefreshCw icon from Lucide.
- Added a new edit dialog component for customs brokers with a comprehensive form for editing broker details, including validation and loading states.
This commit is contained in:
2025-11-24 16:43:53 -06:00
parent cdc3788a05
commit dcd42583d9
63 changed files with 1232 additions and 1810 deletions

View File

@@ -9,6 +9,7 @@
import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types";
import { companyStore } from "$lib/stores/company.svelte";
import { onMount } from 'svelte';
import { LoaderCircle, Home } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -248,21 +249,7 @@
{#if companyStore.activeCompany}
<div class="rounded-md bg-blue-50 border border-blue-200 p-3">
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-blue-600"
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
<Home class="h-4 w-4 text-blue-600" />
<div>
<p class="text-sm font-medium text-blue-900">
{companyStore.activeCompany.name}
@@ -426,26 +413,7 @@
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Guardando...
{:else}
{isEdit ? 'Actualizar' : 'Crear'}

View File

@@ -3,6 +3,7 @@
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { classesApi, type A76Class } from "$lib/api/dashboard/a76/classes";
import { companyStore } from "$lib/stores/company.svelte";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
import CreateEditDialog from "./create-edit-dialog.svelte";
let {
@@ -80,21 +81,7 @@
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="19" r="1" />
</svg>
<EllipsisVertical size={16} />
</Button>
{/snippet}
</DropdownMenu.Trigger>
@@ -102,63 +89,15 @@
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleEdit}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
<path d="m15 5 4 4" />
</svg>
<Pencil size={16} class="mr-2" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
<Trash2 size={16} class="mr-2" />
{/if}
Eliminar
</DropdownMenu.Item>

View File

@@ -56,10 +56,10 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClientProvider>
const typeSnippet = createRawSnippet<[{ type: string | null | undefined }]>((getType) => {
const { type } = getType();
const displayType = type === 'client' ? 'Cliente' : type === 'provider' ? 'Proveedor' : type === 'both' ? 'Ambos' : 'N/A';
const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'
const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'
: type === 'provider' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
: type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
: type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
return {
render: () => `<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">${displayType}</span>`
};
@@ -81,21 +81,21 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClientProvider>
}
},
{
accessorKey: "enabled_disabled",
accessorKey: "is_active",
header: "Estado",
cell: ({ row }) => {
const statusSnippet = createRawSnippet<[{ status: number | undefined }]>((getStatus) => {
const { status } = getStatus();
const isEnabled = status === 1;
const statusText = isEnabled ? 'Activo' : 'Inactivo';
const colorClass = isEnabled
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
const colorClass = isEnabled
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
return {
render: () => `<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">${statusText}</span>`
};
});
return renderSnippet(statusSnippet, { status: row.original.enabled_disabled });
return renderSnippet(statusSnippet, { status: row.original.is_active });
}
},
{

View File

@@ -5,6 +5,7 @@
import { Label } from "$lib/components/ui/label";
import { clientsProvidersApi, type ClientProvider, type CreateClientProviderData, type UpdateClientProviderData } from "$lib/api/dashboard/a76/clients-providers";
import { companyStore } from "$lib/stores/company.svelte";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -24,7 +25,7 @@
domicile_fiscal: "",
foreign_tax_id: "",
client_or_provider: "client",
enabled_disabled: 1,
is_active: true,
// Address fields
street: "",
neighborhood: "",
@@ -51,7 +52,7 @@
domicile_fiscal: item.domicile_fiscal || "",
foreign_tax_id: item.foreign_tax_id || "",
client_or_provider: item.client_or_provider || "client",
enabled_disabled: item.enabled_disabled ?? 1,
is_active: item.is_active ?? true,
street: item.address?.street || "",
neighborhood: item.address?.neighborhood || "",
city: item.address?.city || "",
@@ -70,7 +71,7 @@
domicile_fiscal: "",
foreign_tax_id: "",
client_or_provider: "client",
enabled_disabled: 1,
is_active: true,
street: "",
neighborhood: "",
city: "",
@@ -107,7 +108,7 @@
domicile_fiscal: formData.domicile_fiscal || null,
foreign_tax_id: formData.foreign_tax_id || null,
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
enabled_disabled: formData.enabled_disabled,
is_active: formData.is_active,
address: {
street: formData.street || null,
neighborhood: formData.neighborhood || null,
@@ -132,7 +133,7 @@
domicile_fiscal: formData.domicile_fiscal || null,
foreign_tax_id: formData.foreign_tax_id || null,
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
enabled_disabled: formData.enabled_disabled,
is_active: formData.is_active,
address: {
street: formData.street || null,
neighborhood: formData.neighborhood || null,
@@ -185,7 +186,7 @@
domicile_fiscal: "",
foreign_tax_id: "",
client_or_provider: "client",
enabled_disabled: 1,
is_active: true,
street: "",
neighborhood: "",
city: "",
@@ -432,26 +433,7 @@
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
{isEditing ? "Guardar cambios" : "Crear"}
</Button>

View File

@@ -90,7 +90,7 @@
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleToggleStatus} disabled={isToggling}>
{isToggling ? 'Cambiando...' : item.enabled_disabled === 1 ? 'Desactivar' : 'Activar'}
{isToggling ? 'Cambiando...' : item.is_active === true ? 'Desactivar' : 'Activar'}
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>

View File

@@ -3,6 +3,7 @@
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { clientsProvidersApi, type ClientProvider } from "$lib/api/dashboard/a76/clients-providers";
import { companyStore } from "$lib/stores/company.svelte";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -97,26 +98,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -85,7 +85,7 @@
<div class="flex flex-col gap-1">
<span class="text-xs font-medium text-muted-foreground">Estado</span>
{#if item.enabled_disabled === 1}
{#if item.is_active === true}
<span class="inline-flex w-fit items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-300">
Activo
</span>

View File

@@ -5,6 +5,7 @@
import type { CustomsBroker } from "./columns.js";
import DetailsDialog from "./details-dialog.svelte";
import DeleteDialog from "./delete-dialog.svelte";
import EditDialog from "./edit-dialog.svelte";
let {
broker,
@@ -16,6 +17,7 @@
let showDetailsDialog = $state(false);
let showDeleteDialog = $state(false);
let showEditDialog = $state(false);
function handleCopyKey() {
navigator.clipboard.writeText(broker.broker_key);
@@ -31,6 +33,10 @@
showDetailsDialog = true;
}
function handleEdit() {
showEditDialog = true;
}
function handleDelete() {
showDeleteDialog = true;
}
@@ -64,6 +70,9 @@
<DropdownMenu.Item onclick={handleViewDetails}>
Ver detalles
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleEdit}>
Editar
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleDelete} class="text-destructive">
Eliminar
</DropdownMenu.Item>
@@ -72,4 +81,5 @@
</DropdownMenu.Root>
<DetailsDialog bind:open={showDetailsDialog} {broker} />
<EditDialog bind:open={showEditDialog} {broker} {onSuccess} />
<DeleteDialog bind:open={showDeleteDialog} {broker} {onSuccess} />

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { customsBrokersApi, type CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -92,26 +93,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Eliminando...
{:else}
Eliminar

View File

@@ -0,0 +1,384 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Dialog from "$lib/components/ui/dialog";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { customsBrokersApi, type CreateCustomsBrokerData, type CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers";
import { companyStore } from "$lib/stores/company.svelte";
let {
open = $bindable(false),
broker,
onSuccess
}: {
open: boolean;
broker: CustomsBroker;
onSuccess?: () => void;
} = $props();
let formData = $state({
name: "",
type: "",
address: "",
postal_code: "",
city: "",
state: "",
phone: "",
fax: "",
email: "",
country: "",
tax_id: "",
personal_id: "",
position: "",
license: "",
company: "",
contact: ""
});
let loading = $state(false);
let error = $state<string | null>(null);
// Inicializar formulario cuando cambia el broker
$effect(() => {
if (open && broker) {
formData = {
name: broker.name || "",
type: broker.type || "",
address: broker.address || "",
postal_code: broker.postal_code || "",
city: broker.city || "",
state: broker.state || "",
phone: broker.phone || "",
fax: broker.fax || "",
email: broker.email || "",
country: broker.country || "",
tax_id: broker.tax_id || "",
personal_id: broker.personal_id || "",
position: broker.position || "",
license: broker.license || "",
company: broker.company || "",
contact: broker.contact || ""
};
}
});
async function handleSubmit(e: Event) {
e.preventDefault();
if (!companyStore.activeCompany) {
error = "No hay compañía seleccionada";
return;
}
loading = true;
error = null;
try {
const payload: CreateCustomsBrokerData = {
broker_key: broker.broker_key, // La clave no se edita
name: formData.name || null,
type: formData.type || null,
address: formData.address || null,
postal_code: formData.postal_code || null,
city: formData.city || null,
state: formData.state || null,
phone: formData.phone || null,
fax: formData.fax || null,
email: formData.email || null,
country: formData.country || null,
tax_id: formData.tax_id || null,
personal_id: formData.personal_id || null,
position: formData.position || null,
license: formData.license || null,
company: formData.company || null,
contact: formData.contact || null,
tenant_id: broker.tenant_id,
company_id: companyStore.activeCompany.id.toString()
};
const response = await customsBrokersApi.update(broker.broker_key, payload);
if (response.error) {
if (response.status === 401) {
error = 'Sesión expirada. Recargando página...';
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
error = response.error;
}
return;
}
// Éxito
open = false;
if (onSuccess) {
onSuccess();
}
} catch (e) {
error = e instanceof Error ? e.message : "Error al guardar";
console.error("Error updating:", e);
} finally {
loading = false;
}
}
function handleOpenChange(newOpen: boolean) {
open = newOpen;
if (!newOpen) {
error = null;
}
}
</script>
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
<Dialog.Header>
<Dialog.Title>Editar Agente Aduanal</Dialog.Title>
<Dialog.Description>
Modifica los datos del agente aduanal <span class="font-mono font-semibold">{broker?.broker_key}</span>.
</Dialog.Description>
</Dialog.Header>
<form onsubmit={handleSubmit} class="space-y-6">
{#if error}
<div class="rounded-lg border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<!-- Información básica -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información Básica</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-broker_key">Clave</Label>
<Input
id="edit-broker_key"
value={broker?.broker_key}
disabled
class="bg-muted"
/>
</div>
<div class="space-y-2">
<Label for="edit-type">Tipo</Label>
<Input
id="edit-type"
bind:value={formData.type}
placeholder="Tipo de agente"
maxlength={9}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-name">Nombre</Label>
<Input
id="edit-name"
bind:value={formData.name}
placeholder="Nombre del agente aduanal"
maxlength={80}
disabled={loading}
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-license">Patente</Label>
<Input
id="edit-license"
bind:value={formData.license}
placeholder="Número de patente"
maxlength={4}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-company">Empresa</Label>
<Input
id="edit-company"
bind:value={formData.company}
placeholder="Empresa del agente"
maxlength={200}
disabled={loading}
/>
</div>
</div>
</div>
<!-- Información de contacto -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información de Contacto</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-phone">Teléfono</Label>
<Input
id="edit-phone"
bind:value={formData.phone}
placeholder="Número telefónico"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-fax">Fax</Label>
<Input
id="edit-fax"
bind:value={formData.fax}
placeholder="Número de fax"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-email">Email</Label>
<Input
id="edit-email"
type="email"
bind:value={formData.email}
placeholder="correo@ejemplo.com"
maxlength={100}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-contact">Contacto</Label>
<Input
id="edit-contact"
bind:value={formData.contact}
placeholder="Nombre del contacto"
maxlength={80}
disabled={loading}
/>
</div>
</div>
<!-- Dirección -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Dirección</h3>
<div class="space-y-2">
<Label for="edit-address">Dirección</Label>
<Input
id="edit-address"
bind:value={formData.address}
placeholder="Calle y número"
maxlength={1500}
disabled={loading}
/>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="edit-postal_code">Código Postal</Label>
<Input
id="edit-postal_code"
bind:value={formData.postal_code}
placeholder="C.P."
maxlength={15}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-city">Ciudad</Label>
<Input
id="edit-city"
bind:value={formData.city}
placeholder="Ciudad"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-state">Estado</Label>
<Input
id="edit-state"
bind:value={formData.state}
placeholder="Estado"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-country">País</Label>
<Input
id="edit-country"
bind:value={formData.country}
placeholder="País"
maxlength={3}
disabled={loading}
/>
</div>
</div>
<!-- Información fiscal -->
<div class="space-y-4">
<h3 class="text-sm font-semibold">Información Fiscal</h3>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="edit-tax_id">RFC</Label>
<Input
id="edit-tax_id"
bind:value={formData.tax_id}
placeholder="RFC"
maxlength={30}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="edit-personal_id">CURP</Label>
<Input
id="edit-personal_id"
bind:value={formData.personal_id}
placeholder="CURP"
maxlength={20}
disabled={loading}
/>
</div>
</div>
<div class="space-y-2">
<Label for="edit-position">Posición</Label>
<Input
id="edit-position"
bind:value={formData.position}
placeholder="Cargo o posición"
maxlength={30}
disabled={loading}
/>
</div>
</div>
<Dialog.Footer>
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}>
Cancelar
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<div class="flex items-center gap-2">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"></div>
Guardando...
</div>
{:else}
Guardar Cambios
{/if}
</Button>
</Dialog.Footer>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { pedimentosApi, type Pedimento } from "$lib/api/dashboard/a76/pedimentos";
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
let {
item,
@@ -62,21 +63,7 @@
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
<span class="sr-only">Abrir menú</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="1" />
<circle cx="12" cy="5" r="1" />
<circle cx="12" cy="19" r="1" />
</svg>
<EllipsisVertical class="h-4 w-4" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
@@ -84,63 +71,15 @@
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleEdit}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
<path d="m15 5 4 4" />
</svg>
<Pencil class="mr-2 h-4 w-4" />
Editar
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="mr-2"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>
<Trash2 class="mr-2 h-4 w-4" />
{/if}
Eliminar
</DropdownMenu.Item>

View File

@@ -30,8 +30,6 @@
original_date: datesData.original_date ? datesData.original_date.substring(0, 10) : '',
start_date: datesData.start_date ? datesData.start_date.substring(0, 10) : '',
end_date: datesData.end_date ? datesData.end_date.substring(0, 10) : '',
capture_date: datesData.capture_date ? datesData.capture_date.substring(0, 10) : '',
capture_time: datesData.capture_time || ''
};
}
} else {
@@ -48,7 +46,6 @@
original_date: '',
start_date: '',
end_date: '',
capture_date: '',
capture_time: ''
};
}
@@ -163,27 +160,7 @@
type="date"
bind:value={formData.end_date}
/>
</div>
<!-- Fecha de Captura -->
<div class="space-y-2">
<Label for="capture_date">Fecha de Captura</Label>
<Input
id="capture_date"
type="date"
bind:value={formData.capture_date}
/>
</div>
<!-- Hora de Captura -->
<div class="space-y-2">
<Label for="capture_time">Hora de Captura</Label>
<Input
id="capture_time"
type="time"
bind:value={formData.capture_time}
/>
</div>
</div>
</div>
</div>
</Card.Content>

View File

@@ -5,26 +5,175 @@
import * as Select from '$lib/components/ui/select';
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers';
import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers';
import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens';
let {
pedimento,
formData = $bindable(),
pedimentoCodes = []
pedimentoCodes = [],
customsSections = [],
customsBrokers = [],
clients = [],
codePedimentoRegimens = []
}: {
pedimento: Pedimento | null;
formData?: any;
pedimentoCodes?: PedimentoCode[];
customsSections?: CustomsSection[];
customsBrokers?: CustomsBroker[];
clients?: ClientProvider[];
codePedimentoRegimens?: CodePedimentoRegimen[];
} = $props();
// Debug: verificar que los datos llegan
$effect(() => {
console.log('pedimentoCodes:', pedimentoCodes.length, 'items');
// Extraer regímenes únicos de codePedimentoRegimens
const uniqueRegimens = $derived(
Array.from(new Set(codePedimentoRegimens.map(r => r.regimen_code).filter((code): code is string => code !== null)))
.sort()
.map(code => ({ code, label: code }))
);
// Funciones de mapeo entre type_code (E/I) y operation_type (1/2)
function typeCodeToOperationType(typeCode: string | null | undefined): number | null {
if (!typeCode) return null;
// E = Exportación = 1, I = Importación = 2
if (typeCode.toUpperCase() === 'E') return 1;
if (typeCode.toUpperCase() === 'I') return 2;
return null;
}
function operationTypeToTypeCode(operationType: number | null | undefined): string | null {
if (operationType === null || operationType === undefined) return null;
// 1 = Exportación = E, 2 = Importación = I
if (operationType === 1) return 'E';
if (operationType === 2) return 'I';
return null;
}
// Opciones filtradas para Régimen y Tipo de Operación basadas en las selecciones actuales
// NOTA: La Clave NO se filtra, siempre muestra todas las opciones
const filteredRegimens = $derived(() => {
// Si hay clave o tipo de operación seleccionado, filtrar
if (formData.pedimento_code || formData.operation_type !== null) {
const matches = codePedimentoRegimens.filter(r => {
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
const matchesType = formData.operation_type === null || r.type_code === operationTypeToTypeCode(formData.operation_type);
return matchesCode && matchesType;
});
const validRegimens = new Set(matches.map(m => m.regimen_code).filter((code): code is string => code !== null));
return Array.from(validRegimens).sort().map(code => ({ code, label: code }));
}
return uniqueRegimens;
});
const filteredOperationTypes = $derived(() => {
// Si hay clave o régimen seleccionado, filtrar
if (formData.pedimento_code || formData.regime) {
const matches = codePedimentoRegimens.filter(r => {
const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code;
const matchesRegime = !formData.regime || r.regimen_code === formData.regime;
return matchesCode && matchesRegime;
});
const validTypes = new Set(matches.map(m => typeCodeToOperationType(m.type_code)).filter(t => t !== null));
return operationOptions.filter(opt => validTypes.has(opt.value));
}
return operationOptions;
});
// Reactive synchronization between Clave, Régimen, and Tipo de Operación
// REGLA: La Clave es el campo principal y NUNCA se modifica automáticamente
// Solo se auto-llenan Régimen y Tipo de Operación basándose en la Clave
// Cuando cambia la Clave del Pedimento
$effect(() => {
const currentCode = formData.pedimento_code;
if (!currentCode) return;
const matches = codePedimentoRegimens.filter(r => r.pedimento_code === currentCode);
if (matches.length === 0) return;
// Verificar si los valores actuales de régimen y tipo son válidos para esta clave
const currentIsValid = matches.some(m => {
const matchesRegime = !formData.regime || m.regimen_code === formData.regime;
const matchesType = formData.operation_type === null || m.type_code === operationTypeToTypeCode(formData.operation_type);
return matchesRegime && matchesType;
});
// Si los valores actuales son válidos, NO auto-llenar
if (currentIsValid && (formData.regime || formData.operation_type !== null)) {
return;
}
// Si solo hay un match y no hay valores válidos, auto-llenar
if (matches.length === 1) {
const match = matches[0];
if (match.regimen_code && formData.regime !== match.regimen_code) {
formData.regime = match.regimen_code;
}
const expectedOpType = typeCodeToOperationType(match.type_code);
if (expectedOpType !== null && formData.operation_type !== expectedOpType) {
formData.operation_type = expectedOpType;
}
}
});
// Cuando cambia el Régimen
$effect(() => {
const currentRegime = formData.regime;
if (!currentRegime) return;
const matches = codePedimentoRegimens.filter(r => r.regimen_code === currentRegime);
if (matches.length === 0) return;
// Si hay clave seleccionada, solo validar (NO auto-llenar tipo de operación)
if (formData.pedimento_code) {
const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
}
// Si hay tipo de operación pero no clave, no hacer nada
// (el usuario debe seleccionar la clave primero)
});
// Cuando cambia el Tipo de Operación
$effect(() => {
const currentType = formData.operation_type;
if (currentType === null || currentType === undefined) return;
const expectedTypeCode = operationTypeToTypeCode(currentType);
const matches = codePedimentoRegimens.filter(r => r.type_code === expectedTypeCode);
if (matches.length === 0) return;
// Si hay clave seleccionada, actualizar régimen (forzar si no hay match exacto)
if (formData.pedimento_code) {
const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code);
if (exactMatch?.regimen_code && formData.regime !== exactMatch.regimen_code) {
formData.regime = exactMatch.regimen_code;
} else if (!exactMatch) {
// No hay match exacto - buscar cualquier match con la clave actual
const allMatchesForClave = codePedimentoRegimens.filter(r => r.pedimento_code === formData.pedimento_code);
if (allMatchesForClave.length > 0) {
// Forzar el régimen al primer match disponible para esta clave
const firstMatch = allMatchesForClave[0];
if (firstMatch.regimen_code) formData.regime = firstMatch.regimen_code;
}
}
}
// Si hay régimen pero no clave, no hacer nada
// (el usuario debe seleccionar la clave primero)
});
// Obtener el año actual (últimos 2 dígitos)
const currentYear = String(new Date().getFullYear()).slice(-2);
// Inicializar formData con los valores del pedimento (o vacío si es null)
if (!formData) {
formData = {
year: pedimento?.year || '',
year: pedimento?.year || currentYear,
customs_office: pedimento?.customs_office || '',
license: pedimento?.license || '',
pedimento_number: pedimento?.pedimento_number || '',
@@ -41,6 +190,13 @@
};
}
// Asegurar que el año siempre esté actualizado con el año actual
$effect(() => {
if (formData && !pedimento?.year) {
formData.year = currentYear;
}
});
const operationOptions = [
{ value: 1, label: 'Exportación' },
{ value: 2, label: 'Importación' },
@@ -85,6 +241,8 @@
placeholder="23"
maxlength={2}
class="text-center"
disabled
readonly
/>
</div>
@@ -92,15 +250,28 @@
<div class="pb-2 text-2xl font-semibold text-muted-foreground">-</div>
<!-- Aduana -->
<div class="space-y-2 w-16">
<div class="space-y-2 w-20">
<Label for="customs_office">Aduana</Label>
<Input
id="customs_office"
bind:value={formData.customs_office}
placeholder="01"
maxlength={2}
class="text-center"
/>
<Select.Root
type="single"
value={formData.customs_office || ''}
onValueChange={(v: string) => formData.customs_office = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.customs_office || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[300px] max-h-[300px]">
{#each customsSections as section}
<Select.Item value={section.customs_code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${section.customs_code} - ${section.section_name}`}>
{section.customs_code} - {section.section_name}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
@@ -109,13 +280,26 @@
<!-- Patente -->
<div class="space-y-2 w-24">
<Label for="license">Patente</Label>
<Input
id="license"
bind:value={formData.license}
placeholder="1234"
maxlength={4}
class="text-center"
/>
<Select.Root
type="single"
value={formData.license || ''}
onValueChange={(v: string) => formData.license = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.license || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[300px] max-h-[300px]">
{#each customsBrokers as broker}
<Select.Item value={broker.license}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${broker.broker_key} - ${broker.name || ''}`}>
{broker.broker_key} - {broker.name || ''}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Separador -->
@@ -137,29 +321,24 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- ID del Cliente -->
<div class="space-y-2">
<Label for="client_id">ID del Cliente</Label>
<Input
id="client_id"
type="number"
bind:value={formData.client_id}
placeholder="Ej: 123"
/>
</div>
<!-- Tipo de Operación -->
<div class="space-y-2">
<Label for="operation_type">Tipo de Operación</Label>
<Label for="client_id">Cliente</Label>
<Select.Root
type="single"
value={String(formData.operation_type ?? '')}
onValueChange={(v: string) => formData.operation_type = v ? Number(v) : null}
value={String(formData.client_id ?? '')}
onValueChange={(v: string) => formData.client_id = v ? Number(v) : null}
>
<Select.Trigger class="w-full">
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
<span class="truncate">
{clients.find(c => c.id === formData.client_id)?.name || 'Seleccionar cliente...'}
</span>
</Select.Trigger>
<Select.Content>
{#each operationOptions as option}
<Select.Item value={String(option.value)} label={option.label} />
<Select.Content class="max-h-[300px]">
{#each clients as client}
<Select.Item value={String(client.id)} label={client.name}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={client.name}>
{client.name}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
@@ -176,40 +355,81 @@
/>
</div>
<!-- Clave del Pedimento -->
<div class="space-y-2">
<Label for="pedimento_code">Clave del Pedimento</Label>
<Select.Root
type="single"
value={formData.pedimento_code || ''}
onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Seleccionar...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[600px]">
{#each pedimentoCodes as code}
<Select.Item value={code.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
{code.code} - {code.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<!-- Régimen -->
<div class="space-y-2">
<Label for="regime">Régimen</Label>
<Input
id="regime"
bind:value={formData.regime}
placeholder="Ej: IMD"
/>
</div>
<!-- Fila: Clave (2), Régimen (3), Tipo de Operación (11) -->
<div class="flex items-end gap-2">
<!-- Clave del Pedimento -->
<div class="space-y-2 w-20">
<Label for="pedimento_code">Clave</Label>
<Select.Root
type="single"
value={formData.pedimento_code || ''}
onValueChange={(v: string) => formData.pedimento_code = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[200px]">
{#each pedimentoCodes as code}
<Select.Item value={code.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={`${code.code} - ${code.description}`}>
{code.code} - {code.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Régimen -->
<div class="space-y-2 w-24">
<Label for="regime">Régimen</Label>
<Select.Root
type="single"
value={formData.regime || ''}
onValueChange={(v: string) => formData.regime = v ?? ''}
>
<Select.Trigger class="w-full">
<span class="truncate">
{formData.regime || 'Sel...'}
</span>
</Select.Trigger>
<Select.Content class="max-w-[200px]">
{#each filteredRegimens() as regimen}
<Select.Item value={regimen.code}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={regimen.code}>
{regimen.code}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<!-- Tipo de Operación -->
<div class="space-y-2 flex-1">
<Label for="operation_type">Tipo de Operación</Label>
<Select.Root
type="single"
value={String(formData.operation_type ?? '')}
onValueChange={(v: string) => formData.operation_type = v ? Number(v) : null}
>
<Select.Trigger class="w-full">
{operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'}
</Select.Trigger>
<Select.Content>
{#each filteredOperationTypes() as option}
<Select.Item value={String(option.value)} label={option.label} />
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Estado -->
<div class="space-y-2">

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { codePedimentoRegimensApi, type CodePedimentoRegimen } from "$lib/api/dashboard/refrence_data/code_pedimento_regimens";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { containersApi, type Container } from "$lib/api/dashboard/refrence_data/containers";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { customsSectionsApi, type CustomsSection } from "$lib/api/dashboard/refrence_data/customs_sections";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { customsWarehousesApi, type CustomsWarehouse } from "$lib/api/dashboard/refrence_data/customs_warehouses";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -88,26 +89,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { paymentMethodsApi, type PaymentMethod } from "$lib/api/dashboard/refrence_data/payment_methods";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { pedimentoRegimensApi, type PedimentoRegimen } from "$lib/api/dashboard/refrence_data/pedimento_regimens";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -4,6 +4,7 @@
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { statesApi, type State, type CreateStateData, type UpdateStateData } from "$lib/api/dashboard/refrence_data/states";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -195,26 +196,7 @@
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
{isEditing ? "Guardar cambios" : "Crear"}
</Button>

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { statesApi, type State } from "$lib/api/dashboard/refrence_data/states";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -96,26 +97,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>

View File

@@ -2,6 +2,7 @@
import { Button } from "$lib/components/ui/button";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import { transportModesApi, type TransportMode } from "$lib/api/dashboard/refrence_data/transport_modes";
import { LoaderCircle } from 'lucide-svelte';
let {
open = $bindable(false),
@@ -84,26 +85,7 @@
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{#if loading}
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
{/if}
Eliminar
</AlertDialog.Action>