Phase 25: leaderboard table + trade history in dashboard
- Division tab: leaderboard table (10 clubs, color-coded promo/relegation rows, player row highlighted, sorted by pts) - Market tab: trade history table (last 30 buy/sell events, card name/OVR/price/date) loaded alongside market listings and my listings - Mapper: added GET /division/leaderboard and GET /trade/history exact routes - Mapper assertion bumped to ≥61 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+80
-1
@@ -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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -529,6 +544,10 @@ h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" style="margin-top:28px">
|
||||
<h2>Division Leaderboard</h2>
|
||||
<div id="division-leaderboard"><span class="loading">Loading…</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── FUT Champions tab ─────────────────────────────────────────────────── -->
|
||||
@@ -820,6 +839,13 @@ h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;
|
||||
<div id="my-listings"><span class="loading">Loading…</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" style="margin-top:28px">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
|
||||
<h2 style="border:none;margin:0">Trade History</h2>
|
||||
<button class="btn btn-secondary btn-sm" onclick="loadTradeHistory()">Refresh</button>
|
||||
</div>
|
||||
<div id="trade-history"><span class="loading">Loading…</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Events tab ────────────────────────────────────────────────────────── -->
|
||||
@@ -1437,6 +1463,34 @@ async function loadDivision() {
|
||||
}).join('');
|
||||
}
|
||||
} catch (_) { el('season-history').innerHTML = '<span class="empty">History unavailable.</span>'; }
|
||||
|
||||
// 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 = `
|
||||
<div style="font-size:0.72rem;color:#8b949e;margin-bottom:8px">
|
||||
Division ${lb.division ?? '?'} · Season ${lb.season_number ?? '?'}
|
||||
| <span style="color:#3fb950">●</span> Top ${promoLine} promote
|
||||
| <span style="color:#f85149">●</span> Bottom ${10 - relLine} relegate
|
||||
</div>
|
||||
<table class="ldb-table">
|
||||
<thead><tr><th>#</th><th>Club</th><th>W</th><th>D</th><th>L</th><th>Pts</th></tr></thead>
|
||||
<tbody>${rows.map(r => {
|
||||
const pClass = r.position <= promoLine ? 'ldb-promote'
|
||||
: r.position > relLine ? 'ldb-relegate' : '';
|
||||
const playerCls = r.is_player ? 'ldb-player' : '';
|
||||
return `<tr class="${pClass} ${playerCls}">
|
||||
<td>${r.position}</td>
|
||||
<td>${r.club_name}${r.is_player ? ' <span style="font-size:0.7rem;color:#f5a623">(you)</span>' : ''}</td>
|
||||
<td>${r.wins}</td><td>${r.draws}</td><td>${r.losses}</td>
|
||||
<td><strong>${r.pts}</strong></td>
|
||||
</tr>`;
|
||||
}).join('')}</tbody>
|
||||
</table>`;
|
||||
} catch (_) { el('division-leaderboard').innerHTML = '<span class="empty">Leaderboard unavailable.</span>'; }
|
||||
}
|
||||
|
||||
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 = '<span class="empty">No trade history yet.</span>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = `<table class="trade-table">
|
||||
<thead><tr><th>Card</th><th>OVR</th><th>Type</th><th>Price</th><th>Date</th></tr></thead>
|
||||
<tbody>${trades.map(t => `<tr>
|
||||
<td>${t.card_name}</td>
|
||||
<td>${t.card_overall}</td>
|
||||
<td class="trade-${t.trade_type}">${t.trade_type === 'buy' ? 'Bought' : 'Sold'}</td>
|
||||
<td>${(t.price ?? 0).toLocaleString()}</td>
|
||||
<td style="color:#8b949e">${new Date(t.traded_at).toLocaleDateString()}</td>
|
||||
</tr>`).join('')}
|
||||
</tbody></table>`;
|
||||
} catch (e) {
|
||||
box.innerHTML = `<span class="empty">Trade history unavailable.</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
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 (_) {}
|
||||
|
||||
+13
-2
@@ -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 ─────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user