Files
Speisekarte/components/DishManager.tsx
T

179 lines
5.2 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 { useCallback, useEffect, useMemo, useState } from "react";
import { createClient } from "@/lib/supabase/client";
import { formatPrice } from "@/lib/menu";
import type { Dish } from "@/lib/types";
interface DishForm {
id: string | null; // null = neues Gericht
name: string;
price: string;
}
const EMPTY_FORM: DishForm = { id: null, name: "", price: "" };
export default function DishManager() {
const supabase = useMemo(() => createClient(), []);
const [dishes, setDishes] = useState<Dish[]>([]);
const [form, setForm] = useState<DishForm | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const load = useCallback(async () => {
const { data, error } = await supabase
.from("dishes")
.select("*")
.order("name");
if (error) setError(`Laden fehlgeschlagen: ${error.message}`);
else setDishes(data as Dish[]);
setLoading(false);
}, [supabase]);
useEffect(() => {
load();
}, [load]);
async function save(e: React.FormEvent) {
e.preventDefault();
if (!form) return;
setError(null);
const price = Number.parseFloat(form.price.replace(",", "."));
if (Number.isNaN(price) || price < 0) {
setError("Bitte einen gültigen Preis angeben, z. B. 3,50");
return;
}
const payload = { name: form.name.trim(), price };
const { error } = form.id
? await supabase.from("dishes").update(payload).eq("id", form.id)
: await supabase.from("dishes").insert(payload);
if (error) {
setError(`Speichern fehlgeschlagen: ${error.message}`);
return;
}
setForm(null);
await load();
}
async function remove(dish: Dish) {
if (
!window.confirm(
`„${dish.name}“ wirklich löschen? Das Gericht wird auch aus allen Wochenplänen entfernt.`
)
)
return;
const { error } = await supabase.from("dishes").delete().eq("id", dish.id);
if (error) setError(`Löschen fehlgeschlagen: ${error.message}`);
await load();
}
return (
<div className="max-w-3xl">
<div className="flex items-center gap-3 mb-4">
<h1 className="text-xl font-bold text-[var(--c-primary)] mr-auto">
Gerichte
</h1>
<button
type="button"
className="btn btn-add"
onClick={() => setForm(EMPTY_FORM)}
>
Neues Gericht
</button>
</div>
{error && (
<p className="mb-3 text-sm text-[var(--c-del)]" role="alert">
{error}
</p>
)}
{form && (
<form onSubmit={save} className="card mb-4 space-y-3">
<h2 className="font-semibold">
{form.id ? "Gericht bearbeiten" : "Neues Gericht"}
</h2>
<div>
<label className="label" htmlFor="dish-name">
Bezeichnung
</label>
<input
id="dish-name"
className="input"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
required
/>
</div>
<div className="max-w-40">
<label className="label" htmlFor="dish-price">
Preis in €
</label>
<input
id="dish-price"
className="input"
inputMode="decimal"
placeholder="3,50"
value={form.price}
onChange={(e) => setForm({ ...form, price: e.target.value })}
required
/>
</div>
<div className="flex gap-2">
<button type="submit" className="btn btn-add">
Speichern
</button>
<button
type="button"
className="btn btn-neutral"
onClick={() => setForm(null)}
>
Abbrechen
</button>
</div>
</form>
)}
{loading ? (
<p className="text-gray-500">Lade Gerichte…</p>
) : (
<ul className="divide-y divide-gray-200 border border-gray-200 rounded-md bg-white">
{dishes.map((dish) => (
<li key={dish.id} className="flex items-center gap-3 px-4 py-2">
<span className="mr-auto">
<span className="font-semibold">{dish.name}</span>{" "}
<span className="text-gray-500">– {formatPrice(dish.price)}</span>
</span>
<button
type="button"
className="btn btn-primary"
onClick={() =>
setForm({
id: dish.id,
name: dish.name,
price: String(dish.price).replace(".", ","),
})
}
>
Bearbeiten
</button>
<button
type="button"
className="btn btn-del"
onClick={() => remove(dish)}
>
Löschen
</button>
</li>
))}
{dishes.length === 0 && (
<li className="px-4 py-3 text-gray-500">
Noch keine Gerichte angelegt.
</li>
)}
</ul>
)}
</div>
);
}