# 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 `<div id="showcaseSections"></div>`
      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).
