# FIXPLAN Teil UI — Ranglisten & Showcases sichtbar/korrekt machen > Eigenständiger Plan für die Web-UI (`src/Predictalytics.Api/wwwroot/`). Von oben nach unten > abarbeiten. Nach **jeder** Aufgabe im Browser gegen die laufende API prüfen und committen. --- ## 0. Regeln (zuerst lesen) 1. **Backend-Test-Baseline bleibt grün.** Vor Beginn einmal `dotnet test src/Predictalytics.Application.Tests` laufen lassen und die aktuelle Zahl (grün/übersprungen) notieren. Keine Aufgabe hier darf sie senken. Assertions bestehender Tests **niemals** ändern. 2. **Reine Frontend-Änderungen** (`index.html`, `app.js`, `style.css`) brauchen keine Migration. Die zwei kleinen API-Ergänzungen (U3) sind additiv und nullable/optional. 3. **Nichts „drumherum" umbauen.** Kleinste sichere Variante wählen. Bestehende Optik/Klassen (`card`, `data-table`, `tier-badge`, `platform-select`, CSS-Variablen) wiederverwenden, keine neuen Farbwelten erfinden. 4. **Zahlen:** Prozente 0–100, Beträge USD, Zeiten UTC — konsistent zum restlichen UI. 5. Nach jeder Aufgabe: Browser-Check gegen `page-dashboard` bzw. `page-traders`, dann `git commit`. --- ## Kontext — was ist kaputt (im Code verifiziert 2026-07-23) Der Commit *„Add showcase leaderboards endpoint + sortable trader list"* hat das **Backend** erweitert, aber das **Frontend nicht nachgezogen**: - `GET /api/traders/showcases` liefert 7 kuratierte `ShowcaseSection` (`copy_ready`, `smooth_operators`, `rising_stars`, `high_conviction`, `specialists`, `insider_watch`, `red_flags` — siehe `ShowcaseBuilder.cs`). **Kein** Frontend-Code ruft das je auf → das Feature ist unsichtbar. - `GetTradersAsync(sort)` unterstützt serverseitig `pnl`, `pnl30d`, `winrate`, `copytrading`, `calmar`, `conviction`, `profitfactor` (`AnalyticsService.cs:184`). Das Frontend übergibt **keinen** `sort`-Param, holt stattdessen `take=100` in Default-Order und sortiert clientseitig (`app.js:392,420`). → Die aussagekräftigen Metriken (Calmar, Conviction, Profit-Faktor) sind nicht wählbar, und jede Sortierung reiht nur die ersten 100 der Default-Order um statt die echten Top-N zu zeigen. - `filterWinrateMin` / `filterCopyabilityMin` filtern clientseitig nach dem `take=100`-Fetch (`app.js:416`) → ein passender Trader jenseits Rang 100 erscheint nie. - Zwei Sort-Dropdowns schreiben beide `currentSort`: global `#sortSelect` (`app.js:121`) und lokal `#tradersSort` (`index.html:209`), mit **unterschiedlichen** Optionssätzen. - Empty-State nutzt `colspan="11"` (`app.js:407`), die Tabelle hat aber **10** Spalten (`index.html:270`). --- ## Aufgabe U1 — Showcases auf dem Dashboard rendern (größter Hebel) **Ziel:** Die 7 kuratierten Sektionen sichtbar machen — je Sektion Titel, Beschreibung und die Top-Trader als klickbare Kacheln/Zeilen (Klick → `viewTrader(id)`). - [ ] **HTML:** In `index.html` im `#page-dashboard` einen Container `
` ergänzen (unter den bestehenden Dashboard-Karten, vor/nach „Top Traders" — Platzierung so, dass es nicht mit den bestehenden Kacheln kollidiert). - [ ] **JS:** Neue Funktion `loadShowcases()` in `app.js`: - `const sections = await api('/api/traders/showcases');` - Response-Shape (camelCase): `[{ key, title, description, traders: [TraderDto, ...] }]`. - Pro Sektion eine `card` rendern: `title` als Überschrift, `description` als `page-subtitle`-artiger Untertitel, darunter die Trader als kompakte Liste/Grid mit Name (+ `🤖` bei `isSuspectedBot`), Plattform, `combinedScore`, `copytradingCopyabilityScore`, `totalPnl` (via `fmt.pnl`), Trait-Chips (dieselbe Chip-Logik wie in `loadTraders`, `getTraitDisplayName/Description/Class`). - Leere Sektionen liefert das Backend gar nicht erst — kein Sonderfall nötig; aber wenn die ganze Antwort leer/`null` ist, freundlicher Empty-State. - Jede Trader-Zeile: `onclick="viewTrader(${t.id})"`. - [ ] `loadShowcases()` in `loadDashboard()` aufrufen (bzw. beim Aktivieren von `page-dashboard`). - [ ] **Optik:** `insider_watch` und `red_flags` optisch abheben (z. B. Warn-Akzent für Red Flags über vorhandene CSS-Variablen `--pnl-negative`), aber im bestehenden Kartenstil bleiben. **U1 fertig, wenn:** Dashboard zeigt die vom Backend gelieferten Sektionen; Klick auf einen Trader öffnet die Detailseite; keine Konsolenfehler. Commit: „UI-U1: render dashboard showcases". --- ## Aufgabe U2 — Server-seitige Sortierung statt Client-Sort auf 100 Zeilen **Ziel:** Die Rangliste zeigt die **echten** Top-N nach der gewählten Metrik, nicht nur eine Umsortierung der ersten 100. - [ ] **JS:** In `loadTraders()` den gewählten Sort als Query-Param an die API hängen: `url += '&sort=' + encodeURIComponent(serverSortKey)`. Mapping UI→Server: `score→` (leer/default), `winrate→winrate`, `copyability→copytrading`, `pnl→pnl`, `pnl30d→pnl30d`, `calmar→calmar`, `conviction→conviction`, `profitfactor→profitfactor`. (Der Server ordnet absteigend — das ist für alle diese Metriken die sinnvolle Richtung.) - [ ] **JS:** Den clientseitigen `data.sort(...)`-Block in `loadTraders()` entfernen bzw. nur noch als Fallback für die rein clientseitigen Keys `name`/`platform` behalten (die kennt der Server nicht). - [ ] **take** von 100 auf einen sinnvollen Wert erhöhen (z. B. 200) — die serverseitige Sortierung liefert jetzt die richtige Reihenfolge, die Tabelle kann eine echte Bestenliste zeigen. - [ ] Die Klick-Sortierung der Tabellenköpfe (`setTraderSort`, `app.js:376`) auf denselben Pfad umstellen: Sort setzen → `loadTraders()` (das nun serverseitig sortiert). Die `↕`-Header behalten. **U2 fertig, wenn:** Auswahl „Calmar/Conviction/Profit-Faktor" (nach U4) verändert die Reihenfolge sichtbar und zeigt Trader, die vorher nicht in den ersten 100 waren. Commit: „UI-U2: server-side leaderboard sort". --- ## Aufgabe U3 — Min-Filter serverseitig (Winrate/Copyability) — additive API-Ergänzung **Ziel:** Die Kennzahl-Mindestfilter dürfen nicht an der 100/200-Grenze abschneiden. - [ ] **API:** `IAnalyticsService.GetTradersAsync` und die Implementierung (`AnalyticsService.cs:165`) um zwei **optionale nullable** Parameter erweitern: `decimal? minWinRate = null, decimal? minCopyability = null`. In der In-Memory-Filterkette (dort wird ohnehin schon `highlyCopyable` und `traitFilter` gefiltert) anwenden: `if (minWinRate is > 0) traders = traders.Where(t => t.WinRate >= minWinRate).ToList();` und analog `t.Analytics?.CopytradingCopyabilityScore >= minCopyability`. - [ ] **Endpoint:** In `TraderEndpoints.cs` die `/api/traders`-Route um die zwei Query-Parameter durchreichen. - [ ] **JS:** In `loadTraders()` `filterWinrateMin`/`filterCopyabilityMin` als Query-Param senden statt clientseitig zu filtern; den clientseitigen Filter-Block entfernen. - [ ] **Test:** Kleiner Service-Test (SQLite-Muster wie vorhandene `AnalyticsService`/Repository-Tests): drei Trader mit WinRate 40/60/80, `minWinRate=55` → nur zwei zurück. Baseline bleibt grün. **U3 fertig, wenn:** Ein hoher Mindest-Copyability-Wert zeigt auch Trader, die in der Default-Order weit hinten stehen. Commit: „UI-U3: server-side min-winrate/min-copyability filters". --- ## Aufgabe U4 — Sort-Dropdown vereinheitlichen & vervollständigen **Ziel:** Ein einziges, vollständiges Sort-Steuerelement für die Rangliste. - [ ] **HTML:** `#tradersSort` (`index.html:209`) um die aussagekräftigen Metriken erweitern: `pnl30d` (PnL 30 T), `calmar` (Rendite/Drawdown), `conviction` (Conviction-Edge), `profitfactor` (Profit-Faktor), `copyability` (bereits da), `score`/`winrate`/`pnl`/`name` behalten. - [ ] **JS:** Klarstellen, welches Dropdown die Traders-Seite steuert. `#tradersSort` bleibt maßgeblich für `page-traders`; das globale `#sortSelect` (falls es die Traders-Seite mitsteuert) entkoppeln oder auf denselben State spiegeln, sodass es **kein** widersprüchliches `currentSort` mehr gibt. - [ ] Sicherstellen, dass Header-Klick (`setTraderSort`) und Dropdown denselben `currentSort` schreiben und beide `loadTraders()` (serverseitig, U2) auslösen. **U4 fertig, wenn:** Alle Dropdown-Optionen sortieren korrekt (serverseitig), Header-Klick und Dropdown bleiben synchron. Commit: „UI-U4: unify + extend leaderboard sort control". --- ## Aufgabe U5 — Kleinkram / Konsistenz - [ ] **colspan-Fix:** Empty-State in `loadTraders()` (`app.js:407`) von `colspan="11"` auf die echte Spaltenzahl **10** korrigieren. - [ ] **Spalten-Parität:** Prüfen, dass Tabellenkopf (`index.html:270`) und Zeilen-Template (`app.js:435`) dieselbe Spaltenzahl/-reihenfolge haben. - [ ] **Optional:** Aktive Sortierspalte im Header visuell markieren (Pfeilrichtung ▲/▼ statt neutralem ↕). **U5 fertig, wenn:** Leere Tabelle rendert sauber über die volle Breite; kein Spaltenversatz. Commit: „UI-U5: leaderboard table consistency". --- ## Gesamt-Abnahme 1. `dotnet build` fehlerfrei; `dotnet test` = notierte Baseline weiter grün + neuer U3-Test grün. 2. Dashboard zeigt die 7 Showcase-Sektionen mit echten Tradern (U1). 3. Sortierung nach Calmar/Conviction/Profit-Faktor zeigt Trader jenseits der alten Top-100 (U2/U4). 4. Mindest-Filter schneiden nicht mehr an der Fetch-Grenze ab (U3). 5. Keine JS-Konsolenfehler; Tabelle ohne Spaltenversatz (U5). **Reihenfolge:** U1 (Sichtbarkeit, reines Frontend) → U2 (Sort-Korrektheit) → U3 (Filter-Korrektheit, kleine API-Ergänzung) → U4 (Bedien-Konsistenz) → U5 (Politur).