From 8b73a68a6b14bf5c64f9f5ba7e24f39f8d17f466 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Tue, 4 Nov 2025 21:49:36 +0100 Subject: [PATCH] feat: Add player profile performance charts and visualizations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented comprehensive performance analysis for player profiles with interactive charts and detailed statistics visualization. Key Features: - Performance Trend Chart (K/D and KAST over last 15 matches) - Map Performance Chart (win rate per map with color coding) - Utility Effectiveness Stats (flash assists, enemies blinded, HE/flame damage) - Responsive charts using Chart.js LineChart and BarChart components Technical Updates: - Enhanced page loader to fetch 15 detailed matches with player stats - Fixed DataTable Svelte 5 compatibility and type safety - Updated MatchCard and PlayerCard to use PlayerMeta properties - Proper error handling and typed data structures 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- TODO.md | 53 ++-- .../components/data-display/DataTable.svelte | 37 +-- src/lib/components/match/MatchCard.svelte | 8 +- src/lib/components/player/PlayerCard.svelte | 20 +- src/routes/player/[id]/+page.svelte | 269 +++++++++++++++--- src/routes/player/[id]/+page.ts | 37 ++- 6 files changed, 328 insertions(+), 96 deletions(-) diff --git a/TODO.md b/TODO.md index 73a0a9e..18a052f 100644 --- a/TODO.md +++ b/TODO.md @@ -225,33 +225,33 @@ - [ ] Empty state with helpful message and search tips - [ ] Export filtered results (CSV/JSON download) -### 5.3 Player Profile (`/player/[id]` - `src/routes/player/[id]/+page.svelte`) +### 5.3 Player Profile (`/player/[id]` - `src/routes/player/[id]/+page.svelte`) ✅ COMPLETE -- [ ] Player header: - - Steam avatar (large) - - Player name, Steam ID - - Current CS2 rank/rating with icon - - Last seen/last match date -- [ ] Career statistics grid: - - Overall K/D ratio, win rate, HS%, ADR, KAST - - Total matches played, hours estimated - - Favorite maps (with win rates per map) - - Favorite weapons (with kill counts) -- [ ] Performance charts: - - Rating trend over time (line chart, last 20 matches) - - Map performance (bar chart: win rate per map) - - Role distribution (entry, support, clutch percentages) - - Weapon accuracy breakdown (radar chart) -- [ ] Recent matches table: - - Scrollable list with match date, map, score, KDA, rating - - Click row to navigate to match detail -- [ ] Utility effectiveness stats: - - Flash assists, enemies blinded - - Smoke effectiveness (teammates trading in smoke) - - HE damage, molotov damage +- [x] Player header: + - ✅ Steam avatar (placeholder with User icon) + - ✅ Player name, Steam ID + - ✅ Current CS2 rank/rating display + - ✅ Favorite player toggle (heart icon) +- [x] Career statistics grid: + - ✅ Overall K/D ratio, win rate, HS% + - ✅ Total matches played + - ⚠️ Favorite maps/weapons: **Deferred to future update** (data available, visualization deferred) +- [x] Performance charts: + - ✅ Performance trend over time (K/D ratio and KAST% line chart, last 15 matches) + - ✅ Map performance (bar chart: win rate per map with color coding) + - ⚠️ Role distribution: **Deferred to future update** (requires additional data analysis) + - ⚠️ Weapon accuracy breakdown: **Deferred to future update** (requires weapons endpoint integration) +- [x] Recent matches section: + - ✅ Card-based grid showing last 10 matches + - ✅ MatchCard component with clickable navigation +- [x] Utility effectiveness stats: + - ✅ Flash assists, enemies blinded + - ✅ HE damage, molotov/flame damage + - ✅ Stats displayed with per-match averages - [ ] Achievements/badges (optional): - - Ace count, clutch wins (1vX), MVP stars -- [ ] Share profile button (generate shareable link/image) + - ⚠️ **Deferred to future update** (Ace count, clutch wins, MVP stars) +- [ ] Share profile button: + - ⚠️ **Deferred to future update** (generate shareable link/image) ### 5.4 Match Overview (`/match/[id]` - `src/routes/match/[id]/+page.svelte`) @@ -399,6 +399,7 @@ ### 5.10 Shared Components Library (`src/lib/components/`) - IN PROGRESS #### Chart Components (`src/lib/components/charts/`) ✅ COMPLETE + - [x] `LineChart.svelte` - Line charts with Chart.js - ✅ Responsive, customizable options - ✅ Svelte 5 runes ($effect for reactivity) @@ -411,6 +412,7 @@ - ✅ Legend positioning #### Data Display Components (`src/lib/components/data-display/`) ✅ COMPLETE + - [x] `DataTable.svelte` - Sortable, filterable tables - ✅ Generic TypeScript support - ✅ Sortable columns with visual indicators @@ -881,6 +883,7 @@ VITE_PLAUSIBLE_DOMAIN=cs2.wtf **Completed Phases**: Phase 0 (Planning), Phase 1 (Technical Foundations), Phase 2 (Design System), Phase 3 (Domain Modeling), Phase 4 (Application Architecture) **Next Milestone**: Complete remaining match detail tabs (Flashes, Damage), enhance player profile with charts **Recent Progress**: + - ✅ Implemented chart components (Line, Bar, Pie) with Chart.js - ✅ Created sortable DataTable component - ✅ Match Economy tab with buy type analysis and equipment value charts diff --git a/src/lib/components/data-display/DataTable.svelte b/src/lib/components/data-display/DataTable.svelte index cbe2188..5e75ddf 100644 --- a/src/lib/components/data-display/DataTable.svelte +++ b/src/lib/components/data-display/DataTable.svelte @@ -1,12 +1,13 @@ -
+

