diff --git a/src/dashboard.html b/src/dashboard.html index 42298a3..ec95a05 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -324,6 +324,21 @@ h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; .cd-stat-fill.med { background:#d29922; } .cd-stat-fill.low { background:#f85149; } .cd-actions { display:flex; gap:8px; margin-top:16px; flex-wrap:wrap; } + +/* ── Leaderboard ── */ +.ldb-table { width:100%; border-collapse:collapse; font-size:0.82rem; } +.ldb-table th { text-align:left; color:#8b949e; font-weight:600; border-bottom:1px solid #30363d; padding:6px 8px; } +.ldb-table td { padding:6px 8px; border-bottom:1px solid #161b22; } +.ldb-table tr.ldb-player { background:#1f2d1f; } +.ldb-table tr.ldb-promote td:first-child { border-left:3px solid #3fb950; } +.ldb-table tr.ldb-relegate td:first-child { border-left:3px solid #f85149; } + +/* ── Trade History ── */ +.trade-table { width:100%; border-collapse:collapse; font-size:0.82rem; } +.trade-table th { text-align:left; color:#8b949e; font-weight:600; border-bottom:1px solid #30363d; padding:6px 8px; } +.trade-table td { padding:6px 8px; border-bottom:1px solid #161b22; } +.trade-buy { color:#3fb950; font-weight:700; } +.trade-sell { color:#f5a623; font-weight:700; } @@ -529,6 +544,10 @@ h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; +
+

Division Leaderboard

+
Loading…
+
@@ -820,6 +839,13 @@ h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;
Loading…
+
+
+

Trade History

+ +
+
Loading…
+
@@ -1437,6 +1463,34 @@ async function loadDivision() { }).join(''); } } catch (_) { el('season-history').innerHTML = 'History unavailable.'; } + + // Leaderboard + try { + const lb = await api('GET', '/division/leaderboard'); + const rows = lb.leaderboard || []; + const promoLine = lb.promotion_threshold ?? 3; + const relLine = lb.relegation_threshold ?? 8; + el('division-leaderboard').innerHTML = ` +
+ Division ${lb.division ?? '?'} · Season ${lb.season_number ?? '?'} +  |  Top ${promoLine} promote +  |  Bottom ${10 - relLine} relegate +
+ + + ${rows.map(r => { + const pClass = r.position <= promoLine ? 'ldb-promote' + : r.position > relLine ? 'ldb-relegate' : ''; + const playerCls = r.is_player ? 'ldb-player' : ''; + return ` + + + + + `; + }).join('')} +
#ClubWDLPts
${r.position}${r.club_name}${r.is_player ? ' (you)' : ''}${r.wins}${r.draws}${r.losses}${r.pts}
`; + } catch (_) { el('division-leaderboard').innerHTML = 'Leaderboard unavailable.'; } } async function claimRivalsReward() { @@ -2458,8 +2512,33 @@ function renderDraftSession(data) { let allMarketListings = []; +async function loadTradeHistory() { + const box = el('trade-history'); + if (!box) return; + try { + const d = await api('GET', '/market/trade-history'); + const trades = d.trades || []; + if (!trades.length) { + box.innerHTML = 'No trade history yet.'; + return; + } + box.innerHTML = ` + + ${trades.map(t => ` + + + + + + `).join('')} +
CardOVRTypePriceDate
${t.card_name}${t.card_overall}${t.trade_type === 'buy' ? 'Bought' : 'Sold'}${(t.price ?? 0).toLocaleString()}${new Date(t.traded_at).toLocaleDateString()}
`; + } catch (e) { + box.innerHTML = `Trade history unavailable.`; + } +} + async function loadMarket() { - await Promise.all([loadMarketListings(), loadMyListings()]); + await Promise.all([loadMarketListings(), loadMyListings(), loadTradeHistory()]); // Populate sell card dropdown from collection if (!allCards.length) { try { const d = await api('GET', '/collection'); allCards = d.collection || []; } catch (_) {} diff --git a/src/mapper.rs b/src/mapper.rs index 9c7da51..d9cb0a1 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -237,12 +237,23 @@ const EXACT: &[ExactRoute] = &[ notes: "FUT notifications (plural form) → Core notifications", }, // ── Squad list ──────────────────────────────────────────────────────────── - // ── Division / Season history ───────────────────────────────────────────── + // ── Division / Season history / Leaderboard ─────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/division/history", core_method: "GET", core_path: "/division/history", notes: "FUT division history → Core season history", }, + ExactRoute { + ea_method: "GET", ea_path: "/ut/game/fut/division/leaderboard", + core_method: "GET", core_path: "/division/leaderboard", + notes: "FUT division leaderboard → Core seeded NPC leaderboard", + }, + // ── Market trade history ────────────────────────────────────────────────── + ExactRoute { + ea_method: "GET", ea_path: "/ut/game/fut/trade/history", + core_method: "GET", core_path: "/market/trade-history", + notes: "FUT trade history → Core market trade history", + }, // ── Daily check-in ──────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/dailyObjective", @@ -721,7 +732,7 @@ mod tests { #[test] fn test_total_exact_routes_count() { - assert!(EXACT.len() >= 59, "expected at least 59 exact mappings, got {}", EXACT.len()); + assert!(EXACT.len() >= 61, "expected at least 61 exact mappings, got {}", EXACT.len()); } // ── Phase 22 new mappings ─────────────────────────────────────────────────