Files
taller_automotriz/src/app/cliente/turnos/page.tsx
T
Beldor b6705239a1
Deploy / deploy (push) Failing after 33h14m30s
fix(cliente): cargar servicios desde endpoint de cliente en vez de admin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 23:20:25 -03:00

215 lines
8.1 KiB
TypeScript

"use client";
import { Suspense, useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import Badge from "@/components/ui/Badge";
interface TipoServicio { id: string; nombre: string; duracionHoras: number; }
interface Slot { fechaInicio: string; fechaFin: string; }
interface Turno {
id: string;
fechaInicio: string;
fechaFin: string;
estado: "PENDIENTE" | "CONFIRMADO" | "CANCELADO" | "COMPLETADO";
tipoServicio: { nombre: string };
auto: { marca: string; modelo: string; patente: string };
}
type Step = "servicio" | "slot" | "confirmado";
function TurnosContent() {
const router = useRouter();
const params = useSearchParams();
const autoId = params.get("autoId");
const [step, setStep] = useState<Step>("servicio");
const [servicios, setServicios] = useState<TipoServicio[]>([]);
const [servicioSel, setServicioSel] = useState<TipoServicio | null>(null);
const [slots, setSlots] = useState<Slot[]>([]);
const [slotSel, setSlotSel] = useState<Slot | null>(null);
const [loadingSlots, setLoadingSlots] = useState(false);
const [reservando, setReservando] = useState(false);
const [turnoCreado, setTurnoCreado] = useState<Turno | null>(null);
const [misTurnos, setMisTurnos] = useState<Turno[]>([]);
const [loadingTurnos, setLoadingTurnos] = useState(true);
useEffect(() => {
if (!autoId) { router.push("/cliente/dashboard"); return; }
fetch("/api/cliente/servicios")
.then((r) => r.json())
.then((data) => setServicios(data));
fetch("/api/cliente/turnos")
.then((r) => r.json())
.then((data) => { setMisTurnos(data.filter((t: Turno & { autoId: string }) => t.autoId === autoId)); setLoadingTurnos(false); });
}, [autoId, router]);
async function seleccionarServicio(s: TipoServicio) {
setServicioSel(s);
setLoadingSlots(true);
setStep("slot");
const res = await fetch(`/api/cliente/slots?tipoServicioId=${s.id}`);
const data = await res.json();
setSlots(data);
setLoadingSlots(false);
}
async function reservar() {
if (!slotSel || !servicioSel || !autoId) return;
setReservando(true);
const res = await fetch("/api/cliente/turnos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ autoId, tipoServicioId: servicioSel.id, fechaInicio: slotSel.fechaInicio }),
});
const data = await res.json();
setReservando(false);
if (res.ok) {
setTurnoCreado(data);
setStep("confirmado");
}
}
function formatSlot(slot: Slot) {
const inicio = new Date(slot.fechaInicio);
const fin = new Date(slot.fechaFin);
const dia = inicio.toLocaleDateString("es-AR", { weekday: "long", day: "numeric", month: "long" });
const h1 = inicio.toLocaleTimeString("es-AR", { hour: "2-digit", minute: "2-digit" });
const h2 = fin.toLocaleTimeString("es-AR", { hour: "2-digit", minute: "2-digit" });
return { dia: dia.charAt(0).toUpperCase() + dia.slice(1), horas: `${h1} - ${h2}` };
}
if (step === "confirmado" && turnoCreado) {
const { dia, horas } = formatSlot({ fechaInicio: turnoCreado.fechaInicio, fechaFin: turnoCreado.fechaFin });
return (
<div className="flex flex-col items-center justify-center min-h-[50vh] text-center gap-4">
<div className="text-5xl">✅</div>
<h1 className="text-2xl font-bold text-gray-800">¡Turno reservado!</h1>
<Card className="text-left w-full max-w-sm">
<p className="text-sm text-gray-500 mb-1">Servicio</p>
<p className="font-semibold mb-3">{turnoCreado.tipoServicio.nombre}</p>
<p className="text-sm text-gray-500 mb-1">Fecha</p>
<p className="font-semibold mb-1">{dia}</p>
<p className="text-gray-600">{horas}</p>
</Card>
<Link href="/cliente/dashboard">
<Button variant="secondary">← Volver a mis autos</Button>
</Link>
</div>
);
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
{step === "slot" ? (
<Button variant="ghost" className="px-2" onClick={() => setStep("servicio")}>←</Button>
) : (
<Link href="/cliente/dashboard"><Button variant="ghost" className="px-2">←</Button></Link>
)}
<h1 className="text-2xl font-bold text-gray-800">
{step === "servicio" ? "Elegí el servicio" : "Elegí el horario"}
</h1>
</div>
{step === "servicio" && (
<>
<div className="grid gap-3">
{servicios.map((s) => (
<button
key={s.id}
onClick={() => seleccionarServicio(s)}
className="text-left bg-white border border-gray-200 rounded-xl p-4 hover:border-blue-500 hover:shadow-sm transition flex items-center justify-between"
>
<div>
<p className="font-medium text-gray-800">{s.nombre}</p>
<p className="text-sm text-gray-500 mt-0.5">
Duración: {s.duracionHoras === 1 ? "1 hora" : `${s.duracionHoras} horas`}
</p>
</div>
<span className="text-gray-400 text-lg">→</span>
</button>
))}
</div>
{!loadingTurnos && misTurnos.length > 0 && (
<Card title="Mis turnos para este auto">
<div className="flex flex-col gap-3">
{misTurnos.map((t) => {
const { dia, horas } = formatSlot({ fechaInicio: t.fechaInicio, fechaFin: t.fechaFin });
return (
<div key={t.id} className="flex items-center justify-between flex-wrap gap-2">
<div>
<p className="text-sm font-medium">{t.tipoServicio.nombre}</p>
<p className="text-xs text-gray-500">{dia} · {horas}</p>
</div>
<Badge estado={t.estado} />
</div>
);
})}
</div>
</Card>
)}
</>
)}
{step === "slot" && (
<div className="flex flex-col gap-4">
{loadingSlots ? (
<div className="text-center py-10 text-gray-400">Buscando horarios disponibles...</div>
) : slots.length === 0 ? (
<Card>
<p className="text-center text-gray-400 py-4">
No hay horarios disponibles en los próximos 14 días. Consultá al taller.
</p>
</Card>
) : (
<>
<p className="text-sm text-gray-500">
Servicio: <strong>{servicioSel?.nombre}</strong>
</p>
<div className="grid gap-2">
{slots.map((slot, i) => {
const { dia, horas } = formatSlot(slot);
const sel = slotSel?.fechaInicio === slot.fechaInicio;
return (
<button
key={i}
onClick={() => setSlotSel(slot)}
className={`text-left rounded-xl p-4 border transition ${
sel
? "border-blue-500 bg-blue-50"
: "border-gray-200 bg-white hover:border-blue-300"
}`}
>
<p className="font-medium text-gray-800">{dia}</p>
<p className="text-sm text-gray-500">{horas}</p>
</button>
);
})}
</div>
<Button
onClick={reservar}
disabled={!slotSel}
loading={reservando}
className="w-full mt-2"
>
Confirmar turno
</Button>
</>
)}
</div>
)}
</div>
);
}
export default function TurnosPage() {
return (
<Suspense fallback={<div className="text-center py-10 text-gray-400">Cargando...</div>}>
<TurnosContent />
</Suspense>
);
}