feat(email): Suchleiste + erweiterte Filter im Email-Postfach

Variante B aus der Trade-off-Diskussion: Suchleiste über der Email-Liste
plus eine ausklappbare Box mit Detail-Filtern, alle AND-verknüpft.

Backend:
- EmailListOptions um search + 9 Detail-Filter erweitert (fromFilter,
  toFilter, subjectFilter, bodyFilter, attachmentNameFilter,
  hasAttachments, isRead, isStarred, receivedFrom, receivedTo)
- getCachedEmails baut die where-Klausel:
  * `search` → OR über Subject/From-Address/From-Name/Body (Volltext-
    Quicksearch)
  * Feldspezifische Filter werden AND-verknüpft an die where gehängt;
    From-/Body-Filter intern als kleine OR-Subqueries (Match in
    Adresse ODER Name; Match in textBody ODER htmlBody)
- Controller-Parser akzeptiert die Filter als Query-Parameter
  (parseBoolParam/parseDateParam tolerieren leere/invalide Werte)

Frontend:
- Suchleiste mit X-Button zum Leeren + Filter-Toggle mit Badge (zeigt
  Anzahl aktiver Filter)
- Ausklappbare Filter-Box: Von, An, Betreff, Inhalt, Datum von/bis,
  Anhang-Dateiname, Mit/Ohne Anhang, Gelesen-Status, Markiert-Status
- Filter-State fließt via useMemo + queryKey in den useQuery → React
  Query macht automatisch ein Re-Fetch bei jeder Änderung
- "Alle zurücksetzen"-Button räumt komplett auf
- Nicht für TRASH-Folder eingeblendet (eigener Pfad ohne Filter-API)

Bewusst nicht gebaut: voller AND/OR-Builder mit Plus-Button und
Bool-Verschachtelung. Reale Such-Use-Cases im Email-Kontext sind
quasi immer AND-verknüpft; Bool-Builder bringt mehr Bedienprobleme
als Mehrwert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 14:31:43 +02:00
parent 51eb12b414
commit 185b38dc55
5 changed files with 330 additions and 6 deletions
+20 -1
View File
@@ -463,9 +463,28 @@ export const stressfreiEmailApi = {
};
// Cached Email API (E-Mail-Client)
export interface EmailFilterParams {
accountId?: number;
folder?: 'INBOX' | 'SENT';
limit?: number;
offset?: number;
// Suche / Filter (alle AND-verknüpft)
search?: string;
fromFilter?: string;
toFilter?: string;
subjectFilter?: string;
bodyFilter?: string;
attachmentNameFilter?: string;
hasAttachments?: boolean;
isRead?: boolean;
isStarred?: boolean;
receivedFrom?: string; // ISO date
receivedTo?: string; // ISO date
}
export const cachedEmailApi = {
// E-Mails für Kunden abrufen
getForCustomer: async (customerId: number, options?: { accountId?: number; folder?: 'INBOX' | 'SENT'; limit?: number; offset?: number }) => {
getForCustomer: async (customerId: number, options?: EmailFilterParams) => {
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails`, { params: options });
return res.data;
},