{player.name}

ID: {player.id}

@@ -56,7 +57,7 @@
-
{player.wins + player.losses}
+
{player.recent_matches}
Matches
@@ -64,11 +65,8 @@
- Record: -
- {player.wins}W - {player.losses}L -
+ Avg KAST: + {player.avg_kast.toFixed(1)}%
{/if} diff --git a/src/routes/player/[id]/+page.svelte b/src/routes/player/[id]/+page.svelte index 445126c..6fd8564 100644 --- a/src/routes/player/[id]/+page.svelte +++ b/src/routes/player/[id]/+page.svelte @@ -1,24 +1,28 @@ @@ -42,7 +158,9 @@
-
+
@@ -60,9 +178,7 @@
Steam ID: {profile.id} - {#if profile.rank} - Rating: {profile.rank} - {/if} + Last match: {new Date(profile.last_match_date).toLocaleDateString()}
@@ -86,7 +202,7 @@
{kd}
- {profile.kills} K / {profile.deaths} D + {profile.avg_kills.toFixed(1)} K / {profile.avg_deaths.toFixed(1)} D avg
@@ -97,19 +213,17 @@
{winRate}%
- {profile.wins}W - {profile.losses}L + Last {profile.recent_matches} matches
- Total Matches -
-
{totalMatches}
-
- {profile.wins} wins, {profile.losses} losses + KAST %
+
{profile.avg_kast.toFixed(1)}%
+
Kill/Assist/Survive/Trade average
@@ -119,7 +233,7 @@
{hsPercent}%
- {profile.headshots} headshots + {totalHeadshots} of {totalKills} kills
@@ -129,9 +243,7 @@

Recent Matches

- +
{#if recentMatches.length > 0} @@ -150,15 +262,98 @@ {/if}
- - -
- -

Performance Charts

-

- Rating trends, map performance, favorite weapons, and more visualization coming soon. -

- Coming in Future Update + + {#if playerStats.length > 0} +
+

Performance Analysis

+
+ + +

+
+ + Performance Trend (Last {playerStats.length} Matches) +
+

+
+ +
+

+ Shows K/D ratio and KAST percentage across recent matches. Higher is better. +

+
+ + + +

+
+ + Map Win Rate +
+

+
+ +
+

+ Win rate percentage by map. Green = strong (≥60%), Blue = average (40-60%), Red = weak + (<40%). +

+
+
- + + +
+

Utility Effectiveness

+
+ +
+ + Flash Assists +
+
{utilityStats.flashAssists}
+
+ {(utilityStats.flashAssists / utilityStats.totalMatches).toFixed(1)} per match +
+
+ + +
+ + Enemies Blinded +
+
{utilityStats.enemiesBlinded}
+
+ {(utilityStats.enemiesBlinded / utilityStats.totalMatches).toFixed(1)} per match +
+
+ + +
+ + HE Damage +
+
+ {Math.round(utilityStats.heDamage)} +
+
+ {Math.round(utilityStats.heDamage / utilityStats.totalMatches)} per match +
+
+ + +
+ + Flame Damage +
+
+ {Math.round(utilityStats.flameDamage)} +
+
+ {Math.round(utilityStats.flameDamage / utilityStats.totalMatches)} per match +
+
+
+
+ {/if}
diff --git a/src/routes/player/[id]/+page.ts b/src/routes/player/[id]/+page.ts index 0da27b8..d863f72 100644 --- a/src/routes/player/[id]/+page.ts +++ b/src/routes/player/[id]/+page.ts @@ -13,19 +13,50 @@ export const load: PageLoad = async ({ params }) => { // Fetch player profile and recent matches in parallel const [profile, matchesData] = await Promise.all([ api.players.getPlayerMeta(playerId), - api.matches.getMatches({ player_id: playerId, limit: 10 }) + api.matches.getMatches({ player_id: playerId, limit: 20 }) ]); + // Fetch full match details with player stats for performance charts + // Limit to first 15 matches to avoid too many API calls + const matchDetailsPromises = matchesData.matches + .slice(0, 15) + .map((match) => api.matches.getMatch(match.match_id)); + + const matchesWithDetails = await Promise.all(matchDetailsPromises); + + // Extract player stats from each match + const playerStats = matchesWithDetails + .map((match) => { + const playerData = match.players?.find((p) => p.id === playerId); + if (!playerData) return null; + + return { + match_id: match.match_id, + map: match.map, + date: match.date, + ...playerData, + // Add match result (did player win?) + won: + (playerData.team_id === 2 && match.score_team_a > match.score_team_b) || + (playerData.team_id === 3 && match.score_team_b > match.score_team_a) + }; + }) + .filter((stat): stat is NonNullable => stat !== null); + return { profile, - recentMatches: matchesData.matches, + recentMatches: matchesData.matches.slice(0, 10), // Show 10 in recent matches section + playerStats, // Full stats for charts meta: { title: `${profile.name} - Player Profile | CS2.WTF`, description: `View ${profile.name}'s CS2 statistics, match history, and performance metrics.` } }; } catch (err) { - console.error(`Failed to load player ${playerId}:`, err); + console.error( + `Failed to load player ${playerId}:`, + err instanceof Error ? err.message : String(err) + ); throw error(404, `Player ${playerId} not found`); } };