diff --git a/frontend/src/components/Table.tsx b/frontend/src/components/Table.tsx index 3abee64..eb19edd 100644 --- a/frontend/src/components/Table.tsx +++ b/frontend/src/components/Table.tsx @@ -1,4 +1,10 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; +import React, { + useDeferredValue, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import Cookies from "js-cookie"; import { useQuery } from "@tanstack/react-query"; import { getTableData, readCachedTableData } from "../utils/userHandler"; @@ -13,6 +19,7 @@ interface DataPackage { adresse: string | null; plz: string | null; email: string | null; + _search?: string; [key: string]: any; } @@ -20,14 +27,29 @@ const Table: React.FC = () => { const [rows, setRows] = useState([]); // holds normalized cache view const [files, setFiles] = useState([]); const [search, setSearch] = useState(""); + const deferredSearch = useDeferredValue(search); const containerRef = useRef(null); const firstRowRef = useRef(null); const [rowHeight, setRowHeight] = useState(0); + const [containerHeight, setContainerHeight] = useState(); const [range, setRange] = useState<{ start: number; end: number }>({ start: 0, end: 0, }); - const OVERSCAN = 100; // Render 100 rows above and below the viewport + const OVERSCAN = 30; // Render 30 rows above and below the viewport + + // Build a canonical lowercase search text for a row + const buildSearchText = (r: DataPackage) => + [ + r.losnummer, + r.vorname ?? "", + r.nachname ?? "", + r.adresse ?? "", + r.plz ?? "", + r.email ?? "", + ] + .join(" ") + .toLowerCase(); // Einheitliche Input-Styles (nur Tailwind) const inputClasses = @@ -41,7 +63,9 @@ const Table: React.FC = () => { return; } // Server könnte entweder ein Objekt oder ein Array liefern - const normalized: DataPackage[] = Array.isArray(cached) ? cached : [cached]; + const normalized: DataPackage[] = ( + Array.isArray(cached) ? cached : [cached] + ).map((r: DataPackage) => ({ ...r, _search: buildSearchText(r) })); setRows(normalized); }; @@ -79,22 +103,10 @@ const Table: React.FC = () => { // Filter rows by search query (case-insensitive) const filteredRows = useMemo(() => { - const q = search.trim().toLowerCase(); + const q = deferredSearch.trim().toLowerCase(); if (!q) return rows; - return rows.filter((r) => { - const values = [ - r.losnummer, - r.vorname ?? "", - r.nachname ?? "", - r.adresse ?? "", - r.plz ?? "", - r.email ?? "", - ] - .join(" ") - .toLowerCase(); - return values.includes(q); - }); - }, [rows, search]); + return rows.filter((r) => (r._search ?? buildSearchText(r)).includes(q)); + }, [rows, deferredSearch]); // Measure row height once the first visible row mounts useEffect(() => { @@ -137,6 +149,24 @@ const Table: React.FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [rowHeight, filteredRows.length]); + // Make the scroll container fill to the bottom of the viewport + useEffect(() => { + const compute = () => { + const el = containerRef.current; + if (!el) return; + const rect = el.getBoundingClientRect(); + const available = Math.max(0, window.innerHeight - rect.top - 16); // 16px bottom breathing room + if (!containerHeight || Math.abs(available - containerHeight) > 1) { + setContainerHeight(available); + requestAnimationFrame(recomputeRange); + } + }; + compute(); + window.addEventListener("resize", compute); + return () => window.removeEventListener("resize", compute); + // Re-evaluate when content above changes size noticeably + }, [deferredSearch, rows.length]); + // Recompute range whenever data set changes significantly (search, data load) useEffect(() => { // Reset scroll position when filter changes to show top of list @@ -145,7 +175,7 @@ const Table: React.FC = () => { // Next frame compute range for new data requestAnimationFrame(recomputeRange); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [search, rows.length]); + }, [deferredSearch, rows.length]); // Handles input changes for table rows const handleInputChange = ( @@ -155,7 +185,16 @@ const Table: React.FC = () => { ) => { setRows((prevRows) => prevRows.map((row) => - row.losnummer === losnummer ? { ...row, [field]: value } : row + row.losnummer === losnummer + ? { + ...row, + [field]: value, + _search: buildSearchText({ + ...(row as DataPackage), + [field]: value, + } as DataPackage), + } + : row ) ); }; @@ -196,10 +235,11 @@ const Table: React.FC = () => { Refresh - */} + */}