Postfach- und Ordneransicht fuer archivierte Mails (Next.js/React/TS, @nexarch/shl). Ordnerbaum (aus der flachen Postfachliste der Mail-API clientseitig aufgebaut), virtualisierte Nachrichtenliste mit Sortierung nach Datum/Absender/Betreff, Grundsuche. Aufbau nach etabliertem Muster (web/mail-search fuer die App-Struktur, web/dms-browser fuer Ordnerbaum/ virtualisierte Liste), bewusst schlicht gehalten (Mailpit/MailHog als Vorbild, nicht die Komplexitaet eines nativen Mailprogramms). Automatisierte Tests je Akzeptanzkriterium, inkl. eines echten Backend-Integrationstests (e2e/ux01.e2e.test.ts) gegen den echten mailapi.Server (mail/cmd/mailpostfach-fixture) mit zwei Mandanten und einem gleichnamigen Ordner — keine Mandanten-Isolationslogik wird gemockt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
36 lines
1.2 KiB
TypeScript
36 lines
1.2 KiB
TypeScript
// Reine Virtualisierungs-Logik (Akzeptanzkriterium 3: Liste bleibt bei
|
|
// mehreren tausend Nachrichten performant bedienbar) — bewusst ohne
|
|
// Drittanbieter-Bibliothek, gleiche Technik wie
|
|
// web/dms-browser/lib/virtualization.ts: feste Zeilenhöhe genügt für eine
|
|
// tabellarische Liste, die Berechnung ist eine reine Funktion von
|
|
// Scrollposition/Containerhöhe, unabhängig vom DOM testbar.
|
|
|
|
export interface VisibleRange {
|
|
startIndex: number;
|
|
endIndex: number;
|
|
offsetY: number;
|
|
}
|
|
|
|
export function computeVisibleRange(
|
|
scrollTop: number,
|
|
containerHeight: number,
|
|
rowHeight: number,
|
|
totalRows: number,
|
|
overscan = 5
|
|
): VisibleRange {
|
|
if (totalRows <= 0 || rowHeight <= 0) {
|
|
return { startIndex: 0, endIndex: 0, offsetY: 0 };
|
|
}
|
|
const firstVisible = Math.floor(scrollTop / rowHeight);
|
|
const visibleCount = Math.ceil(containerHeight / rowHeight);
|
|
|
|
const startIndex = Math.min(Math.max(0, firstVisible - overscan), totalRows);
|
|
const endIndex = Math.min(totalRows, firstVisible + visibleCount + overscan);
|
|
|
|
return { startIndex, endIndex, offsetY: startIndex * rowHeight };
|
|
}
|
|
|
|
export function totalContentHeight(totalRows: number, rowHeight: number): number {
|
|
return totalRows * rowHeight;
|
|
}
|