diff --git a/FIXPLAN-UI-Ranglisten.md b/FIXPLAN-UI-Ranglisten.md new file mode 100644 index 0000000..a81a358 --- /dev/null +++ b/FIXPLAN-UI-Ranglisten.md @@ -0,0 +1,154 @@ +# 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). diff --git a/src/Predictalytics.Api/wwwroot/css/style.css b/src/Predictalytics.Api/wwwroot/css/style.css index 9c412d7..7f6bfc3 100644 --- a/src/Predictalytics.Api/wwwroot/css/style.css +++ b/src/Predictalytics.Api/wwwroot/css/style.css @@ -1106,4 +1106,84 @@ a:hover { color: #8ab8ff; } .detail-grid { grid-template-columns: 1fr; } + .showcase-wrap { + grid-template-columns: 1fr; + } +} + +/* ─── Curated Showcases (dashboard leaderboards) ─── */ +.showcase-wrap { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); + gap: 16px; + margin-bottom: 20px; +} +.showcase-card { + padding: 18px 18px 8px; +} +.showcase-card--warn { + border-color: var(--danger-glow); + box-shadow: 0 0 0 1px var(--danger-glow) inset; +} +.showcase-header { + margin-bottom: 12px; +} +.showcase-header h2 { + font-size: 15px; + font-weight: 700; +} +.showcase-desc { + font-size: 12px; + color: var(--text-muted); + margin-top: 3px; + line-height: 1.4; +} +.showcase-list { + display: flex; + flex-direction: column; +} +.showcase-row { + display: grid; + grid-template-columns: 22px 1fr auto auto auto; + align-items: center; + gap: 10px; + padding: 9px 4px; + border-top: 1px solid var(--border); + cursor: pointer; + transition: var(--transition); +} +.showcase-row:hover { + background: var(--bg-card-hover); +} +.showcase-rank { + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-darker); + text-align: center; +} +.showcase-name { + font-weight: 600; + font-size: 13px; + color: var(--text-primary); + min-width: 0; + overflow: hidden; +} +.showcase-platform { + font-size: 11px; + color: var(--text-muted); + font-weight: 500; + margin-left: 6px; +} +.showcase-metric { + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-secondary); + text-align: right; + white-space: nowrap; +} +.showcase-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 4px; } diff --git a/src/Predictalytics.Api/wwwroot/index.html b/src/Predictalytics.Api/wwwroot/index.html index c7d85d7..81e4db5 100644 --- a/src/Predictalytics.Api/wwwroot/index.html +++ b/src/Predictalytics.Api/wwwroot/index.html @@ -156,6 +156,9 @@ + + +Noch keine Showcases verfügbar.
No traders tracked yet.
No traders tracked yet.