240 lines
9.7 KiB
JavaScript
240 lines
9.7 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import './MatchesSchedule.css';
|
|
import TeamLogo from './TeamLogo';
|
|
|
|
const poolColors = [
|
|
'#bae6fd', // darker blue
|
|
'#fde68a', // darker yellow
|
|
'#f9a8d4', // darker pink
|
|
'#6ee7b7', // darker green
|
|
'#fca5a5', // darker red
|
|
'#a5b4fc', // darker purple
|
|
];
|
|
|
|
function groupMatchesByPoolAndRound(matches) {
|
|
// Only round robin matches
|
|
const rrMatches = matches.filter(m => m.stage?.type === 'ROUND_ROBIN');
|
|
let poolMap = {};
|
|
rrMatches.forEach(m => {
|
|
const pool = m.pool || 1;
|
|
if (!poolMap[pool]) poolMap[pool] = [];
|
|
poolMap[pool].push(m);
|
|
});
|
|
// For each pool, group matches by round (scheduledAt)
|
|
const poolRounds = {};
|
|
Object.entries(poolMap).forEach(([pool, matches]) => {
|
|
const rounds = {};
|
|
matches.forEach(match => {
|
|
const key = match.scheduledAt;
|
|
if (!rounds[key]) rounds[key] = [];
|
|
rounds[key].push(match);
|
|
});
|
|
poolRounds[pool] = Object.values(rounds).sort((a, b) => new Date(a[0].scheduledAt) - new Date(b[0].scheduledAt));
|
|
});
|
|
return poolRounds;
|
|
}
|
|
|
|
|
|
|
|
const MatchesSchedule = () => {
|
|
const [matches, setMatches] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
const [editingMatchId, setEditingMatchId] = useState(null);
|
|
const [score1, setScore1] = useState('');
|
|
const [score2, setScore2] = useState('');
|
|
const [submitMsg, setSubmitMsg] = useState('');
|
|
|
|
const [mobileTab, setMobileTab] = useState(null);
|
|
|
|
// Calculate poolRounds and poolNumbers BEFORE any useEffect/useState that uses them
|
|
const poolRounds = groupMatchesByPoolAndRound(matches);
|
|
const poolNumbers = Object.keys(poolRounds).sort((a, b) => Number(a) - Number(b));
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
fetch('/api/matches')
|
|
.then(res => res.ok ? res.json() : Promise.reject(res))
|
|
.then(data => {
|
|
setMatches(data);
|
|
setLoading(false);
|
|
})
|
|
.catch(() => {
|
|
setError('Failed to load matches');
|
|
setLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
// Set default mobile tab
|
|
React.useEffect(() => {
|
|
if (mobileTab === null && poolNumbers.length > 0) setMobileTab(poolNumbers[0]);
|
|
}, [poolNumbers, mobileTab]);
|
|
|
|
// Responsive: tabs for mobile, columns for desktop
|
|
const isMobile = window.innerWidth < 700;
|
|
|
|
const handleEdit = (match) => {
|
|
setEditingMatchId(match.id);
|
|
setScore1('');
|
|
setScore2('');
|
|
setSubmitMsg('');
|
|
};
|
|
|
|
const handleSubmit = async (match) => {
|
|
setSubmitMsg('');
|
|
if (score1 === '' || score2 === '' || score1 === score2) {
|
|
setSubmitMsg('Scores must be different and not empty.');
|
|
return;
|
|
}
|
|
try {
|
|
const res = await fetch(`/api/admin/matches/${match.id}/result`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ team1Score: Number(score1), team2Score: Number(score2) })
|
|
});
|
|
const data = await res.json();
|
|
if (res.ok) {
|
|
setSubmitMsg('Result saved!');
|
|
setEditingMatchId(null);
|
|
setLoading(true);
|
|
fetch('/api/matches')
|
|
.then(res => res.ok ? res.json() : Promise.reject(res))
|
|
.then(data => {
|
|
setMatches(data);
|
|
setLoading(false);
|
|
});
|
|
} else {
|
|
setSubmitMsg(data.error || 'Failed to save result');
|
|
}
|
|
} catch {
|
|
setSubmitMsg('Failed to save result');
|
|
}
|
|
};
|
|
|
|
if (loading) return <div style={{ padding: 32 }}>Loading matches...</div>;
|
|
if (error) return <div style={{ padding: 32, color: 'red' }}>{error}</div>;
|
|
|
|
return (
|
|
<div className="matches-schedule-container">
|
|
<h2>Matches Schedule (by Pool)</h2>
|
|
{isMobile ? (
|
|
<>
|
|
<div style={{ display: 'flex', gap: 12, marginBottom: 24 }}>
|
|
{poolNumbers.map((pool, idx) => (
|
|
<button
|
|
key={pool}
|
|
onClick={() => setMobileTab(pool)}
|
|
style={{
|
|
padding: '8px 20px',
|
|
borderRadius: 8,
|
|
border: 'none',
|
|
background: mobileTab === pool ? poolColors[idx % poolColors.length] : '#e5e7eb',
|
|
color: '#222',
|
|
fontWeight: mobileTab === pool ? 'bold' : 'normal',
|
|
cursor: 'pointer',
|
|
boxShadow: mobileTab === pool ? '0 2px 8px rgba(0,0,0,0.08)' : 'none',
|
|
outline: mobileTab === pool ? '2px solid #2563eb' : 'none',
|
|
transition: 'background 0.2s, box-shadow 0.2s',
|
|
}}
|
|
>
|
|
{`Pool ${pool}`}
|
|
</button>
|
|
))}
|
|
</div>
|
|
{poolNumbers.map((pool, idx) => (
|
|
mobileTab === pool && (
|
|
<div key={pool} style={{
|
|
minWidth: 320,
|
|
background: poolColors[idx % poolColors.length],
|
|
borderRadius: 8,
|
|
padding: 16,
|
|
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
|
marginBottom: 24
|
|
}}>
|
|
<h3 style={{ textAlign: 'center' }}>{`Pool ${pool}`}</h3>
|
|
{poolRounds[pool].map((round, roundIdx) => (
|
|
<div key={roundIdx} className="matches-grid">
|
|
<div style={{ fontWeight: 'bold', margin: '8px 0', textAlign: 'center' }}>{`Round ${roundIdx + 1}`}</div>
|
|
{round.map(match => (
|
|
<div className="match-card" key={match.id}>
|
|
<div className="match-players">
|
|
<div style={{ textAlign: 'center' }}>
|
|
<TeamLogo team={match.team1} size="small" />
|
|
</div>
|
|
<span className="vs">vs</span>
|
|
<div style={{ textAlign: 'center' }}>
|
|
<TeamLogo team={match.team2} size="small" />
|
|
</div>
|
|
</div>
|
|
<div className="match-time">{new Date(match.scheduledAt).toLocaleString()}</div>
|
|
{match.result ? (
|
|
<div className="match-result">Result: {match.result.team1Score} - {match.result.team2Score}</div>
|
|
) : (
|
|
editingMatchId === match.id ? (
|
|
<div className="match-edit-form">
|
|
<input type="number" value={score1} onChange={e => setScore1(e.target.value)} placeholder="Team 1 Score" />
|
|
<input type="number" value={score2} onChange={e => setScore2(e.target.value)} placeholder="Team 2 Score" />
|
|
<button onClick={() => handleSubmit(match)}>Save</button>
|
|
<button onClick={() => setEditingMatchId(null)}>Cancel</button>
|
|
<div className="match-edit-msg">{submitMsg}</div>
|
|
</div>
|
|
) : (
|
|
<button className="match-edit-btn" onClick={() => handleEdit(match)}>Enter Result</button>
|
|
)
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
))}
|
|
</>
|
|
) : (
|
|
<div style={{ display: 'flex', gap: 32, alignItems: 'flex-start', overflowX: 'auto' }}>
|
|
{poolNumbers.map((pool, idx) => (
|
|
<div className="matches-round" key={pool} style={{ minWidth: 320, background: poolColors[idx % poolColors.length], borderRadius: 8, padding: 16, boxShadow: '0 2px 8px rgba(0,0,0,0.04)', marginBottom: 24 }}>
|
|
<div className="matches-round-title">{`Pool ${pool}`}</div>
|
|
{poolRounds[pool].map((round, roundIdx) => (
|
|
<div key={roundIdx} className="matches-grid">
|
|
<div style={{ fontWeight: 'bold', margin: '8px 0', textAlign: 'center' }}>{`Round ${roundIdx + 1}`}</div>
|
|
{round.map(match => (
|
|
<div className="match-card" key={match.id}>
|
|
<div className="match-players">
|
|
<div style={{ textAlign: 'center' }}>
|
|
<TeamLogo team={match.team1} size="small" />
|
|
</div>
|
|
<span className="vs">vs</span>
|
|
<div style={{ textAlign: 'center' }}>
|
|
<TeamLogo team={match.team2} size="small" />
|
|
</div>
|
|
</div>
|
|
<div className="match-time">{new Date(match.scheduledAt).toLocaleString()}</div>
|
|
{match.result ? (
|
|
<div className="match-result">Result: {match.result.team1Score} - {match.result.team2Score}</div>
|
|
) : (
|
|
editingMatchId === match.id ? (
|
|
<div className="match-edit-form">
|
|
<input type="number" value={score1} onChange={e => setScore1(e.target.value)} placeholder="Team 1 Score" />
|
|
<input type="number" value={score2} onChange={e => setScore2(e.target.value)} placeholder="Team 2 Score" />
|
|
<button onClick={() => handleSubmit(match)}>Save</button>
|
|
<button onClick={() => setEditingMatchId(null)}>Cancel</button>
|
|
<div className="match-edit-msg">{submitMsg}</div>
|
|
</div>
|
|
) : (
|
|
<button className="match-edit-btn" onClick={() => handleEdit(match)}>Enter Result</button>
|
|
)
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MatchesSchedule;
|