Skip to content

Commit f9c714f

Browse files
committed
feat: improve dashboard UI with blue theme and best defense stats
Chart colors now use blue in light mode: - Goals Trend, League Table, Goal Distribution: blue instead of green - Results Distribution: blue/purple/cyan palette in light mode - Team Performance: blue for primary dataset in light mode Replace Home Win % with Best Defense stat: - New stat card shows team with fewest goals conceded per league - Added Shield icon and purple color scheme - Calculates best defense from match data (min 5 matches played) Enhanced Results Distribution tooltips: - Format: "{count}/{total} {label} ({percentage}%)" - Example: "155/380 home wins (40.8%)" Update "How This Was All Put Together" section: - Changed title from "How We Analyse" - Updated all 4 cards with detailed tech stack information - RAG System: OpenAI GPT-4, FastAPI, Python - Data Source: Supabase, football-data.co.uk - Pattern Discovery: Chart.js, Plotly, Matplotlib - Tech Stack: React, Svelte, Docker, Supabase, Render, Node.js
1 parent 8088dd4 commit f9c714f

2 files changed

Lines changed: 134 additions & 64 deletions

File tree

src/components/Dashboard.svelte

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import { format } from 'date-fns';
2828
import { tweened } from 'svelte/motion';
2929
import { cubicOut } from 'svelte/easing';
30-
import { TrendingUp, Users, Target, Zap } from 'lucide-svelte';
30+
import { TrendingUp, Users, Target, Zap, Shield } from 'lucide-svelte';
3131
import { supabase, hasValidSupabaseConfig, getRecentMatches } from '../lib/supabase';
3232
import { apiService } from '../services/apiService';
3333
import EnhancedVisualizations from './EnhancedVisualizations.svelte';
@@ -61,8 +61,8 @@
6161
let averageGoals = tweened(0, { duration: 1200, easing: cubicOut });
6262
6363
// Statistics
64-
let homeWinPercentage = 0;
65-
let mostCommonScore = '0-0';
64+
let bestDefenseTeam = '';
65+
let bestDefenseGoalsConceded = 0;
6666
let topScoringTeam = '';
6767
let totalTeams = 0;
6868
let highestScoringMatch = { goals: 0, score: '0-0', homeTeam: '', awayTeam: '', date: '' };
@@ -101,7 +101,6 @@
101101
totalMatches.set(dashboardData.stats.total_matches);
102102
totalGoals.set(dashboardData.stats.total_goals);
103103
averageGoals.set(dashboardData.stats.avg_goals_per_match);
104-
homeWinPercentage = dashboardData.stats.home_win_percentage;
105104
totalTeams = dashboardData.stats.total_teams;
106105
107106
// Set matches for visualizations
@@ -140,6 +139,30 @@
140139
awayTeam: highestMatchAwayTeam,
141140
date: highestMatchDate
142141
};
142+
143+
// Calculate best defense (team with fewest goals conceded)
144+
const teamDefenseStats: Record<string, {conceded: number, matches: number}> = {};
145+
dashboardData.recent_matches.forEach(m => {
146+
const homeTeam = m.home_team;
147+
const awayTeam = m.away_team;
148+
149+
if (!teamDefenseStats[homeTeam]) teamDefenseStats[homeTeam] = {conceded: 0, matches: 0};
150+
if (!teamDefenseStats[awayTeam]) teamDefenseStats[awayTeam] = {conceded: 0, matches: 0};
151+
152+
teamDefenseStats[homeTeam].conceded += (m.away_score || 0);
153+
teamDefenseStats[homeTeam].matches += 1;
154+
teamDefenseStats[awayTeam].conceded += (m.home_score || 0);
155+
teamDefenseStats[awayTeam].matches += 1;
156+
});
157+
158+
let minGoalsConceded = Infinity;
159+
Object.entries(teamDefenseStats).forEach(([team, stats]) => {
160+
if (stats.matches >= 5 && stats.conceded < minGoalsConceded) {
161+
minGoalsConceded = stats.conceded;
162+
bestDefenseTeam = team;
163+
bestDefenseGoalsConceded = stats.conceded;
164+
}
165+
});
143166
}
144167
145168
console.log('Dashboard loaded from API:', {
@@ -229,24 +252,27 @@
229252
const avgGoals = validMatches.length > 0 ? sampleGoals / validMatches.length : 0;
230253
averageGoals.set(avgGoals);
231254
232-
// Home win percentage from European data
233-
const homeWins = validMatches.filter(m => m.result === 'H').length;
234-
homeWinPercentage = validMatches.length > 0
235-
? (homeWins / validMatches.length) * 100
236-
: 0;
237-
238-
// Most common score from European leagues
239-
const scoreMap = new Map<string, number>();
255+
// Calculate best defense (team with fewest goals conceded)
256+
const teamDefenseStats: Record<string, {conceded: number, matches: number}> = {};
240257
validMatches.forEach(m => {
241-
const score = `${m.home_score}-${m.away_score}`;
242-
scoreMap.set(score, (scoreMap.get(score) || 0) + 1);
258+
const homeTeam = m.home_team;
259+
const awayTeam = m.away_team;
260+
261+
if (!teamDefenseStats[homeTeam]) teamDefenseStats[homeTeam] = {conceded: 0, matches: 0};
262+
if (!teamDefenseStats[awayTeam]) teamDefenseStats[awayTeam] = {conceded: 0, matches: 0};
263+
264+
teamDefenseStats[homeTeam].conceded += (m.away_score || 0);
265+
teamDefenseStats[homeTeam].matches += 1;
266+
teamDefenseStats[awayTeam].conceded += (m.home_score || 0);
267+
teamDefenseStats[awayTeam].matches += 1;
243268
});
244269
245-
let maxCount = 0;
246-
scoreMap.forEach((count, score) => {
247-
if (count > maxCount) {
248-
maxCount = count;
249-
mostCommonScore = score;
270+
let minGoalsConceded = Infinity;
271+
Object.entries(teamDefenseStats).forEach(([team, stats]) => {
272+
if (stats.matches >= 5 && stats.conceded < minGoalsConceded) {
273+
minGoalsConceded = stats.conceded;
274+
bestDefenseTeam = team;
275+
bestDefenseGoalsConceded = stats.conceded;
250276
}
251277
});
252278
@@ -338,7 +364,6 @@
338364
totalMatches.set(freshData.stats.total_matches);
339365
totalGoals.set(freshData.stats.total_goals);
340366
averageGoals.set(freshData.stats.avg_goals_per_match);
341-
homeWinPercentage = freshData.stats.home_win_percentage;
342367
totalTeams = freshData.stats.total_teams;
343368
344369
// Update goalsChart with new league data
@@ -372,6 +397,30 @@
372397
date: highestMatchDate
373398
};
374399
400+
// Recalculate best defense for the selected league
401+
const teamDefenseStats: Record<string, {conceded: number, matches: number}> = {};
402+
freshData.recent_matches.forEach(m => {
403+
const homeTeam = m.home_team;
404+
const awayTeam = m.away_team;
405+
406+
if (!teamDefenseStats[homeTeam]) teamDefenseStats[homeTeam] = {conceded: 0, matches: 0};
407+
if (!teamDefenseStats[awayTeam]) teamDefenseStats[awayTeam] = {conceded: 0, matches: 0};
408+
409+
teamDefenseStats[homeTeam].conceded += (m.away_score || 0);
410+
teamDefenseStats[homeTeam].matches += 1;
411+
teamDefenseStats[awayTeam].conceded += (m.home_score || 0);
412+
teamDefenseStats[awayTeam].matches += 1;
413+
});
414+
415+
let minGoalsConceded = Infinity;
416+
Object.entries(teamDefenseStats).forEach(([team, stats]) => {
417+
if (stats.matches >= 5 && stats.conceded < minGoalsConceded) {
418+
minGoalsConceded = stats.conceded;
419+
bestDefenseTeam = team;
420+
bestDefenseGoalsConceded = stats.conceded;
421+
}
422+
});
423+
375424
console.log('Dashboard refreshed with league data:', {
376425
league: league || 'all',
377426
matches: freshData.recent_matches.length,
@@ -524,40 +573,40 @@
524573
</div>
525574
</div>
526575

527-
<!-- How We Analyse -->
576+
<!-- How This Was All Put Together -->
528577
<div class="bg-white dark:bg-slate-900 rounded-xl p-6 shadow-lg">
529578
<h3 class="text-lg font-semibold text-slate-800 dark:text-slate-200 mb-4 flex items-center gap-2">
530579
<Target class="w-5 h-5 text-blue-600 dark:text-green-500" />
531-
How We Analyse
580+
How This Was All Put Together
532581
</h3>
533582
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
534583
<div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg">
535584
<div class="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center">
536585
<Zap class="w-5 h-5 text-blue-600" />
537586
</div>
538587
<h4 class="font-semibold text-sm mb-1">RAG System</h4>
539-
<p class="text-xs text-slate-600 dark:text-slate-400">Natural language to SQL</p>
588+
<p class="text-xs text-slate-600 dark:text-slate-400">Natural language to SQL using OpenAI API with GPT-4, custom backend APIs using FastAPI with Python scripts</p>
540589
</div>
541590
<div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg">
542591
<div class="w-10 h-10 bg-indigo-100 dark:bg-green-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center">
543592
<TrendingUp class="w-5 h-5 text-indigo-600 dark:text-green-600" />
544593
</div>
545-
<h4 class="font-semibold text-sm mb-1">Pattern Discovery</h4>
546-
<p class="text-xs text-slate-600 dark:text-slate-400">Automatic anomaly detection</p>
594+
<h4 class="font-semibold text-sm mb-1">Data Source</h4>
595+
<p class="text-xs text-slate-600 dark:text-slate-400">Supabase Vector Embeddings, custom database configurations created using open source data from football-data.co.uk</p>
547596
</div>
548597
<div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg">
549598
<div class="w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center">
550599
<Users class="w-5 h-5 text-purple-600" />
551600
</div>
552-
<h4 class="font-semibold text-sm mb-1">Vector Embeddings</h4>
553-
<p class="text-xs text-slate-600 dark:text-slate-400">Semantic database search</p>
601+
<h4 class="font-semibold text-sm mb-1">Pattern Discovery</h4>
602+
<p class="text-xs text-slate-600 dark:text-slate-400">Using smart Python visualisations and charts (Chart.js, Plotly, Matplotlib)</p>
554603
</div>
555604
<div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg">
556605
<div class="w-10 h-10 bg-amber-100 dark:bg-amber-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center">
557606
<Target class="w-5 h-5 text-amber-600" />
558607
</div>
559-
<h4 class="font-semibold text-sm mb-1">Statistical Analysis</h4>
560-
<p class="text-xs text-slate-600 dark:text-slate-400">Historical trend analysis</p>
608+
<h4 class="font-semibold text-sm mb-1">Tech Stack</h4>
609+
<p class="text-xs text-slate-600 dark:text-slate-400">React, Svelte, Docker, Supabase, Render, Node.js</p>
561610
</div>
562611
</div>
563612
</div>
@@ -636,18 +685,18 @@
636685

637686
<div class="bg-white dark:bg-slate-900 rounded-xl p-3 sm:p-6 shadow-lg hover:shadow-xl transition-shadow">
638687
<div class="flex items-start justify-between mb-2 sm:mb-4">
639-
<div class="bg-sky-500/10 dark:bg-sky-500/20 p-2 sm:p-3 rounded-lg">
640-
<Users class="w-4 h-4 sm:w-6 sm:h-6 text-sky-600 dark:text-sky-400" />
688+
<div class="bg-purple-500/10 dark:bg-purple-500/20 p-2 sm:p-3 rounded-lg">
689+
<Shield class="w-4 h-4 sm:w-6 sm:h-6 text-purple-600 dark:text-purple-400" />
641690
</div>
642691
</div>
643692
<div class="text-xs sm:text-sm font-medium text-slate-600 dark:text-slate-400 mb-1">
644-
Home Win %
693+
Best Defense
645694
</div>
646695
<div class="text-lg sm:text-2xl font-bold text-slate-900 dark:text-white mb-1">
647-
{homeWinPercentage.toFixed(1)}%
696+
{bestDefenseTeam || 'N/A'}
648697
</div>
649698
<div class="text-xs text-slate-500 dark:text-slate-500 leading-tight">
650-
{mostCommonScore} most common
699+
{bestDefenseGoalsConceded} goals conceded
651700
</div>
652701
</div>
653702

0 commit comments

Comments
 (0)