Files
taller_automotriz/src/app/admin/dashboard/page.tsx
T
Beldor f79e5ce75c
Deploy / deploy (push) Successful in 4m2s
Lighten admin page titles and loading text over background image
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 17:24:10 -03:00

154 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import Card from "@/components/ui/Card";
import Badge from "@/components/ui/Badge";
import Button from "@/components/ui/Button";
interface Turno {
id: string;
fechaInicio: string;
fechaFin: string;
estado: "PENDIENTE" | "CONFIRMADO" | "CANCELADO" | "COMPLETADO";
tipoServicio: { nombre: string };
auto: { marca: string; modelo: string; patente: string };
cliente: { nombre: string; apellido: string; telefono: string };
}
export default function AdminDashboard() {
const [fecha, setFecha] = useState(() => new Date().toISOString().slice(0, 10));
const [turnos, setTurnos] = useState<Turno[]>([]);
const [loading, setLoading] = useState(true);
const [completando, setCompletando] = useState<Set<string>>(new Set());
const [comentarios, setComentarios] = useState<Record<string, string>>({});
useEffect(() => { cargar(); }, [fecha]);
async function cargar() {
setLoading(true);
const res = await fetch(`/api/admin/turnos?fecha=${fecha}`);
const data = await res.json();
setTurnos(data);
setLoading(false);
}
async function cambiarEstado(id: string, estado: string, comentario?: string) {
await fetch(`/api/admin/turnos/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ estado, comentario }),
});
setCompletando((prev) => { const s = new Set(prev); s.delete(id); return s; });
setComentarios((prev) => { const c = { ...prev }; delete c[id]; return c; });
cargar();
}
function iniciarCompletado(id: string) {
setCompletando((prev) => new Set(prev).add(id));
}
function cancelarCompletado(id: string) {
setCompletando((prev) => { const s = new Set(prev); s.delete(id); return s; });
setComentarios((prev) => { const c = { ...prev }; delete c[id]; return c; });
}
function formatHora(iso: string) {
return new Date(iso).toLocaleTimeString("es-AR", { hour: "2-digit", minute: "2-digit" });
}
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between flex-wrap gap-3">
<h1 className="text-xl sm:text-2xl font-bold text-white">Turnos del día</h1>
<input
type="date"
value={fecha}
onChange={(e) => setFecha(e.target.value)}
className="border border-gray-300 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-500 w-full sm:w-auto"
/>
</div>
{loading ? (
<div className="text-center py-10 text-gray-200">Cargando...</div>
) : turnos.length === 0 ? (
<Card>
<p className="text-center text-gray-400 py-4">No hay turnos para esta fecha.</p>
</Card>
) : (
<div className="flex flex-col gap-4">
{turnos.map((t) => (
<Card key={t.id}>
<div className="flex flex-col gap-3">
<div className="flex items-start justify-between flex-wrap gap-2">
<div>
<p className="font-semibold text-gray-800">
{formatHora(t.fechaInicio)} – {formatHora(t.fechaFin)}
</p>
<p className="text-sm text-gray-600 mt-0.5">{t.tipoServicio.nombre}</p>
</div>
<Badge estado={t.estado} />
</div>
<div className="grid grid-cols-2 gap-x-4 gap-y-1 text-sm text-gray-600 bg-gray-50 rounded-lg p-3">
<span className="text-gray-400">Cliente</span>
<span>{t.cliente.nombre} {t.cliente.apellido}</span>
<span className="text-gray-400">Teléfono</span>
<span>{t.cliente.telefono}</span>
<span className="text-gray-400">Auto</span>
<span>{t.auto.marca} {t.auto.modelo}</span>
<span className="text-gray-400">Patente</span>
<span className="font-mono">{t.auto.patente}</span>
</div>
{t.estado === "PENDIENTE" && (
<div className="flex gap-2">
<Button onClick={() => cambiarEstado(t.id, "CONFIRMADO")} className="flex-1 text-sm">
✓ Confirmar
</Button>
<Button variant="danger" onClick={() => cambiarEstado(t.id, "CANCELADO")} className="flex-1 text-sm">
✕ Cancelar
</Button>
</div>
)}
{t.estado === "CONFIRMADO" && !completando.has(t.id) && (
<Button onClick={() => iniciarCompletado(t.id)} variant="secondary" className="text-sm">
✓ Marcar completado
</Button>
)}
{t.estado === "CONFIRMADO" && completando.has(t.id) && (
<div className="flex flex-col gap-2">
<textarea
value={comentarios[t.id] ?? ""}
onChange={(e) => setComentarios((prev) => ({ ...prev, [t.id]: e.target.value }))}
placeholder="Observaciones (opcional)..."
rows={3}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-500 resize-none"
/>
<div className="flex gap-2">
<Button
onClick={() => cambiarEstado(t.id, "COMPLETADO", comentarios[t.id])}
variant="secondary"
className="flex-1 text-sm"
>
✓ Confirmar completado
</Button>
<Button
variant="danger"
onClick={() => cancelarCompletado(t.id)}
className="flex-1 text-sm"
>
Cancelar
</Button>
</div>
</div>
)}
</div>
</Card>
))}
</div>
)}
</div>
);
}