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:
@@ -21,6 +21,13 @@ export default function DishManager() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
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 { data, error } = await supabase
|
||||
@@ -83,10 +90,16 @@ export default function DishManager() {
|
||||
});
|
||||
}
|
||||
|
||||
// "Alle auswählen" bezieht sich auf die aktuell gefilterte Liste
|
||||
function toggleAll() {
|
||||
setSelected((prev) =>
|
||||
prev.size === dishes.length ? new Set() : new Set(dishes.map((d) => d.id))
|
||||
);
|
||||
setSelected((prev) => {
|
||||
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() {
|
||||
@@ -109,7 +122,8 @@ export default function DishManager() {
|
||||
await load();
|
||||
}
|
||||
|
||||
const allSelected = dishes.length > 0 && selected.size === dishes.length;
|
||||
const allSelected =
|
||||
visible.length > 0 && visible.every((d) => selected.has(d.id));
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl">
|
||||
@@ -182,6 +196,16 @@ export default function DishManager() {
|
||||
<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 && (
|
||||
<div className="flex items-center gap-3 mb-2 text-sm">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
@@ -206,7 +230,7 @@ export default function DishManager() {
|
||||
</div>
|
||||
)}
|
||||
<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">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -245,6 +269,11 @@ export default function DishManager() {
|
||||
Noch keine Gerichte angelegt.
|
||||
</li>
|
||||
)}
|
||||
{dishes.length > 0 && visible.length === 0 && (
|
||||
<li className="px-4 py-3 text-gray-500">
|
||||
Keine Treffer für „{search}“.
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user