Gerichte: Live-Suche

Filtert die Liste bereits während des Tippens (Name, ohne Groß-/
Kleinschreibung). "Alle auswählen" bezieht sich auf die gefilterte
Liste, Treffer-Hinweis bei leerem Ergebnis.
This commit is contained in:
2026-07-17 23:53:12 +02:00
parent 2a62d07dc4
commit 5c7112ecd2
+34 -5
View File
@@ -21,6 +21,13 @@ export default function DishManager() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [selected, setSelected] = useState<Set<string>>(new Set()); const [selected, setSelected] = useState<Set<string>>(new Set());
const [bulkBusy, setBulkBusy] = useState(false); const [bulkBusy, setBulkBusy] = useState(false);
const [search, setSearch] = useState("");
// Live-Suche: filtert die Liste bereits während des Tippens
const visible = useMemo(() => {
const q = search.trim().toLowerCase();
return q ? dishes.filter((d) => d.name.toLowerCase().includes(q)) : dishes;
}, [dishes, search]);
const load = useCallback(async () => { const load = useCallback(async () => {
const { data, error } = await supabase const { data, error } = await supabase
@@ -83,10 +90,16 @@ export default function DishManager() {
}); });
} }
// "Alle auswählen" bezieht sich auf die aktuell gefilterte Liste
function toggleAll() { function toggleAll() {
setSelected((prev) => setSelected((prev) => {
prev.size === dishes.length ? new Set() : new Set(dishes.map((d) => d.id)) const next = new Set(prev);
); const everyVisibleSelected = visible.every((d) => next.has(d.id));
visible.forEach((d) =>
everyVisibleSelected ? next.delete(d.id) : next.add(d.id)
);
return next;
});
} }
async function removeSelected() { async function removeSelected() {
@@ -109,7 +122,8 @@ export default function DishManager() {
await load(); await load();
} }
const allSelected = dishes.length > 0 && selected.size === dishes.length; const allSelected =
visible.length > 0 && visible.every((d) => selected.has(d.id));
return ( return (
<div className="max-w-3xl"> <div className="max-w-3xl">
@@ -182,6 +196,16 @@ export default function DishManager() {
<p className="text-gray-500">Lade Gerichte…</p> <p className="text-gray-500">Lade Gerichte…</p>
) : ( ) : (
<> <>
{dishes.length > 0 && (
<input
type="search"
className="input mb-3"
placeholder="Gericht suchen…"
value={search}
onChange={(e) => setSearch(e.target.value)}
aria-label="Gerichte durchsuchen"
/>
)}
{dishes.length > 0 && ( {dishes.length > 0 && (
<div className="flex items-center gap-3 mb-2 text-sm"> <div className="flex items-center gap-3 mb-2 text-sm">
<label className="flex items-center gap-2 cursor-pointer"> <label className="flex items-center gap-2 cursor-pointer">
@@ -206,7 +230,7 @@ export default function DishManager() {
</div> </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) => ( {visible.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 <input
type="checkbox" type="checkbox"
@@ -245,6 +269,11 @@ export default function DishManager() {
Noch keine Gerichte angelegt. Noch keine Gerichte angelegt.
</li> </li>
)} )}
{dishes.length > 0 && visible.length === 0 && (
<li className="px-4 py-3 text-gray-500">
Keine Treffer für „{search}“.
</li>
)}
</ul> </ul>
</> </>
)} )}