Web: DataTable - Spalten ein-/ausblenden (Spalten-Waehler)

Neuer 'Spalten'-Button in der Tabellen-Toolbar oeffnet ein Popover mit einer Checkbox je Spalte. Abgewaehlte Spalten werden ausgeblendet (Kopf, Filterzeile, Zellen), wieder anhaken zeigt sie an alter Position. Die Auswahl merkt sich der Browser (localStorage, je Tabelle), mindestens eine Spalte bleibt sichtbar, 'Spalten zuruecksetzen' zeigt wieder alle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-27 17:58:01 +02:00
parent 08ee1f20f5
commit e2d2347524

View File

@@ -44,8 +44,14 @@ export default function DataTable({
const [filterShown, setFilterShown] = useState(true);
const [openFilter, setOpenFilter] = useState(null);
const [collapsed, setCollapsed] = useState(() => new Set());
const [hidden, setHidden] = useState(
() => new Set((loadPersisted(id).hidden || []).filter((k) => colByKey[k])),
);
const [chooserOpen, setChooserOpen] = useState(false);
useEffect(() => { savePersisted(id, { order, widths }); }, [id, order, widths]);
useEffect(() => {
savePersisted(id, { order, widths, hidden: [...hidden] });
}, [id, order, widths, hidden]);
// Containerbreite messen, damit die grow-Spalte den Rest füllt.
const wrapRef = useRef(null);
@@ -59,7 +65,39 @@ export default function DataTable({
return () => ro.disconnect();
}, []);
const orderedCols = order.map((k) => colByKey[k]).filter(Boolean);
const allOrdered = order.map((k) => colByKey[k]).filter(Boolean);
const orderedCols = allOrdered.filter((c) => !hidden.has(c.key));
// Spalten-Wähler-Popover.
const chooserBtn = useRef(null);
const chooserPop = useRef(null);
const [chooserPos, setChooserPos] = useState(null);
useLayoutEffect(() => {
if (chooserOpen && chooserBtn.current) {
const r = chooserBtn.current.getBoundingClientRect();
setChooserPos({ left: Math.min(r.left, window.innerWidth - 240), top: r.bottom + 4 });
}
}, [chooserOpen]);
useEffect(() => {
if (!chooserOpen) return undefined;
function onDoc(e) {
if (chooserBtn.current?.contains(e.target) || chooserPop.current?.contains(e.target)) return;
setChooserOpen(false);
}
document.addEventListener("mousedown", onDoc);
return () => document.removeEventListener("mousedown", onDoc);
}, [chooserOpen]);
function toggleHidden(key) {
setHidden((s) => {
const n = new Set(s);
if (n.has(key)) { n.delete(key); return n; }
// Mindestens eine Spalte sichtbar lassen.
if (allOrdered.filter((c) => !n.has(c.key) && c.key !== key).length === 0) return s;
n.add(key);
return n;
});
}
const isGrow = (c) => c.grow && widths[c.key] == null;
const fixedWidth = (c) => widths[c.key] ?? c.width ?? DEFAULT_WIDTH;
@@ -224,6 +262,7 @@ export default function DataTable({
function resetColumns() {
setOrder(columns.map((c) => c.key));
setWidths({});
setHidden(new Set());
savePersisted(id, {});
}
@@ -235,9 +274,26 @@ export default function DataTable({
<Icon name="search" size={14} />Filter
</button>
<button type="button" className="btn sm ghost" onClick={() => setFilters({})}>Filter löschen</button>
<button type="button" ref={chooserBtn} className={`btn sm ${chooserOpen ? "" : "ghost"}`}
onClick={() => setChooserOpen((o) => !o)}>
<Icon name="settings" size={14} />Spalten
</button>
<button type="button" className="btn sm ghost" onClick={resetColumns}>Spalten zurücksetzen</button>
{toolbarExtra}
</div>
{chooserOpen && chooserPos && createPortal(
<div ref={chooserPop} className="dt-pop" style={{ left: chooserPos.left, top: chooserPos.top }}>
<div className="dt-pop-list">
{allOrdered.map((c) => (
<label key={c.key} className="dt-pop-opt">
<input type="checkbox" checked={!hidden.has(c.key)} onChange={() => toggleHidden(c.key)} />
<span>{c.header || c.key}</span>
</label>
))}
</div>
</div>,
document.body,
)}
<div className="table-wrap" ref={wrapRef}>
<table className="table datatable" style={{ width: totalWidth ? `${totalWidth}px` : "100%" }}>
<colgroup>