rebreak-monorepo/backend/server/api/mail/stats/blocked-by-day.get.ts
chahinebrini 432d9d27a3 feat(mail-page): hero-donut + FAB + collapsible bar-chart + legend truncation
UX-Welle nach User-Feedback aus dem ersten Live-Test der Mail-Page:

Page-Hierarchie neu (top → bottom):

1. HALF-DONUT als HERO-Karte — bisherige "BLOCKIERT XX über N Postfächer Live"-
   Banner-Card weg, Inhalt ist jetzt Title-Zeile innerhalb der Donut-Karte
   (rendert nur ab ≥2 Connections; Fallback-Stats-Row für 0-1 Connections)
2. Postfach-Liste (Account-Cards aus letztem Refactor — schlanker Header)
3. NEU: "Mehr Infos"-Collapsible — Bar-Chart "Blockiert letzte 30 Tage"
   liegt jetzt versteckt drin (default collapsed)
4. Activity-Log "Kürzlich blockiert" (unverändert)
5. NEU: FAB unten rechts — 56pt brandOrange Kreis mit "+"-Icon,
   öffnet ConnectMailSheet. Section-Header-Plus-Button entfällt.

Half-Donut Legend-Truncation:
- ≤3 Connections → alle anzeigen
- =4 Connections → alle anzeigen
- ≥5 Connections → Top-3 by blocked-count + "Sonstige"-Bucket
  · Donut: 4 Segmente (Top-3 + OTHER_COLOR grau)
  · Legend: 4 Zeilen (Top-3 fett, "weitere"-Zeile in regular grau)

Backend: GET /api/mail/stats/blocked-by-day?connectionId=<uuid> als
optionaler Filter (für per-Connection-Bar-Chart in expanded Account-Card,
in dieser Welle noch nicht im UI verdrahtet — Erweiterung kommt wenn
gewünscht).

FAB-Details (iOS-diskreter Shadow statt Material-Glow):
- position absolute, right 24, bottom = tabBarHeight + insets.bottom + 16
- 56pt, borderRadius 28, brandOrange BG, weißes Plus-Icon
- ScrollView paddingBottom angehoben damit kein Content unter dem FAB clipped

Edge-Cases:
- 0 Accounts → FAB sichtbar, Donut/Stats/Charts/Log versteckt + EmptyState
- 1 Account → Donut hidden (nur mit ≥2 Connections sinnvoll), Fallback-Stats-Row
- limitReached + FAB-Tap → bestehender Plan-Alert (FAB ist visuell nicht disabled)

Memory: Pull-to-refresh + bestehendes 30s-Status-Polling reichen für "wartet
auf erste verbindung"→"aktiv"-Übergang nach OAuth-Connect (Daemon-Heartbeat
braucht initial 2-9min, mo-Befund). UX-Polish-Option für später: in der
Initial-Phase einen freundlicheren "Verbinde gerade…"-Status anzeigen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:39:45 +02:00

30 lines
1.1 KiB
TypeScript

import { getBlockedMailsByDay } from "../../../db/mail";
/**
* GET /api/mail/stats/blocked-by-day?days=30[&connectionId=<uuid>]
*
* Blockierte Mails pro Tag (UTC) — Bar-Chart-Datenquelle.
*
* Query params:
* days? number — Anzahl Tage zurück (default 30, max 90)
* connectionId? uuid — Wenn angegeben: nur diese Connection. Gehört die UUID
* einem fremden User, kommen 0-Rows zurück (implizit 404).
*
* Response: { date: 'YYYY-MM-DD', count: number }[]
* — Alle N Tage sind enthalten, auch wenn count=0 (Frontend zeichnet flatline statt Lücken).
* — Timestamps sind UTC.
*/
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const query = getQuery(event);
const rawDays = parseInt((query.days as string) || "30");
const days = Math.min(Math.max(1, isNaN(rawDays) ? 30 : rawDays), 90);
const connectionId = (query.connectionId as string | undefined) || undefined;
const data = await getBlockedMailsByDay(user.id, days, connectionId);
return { success: true, data };
});