Skip to content

Commit 3c1d550

Browse files
committed
feat: dashboard and chart improvements
- Add 4 new leagues: Eredivisie (N1), Primeira Liga (P1), Scottish Premiership (SC0), Championship (E1) - Replace Defence with Clean Sheets in Team Performance radar chart - Rename Goals Trend to Goal Trends - Convert date labels to gameweeks (GW1-GW38) for better readability - Show team names and date in Highest Scoring Match stat - Remove SQL Ball logo from European League Analytics heading
1 parent 352106b commit 3c1d550

5 files changed

Lines changed: 70 additions & 31 deletions

File tree

backend/api/dashboard.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -202,23 +202,16 @@ async def get_chart_data(
202202
chart_data = None
203203

204204
if chart_type == "goals_trend":
205-
# Calculate goals per match over time
205+
# Calculate goals per match over time with gameweek labels
206206
dates = []
207207
home_goals = []
208208
away_goals = []
209209
total_goals = []
210210

211-
for match in matches[:380]: # Full season of matches to show complete timeline
212-
# Format date as "Mon DD" for better display
213-
match_date = match.get('match_date', '')
214-
if match_date:
215-
try:
216-
dt = datetime.fromisoformat(str(match_date).replace('Z', '+00:00'))
217-
formatted_date = dt.strftime('%b %d')
218-
except:
219-
formatted_date = str(match_date)[:10] # Fallback to YYYY-MM-DD
220-
else:
221-
formatted_date = ''
211+
for idx, match in enumerate(matches[:380]): # Full season of matches to show complete timeline
212+
# Convert to gameweek format (every 10 games = 1 gameweek)
213+
gameweek = (idx // 10) + 1
214+
formatted_date = f'GW{gameweek}'
222215

223216
dates.append(formatted_date)
224217
home_goals.append(match.get('home_score', 0) or 0)
Binary file not shown.

src/components/Dashboard.svelte

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
let mostCommonScore = '0-0';
6666
let topScoringTeam = '';
6767
let totalTeams = 0;
68-
let highestScoringMatch = { goals: 0, score: '0-0' };
68+
let highestScoringMatch = { goals: 0, score: '0-0', homeTeam: '', awayTeam: '', date: '' };
6969
7070
let goalsChart: ChartData<"line", number[], string> = {
7171
labels: [],
@@ -120,14 +120,26 @@
120120
// Calculate highest scoring match
121121
let highestMatchGoals = 0;
122122
let highestMatchScore = '0-0';
123+
let highestMatchHomeTeam = '';
124+
let highestMatchAwayTeam = '';
125+
let highestMatchDate = '';
123126
dashboardData.recent_matches.forEach(m => {
124127
const totalGoals = (m.home_score || 0) + (m.away_score || 0);
125128
if (totalGoals > highestMatchGoals) {
126129
highestMatchGoals = totalGoals;
127130
highestMatchScore = `${m.home_score || 0}-${m.away_score || 0}`;
131+
highestMatchHomeTeam = m.home_team || '';
132+
highestMatchAwayTeam = m.away_team || '';
133+
highestMatchDate = m.match_date || '';
128134
}
129135
});
130-
highestScoringMatch = { goals: highestMatchGoals, score: highestMatchScore };
136+
highestScoringMatch = {
137+
goals: highestMatchGoals,
138+
score: highestMatchScore,
139+
homeTeam: highestMatchHomeTeam,
140+
awayTeam: highestMatchAwayTeam,
141+
date: highestMatchDate
142+
};
131143
}
132144
133145
console.log('Dashboard loaded from API:', {
@@ -241,14 +253,26 @@
241253
// Calculate highest scoring match
242254
let highestMatchGoals = 0;
243255
let highestMatchScore = '0-0';
256+
let highestMatchHomeTeam = '';
257+
let highestMatchAwayTeam = '';
258+
let highestMatchDate = '';
244259
validMatches.forEach(m => {
245260
const totalGoals = (m.home_score || 0) + (m.away_score || 0);
246261
if (totalGoals > highestMatchGoals) {
247262
highestMatchGoals = totalGoals;
248263
highestMatchScore = `${m.home_score || 0}-${m.away_score || 0}`;
264+
highestMatchHomeTeam = m.home_team || '';
265+
highestMatchAwayTeam = m.away_team || '';
266+
highestMatchDate = m.match_date || '';
249267
}
250268
});
251-
highestScoringMatch = { goals: highestMatchGoals, score: highestMatchScore };
269+
highestScoringMatch = {
270+
goals: highestMatchGoals,
271+
score: highestMatchScore,
272+
homeTeam: highestMatchHomeTeam,
273+
awayTeam: highestMatchAwayTeam,
274+
date: highestMatchDate
275+
};
252276
253277
// Count unique teams (should be close to 397 from European leagues)
254278
const teams = new Set<string>();
@@ -527,8 +551,7 @@
527551
</div>
528552
{/if}
529553

530-
<h2 class="text-2xl font-bold text-slate-800 dark:text-green-400 mb-6 flex items-center gap-3">
531-
<img src="/sqlballlogo.svg" alt="SQL Ball Logo" class="w-8 h-8" />
554+
<h2 class="text-2xl font-bold text-slate-800 dark:text-green-400 mb-6">
532555
European League Analytics
533556
</h2>
534557
<EnhancedVisualizations
@@ -619,6 +642,16 @@
619642
<div class="text-xs text-slate-500 dark:text-slate-500 leading-tight">
620643
{highestScoringMatch.score}
621644
</div>
645+
{#if highestScoringMatch.homeTeam && highestScoringMatch.awayTeam}
646+
<div class="text-xs text-slate-600 dark:text-slate-400 mt-2 font-medium">
647+
{highestScoringMatch.homeTeam} vs {highestScoringMatch.awayTeam}
648+
</div>
649+
{/if}
650+
{#if highestScoringMatch.date}
651+
<div class="text-xs text-slate-500 dark:text-slate-500 mt-1">
652+
{format(new Date(highestScoringMatch.date), 'MMM d, yyyy')}
653+
</div>
654+
{/if}
622655
</div>
623656
</div>
624657
{/if}

src/components/EnhancedVisualizations.svelte

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@
7575
{ value: 'SP1', label: 'La Liga', icon: '🇪🇸' },
7676
{ value: 'D1', label: 'Bundesliga', icon: '🇩🇪' },
7777
{ value: 'I1', label: 'Serie A', icon: '🇮🇹' },
78-
{ value: 'F1', label: 'Ligue 1', icon: '🇫🇷' }
78+
{ value: 'F1', label: 'Ligue 1', icon: '🇫🇷' },
79+
{ value: 'N1', label: 'Eredivisie', icon: '🇳🇱' },
80+
{ value: 'P1', label: 'Primeira Liga', icon: '🇵🇹' },
81+
{ value: 'SC0', label: 'Scottish Premiership', icon: '🏴󠁧󠁢󠁳󠁣󠁴󠁿' },
82+
{ value: 'E1', label: 'Championship', icon: '🏴󠁧󠁢󠁥󠁮󠁧󠁿' }
7983
];
8084
8185
// League-specific top teams (based on typical strong performers)
@@ -84,7 +88,11 @@
8488
'SP1': ['Real Madrid', 'Barcelona', 'Atletico Madrid', 'Real Sociedad', 'Betis', 'Villarreal', 'Athletic Club', 'Valencia'],
8589
'D1': ['Bayern Munich', 'Dortmund', 'RB Leipzig', 'Union Berlin', 'Eintracht Frankfurt', 'Bayer Leverkusen', 'Freiburg', 'Wolfsburg'],
8690
'I1': ['Inter', 'Juventus', 'AC Milan', 'Napoli', 'Roma', 'Lazio', 'Atalanta', 'Fiorentina'],
87-
'F1': ['PSG', 'Marseille', 'Monaco', 'Lyon', 'Lille', 'Rennes', 'Nice', 'Lens']
91+
'F1': ['PSG', 'Marseille', 'Monaco', 'Lyon', 'Lille', 'Rennes', 'Nice', 'Lens'],
92+
'N1': ['PSV', 'Ajax', 'Feyenoord', 'AZ Alkmaar', 'Twente', 'Utrecht', 'Go Ahead Eagles', 'Heerenveen'],
93+
'P1': ['Benfica', 'Porto', 'Sporting CP', 'Braga', 'Vitoria Guimaraes', 'Moreirense', 'Famalicao', 'Casa Pia'],
94+
'SC0': ['Celtic', 'Rangers', 'Hearts', 'Aberdeen', 'Hibernian', 'Motherwell', 'Dundee Utd', 'Kilmarnock'],
95+
'E1': ['Leicester', 'Leeds', 'Southampton', 'Burnley', 'Sheffield Utd', 'Norwich', 'Middlesbrough', 'West Brom']
8896
};
8997
9098
// Filter matches based on league with loading state
@@ -336,12 +344,10 @@
336344
};
337345
}
338346
339-
const labels = recentMatches.map(m => {
340-
try {
341-
return format(new Date(m.match_date), 'MMM dd');
342-
} catch (e) {
343-
return 'Invalid Date';
344-
}
347+
// Convert to gameweek labels (every 10 games = 1 gameweek)
348+
const labels = recentMatches.map((m, idx) => {
349+
const gameweek = Math.floor(idx / 10) + 1;
350+
return `GW${gameweek}`;
345351
});
346352
const homeGoals = recentMatches.map(m => m.home_score || 0);
347353
const awayGoals = recentMatches.map(m => m.away_score || 0);
@@ -447,7 +453,7 @@
447453
const apiData = apiChartData.team_performance;
448454
const colors = ['#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4', '#f97316'];
449455
return {
450-
labels: apiData.labels || ['Wins', 'Points/Game', 'Goals/Game', 'Defense', 'Form'],
456+
labels: apiData.labels || ['Wins', 'Points/Game', 'Goals/Game', 'Clean Sheets', 'Form'],
451457
datasets: (apiData.datasets || []).map((dataset: any, index: number) => ({
452458
...dataset,
453459
borderColor: dataset.borderColor || colors[index] || '#10b981',
@@ -493,24 +499,31 @@
493499
const matchesPlayed = teamMatches.length;
494500
const goalDifference = goals - goalsAgainst;
495501
502+
// Calculate clean sheets (matches where team conceded 0 goals)
503+
const cleanSheets = teamMatches.filter(m => {
504+
if (m.home_team === team) return (m.away_score || 0) === 0;
505+
if (m.away_team === team) return (m.home_score || 0) === 0;
506+
return false;
507+
}).length;
508+
496509
return {
497510
wins: wins * 2, // Scale for visualization
498511
points: points / Math.max(matchesPlayed, 1) * 10, // Points per game * 10
499512
goals: goals / Math.max(matchesPlayed, 1) * 15, // Goals per game * 15
500-
defense: Math.max(0, (20 - (goalsAgainst / Math.max(matchesPlayed, 1) * 10))), // Defensive rating
513+
cleanSheets: cleanSheets * 2, // Clean sheets scaled for visualization
501514
form: goalDifference > 0 ? Math.min(goalDifference * 2, 20) : 0 // Goal difference scaled
502515
};
503516
});
504517
505518
return {
506-
labels: ['Wins', 'Points/Game', 'Goals/Game', 'Defense', 'Form'],
519+
labels: ['Wins', 'Points/Game', 'Goals/Game', 'Clean Sheets', 'Form'],
507520
datasets: teams.map((team, i) => ({
508521
label: team,
509522
data: [
510523
stats[i].wins,
511524
stats[i].points,
512525
stats[i].goals,
513-
stats[i].defense,
526+
stats[i].cleanSheets,
514527
stats[i].form
515528
],
516529
borderColor: colors[i] || '#10b981',
@@ -835,10 +848,10 @@
835848
<div class="flex items-center justify-between mb-4">
836849
<div class="flex items-center gap-2">
837850
<TrendingUp class="w-4 h-4 sm:w-5 sm:h-5 text-green-500" />
838-
<h3 class="text-base sm:text-lg font-bold text-slate-900 dark:text-green-400 font-mono">Goals Trend</h3>
851+
<h3 class="text-base sm:text-lg font-bold text-slate-900 dark:text-green-400 font-mono">Goal Trends</h3>
839852
</div>
840853
<button
841-
on:click={() => handleQueryClick('goals_trend', 'Goals Trend')}
854+
on:click={() => handleQueryClick('goals_trend', 'Goal Trends')}
842855
class="flex items-center gap-1.5 px-3 py-1.5 bg-green-500 hover:bg-green-600 text-white text-xs sm:text-sm rounded-lg transition-colors shadow-sm"
843856
title="Generate SQL query for this chart"
844857
>

0 commit comments

Comments
 (0)