Gerichte: Mehrfachauswahl zum Batch-Löschen
Checkbox je Zeile plus "Alle auswählen"; "Ausgewählte löschen (n)" löscht alle markierten Gerichte in einem einzigen delete().in()-Request.
This commit is contained in:
+104
-29
@@ -19,6 +19,8 @@ export default function DishManager() {
|
||||
const [form, setForm] = useState<DishForm | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [bulkBusy, setBulkBusy] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const { data, error } = await supabase
|
||||
@@ -64,9 +66,51 @@ export default function DishManager() {
|
||||
return;
|
||||
const { error } = await supabase.from("dishes").delete().eq("id", dish.id);
|
||||
if (error) setError(`Löschen fehlgeschlagen: ${error.message}`);
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(dish.id);
|
||||
return next;
|
||||
});
|
||||
await load();
|
||||
}
|
||||
|
||||
function toggleOne(id: string) {
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) next.delete(id);
|
||||
else next.add(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAll() {
|
||||
setSelected((prev) =>
|
||||
prev.size === dishes.length ? new Set() : new Set(dishes.map((d) => d.id))
|
||||
);
|
||||
}
|
||||
|
||||
async function removeSelected() {
|
||||
if (selected.size === 0) return;
|
||||
if (
|
||||
!window.confirm(
|
||||
`${selected.size} Gericht(e) wirklich löschen? Sie werden auch aus allen Wochenplänen entfernt.`
|
||||
)
|
||||
)
|
||||
return;
|
||||
setBulkBusy(true);
|
||||
setError(null);
|
||||
const { error } = await supabase
|
||||
.from("dishes")
|
||||
.delete()
|
||||
.in("id", Array.from(selected));
|
||||
if (error) setError(`Löschen fehlgeschlagen: ${error.message}`);
|
||||
setSelected(new Set());
|
||||
setBulkBusy(false);
|
||||
await load();
|
||||
}
|
||||
|
||||
const allSelected = dishes.length > 0 && selected.size === dishes.length;
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
@@ -137,41 +181,72 @@ export default function DishManager() {
|
||||
{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>
|
||||
<>
|
||||
{dishes.length > 0 && (
|
||||
<div className="flex items-center gap-3 mb-2 text-sm">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={allSelected}
|
||||
onChange={toggleAll}
|
||||
/>
|
||||
Alle auswählen
|
||||
</label>
|
||||
<span className="text-gray-500">
|
||||
{selected.size} ausgewählt
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() =>
|
||||
setForm({
|
||||
id: dish.id,
|
||||
name: dish.name,
|
||||
price: String(dish.price).replace(".", ","),
|
||||
})
|
||||
}
|
||||
className="btn btn-del ml-auto"
|
||||
onClick={removeSelected}
|
||||
disabled={selected.size === 0 || bulkBusy}
|
||||
>
|
||||
Bearbeiten
|
||||
Ausgewählte löschen{selected.size > 0 ? ` (${selected.size})` : ""}
|
||||
</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>
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
<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">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.has(dish.id)}
|
||||
onChange={() => toggleOne(dish.id)}
|
||||
aria-label={`${dish.name} auswählen`}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user