Dashboard: sortier- und filterbare Tabellen - #292
Merged
Conversation
Jede Dashboard-Liste hat ihre Tabelle bisher selbst gebaut, mit jeweils eigener (oder gar keiner) Sortierung. Diese gemeinsame Komponente auf Basis von TanStack Table v9 gibt allen dasselbe Verhalten: Sortierung je Spalte (Umschalt+Klick für mehrere), ein Trichter je Spalte mit Text-, Set-, Zahlen- und Datumsfilter, eine spaltenübergreifende Suche, ein Menü zum Ein- und Ausblenden von Spalten und einen Seitenumbruch. Der Set-Filter arbeitet mit Faceting: er bietet nur Werte an, die nach den übrigen Spaltenfiltern noch vorkommen, sodass keine Auswahl in eine leere Trefferliste führt. Die Menüs hängen in einem Portal, weil die Tabelle in einem `overflow-x-auto` scrollt und sie sonst abgeschnitten würden. Listen, die serverseitig geblättert werden, können Sortierung, Filter, Suche und Blättern über die `manual*`-Schalter selbst übernehmen — im Browser zu sortieren würde dort nur die gerade geladene Seite betreffen.
Audit-Log, Benutzer, Newsletter-Abonnenten und Anmeldungen werden serverseitig geblättert. Damit die Spaltenköpfe der neuen Tabellen dort über den ganzen Bestand sortieren und filtern statt nur über die gerade geladene Seite, nehmen die Abfragen jetzt `sortBy`/`sortOrder` entgegen und die bisherigen Einzelwert-Filter zusätzlich als Liste. Jede Sortierung außer der nach `createdAt` bekommt `createdAt` als zweites Kriterium: bei gleichen Werten ist die Reihenfolge sonst nicht festgelegt und dieselbe Zeile kann beim Blättern auf zwei Seiten auftauchen oder ganz ausfallen. Die Obergrenze für `limit` steigt mit, weil die Tabellen 50 bis 250 Zeilen je Seite anbieten. Die Standortliste holt ihren Bestand künftig am Stück; sie ist eine überschaubare Stammdatei, und nur so greifen Sortierung und Spaltenfilter über alle Zeilen.
Alle Listen des Dashboards nutzen jetzt dieselbe Tabelle. Damit lässt sich überall nach jeder Spalte sortieren und filtern, statt wie bisher je nach Seite mit einer Sortier-Auswahlliste, einem Suchfeld oder gar nichts vorliebzunehmen. Wo der Bestand überschaubar ist, holt die Seite ihn am Stück und sortiert und filtert im Browser. Audit-Log, Benutzer, Anmeldungen und Newsletter-Abonnenten bleiben serverseitig geblättert und reichen Sortierung, Filter und Suche an die Abfrage weiter; dort tragen nur die Spalten einen Schalter, die der Server auch bedienen kann — ein Bedienelement, das nichts tut, ist schlimmer als keines. Die Listen mit gespeicherter Reihenfolge (Team, Vorstand, Posaunenrat, Posaunenwarte, Förderverein) bekommen eine eigene Spalte dafür und sortieren zunächst danach. Die Hoch-/Runter-Pfeile verschieben weiterhin die gespeicherte Position, egal wonach die Ansicht gerade sortiert ist. Das Audit-Log behält seine gestapelte Kartenansicht auf schmalen Fenstern: fünf Spalten mit einer Detailspalte erreichen ein Telefon sonst nur über waagerechtes Scrollen.
Das Archiv listet Rechnungen vieler Kurse nebeneinander, bot aber keinen Weg in die Rechnungsliste des jeweiligen Kurses. Der Kurstitel war bewusst kein Link, weil diese Seite nur Kursverwaltungen offensteht, das Archiv aber jeder Person mit `invoices.view`. `manageableCourseIds` löst die Abrechnungsberechtigung jetzt für eine ganze Kursliste auf einmal auf, statt für jede Zeile einzeln. Damit kann das Archiv genau die Titel verlinken, hinter denen auch wirklich eine zugängliche Seite liegt; die übrigen bleiben Text. Ist genau ein Kurs gefiltert, führt zusätzlich ein Knopf im Seitenkopf dorthin. Die Filterleiste des Archivs wandert in die Spaltenköpfe: Status, Kurs und Jahr sind Set-Filter, die Suche und die Sortierung laufen weiter über die Abfrage. Die Rechnungsliste eines Kurses wird von einer Aufzählung zur Tabelle und zeigt die Teilnehmer:innen mit an.
Der Weg in die Rechnungen eines Kurses lag bisher an zwei Stellen und an beiden versteckt: einmal nur im Teilnehmer-Reiter der Kursübersicht, einmal unten im Export-Menü der Teilnehmerliste. Von der Mail-Seite, der Kursbearbeitung und einer einzelnen Anmeldung führte gar kein Weg dorthin. Ein gemeinsamer Knopf sitzt jetzt im Kopf jeder Kursseite. Ob er erscheint, entscheidet der Server über `canManageCourseInvoices` — nicht die einzelne Seite, sonst driften Knopf und Guard auseinander und es entsteht entweder ein Knopf, der 403 wirft, oder eine Berechtigung ohne Knopf. Die beiden bisherigen Einsprünge entfallen dafür. Er erscheint auch, wenn die Rechnungsstellung für den Kurs nicht freigeschaltet ist: die Rechnungsseite zeigt dann die bereits bestehenden Rechnungen samt Hinweisbanner, und genau dorthin will man in dem Fall. Das Formular für eine neue Anmeldung bleibt außen vor — ein Absprung mitten im Ausfüllen kostet die Eingaben. Die Teilnehmer-Ansicht wird dabei zur Tabelle: eine Zeile je Teilnehmer:in, mit Sortierung und Spaltenfiltern auch über die kursspezifischen Zusatzfelder.
Termin-, Kurs- und Beitragskarte hielten jeweils eine eigene, identische Tabelle aus Statusbeschriftung und Farbe. Sie liegt jetzt einmal in `content-status` und wird von allen dreien genutzt, damit derselbe Status nicht je nach Ansicht anders heißt.
Die drei Übersichten zeigten ihre Einträge nur als Kartenraster. Wer viele Einträge pflegt, will sie aber vergleichen und nicht durchblättern. Ein Umschalter neben "Auswählen" bietet jetzt beides an und merkt sich die Wahl je Liste; die Kartenansicht bleibt die Vorgabe. In der Tabelle wandern Status, Kategorie und Sortierung in die Spaltenköpfe, in der Kartenansicht bleiben sie in der Leiste — beides gleichzeitig wären zwei Schalter für dieselbe Sache. Die Zeitraumwahl gilt in beiden Ansichten, weil sie kein Spaltenmerkmal ist. Neu für beide Ansichten ist ein Bezirksfilter; er kommt als Liste an den Server und erlaubt in der Tabelle mehrere Bezirke auf einmal. Die Suche der Tabelle geht ebenfalls an die Abfrage und greift über alle Seiten. Der Bezirksfilter tritt neben die bestehende Bezirksbindung, nicht an ihre Stelle: wer nur den eigenen Bezirk sehen darf, filtert damit innerhalb dieser Auswahl. Die Sortierung ist der Zustand der Liste, nicht mehr eine Ableitung aus Spalte und Richtung. Nur so kann der dritte Klick auf einen Spaltenkopf die Sortierung wirklich aufheben und zur Standardordnung zurückkehren — vorher verwarf der Aufrufer die leere Sortierung und die Spalte hing auf "absteigend" fest. Die Filterleiste bekommt dabei ihre eigene Zeile unter Zählung und Umschalter. Die Auswahllisten haben feste Breiten und drängelten sich in einer gemeinsamen Zeile bei mittleren Fenstern gegenseitig weg.
Die Karten haben den Rohwert aus der Datenbank durch `capitalizeFirstLetter` geschickt. Für die meisten Werte geht das gut, für `OTHER` nicht: die Karte schrieb "Other", während der Filter daneben dieselbe Kursart "Andere" nennt. Die Zuordnung von Klartext zu Datenbankwert stand ohnehin schon in der Filterleiste. Sie liegt jetzt in `termine-labels` und wird in beide Richtungen aus derselben Tabelle abgeleitet, damit Filter und Liste nicht wieder auseinanderlaufen.
Jeder Monat saß in einer Box mit grauer Kopfzeile, und darin standen Karten — eine Verschachtelung, die es inhaltlich nicht gibt. Die Überschrift ist jetzt Typografie mit einer Haarlinie darunter und einem Chevron, der sich beim Zuklappen dreht. Aufklappbar bleibt sie. Die vergangenen Termine hatten zusätzlich eine eigene Monatsebene: ein Klapper im Klapper, zwei Ebenen für eine Liste, die man ohnehin von neu nach alt durchliest. Sie stehen jetzt flach unter ihrem Abschnitt. Neu ist ein Schalter in der Filterleiste, der die Monatsüberschriften ganz abschaltet. Bei wenigen Terminen je Monat bricht das Kartenraster sonst nach jeder einzelnen Karte um, und es steht mehr Überschrift als Inhalt auf der Seite; ohne Gruppierung fließen alle Termine durch dasselbe Raster. Die Wahl wird gespeichert. Der Speicher-Hook dafür lag schon im Ansichtsumschalter des Dashboards und ist jetzt geteilt, statt das Abonnenten-Muster ein zweites Mal abzuschreiben.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alle Listen des Dashboards nutzen jetzt dieselbe Tabelle mit Sortierung und
Filtern je Spalte. Dazu kommen zwei kleinere Wege rund um die Rechnungen und
eine wahlweise Tabellenansicht für die drei Inhaltsübersichten.
Sortier- und filterbare Tabellen
Bisher hat jede Liste ihre Tabelle selbst gebaut — mit einer Sortier-Auswahl,
einem Suchfeld oder gar nichts. Die neue
DataTable(TanStack Table v9) gibtallen dasselbe Verhalten:
Filtern noch vorkommen — eine Auswahl kann nie in eine leere Liste führen
Umgestellt sind alle rund 20 Tabellen des Dashboards.
Client- oder serverseitig. Wo der Bestand überschaubar ist, holt die Seite
ihn am Stück und sortiert und filtert im Browser. Audit-Log, Benutzer,
Anmeldungen, Newsletter-Abonnenten und das Rechnungsarchiv bleiben serverseitig
geblättert und reichen Sortierung, Filter und Suche an die Abfrage weiter — im
Browser zu sortieren würde dort nur die gerade geladene Seite betreffen. Dort
trägt nur die Spalte einen Schalter, die der Server auch bedienen kann.
Die betroffenen Abfragen nehmen dafür
sortBy/sortOrderentgegen und diebisherigen Einzelwert-Filter zusätzlich als Liste. Jede Sortierung außer der
nach
createdAtbekommtcreatedAtals zweites Kriterium, damit beim Blätternkeine Zeile doppelt auftaucht oder ausfällt.
Vom Rechnungsarchiv in die Kursrechnungen
Der Kurstitel im Archiv war bewusst kein Link: die Rechnungsliste eines Kurses
steht nur der Kursverwaltung offen, das Archiv aber jeder Person mit
invoices.view.manageableCourseIdslöst die Abrechnungsberechtigung jetztfür eine ganze Kursliste auf einmal auf. Verlinkt werden genau die Titel, hinter
denen auch eine zugängliche Seite liegt; ist genau ein Kurs gefiltert, führt
zusätzlich ein Knopf im Seitenkopf dorthin.
Rechnungen-Knopf auf allen Kursseiten
Der Weg in die Rechnungen lag an zwei Stellen und an beiden versteckt — im
Teilnehmer-Reiter der Kursübersicht und unten im Export-Menü der
Teilnehmerliste. Von Mail-Seite, Kursbearbeitung und einzelner Anmeldung führte
gar kein Weg dorthin. Ein gemeinsamer Knopf sitzt jetzt im Kopf jeder
Kursseite; ob er erscheint, entscheidet der Server über
canManageCourseInvoices, damit Knopf und Guard nicht auseinanderdriften. Diebeiden bisherigen Einsprünge entfallen.
Tabellenansicht für Termine, Kurse und Beiträge
Ein Umschalter neben "Auswählen" bietet Karten oder Tabelle an und merkt sich
die Wahl je Liste; Karten bleiben die Vorgabe. In der Tabelle wandern Status,
Kategorie und Sortierung in die Spaltenköpfe, in der Kartenansicht bleiben sie
in der Leiste. Neu für beide Ansichten ist ein Bezirksfilter — er tritt neben
die bestehende Bezirksbindung, nicht an ihre Stelle.
Bewusst gelassen
eine Mehrfachauswahl im Spaltenfilter fällt dort auf den ersten Wert zurück.
Der Bezirksfilter kann mehrere.
schlichte Tabelle: ein bis fünf Zeilen Kartendetail, kein Datengitter.
Geprüft
tsc --noEmit,eslint ., 556 Tests undnext buildlaufen durch. Alleumgestellten Seiten in der laufenden Anwendung durchgeklickt, Karten- und
Tabellenansicht, Desktop und 375 px.