f79e5ce75c
Deploy / deploy (push) Successful in 4m2s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
136 lines
5.0 KiB
TypeScript
136 lines
5.0 KiB
TypeScript
"use client";
|
|
import { useEffect, useState } from "react";
|
|
import Card from "@/components/ui/Card";
|
|
import Button from "@/components/ui/Button";
|
|
import Input from "@/components/ui/Input";
|
|
|
|
interface Servicio { id: string; nombre: string; duracionHoras: number; activo: boolean; }
|
|
|
|
export default function ServiciosPage() {
|
|
const [servicios, setServicios] = useState<Servicio[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [mostrarForm, setMostrarForm] = useState(false);
|
|
const [form, setForm] = useState({ nombre: "", duracionHoras: "" });
|
|
const [guardando, setGuardando] = useState(false);
|
|
const [editando, setEditando] = useState<Servicio | null>(null);
|
|
const [errorForm, setErrorForm] = useState("");
|
|
|
|
useEffect(() => { cargar(); }, []);
|
|
|
|
async function cargar() {
|
|
setLoading(true);
|
|
const res = await fetch("/api/admin/servicios");
|
|
setServicios(await res.json());
|
|
setLoading(false);
|
|
}
|
|
|
|
function set(f: string) {
|
|
return (e: React.ChangeEvent<HTMLInputElement>) => setForm((v) => ({ ...v, [f]: e.target.value }));
|
|
}
|
|
|
|
async function guardar(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
setGuardando(true);
|
|
setErrorForm("");
|
|
|
|
if (editando) {
|
|
const res = await fetch(`/api/admin/servicios/${editando.id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ nombre: form.nombre, duracionHoras: Number(form.duracionHoras) }),
|
|
});
|
|
if (!res.ok) { setErrorForm("Error al guardar"); setGuardando(false); return; }
|
|
} else {
|
|
const res = await fetch("/api/admin/servicios", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ nombre: form.nombre, duracionHoras: Number(form.duracionHoras) }),
|
|
});
|
|
if (!res.ok) { setErrorForm("Error al guardar"); setGuardando(false); return; }
|
|
}
|
|
|
|
setGuardando(false);
|
|
setMostrarForm(false);
|
|
setEditando(null);
|
|
setForm({ nombre: "", duracionHoras: "" });
|
|
cargar();
|
|
}
|
|
|
|
async function toggleActivo(s: Servicio) {
|
|
await fetch(`/api/admin/servicios/${s.id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ activo: !s.activo }),
|
|
});
|
|
cargar();
|
|
}
|
|
|
|
function iniciarEdicion(s: Servicio) {
|
|
setEditando(s);
|
|
setForm({ nombre: s.nombre, duracionHoras: String(s.duracionHoras) });
|
|
setMostrarForm(true);
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6">
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-2xl font-bold text-white">Tipos de servicio</h1>
|
|
<Button
|
|
onClick={() => { setMostrarForm(true); setEditando(null); setForm({ nombre: "", duracionHoras: "" }); }}
|
|
className="text-sm"
|
|
>
|
|
+ Nuevo servicio
|
|
</Button>
|
|
</div>
|
|
|
|
{mostrarForm && (
|
|
<Card title={editando ? "Editar servicio" : "Nuevo servicio"}>
|
|
<form onSubmit={guardar} className="flex flex-col gap-4">
|
|
<Input id="nombre" label="Nombre del servicio" placeholder="Cambio de aceite" value={form.nombre} onChange={set("nombre")} required />
|
|
<Input id="duracion" type="number" label="Duración (horas)" placeholder="1.5" step="0.5" min="0.5" max="10" value={form.duracionHoras} onChange={set("duracionHoras")} required />
|
|
{errorForm && <p className="text-sm text-red-500">{errorForm}</p>}
|
|
<div className="flex gap-2 justify-end">
|
|
<Button variant="secondary" type="button" onClick={() => { setMostrarForm(false); setEditando(null); }}>Cancelar</Button>
|
|
<Button type="submit" loading={guardando}>Guardar</Button>
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
)}
|
|
|
|
{loading ? (
|
|
<div className="text-center py-10 text-gray-200">Cargando...</div>
|
|
) : (
|
|
<div className="flex flex-col gap-3">
|
|
{servicios.map((s) => (
|
|
<div
|
|
key={s.id}
|
|
className={`flex items-center justify-between gap-3 bg-white border rounded-xl px-4 py-3 ${!s.activo ? "opacity-50" : ""}`}
|
|
>
|
|
<div>
|
|
<p className={`font-medium ${s.activo ? "text-gray-800" : "text-gray-400 line-through"}`}>
|
|
{s.nombre}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
{s.duracionHoras === 1 ? "1 hora" : `${s.duracionHoras} horas`}
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="ghost" onClick={() => iniciarEdicion(s)} className="text-sm px-2 py-1">
|
|
✏️
|
|
</Button>
|
|
<Button
|
|
variant={s.activo ? "secondary" : "ghost"}
|
|
onClick={() => toggleActivo(s)}
|
|
className="text-sm px-3 py-1"
|
|
>
|
|
{s.activo ? "Desactivar" : "Activar"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|