183 lines
8.2 KiB
TypeScript
183 lines
8.2 KiB
TypeScript
import { useState } from "react";
|
|
import { CalendarDays, ChevronDown, CircleCheck, Clock, MapPin, Users } from "lucide-react";
|
|
import { BuscadorSelect } from "@/components/BuscadorSelect";
|
|
import { LUGARES_CITA, VUELO_INCLUYE } from "@/lib/extras-catalogo";
|
|
import { HORA_CITA, type PasajeroDatos, type ReservaEstado } from "@/lib/reserva-flujo";
|
|
|
|
export function Paso2Datos({
|
|
estado,
|
|
set,
|
|
setPasajero,
|
|
}: {
|
|
estado: ReservaEstado;
|
|
set: (parcial: Partial<ReservaEstado>) => void;
|
|
setPasajero: (i: number, parcial: Partial<PasajeroDatos>) => void;
|
|
}) {
|
|
const [resumenAbierto, setResumenAbierto] = useState(true);
|
|
|
|
return (
|
|
<div className="grid gap-6 xl:grid-cols-[1fr_340px]">
|
|
<div className="space-y-6">
|
|
<section className="rounded-3xl bg-card p-5 shadow-soft">
|
|
<h2 className="text-base font-bold">Datos del vuelo</h2>
|
|
|
|
<div className="mt-4 grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label htmlFor="fecha" className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
<CalendarDays className="mr-1 inline size-3.5" /> Fecha de vuelo
|
|
</label>
|
|
<input
|
|
id="fecha"
|
|
type="date"
|
|
value={estado.fecha}
|
|
disabled={estado.fechaPorConfirmar}
|
|
onChange={(e) => set({ fecha: e.target.value })}
|
|
className="w-full rounded-2xl border border-border bg-card px-4 py-3 text-sm transition-all hover:border-primary/60 focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
|
|
/>
|
|
<label className="mt-2 flex cursor-pointer items-center gap-2 text-xs text-muted-foreground">
|
|
<input
|
|
type="checkbox"
|
|
checked={estado.fechaPorConfirmar}
|
|
onChange={(e) => set({ fechaPorConfirmar: e.target.checked })}
|
|
className="size-4 rounded border-border accent-primary"
|
|
/>
|
|
Fecha por confirmar
|
|
</label>
|
|
</div>
|
|
|
|
<div>
|
|
<span className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
<Clock className="mr-1 inline size-3.5" /> Hora de cita
|
|
</span>
|
|
<div className="rounded-2xl border border-border bg-muted px-4 py-3 text-sm font-semibold">
|
|
{HORA_CITA}
|
|
</div>
|
|
<p className="mt-2 text-xs text-muted-foreground">Horario fijo de recepción de pasajeros.</p>
|
|
</div>
|
|
|
|
<div className="sm:col-span-2">
|
|
<label htmlFor="lugar" className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
<MapPin className="mr-1 inline size-3.5" /> Lugar de cita
|
|
</label>
|
|
<BuscadorSelect
|
|
id="lugar"
|
|
opciones={LUGARES_CITA}
|
|
valor={estado.lugarCita}
|
|
onChange={(v) => set({ lugarCita: v })}
|
|
placeholder="Busca tu punto de encuentro"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<span className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
Tipo de vuelo
|
|
</span>
|
|
<div className="rounded-2xl border border-border bg-muted px-4 py-3 text-sm font-semibold text-muted-foreground">
|
|
Compartidos
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<span className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
Idioma del pase de abordar
|
|
</span>
|
|
<div className="flex gap-2">
|
|
{(["Español", "Inglés"] as const).map((idioma) => (
|
|
<button
|
|
key={idioma}
|
|
type="button"
|
|
role="radio"
|
|
aria-checked={estado.idioma === idioma}
|
|
onClick={() => set({ idioma })}
|
|
className={`flex-1 rounded-2xl border px-4 py-3 text-sm font-semibold transition-all active:scale-95 ${
|
|
estado.idioma === idioma
|
|
? "border-primary bg-primary text-primary-foreground shadow-soft"
|
|
: "border-border bg-card hover:border-primary/60"
|
|
}`}
|
|
>
|
|
{idioma}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="rounded-3xl bg-card p-5 shadow-soft">
|
|
<h2 className="flex items-center gap-2 text-base font-bold">
|
|
<Users className="size-4 text-secondary" /> Datos de los pasajeros
|
|
</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
{estado.pasajeros.length} pasajero(s) según tu selección del paso 1.
|
|
</p>
|
|
|
|
<div className="mt-4 space-y-4">
|
|
{estado.pasajeros.map((p, i) => (
|
|
<div key={i} className="rounded-3xl border border-border p-4 transition-shadow hover:shadow-soft">
|
|
<p className="mb-3 text-xs font-bold uppercase tracking-wide text-primary">
|
|
Pasajero {i + 1} {i < estado.adultos ? "· Adulto" : "· Niño"}
|
|
</p>
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
<input
|
|
value={p.nombre}
|
|
onChange={(e) => setPasajero(i, { nombre: e.target.value })}
|
|
placeholder="Nombre(s)"
|
|
className="rounded-2xl border border-border bg-card px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
/>
|
|
<input
|
|
value={p.apellidos}
|
|
onChange={(e) => setPasajero(i, { apellidos: e.target.value })}
|
|
placeholder="Apellidos"
|
|
className="rounded-2xl border border-border bg-card px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
/>
|
|
<input
|
|
value={p.telefono}
|
|
onChange={(e) => setPasajero(i, { telefono: e.target.value })}
|
|
placeholder="Teléfono de contacto"
|
|
className="rounded-2xl border border-border bg-card px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
/>
|
|
<input
|
|
value={p.peso}
|
|
onChange={(e) => setPasajero(i, { peso: e.target.value })}
|
|
placeholder="Peso aproximado (kg)"
|
|
className="rounded-2xl border border-border bg-card px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
{/* Resumen del vuelo colapsable */}
|
|
<aside className="xl:sticky xl:top-24 xl:h-fit">
|
|
<div className="overflow-hidden rounded-3xl bg-ink text-ink-foreground shadow-lift">
|
|
<button
|
|
type="button"
|
|
onClick={() => setResumenAbierto((v) => !v)}
|
|
className="flex w-full items-center justify-between gap-3 px-5 py-4 text-left"
|
|
>
|
|
<div>
|
|
<p className="text-sm font-bold">Vuelo Aventura 20% OFF</p>
|
|
<p className="text-xs opacity-70">Experiencia compartida · Teotihuacán</p>
|
|
</div>
|
|
<ChevronDown className={`size-4 transition-transform duration-300 ${resumenAbierto ? "rotate-180" : ""}`} />
|
|
</button>
|
|
<div
|
|
className={`grid transition-all duration-500 ease-out ${resumenAbierto ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"}`}
|
|
>
|
|
<ul className="overflow-hidden px-5 pb-5 text-sm">
|
|
{VUELO_INCLUYE.map((i) => (
|
|
<li key={i} className="flex items-start gap-2 py-1.5">
|
|
<CircleCheck className="mt-0.5 size-4 shrink-0 text-brand" />
|
|
<span className="opacity-90">{i}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
);
|
|
}
|