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:
@@ -19,6 +19,8 @@ export default function DishManager() {
|
|||||||
const [form, setForm] = useState<DishForm | null>(null);
|
const [form, setForm] = useState<DishForm | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
|
const [bulkBusy, setBulkBusy] = useState(false);
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
@@ -64,9 +66,51 @@ export default function DishManager() {
|
|||||||
return;
|
return;
|
||||||
const { error } = await supabase.from("dishes").delete().eq("id", dish.id);
|
const { error } = await supabase.from("dishes").delete().eq("id", dish.id);
|
||||||
if (error) setError(`Löschen fehlgeschlagen: ${error.message}`);
|
if (error) setError(`Löschen fehlgeschlagen: ${error.message}`);
|
||||||
|
setSelected((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(dish.id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
await load();
|
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 (
|
return (
|
||||||
<div className="max-w-3xl">
|
<div className="max-w-3xl">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
@@ -137,9 +181,39 @@ export default function DishManager() {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-gray-500">Lade Gerichte…</p>
|
<p className="text-gray-500">Lade Gerichte…</p>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
|
{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-del ml-auto"
|
||||||
|
onClick={removeSelected}
|
||||||
|
disabled={selected.size === 0 || bulkBusy}
|
||||||
|
>
|
||||||
|
Ausgewählte löschen{selected.size > 0 ? ` (${selected.size})` : ""}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ul className="divide-y divide-gray-200 border border-gray-200 rounded-md bg-white">
|
<ul className="divide-y divide-gray-200 border border-gray-200 rounded-md bg-white">
|
||||||
{dishes.map((dish) => (
|
{dishes.map((dish) => (
|
||||||
<li key={dish.id} className="flex items-center gap-3 px-4 py-2">
|
<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="mr-auto">
|
||||||
<span className="font-semibold">{dish.name}</span>{" "}
|
<span className="font-semibold">{dish.name}</span>{" "}
|
||||||
<span className="text-gray-500">– {formatPrice(dish.price)}</span>
|
<span className="text-gray-500">– {formatPrice(dish.price)}</span>
|
||||||
@@ -172,6 +246,7 @@ export default function DishManager() {
|
|||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user