Ajustes visuales y arreglo de bugs
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
X,
|
||||
Save
|
||||
} from 'lucide-svelte';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { manifestDriversApi, type ManifestDriver } from '$lib/api/dashboard/a76/manifest-drivers';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -200,278 +201,254 @@
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-6 {showForm ? 'lg:grid-cols-12' : ''}">
|
||||
<!-- Main Table Column -->
|
||||
<Card.Root
|
||||
class="overflow-hidden border shadow-sm {showForm ? 'lg:col-span-7 xl:col-span-8' : ''}"
|
||||
>
|
||||
<Card.Header class="border-b bg-muted/30 p-2 px-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="rounded-md bg-primary/10 p-1.5">
|
||||
<Users class="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<Card.Title class="text-base font-bold tracking-tight">Tripulación</Card.Title>
|
||||
<Card.Description class="text-xs">Personal activo</Card.Description>
|
||||
<!-- Main Table -->
|
||||
<Card.Root class="overflow-hidden border shadow-sm">
|
||||
<Card.Header class="border-b bg-muted/30 p-2 px-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="rounded-md bg-primary/10 p-1.5">
|
||||
<Users class="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<Card.Title class="text-base font-bold tracking-tight">Tripulación</Card.Title>
|
||||
<Card.Description class="text-xs">Personal activo</Card.Description>
|
||||
</div>
|
||||
</div>
|
||||
<Button onclick={handleAdd} size="sm" class="h-7 gap-1.5 px-3 text-xs">
|
||||
<Plus class="h-3.5 w-3.5" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="p-0">
|
||||
<div class="min-h-[300px] overflow-x-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead
|
||||
class="border-b bg-muted/50 text-xs font-bold tracking-wider text-muted-foreground uppercase"
|
||||
>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Nombre</th>
|
||||
<th class="px-4 py-2">Tipo</th>
|
||||
<th class="px-4 py-2">Ubicación</th>
|
||||
<th class="w-[80px] px-4 py-2 text-right"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
{#if loading && drivers.length === 0}
|
||||
<tr>
|
||||
<td colspan="4" class="h-48 text-center text-muted-foreground">
|
||||
<Loader2 class="mx-auto mb-2 h-6 w-6 animate-spin opacity-50" />
|
||||
Cargando registros...
|
||||
</td>
|
||||
</tr>
|
||||
{:else if drivers.length === 0}
|
||||
<tr>
|
||||
<td colspan="4" class="h-48 text-center text-muted-foreground">
|
||||
No hay tripulación registrada en este manifiesto.
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
{#each drivers as driver, i}
|
||||
<tr
|
||||
class="group transition-colors hover:bg-muted/30 {editingIndex === i
|
||||
? 'bg-primary/5'
|
||||
: ''}"
|
||||
>
|
||||
<td class="px-4 py-2 text-sm font-medium">
|
||||
{driver.driver_name}
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="h-5 text-[10px] font-bold uppercase {typeLabels[
|
||||
driver.driver_type as keyof typeof typeLabels
|
||||
]?.color || ''}"
|
||||
>
|
||||
{typeLabels[driver.driver_type as keyof typeof typeLabels]?.label ||
|
||||
driver.driver_type}
|
||||
</Badge>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-muted-foreground">
|
||||
<div class="flex items-center gap-1 text-xs">
|
||||
<MapPin class="h-3 w-3" />
|
||||
{driver.city || ''}, {driver.country || ''}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-right">
|
||||
<div class="flex justify-end gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
onclick={() => handleEdit(i)}
|
||||
>
|
||||
<Pencil class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7 text-destructive"
|
||||
onclick={() => handleDelete(i)}
|
||||
>
|
||||
<Trash2 class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Member Form Dialog -->
|
||||
<Dialog.Root bind:open={showForm}>
|
||||
<Dialog.Content class="sm:max-w-[550px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title class="flex items-center gap-2">
|
||||
<UserPlus class="h-5 w-5 text-primary" />
|
||||
{editingIndex !== null ? 'Editar' : 'Agregar'} Miembro de Tripulación
|
||||
</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Ingrese los detalles del personal para este manifiesto.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-6 py-4">
|
||||
<!-- Name Section -->
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold tracking-wide uppercase">Nombre Completo</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
value={formData.driver_name}
|
||||
placeholder="Seleccione del catálogo..."
|
||||
class="h-10 cursor-pointer bg-muted/30 text-sm"
|
||||
readonly
|
||||
onclick={() => (showDriverDialog = true)}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-10 w-10 shrink-0"
|
||||
onclick={() => (showDriverDialog = true)}
|
||||
>
|
||||
<FolderSearch class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address Grid -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2 space-y-2">
|
||||
<Label class="text-xs font-bold tracking-wide uppercase">Dirección Principal</Label>
|
||||
<Input
|
||||
bind:value={formData.address_1}
|
||||
placeholder="Calle, número..."
|
||||
class="h-10 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold tracking-wide uppercase">Ciudad</Label>
|
||||
<Input bind:value={formData.city} placeholder="Ciudad..." class="h-10 text-sm" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold tracking-wide uppercase">Estado</Label>
|
||||
<Input bind:value={formData.state} placeholder="Estado..." class="h-10 text-sm" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold tracking-wide uppercase">CP</Label>
|
||||
<Input bind:value={formData.postal_code} placeholder="00000" class="h-10 text-sm" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold tracking-wide uppercase">País</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
value={selectedCountryName || formData.country}
|
||||
readonly
|
||||
class="h-10 bg-muted/30 text-sm"
|
||||
onclick={() => (showCountryDialog = true)}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-10 w-10 shrink-0"
|
||||
onclick={() => (showCountryDialog = true)}
|
||||
>
|
||||
<Globe class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if !showForm}
|
||||
<Button onclick={handleAdd} size="sm" class="h-7 gap-1.5 px-3 text-xs">
|
||||
<Plus class="h-3.5 w-3.5" />
|
||||
Nuevo
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="p-0">
|
||||
<div class="min-h-[300px] overflow-x-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead
|
||||
class="border-b bg-muted/50 text-xs font-bold tracking-wider text-muted-foreground uppercase"
|
||||
|
||||
<!-- Role Selection -->
|
||||
<div class="space-y-3 border-t pt-4">
|
||||
<Label class="text-xs font-bold tracking-wide uppercase">Rol en el viaje</Label>
|
||||
<RadioGroup.Root bind:value={formData.driver_type} class="grid grid-cols-3 gap-3">
|
||||
<label
|
||||
for="modal-r-cond"
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-xl border p-3 py-4 transition-all hover:bg-muted/50 {formData.driver_type ===
|
||||
'C'
|
||||
? 'border-primary bg-primary/5 ring-1 ring-primary'
|
||||
: 'bg-muted/10'}"
|
||||
>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Nombre</th>
|
||||
<th class="px-4 py-2">Tipo</th>
|
||||
<th class="px-4 py-2 {showForm ? 'hidden xl:table-cell' : ''}">Ubicación</th>
|
||||
<th class="w-[80px] px-4 py-2 text-right"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
{#if loading && drivers.length === 0}
|
||||
<tr>
|
||||
<td colspan="4" class="h-48 text-center text-muted-foreground">
|
||||
<Loader2 class="mx-auto mb-2 h-6 w-6 animate-spin opacity-50" />
|
||||
Cargando registros...
|
||||
</td>
|
||||
</tr>
|
||||
{:else if drivers.length === 0}
|
||||
<tr>
|
||||
<td colspan="4" class="h-48 text-center text-muted-foreground">
|
||||
No hay tripulación registrada en este manifiesto.
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
{#each drivers as driver, i}
|
||||
<tr
|
||||
class="group transition-colors hover:bg-muted/30 {editingIndex === i
|
||||
? 'bg-primary/5'
|
||||
: ''}"
|
||||
>
|
||||
<td class="px-4 py-2 text-sm font-medium">
|
||||
{driver.driver_name}
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="h-5 text-[10px] font-bold uppercase {typeLabels[
|
||||
driver.driver_type as keyof typeof typeLabels
|
||||
]?.color || ''}"
|
||||
>
|
||||
{typeLabels[driver.driver_type as keyof typeof typeLabels]?.label ||
|
||||
driver.driver_type}
|
||||
</Badge>
|
||||
</td>
|
||||
<td
|
||||
class="px-4 py-2 text-muted-foreground {showForm
|
||||
? 'hidden xl:table-cell'
|
||||
: ''}"
|
||||
>
|
||||
<div class="flex items-center gap-1 text-xs">
|
||||
<MapPin class="h-3 w-3" />
|
||||
{driver.city || ''}, {driver.country || ''}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-right">
|
||||
<div class="flex justify-end gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
onclick={() => handleEdit(i)}
|
||||
>
|
||||
<Pencil class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7 text-destructive"
|
||||
onclick={() => handleDelete(i)}
|
||||
>
|
||||
<Trash2 class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
<User
|
||||
class="mb-2 h-5 w-5 {formData.driver_type === 'C'
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
<span class="text-xs font-bold">Conductor</span>
|
||||
<RadioGroup.Item value="C" id="modal-r-cond" class="sr-only" />
|
||||
</label>
|
||||
<label
|
||||
for="modal-r-miem"
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-xl border p-3 py-4 transition-all hover:bg-muted/50 {formData.driver_type ===
|
||||
'M'
|
||||
? 'border-primary bg-primary/5 ring-1 ring-primary'
|
||||
: 'bg-muted/10'}"
|
||||
>
|
||||
<Users
|
||||
class="mb-2 h-5 w-5 {formData.driver_type === 'M'
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
<span class="text-xs font-bold">Tripulante</span>
|
||||
<RadioGroup.Item value="M" id="modal-r-miem" class="sr-only" />
|
||||
</label>
|
||||
<label
|
||||
for="modal-r-pas"
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-xl border p-3 py-4 transition-all hover:bg-muted/50 {formData.driver_type ===
|
||||
'P'
|
||||
? 'border-primary bg-primary/5 ring-1 ring-primary'
|
||||
: 'bg-muted/10'}"
|
||||
>
|
||||
<UserCheck
|
||||
class="mb-2 h-5 w-5 {formData.driver_type === 'P'
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
<span class="text-xs font-bold">Pasajero</span>
|
||||
<RadioGroup.Item value="P" id="modal-r-pas" class="sr-only" />
|
||||
</label>
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Side Form Column -->
|
||||
{#if showForm}
|
||||
<div class="lg:col-span-5 xl:col-span-4" transition:fly={{ x: 20, duration: 300 }}>
|
||||
<Card.Root class="sticky top-6 border shadow-lg">
|
||||
<Card.Header class="flex flex-row items-center justify-between border-b pb-3">
|
||||
<div>
|
||||
<Card.Title class="text-base font-bold">
|
||||
{editingIndex !== null ? 'Editar' : 'Agregar'} Miembro
|
||||
</Card.Title>
|
||||
<Card.Description class="text-[10px] font-medium"
|
||||
>Llene los datos requeridos</Card.Description
|
||||
>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 rounded-full" onclick={handleCancel}>
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4 p-6">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-bold">Nombre Completo</Label>
|
||||
<div class="flex gap-1">
|
||||
<Input
|
||||
bind:value={formData.driver_name}
|
||||
placeholder="Nombre del personal..."
|
||||
class="h-9 text-sm"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-9 w-9 shrink-0"
|
||||
onclick={() => (showDriverDialog = true)}
|
||||
>
|
||||
<FolderSearch class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 text-xs font-bold">
|
||||
<div class="col-span-2 space-y-1.5">
|
||||
<Label class="text-xs font-bold">Calle 1/Principal</Label>
|
||||
<Input
|
||||
bind:value={formData.address_1}
|
||||
placeholder="Dirección principal..."
|
||||
class="h-9 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-2 space-y-1.5">
|
||||
<Label class="text-xs font-bold">Calle 2 (Opcional)</Label>
|
||||
<Input
|
||||
bind:value={formData.address_2}
|
||||
placeholder="Referencias..."
|
||||
class="h-9 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-bold">Ciudad</Label>
|
||||
<Input bind:value={formData.city} placeholder="Ciudad..." class="h-9 text-sm" />
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-bold">Estado</Label>
|
||||
<Input bind:value={formData.state} placeholder="Estado..." class="h-9 text-sm" />
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs font-bold">CP</Label>
|
||||
<Input bind:value={formData.postal_code} placeholder="00000" class="h-9 text-sm" />
|
||||
</div>
|
||||
<div class="col-span-2 space-y-1.5 pt-1">
|
||||
<Label class="text-xs font-bold">País</Label>
|
||||
<div class="flex gap-1">
|
||||
<Input
|
||||
value={selectedCountryName || formData.country}
|
||||
readonly
|
||||
class="h-9 bg-muted/30 text-sm"
|
||||
onclick={() => (showCountryDialog = true)}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-9 w-9 shrink-0"
|
||||
onclick={() => (showCountryDialog = true)}
|
||||
>
|
||||
<Globe class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3 pt-2">
|
||||
<Label class="text-xs font-bold">Rol en el viaje</Label>
|
||||
<RadioGroup.Root bind:value={formData.driver_type} class="grid grid-cols-3 gap-2">
|
||||
<label
|
||||
for="r-cond"
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-lg border p-2 py-3 transition-colors hover:bg-muted/50 {formData.driver_type ===
|
||||
'C'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'bg-muted/10'}"
|
||||
>
|
||||
<User
|
||||
class="mb-1 h-4 w-4 {formData.driver_type === 'C'
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
<span class="text-[10px] font-bold">Conductor</span>
|
||||
<RadioGroup.Item value="C" id="r-cond" class="sr-only" />
|
||||
</label>
|
||||
<label
|
||||
for="r-miem"
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-lg border p-2 py-3 transition-colors hover:bg-muted/50 {formData.driver_type ===
|
||||
'M'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'bg-muted/10'}"
|
||||
>
|
||||
<Users
|
||||
class="mb-1 h-4 w-4 {formData.driver_type === 'M'
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
<span class="text-[10px] font-bold">Tripulante</span>
|
||||
<RadioGroup.Item value="M" id="r-miem" class="sr-only" />
|
||||
</label>
|
||||
<label
|
||||
for="r-pas"
|
||||
class="flex cursor-pointer flex-col items-center justify-center rounded-lg border p-2 py-3 transition-colors hover:bg-muted/50 {formData.driver_type ===
|
||||
'P'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'bg-muted/10'}"
|
||||
>
|
||||
<UserCheck
|
||||
class="mb-1 h-4 w-4 {formData.driver_type === 'P'
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'}"
|
||||
/>
|
||||
<span class="text-[10px] font-bold">Pasajero</span>
|
||||
<RadioGroup.Item value="P" id="r-pas" class="sr-only" />
|
||||
</label>
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
</Card.Content>
|
||||
<Card.Footer class="flex justify-end gap-2 border-t bg-muted/20 p-4">
|
||||
<Button variant="ghost" size="sm" onclick={handleCancel} disabled={saving}
|
||||
>Cancelar</Button
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
onclick={handleSave}
|
||||
disabled={saving}
|
||||
class="gap-2 font-bold shadow-sm"
|
||||
>
|
||||
{#if saving}
|
||||
<Loader2 class="h-3.5 w-3.5 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="h-3.5 w-3.5" />
|
||||
Guardar
|
||||
{/if}
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Dialog.Footer class="gap-2 sm:gap-0">
|
||||
<Button variant="ghost" onclick={handleCancel} disabled={saving}>Cancelar</Button>
|
||||
<Button onclick={handleSave} disabled={saving} class="min-w-[120px] font-bold">
|
||||
{#if saving}
|
||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
||||
Guardando...
|
||||
{:else}
|
||||
<Save class="mr-2 h-4 w-4" />
|
||||
Guardar cambios
|
||||
{/if}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
</div>
|
||||
|
||||
<DriverSelectorDialog bind:open={showDriverDialog} onSelect={handleDriverSelect} />
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
@@ -29,7 +30,8 @@
|
||||
DollarSign,
|
||||
FileSpreadsheet,
|
||||
Globe,
|
||||
Users
|
||||
Users,
|
||||
Circle
|
||||
} from 'lucide-svelte';
|
||||
import { manifestApi, type Manifest } from '$lib/api/dashboard/a76/manifests';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -138,7 +140,8 @@
|
||||
transport_key: '',
|
||||
transport_mode: '',
|
||||
trailer_number: '',
|
||||
status_description: ''
|
||||
status_description: '',
|
||||
vehicle_status: 'NUEVO'
|
||||
});
|
||||
|
||||
// Consolidated Categorized Lists (Merges Available + Selected)
|
||||
@@ -690,74 +693,70 @@
|
||||
<div class="mx-auto max-w-[1400px] pb-48">
|
||||
<Tabs.Root bind:value={activeTab}>
|
||||
<!-- Top Fields (Global across tabs) -->
|
||||
<Card.Root class="mb-2.5 border-primary/5 shadow-sm">
|
||||
<Card.Content class="p-2.5 py-1.5">
|
||||
<div class="grid grid-cols-1 gap-2.5 md:grid-cols-4">
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[8px] font-bold text-muted-foreground uppercase"
|
||||
>Número de Manifiesto</Label
|
||||
>
|
||||
<div class="relative">
|
||||
<Package class="absolute top-1.5 left-2 h-3 w-3 text-muted-foreground" />
|
||||
<Input
|
||||
bind:value={formData.manifest_number}
|
||||
class="h-7 bg-muted/20 pl-7 font-mono text-[10px]"
|
||||
placeholder="MAN-000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[8px] font-bold text-muted-foreground uppercase"
|
||||
>Fecha Entrada/Arribo</Label
|
||||
>
|
||||
<div class="relative">
|
||||
<Calendar
|
||||
class="pointer-events-none absolute top-1.5 left-2 h-3 w-3 text-muted-foreground"
|
||||
/>
|
||||
<Input type="date" bind:value={entryDateStr} class="h-7 pl-7 text-[10px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[8px] font-bold text-muted-foreground uppercase"
|
||||
>Transportista</Label
|
||||
>
|
||||
<div class="flex gap-1">
|
||||
<div class="relative w-full">
|
||||
<Truck class="absolute top-1.5 left-2 h-3 w-3 text-muted-foreground" />
|
||||
<Input
|
||||
value={selectedTransporterName || formData.carrier_code}
|
||||
class="h-7 pl-7 font-mono text-[10px]"
|
||||
placeholder="Catálogo..."
|
||||
readonly
|
||||
onclick={() => (showTransporterModal = true)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-7 w-7 shrink-0"
|
||||
onclick={() => (showTransporterModal = true)}
|
||||
>
|
||||
<FolderSearch class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[8px] font-bold text-muted-foreground uppercase"
|
||||
>Persona a Cargo</Label
|
||||
>
|
||||
<div class="relative">
|
||||
<User class="absolute top-1.5 left-2 h-3 w-3 text-muted-foreground" />
|
||||
<Input
|
||||
bind:value={formData.person_in_charge}
|
||||
class="h-7 pl-7 text-[10px]"
|
||||
placeholder="Nombre completo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Top Fields (Global across tabs) - Compact & Clean (Like Invoice Form) -->
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 border-b pb-4 md:grid-cols-4">
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[9px] font-bold text-muted-foreground uppercase"
|
||||
>Número de Manifiesto</Label
|
||||
>
|
||||
<div class="relative">
|
||||
<Package class="absolute top-1.5 left-2 h-3 w-3 text-muted-foreground" />
|
||||
<Input
|
||||
bind:value={formData.manifest_number}
|
||||
class="h-7 pl-7 font-mono text-[10px]"
|
||||
placeholder="MAN-000"
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[9px] font-bold text-muted-foreground uppercase"
|
||||
>Fecha Entrada/Arribo</Label
|
||||
>
|
||||
<div class="relative">
|
||||
<Calendar
|
||||
class="pointer-events-none absolute top-1.5 left-2 h-3 w-3 text-muted-foreground"
|
||||
/>
|
||||
<Input type="date" bind:value={entryDateStr} class="h-7 pl-7 text-[10px]" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[9px] font-bold text-muted-foreground uppercase">Transportista</Label
|
||||
>
|
||||
<div class="flex gap-1">
|
||||
<div class="relative w-full">
|
||||
<Truck class="absolute top-1.5 left-2 h-3 w-3 text-muted-foreground" />
|
||||
<Input
|
||||
value={selectedTransporterName || formData.carrier_code}
|
||||
class="h-7 pl-7 font-mono text-[10px]"
|
||||
placeholder="Catálogo..."
|
||||
readonly
|
||||
onclick={() => (showTransporterModal = true)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-7 w-7 shrink-0"
|
||||
onclick={() => (showTransporterModal = true)}
|
||||
>
|
||||
<FolderSearch class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-[9px] font-bold text-muted-foreground uppercase"
|
||||
>Persona a Cargo</Label
|
||||
>
|
||||
<div class="relative">
|
||||
<User class="absolute top-1.5 left-2 h-3 w-3 text-muted-foreground" />
|
||||
<Input
|
||||
bind:value={formData.person_in_charge}
|
||||
class="h-7 pl-7 text-[10px]"
|
||||
placeholder="Nombre completo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs.Content value="generales" class="animate-in fade-in space-y-6 duration-300">
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-12">
|
||||
@@ -1032,133 +1031,176 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="cont" class="animate-in fade-in space-y-6 duration-300">
|
||||
<ManifestDriverSubForm
|
||||
manifestNumber={formData.manifest_number}
|
||||
carrierName={selectedTransporterName || formData.carrier_code}
|
||||
isNewManifest={isNew}
|
||||
/>
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-12">
|
||||
<!-- Left Side: Attributes and Truck Info -->
|
||||
<div class="space-y-6 lg:col-span-5 xl:col-span-4">
|
||||
<!-- Attributes -->
|
||||
<Card.Root class="shadow-sm">
|
||||
<Card.Header class="pb-3">
|
||||
<Card.Title
|
||||
class="flex items-center gap-2 text-sm font-bold tracking-wider uppercase"
|
||||
>
|
||||
<Info class="h-4 w-4 text-primary" />
|
||||
Atributos
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold">Entry Type (Tipo Manifiesto)</Label>
|
||||
<Input bind:value={formData.entry_type} placeholder="Ej: General" class="h-9" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between rounded-xl border border-orange-100 bg-orange-50/20 p-4"
|
||||
>
|
||||
<div class="flex gap-3">
|
||||
<AlertTriangle class="h-5 w-5 text-orange-600" />
|
||||
<div>
|
||||
<Label class="block text-sm font-bold">Material Peligroso</Label>
|
||||
<p class="text-xs text-muted-foreground italic">¿Riesgo?</p>
|
||||
</div>
|
||||
</div>
|
||||
<Switch bind:checked={formData.is_hazardous} />
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Separator />
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<!-- Config -->
|
||||
<Card.Root class="shadow-sm">
|
||||
<Card.Header>
|
||||
<Card.Title class="flex items-center gap-2 text-base">
|
||||
<Info class="h-4 w-4 text-primary" />
|
||||
Atributos del Manifiesto
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>Entry Type (Tipo Manifiesto)</Label>
|
||||
<Input bind:value={formData.entry_type} placeholder="Ej: General" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between rounded-xl border border-orange-100 bg-orange-50/20 p-4"
|
||||
>
|
||||
<div class="flex gap-3">
|
||||
<AlertTriangle class="h-5 w-5 text-orange-600" />
|
||||
<div>
|
||||
<Label class="block text-sm font-bold">Material Peligroso</Label>
|
||||
<p class="text-xs text-muted-foreground italic">
|
||||
¿Contiene mercancía de riesgo?
|
||||
</p>
|
||||
<!-- Truck Details -->
|
||||
<Card.Root class="shadow-sm">
|
||||
<Card.Header class="pb-3">
|
||||
<Card.Title
|
||||
class="flex items-center gap-2 text-sm font-bold tracking-wider uppercase"
|
||||
>
|
||||
<Truck class="h-4 w-4 text-primary" />
|
||||
Vehículo
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase">Clave Transporte</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
bind:value={formData.transport_key}
|
||||
placeholder="..."
|
||||
readonly
|
||||
onclick={() => (showVehicleModal = true)}
|
||||
class="h-9 font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-9 w-9 shrink-0"
|
||||
onclick={() => (showVehicleModal = true)}
|
||||
><FolderSearch class="h-4 w-4" /></Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase">Modo Transporte</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
bind:value={formData.transport_mode}
|
||||
placeholder="..."
|
||||
readonly
|
||||
onclick={() => (showTransportModeModal = true)}
|
||||
class="h-9 font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-9 w-9 shrink-0"
|
||||
onclick={() => (showTransportModeModal = true)}
|
||||
><FolderSearch class="h-4 w-4" /></Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase"># Trailer</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
value={selectedTrailerPlate || formData.trailer_number}
|
||||
placeholder="..."
|
||||
readonly
|
||||
onclick={() => (showTrailerModal = true)}
|
||||
class="h-9 font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-9 w-9 shrink-0"
|
||||
onclick={() => (showTrailerModal = true)}
|
||||
><FolderSearch class="h-4 w-4" /></Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Status Field (Vehicle specific, local state for now) -->
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase">Estatus Vehículo</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
bind:value={formData.vehicle_status}
|
||||
onValueChange={(v) => (formData.vehicle_status = v)}
|
||||
>
|
||||
<Select.Trigger class="h-9 px-3 text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if formData.vehicle_status === 'NUEVO'}
|
||||
<Circle class="h-2 w-2 fill-green-500 text-green-500" />
|
||||
{:else if formData.vehicle_status === 'USADO'}
|
||||
<Circle class="h-2 w-2 fill-blue-500 text-blue-500" />
|
||||
{:else if formData.vehicle_status === 'CANCELADO'}
|
||||
<Circle class="h-2 w-2 fill-red-500 text-red-500" />
|
||||
{:else}
|
||||
<Circle class="h-2 w-2 fill-slate-300 text-slate-300" />
|
||||
{/if}
|
||||
<span class="truncate uppercase"
|
||||
>{formData.vehicle_status || 'Estado'}</span
|
||||
>
|
||||
</div>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="NUEVO" class="text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<Circle class="h-2 w-2 fill-green-500 text-green-500" />
|
||||
NUEVO
|
||||
</div>
|
||||
</Select.Item>
|
||||
<Select.Item value="USADO" class="text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<Circle class="h-2 w-2 fill-blue-500 text-blue-500" />
|
||||
USADO
|
||||
</div>
|
||||
</Select.Item>
|
||||
<Select.Item value="CANCELADO" class="text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<Circle class="h-2 w-2 fill-red-500 text-red-500" />
|
||||
CANCELADO
|
||||
</div>
|
||||
</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
<Switch bind:checked={formData.is_hazardous} />
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<Separator />
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase">Observaciones</Label>
|
||||
<Textarea
|
||||
bind:value={formData.status_description}
|
||||
placeholder="Notas sobre el estado..."
|
||||
class="min-h-[80px] text-xs"
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
<!-- Truck -->
|
||||
<Card.Root class="shadow-sm">
|
||||
<Card.Header>
|
||||
<Card.Title class="flex items-center gap-2 text-base">
|
||||
<Truck class="h-4 w-4 text-primary" />
|
||||
Detalles del Camión (Truck)
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase">Clave Transporte</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
bind:value={formData.transport_key}
|
||||
placeholder="..."
|
||||
readonly
|
||||
onclick={() => (showVehicleModal = true)}
|
||||
class="font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onclick={() => (showVehicleModal = true)}
|
||||
><FolderSearch class="h-4 w-4" /></Button
|
||||
>
|
||||
</div>
|
||||
{#if selectedVehicleName}
|
||||
<p class="truncate px-1 text-[10px] text-muted-foreground">
|
||||
{selectedVehicleName}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase">Modo Transporte</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
bind:value={formData.transport_mode}
|
||||
placeholder="..."
|
||||
readonly
|
||||
onclick={() => (showTransportModeModal = true)}
|
||||
class="font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onclick={() => (showTransportModeModal = true)}
|
||||
><FolderSearch class="h-4 w-4" /></Button
|
||||
>
|
||||
</div>
|
||||
{#if selectedTransportTypeName}
|
||||
<p class="truncate px-1 text-[10px] text-muted-foreground">
|
||||
{selectedTransportTypeName}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase"># Trailer</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
value={selectedTrailerPlate || formData.trailer_number}
|
||||
placeholder="..."
|
||||
readonly
|
||||
onclick={() => (showTrailerModal = true)}
|
||||
class="font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onclick={() => (showTrailerModal = true)}
|
||||
><FolderSearch class="h-4 w-4" /></Button
|
||||
>
|
||||
</div>
|
||||
{#if selectedTrailerPlate && selectedTrailerPlate !== formData.trailer_number}
|
||||
<!-- Plate already shown in Input -->
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-bold uppercase">Estatus y Observaciones</Label>
|
||||
<Textarea
|
||||
bind:value={formData.status_description}
|
||||
placeholder="Notas sobre el estado del transporte..."
|
||||
class="min-h-[100px]"
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<!-- Right Side: Crew/Tripulación Table -->
|
||||
<div class="lg:col-span-7 xl:col-span-8">
|
||||
<ManifestDriverSubForm
|
||||
manifestNumber={formData.manifest_number}
|
||||
carrierName={selectedTransporterName || formData.carrier_code}
|
||||
isNewManifest={isNew}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="facturas_expo" class="animate-in fade-in space-y-8 duration-300">
|
||||
|
||||
Reference in New Issue
Block a user