Files
nexarch/web/mail-postfach/lib/folderTree.ts
patrickandClaude Sonnet 5 9b1661d6ff UX-01: postfach-ordneransicht
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>
2026-10-16 06:54:40 +00:00

62 lines
2.0 KiB
TypeScript

// Akzeptanzkriterium 1: Ordnerbaum. Die Mail-API liefert Postfächer/Ordner
// als flache Liste von Namen (mail/internal/search: mailbox-Facette, keine
// Hierarchie im Datenmodell) — IMAP-Importe benennen verschachtelte Ordner
// aber üblicherweise mit "/" als Trenner (z. B. "INBOX/Projekte"). Dieser
// Baum wird daher rein clientseitig aus den flachen Namen aufgebaut, ohne
// eine Backend-Hierarchie zu erfinden, die es nicht gibt.
import type { MailboxDTO } from "./api";
export interface FolderNode {
name: string; // voller Pfad, z.B. "INBOX/Projekte" — das ist der Wert für fetchMessages({ mailbox })
label: string; // letztes Pfadsegment, für die Anzeige
depth: number;
messageCount: number | null; // null = reine Gruppierungsebene, kein eigenes Postfach
children: FolderNode[];
}
export function buildFolderTree(mailboxes: MailboxDTO[]): FolderNode[] {
const roots: FolderNode[] = [];
const byPath = new Map<string, FolderNode>();
function ensureNode(segments: string[]): FolderNode {
const path = segments.join("/");
const existing = byPath.get(path);
if (existing) return existing;
const node: FolderNode = {
name: path,
label: segments[segments.length - 1],
depth: segments.length - 1,
messageCount: null,
children: [],
};
byPath.set(path, node);
if (segments.length === 1) {
roots.push(node);
} else {
const parent = ensureNode(segments.slice(0, -1));
parent.children.push(node);
}
return node;
}
for (const mailbox of mailboxes) {
const node = ensureNode(mailbox.name.split("/"));
node.messageCount = mailbox.messageCount;
}
return roots;
}
export function flattenVisible(nodes: FolderNode[], expanded: Set<string>): FolderNode[] {
const out: FolderNode[] = [];
for (const node of nodes) {
out.push(node);
if (node.children.length > 0 && expanded.has(node.name)) {
out.push(...flattenVisible(node.children, expanded));
}
}
return out;
}