feature/limit-characters-customs-brockers
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CustomsBrokerBaseDTO(BaseModel):
|
||||
@@ -19,7 +19,7 @@ class CustomsBrokerBaseDTO(BaseModel):
|
||||
tax_id: Optional[str] = None
|
||||
personal_id: Optional[str] = None
|
||||
position: Optional[str] = None
|
||||
license: Optional[str] = None
|
||||
license: Optional[str] = Field(None, max_length=4, pattern=r"^\d*$")
|
||||
company: Optional[str] = None
|
||||
contact: Optional[str] = None
|
||||
|
||||
@@ -27,7 +27,7 @@ class CustomsBrokerBaseDTO(BaseModel):
|
||||
class CustomsBrokerCreateDTO(CustomsBrokerBaseDTO):
|
||||
"""Schema for creating a new CustomsBroker"""
|
||||
|
||||
broker_key: str
|
||||
broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$", description="Clave única del agente aduanal (máx 5 caracteres)")
|
||||
|
||||
|
||||
class CustomsBrokerUpdateDTO(CustomsBrokerBaseDTO):
|
||||
@@ -51,7 +51,7 @@ class CustomsBrokerResponseDTO(CustomsBrokerBaseDTO):
|
||||
# Legacy DTO for backwards compatibility (if needed elsewhere)
|
||||
class CustomsBrokerDTO(BaseModel):
|
||||
type: Optional[str] = None
|
||||
broker_key: str
|
||||
broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$")
|
||||
name: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
postal_code: Optional[str] = None
|
||||
@@ -64,7 +64,7 @@ class CustomsBrokerDTO(BaseModel):
|
||||
tax_id: Optional[str] = None
|
||||
personal_id: Optional[str] = None
|
||||
position: Optional[str] = None
|
||||
license: Optional[str] = None
|
||||
license: Optional[str] = Field(None, max_length=4, pattern=r"^\d*$")
|
||||
company: Optional[str] = None
|
||||
contact: Optional[str] = None
|
||||
tenant_id: str
|
||||
@@ -100,13 +100,13 @@ class CustomsBrokerVUCreateDTO(BaseModel):
|
||||
|
||||
|
||||
class CustomsBrokerPersonnelDTO(BaseModel):
|
||||
broker_key: str
|
||||
broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$")
|
||||
line: int
|
||||
name: Optional[str]
|
||||
tax_id: Optional[str]
|
||||
personal_id: Optional[str]
|
||||
position: Optional[str]
|
||||
license: Optional[str]
|
||||
license: Optional[str] = Field(None, max_length=4, pattern=r"^\d*$")
|
||||
first_name: Optional[str]
|
||||
last_name: Optional[str]
|
||||
middle_name: Optional[str]
|
||||
|
||||
@@ -1,221 +1,310 @@
|
||||
<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 { Separator } from "$lib/components/ui/separator";
|
||||
import { Loader2 } from "lucide-svelte";
|
||||
import type { CreateCustomsBrokerData, CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers"; // Ajusta la ruta
|
||||
import { toast } from "svelte-sonner";
|
||||
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 { Separator } from '$lib/components/ui/separator';
|
||||
import { Loader2 } from 'lucide-svelte';
|
||||
import type {
|
||||
CreateCustomsBrokerData,
|
||||
CustomsBroker
|
||||
} from '$lib/api/dashboard/a76/customs-brokers'; // Ajusta la ruta
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
// --- Props ---
|
||||
export let open = false;
|
||||
export let mode: "create" | "edit" = "create";
|
||||
export let initialData: CustomsBroker | null = null;
|
||||
export let companyId: string; // Necesario según tu API
|
||||
// --- Props ---
|
||||
export let open = false;
|
||||
export let mode: 'create' | 'edit' = 'create';
|
||||
export let initialData: CustomsBroker | null = null;
|
||||
export let companyId: string; // Necesario según tu API
|
||||
|
||||
// La función onSave ahora devuelve una promesa para manejar el loading aquí
|
||||
export let onSave: (data: CreateCustomsBrokerData) => Promise<void>;
|
||||
// La función onSave ahora devuelve una promesa para manejar el loading aquí
|
||||
export let onSave: (data: CreateCustomsBrokerData) => Promise<void>;
|
||||
|
||||
// --- Estado ---
|
||||
let loading = false;
|
||||
// --- Estado ---
|
||||
let loading = false;
|
||||
|
||||
// Estado del formulario
|
||||
let formData: CreateCustomsBrokerData = {
|
||||
broker_key: "",
|
||||
license: "",
|
||||
name: "",
|
||||
tax_id: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
fax: "",
|
||||
contact: "",
|
||||
address: "",
|
||||
postal_code: "",
|
||||
city: "",
|
||||
state: "",
|
||||
country: "",
|
||||
tenant_id: "", // Se llenará en el submit o por defecto
|
||||
company_id: ""
|
||||
};
|
||||
// Estado del formulario
|
||||
let formData: CreateCustomsBrokerData = {
|
||||
broker_key: '',
|
||||
license: '',
|
||||
name: '',
|
||||
tax_id: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
fax: '',
|
||||
contact: '',
|
||||
address: '',
|
||||
postal_code: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: '',
|
||||
tenant_id: '', // Se llenará en el submit o por defecto
|
||||
company_id: ''
|
||||
};
|
||||
|
||||
// --- Reactividad ---
|
||||
$: if (open) {
|
||||
if (mode === "edit" && initialData) {
|
||||
// Cargar datos existentes
|
||||
formData = {
|
||||
...initialData,
|
||||
// Aseguramos que no sean null/undefined para los inputs
|
||||
name: initialData.name || "",
|
||||
tax_id: initialData.tax_id || "",
|
||||
email: initialData.email || "",
|
||||
phone: initialData.phone || "",
|
||||
fax: initialData.fax || "",
|
||||
contact: initialData.contact || "",
|
||||
address: initialData.address || "",
|
||||
postal_code: initialData.postal_code || "",
|
||||
city: initialData.city || "",
|
||||
state: initialData.state || "",
|
||||
country: initialData.country || "",
|
||||
license: initialData.license || ""
|
||||
};
|
||||
} else {
|
||||
// Reset para crear
|
||||
formData = {
|
||||
broker_key: "",
|
||||
license: "",
|
||||
name: "",
|
||||
tax_id: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
fax: "",
|
||||
contact: "",
|
||||
address: "",
|
||||
postal_code: "",
|
||||
city: "",
|
||||
state: "",
|
||||
country: "MEX", // Valor por defecto sugerido
|
||||
tenant_id: "default", // Ajustar según lógica de tu app
|
||||
company_id: companyId
|
||||
};
|
||||
}
|
||||
}
|
||||
let brokerKeyError = false;
|
||||
let licenseError = false;
|
||||
let brokerKeyTimeout: ReturnType<typeof setTimeout>;
|
||||
let licenseTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
// --- Handlers ---
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
loading = true;
|
||||
// --- Reactividad ---
|
||||
$: if (open) {
|
||||
if (mode === 'edit' && initialData) {
|
||||
// Cargar datos existentes
|
||||
formData = {
|
||||
...initialData,
|
||||
// Aseguramos que no sean null/undefined para los inputs
|
||||
name: initialData.name || '',
|
||||
tax_id: initialData.tax_id || '',
|
||||
email: initialData.email || '',
|
||||
phone: initialData.phone || '',
|
||||
fax: initialData.fax || '',
|
||||
contact: initialData.contact || '',
|
||||
address: initialData.address || '',
|
||||
postal_code: initialData.postal_code || '',
|
||||
city: initialData.city || '',
|
||||
state: initialData.state || '',
|
||||
country: initialData.country || '',
|
||||
license: initialData.license || ''
|
||||
};
|
||||
} else {
|
||||
// Reset para crear
|
||||
formData = {
|
||||
broker_key: '',
|
||||
license: '',
|
||||
name: '',
|
||||
tax_id: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
fax: '',
|
||||
contact: '',
|
||||
address: '',
|
||||
postal_code: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: 'MEX', // Valor por defecto sugerido
|
||||
tenant_id: 'default', // Ajustar según lógica de tu app
|
||||
company_id: companyId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Validaciones básicas
|
||||
if (!formData.broker_key) {
|
||||
toast.error("La Clave del Agente es obligatoria");
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
if (!formData.license) {
|
||||
toast.error("La Patente es obligatoria");
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
// --- Handlers ---
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
loading = true;
|
||||
|
||||
// Inyectar company_id si no viene
|
||||
const payload = { ...formData, company_id: companyId };
|
||||
// Validaciones básicas
|
||||
if (!formData.broker_key) {
|
||||
toast.error('La Clave del Agente es obligatoria');
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
if (!formData.license) {
|
||||
toast.error('La Patente es obligatoria');
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await onSave(payload);
|
||||
open = false;
|
||||
toast.success(mode === 'create' ? "Agente creado correctamente" : "Agente actualizado correctamente");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Error al guardar el agente aduanal");
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
if (formData.broker_key.length > 5) {
|
||||
toast.error('La Clave del Agente no debe superar los 5 caracteres');
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
if (formData.license && formData.license.length > 4) {
|
||||
toast.error('La Patente no debe superar los 4 dígitos');
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Inyectar company_id si no viene
|
||||
const payload = { ...formData, company_id: companyId };
|
||||
|
||||
await onSave(payload);
|
||||
open = false;
|
||||
toast.success(
|
||||
mode === 'create' ? 'Agente creado correctamente' : 'Agente actualizado correctamente'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('Error al guardar el agente aduanal');
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>
|
||||
{mode === "create" ? "Nuevo Agente Aduanal" : "Editar Agente Aduanal"}
|
||||
</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Ingresa los datos generales del agente. La configuración de VU y Personal se gestiona aparte.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>
|
||||
{mode === 'create' ? 'Nuevo Agente Aduanal' : 'Editar Agente Aduanal'}
|
||||
</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Ingresa los datos generales del agente. La configuración de VU y Personal se gestiona
|
||||
aparte.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-6 py-4">
|
||||
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-medium leading-none text-muted-foreground">Identificación</h4>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="broker_key">Clave Agente *</Label>
|
||||
<Input id="broker_key" bind:value={formData.broker_key} placeholder="Ej. 550" disabled={mode === 'edit' || loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="license">Patente *</Label>
|
||||
<Input id="license" bind:value={formData.license} placeholder="Ej. 3421" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2 col-span-2">
|
||||
<Label for="name">Nombre / Razón Social</Label>
|
||||
<Input id="name" bind:value={formData.name} placeholder="Nombre del Agente o Agencia" disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="tax_id">RFC</Label>
|
||||
<Input id="tax_id" bind:value={formData.tax_id} placeholder="RFC de la agencia" disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="contact">Nombre Contacto</Label>
|
||||
<Input id="contact" bind:value={formData.contact} placeholder="Persona de contacto" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-6 py-4">
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-medium leading-none text-muted-foreground">Identificación</h4>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="broker_key">Clave Agente *</Label>
|
||||
<Input
|
||||
id="broker_key"
|
||||
value={formData.broker_key}
|
||||
oninput={(e) => {
|
||||
const val = e.currentTarget.value.toUpperCase();
|
||||
if (val.length > 5) {
|
||||
brokerKeyError = true;
|
||||
formData.broker_key = val.slice(0, 5);
|
||||
e.currentTarget.value = formData.broker_key;
|
||||
|
||||
<Separator />
|
||||
clearTimeout(brokerKeyTimeout);
|
||||
brokerKeyTimeout = setTimeout(() => {
|
||||
brokerKeyError = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
brokerKeyError = false;
|
||||
formData.broker_key = val;
|
||||
}
|
||||
}}
|
||||
placeholder="Ej. 550"
|
||||
maxlength="6"
|
||||
class={brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
disabled={mode === 'edit' || loading}
|
||||
/>
|
||||
{#if brokerKeyError}
|
||||
<p class="text-[0.8rem] font-medium text-destructive">
|
||||
La clave no debe superar los 5 caracteres
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="license">Patente *</Label>
|
||||
<Input
|
||||
id="license"
|
||||
value={formData.license}
|
||||
oninput={(e) => {
|
||||
const val = e.currentTarget.value.replace(/\D/g, '');
|
||||
if (val.length > 4) {
|
||||
licenseError = true;
|
||||
formData.license = val.slice(0, 4);
|
||||
e.currentTarget.value = formData.license;
|
||||
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-medium leading-none text-muted-foreground">Contacto</h4>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="space-y-2 col-span-1">
|
||||
<Label for="phone">Teléfono</Label>
|
||||
<Input id="phone" bind:value={formData.phone} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2 col-span-2">
|
||||
<Label for="email">Correo Electrónico</Label>
|
||||
<Input id="email" type="email" bind:value={formData.email} disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
clearTimeout(licenseTimeout);
|
||||
licenseTimeout = setTimeout(() => {
|
||||
licenseError = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
licenseError = false;
|
||||
formData.license = val;
|
||||
}
|
||||
}}
|
||||
placeholder="Ej. 3421"
|
||||
maxlength="5"
|
||||
class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
disabled={loading}
|
||||
/>
|
||||
{#if licenseError}
|
||||
<p class="text-[0.8rem] font-medium text-destructive">
|
||||
La patente no debe superar los 4 dígitos
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2 col-span-2">
|
||||
<Label for="name">Nombre / Razón Social</Label>
|
||||
<Input
|
||||
id="name"
|
||||
bind:value={formData.name}
|
||||
placeholder="Nombre del Agente o Agencia"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="tax_id">RFC</Label>
|
||||
<Input
|
||||
id="tax_id"
|
||||
bind:value={formData.tax_id}
|
||||
placeholder="RFC de la agencia"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="contact">Nombre Contacto</Label>
|
||||
<Input
|
||||
id="contact"
|
||||
bind:value={formData.contact}
|
||||
placeholder="Persona de contacto"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-medium leading-none text-muted-foreground">Dirección Fiscal</h4>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="address">Calle y Número</Label>
|
||||
<Input id="address" bind:value={formData.address} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-medium leading-none text-muted-foreground">Contacto</h4>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="space-y-2 col-span-1">
|
||||
<Label for="phone">Teléfono</Label>
|
||||
<Input id="phone" bind:value={formData.phone} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2 col-span-2">
|
||||
<Label for="email">Correo Electrónico</Label>
|
||||
<Input id="email" type="email" bind:value={formData.email} disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="postal_code">C.P.</Label>
|
||||
<Input id="postal_code" bind:value={formData.postal_code} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2 col-span-2">
|
||||
<Label for="city">Ciudad</Label>
|
||||
<Input id="city" bind:value={formData.city} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="state">Estado</Label>
|
||||
<Input id="state" bind:value={formData.state} disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="country">País</Label>
|
||||
<Input id="country" bind:value={formData.country} disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-medium leading-none text-muted-foreground">Dirección Fiscal</h4>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button variant="outline" onclick={() => (open = false)} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onclick={handleSubmit} disabled={loading}>
|
||||
{#if loading}
|
||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando
|
||||
{:else}
|
||||
Guardar Agente
|
||||
{/if}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
<div class="space-y-2">
|
||||
<Label for="address">Calle y Número</Label>
|
||||
<Input id="address" bind:value={formData.address} disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="postal_code">C.P.</Label>
|
||||
<Input id="postal_code" bind:value={formData.postal_code} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2 col-span-2">
|
||||
<Label for="city">Ciudad</Label>
|
||||
<Input id="city" bind:value={formData.city} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="state">Estado</Label>
|
||||
<Input id="state" bind:value={formData.state} disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="country">País</Label>
|
||||
<Input id="country" bind:value={formData.country} disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button variant="outline" onclick={() => (open = false)} disabled={loading}>Cancelar</Button>
|
||||
<Button onclick={handleSubmit} disabled={loading}>
|
||||
{#if loading}
|
||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando
|
||||
{:else}
|
||||
Guardar Agente
|
||||
{/if}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<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";
|
||||
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),
|
||||
@@ -17,56 +21,58 @@
|
||||
} = $props();
|
||||
|
||||
let formData = $state({
|
||||
name: "",
|
||||
type: "",
|
||||
address: "",
|
||||
postal_code: "",
|
||||
city: "",
|
||||
state: "",
|
||||
phone: "",
|
||||
fax: "",
|
||||
email: "",
|
||||
country: "",
|
||||
tax_id: "",
|
||||
personal_id: "",
|
||||
position: "",
|
||||
license: "",
|
||||
company: "",
|
||||
contact: ""
|
||||
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);
|
||||
let licenseError = $state(false);
|
||||
let licenseTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
// 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 || ""
|
||||
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";
|
||||
error = 'No hay compañía seleccionada';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,8 +122,8 @@
|
||||
onSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : "Error al guardar";
|
||||
console.error("Error updating:", e);
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
console.error('Error updating:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -136,13 +142,17 @@
|
||||
<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>.
|
||||
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">
|
||||
<div
|
||||
class="rounded-lg border border-destructive bg-destructive/10 p-3 text-sm text-destructive"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -150,7 +160,7 @@
|
||||
<!-- 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>
|
||||
@@ -158,7 +168,7 @@
|
||||
id="edit-broker_key"
|
||||
value={broker?.broker_key}
|
||||
disabled
|
||||
class="bg-muted"
|
||||
class="bg-muted {broker?.broker_key?.length > 5 ? 'border-red-500' : ''}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -190,11 +200,33 @@
|
||||
<Label for="edit-license">Patente</Label>
|
||||
<Input
|
||||
id="edit-license"
|
||||
bind:value={formData.license}
|
||||
value={formData.license}
|
||||
oninput={(e) => {
|
||||
const val = e.currentTarget.value.replace(/\D/g, '');
|
||||
if (val.length > 4) {
|
||||
licenseError = true;
|
||||
formData.license = val.slice(0, 4);
|
||||
e.currentTarget.value = formData.license;
|
||||
|
||||
clearTimeout(licenseTimeout);
|
||||
licenseTimeout = setTimeout(() => {
|
||||
licenseError = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
licenseError = false;
|
||||
formData.license = val;
|
||||
}
|
||||
}}
|
||||
placeholder="Número de patente"
|
||||
maxlength={4}
|
||||
maxlength="5"
|
||||
class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
disabled={loading}
|
||||
/>
|
||||
{#if licenseError}
|
||||
<p class="text-[0.8rem] font-medium text-destructive">
|
||||
La patente no debe superar los 4 dígitos
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
@@ -213,7 +245,7 @@
|
||||
<!-- 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>
|
||||
@@ -265,7 +297,7 @@
|
||||
<!-- 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
|
||||
@@ -327,7 +359,7 @@
|
||||
<!-- 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>
|
||||
@@ -371,7 +403,9 @@
|
||||
<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>
|
||||
<div
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"
|
||||
></div>
|
||||
Guardando...
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
company_id: ''
|
||||
});
|
||||
|
||||
let brokerKeyError = $state(false);
|
||||
let licenseError = $state(false);
|
||||
let brokerKeyTimeout: ReturnType<typeof setTimeout>;
|
||||
let licenseTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
// --- 3. CARGA DE DATOS REACTIVA ---
|
||||
$effect(() => {
|
||||
const company = companyStore.activeCompany;
|
||||
@@ -126,6 +131,18 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.broker_key.length > 5) {
|
||||
error = 'La Clave del Agente no debe superar los 5 caracteres';
|
||||
toast.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.license && formData.license.length > 4) {
|
||||
error = 'La Patente no debe superar los 4 dígitos';
|
||||
toast.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
@@ -213,19 +230,66 @@
|
||||
>Clave Agente <span class="text-destructive">*</span></Label
|
||||
>
|
||||
<Input
|
||||
bind:value={formData.broker_key}
|
||||
value={formData.broker_key}
|
||||
oninput={(e) => {
|
||||
const val = e.currentTarget.value.toUpperCase();
|
||||
if (val.length > 5) {
|
||||
brokerKeyError = true;
|
||||
formData.broker_key = val.slice(0, 5);
|
||||
e.currentTarget.value = formData.broker_key;
|
||||
|
||||
clearTimeout(brokerKeyTimeout);
|
||||
brokerKeyTimeout = setTimeout(() => {
|
||||
brokerKeyError = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
brokerKeyError = false;
|
||||
formData.broker_key = val;
|
||||
}
|
||||
}}
|
||||
placeholder="Ej. 550"
|
||||
maxlength="6"
|
||||
class={brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
disabled={isEdit || loading}
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Clave interna o número de patente único.
|
||||
</p>
|
||||
{#if brokerKeyError}
|
||||
<p class="text-[0.8rem] font-medium text-destructive">
|
||||
La clave no debe superar los 5 caracteres
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label class="required"
|
||||
>Patente / Autorización <span class="text-destructive">*</span></Label
|
||||
>
|
||||
<Input bind:value={formData.license} placeholder="Ej. 3421" disabled={loading} />
|
||||
<Input
|
||||
value={formData.license}
|
||||
oninput={(e) => {
|
||||
const val = e.currentTarget.value.replace(/\D/g, '');
|
||||
if (val.length > 4) {
|
||||
licenseError = true;
|
||||
formData.license = val.slice(0, 4);
|
||||
e.currentTarget.value = formData.license;
|
||||
|
||||
clearTimeout(licenseTimeout);
|
||||
licenseTimeout = setTimeout(() => {
|
||||
licenseError = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
licenseError = false;
|
||||
formData.license = val;
|
||||
}
|
||||
}}
|
||||
placeholder="Ej. 3421"
|
||||
maxlength="5"
|
||||
class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
disabled={loading}
|
||||
/>
|
||||
{#if licenseError}
|
||||
<p class="text-[0.8rem] font-medium text-destructive">
|
||||
La patente no debe superar los 4 dígitos
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user