From 7d04ceb2a3fbe34783287074f6b625a0136d98ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20S=C3=B6derberg?= Date: Sun, 22 Feb 2026 14:01:47 +0100 Subject: [PATCH] General clean-up --- backend/app/core/brackets.py | 2 + backend/app/logic.py | 32 ++++-- .../src/components/Bracket/BracketView.jsx | 107 +++++++++++++----- frontend/src/components/Bracket/MatchCard.jsx | 47 ++++---- frontend/src/components/Bracket/Podium.jsx | 43 +++++-- .../src/components/Dashboard/DashCard.jsx | 48 ++++++++ .../src/components/Forms/TournamentForm.jsx | 5 +- frontend/src/components/Layout/Layout.jsx | 21 ++-- .../src/components/Schedule/ScheduleRow.jsx | 101 +++++++++++++++++ .../src/components/Schedule/ScheduleView.jsx | 100 +++++----------- .../src/components/Tournament/ScoreModal.jsx | 4 +- frontend/src/components/UI/Modal.jsx | 9 +- frontend/src/index.css | 6 +- frontend/src/pages/Dashboard.jsx | 68 +++-------- frontend/src/pages/Login.jsx | 14 ++- frontend/src/pages/Tournament.jsx | 18 ++- frontend/src/utils/helpers.js | 7 ++ 17 files changed, 401 insertions(+), 231 deletions(-) create mode 100644 frontend/src/components/Dashboard/DashCard.jsx create mode 100644 frontend/src/components/Schedule/ScheduleRow.jsx diff --git a/backend/app/core/brackets.py b/backend/app/core/brackets.py index 0b03b8c..bad0094 100644 --- a/backend/app/core/brackets.py +++ b/backend/app/core/brackets.py @@ -168,6 +168,8 @@ class BracketGenerator: self.match_counter += 1 gf.next_loss = reset gf.next_loss_slot = 0 + gf.next_win = reset + gf.next_win_slot = 1 def _resolve_byes(self, num_players: int) -> None: """ diff --git a/backend/app/logic.py b/backend/app/logic.py index c7aede2..b812f09 100644 --- a/backend/app/logic.py +++ b/backend/app/logic.py @@ -6,7 +6,7 @@ from uuid import uuid4 from sqlalchemy.orm import Session from . import models -from .constants import MatchStatus, TournamentTypes +from .constants import BracketTypes, MatchStatus, TournamentTypes from .core.brackets import BracketGenerator @@ -181,6 +181,25 @@ def advance_winner(db: Session, match: models.Match, winner_id: int): loser_id = match.p1_team_id if match.p1_team_id != winner_id else match.p2_team_id + if ( + match.bracket_type == BracketTypes.FINALS + and match.winner_next_match + and match.winner_next_match.bracket_type == BracketTypes.FINALS + ): + if winner_id == match.p1_team_id: + reset_match = match.winner_next_match + if reset_match.winner_team_id: + undo_advancement(db, reset_match) + reset_match.winner_team_id = None + reset_match.sets = [] + reset_match.p1_team_id = None + reset_match.p2_team_id = None + reset_match.status = MatchStatus.SCHEDULED + + db.add(reset_match) + db.commit() + return + def update_next_match( next_match: models.Match, team_id: int, target_slot: int | None ): @@ -214,9 +233,7 @@ def undo_advancement(db: Session, match: models.Match): winner_id = match.winner_team_id loser_id = match.p1_team_id if match.p1_team_id != winner_id else match.p2_team_id - def clear_from_next( - next_match: models.Match, team_id: int, target_slot: int | None - ): + def clear_from_next(next_match: models.Match, target_slot: int | None): if not next_match or target_slot is None: return @@ -230,11 +247,10 @@ def undo_advancement(db: Session, match: models.Match): elif target_slot == 1: next_match.p2_team_id = None - if next_match.status == MatchStatus.PENDING: - next_match.status = MatchStatus.SCHEDULED + next_match.status = MatchStatus.SCHEDULED db.add(next_match) - clear_from_next(match.winner_next_match, winner_id, match.winner_next_match_slot) + clear_from_next(match.winner_next_match, match.winner_next_match_slot) if match.loser_next_match and loser_id: - clear_from_next(match.loser_next_match, loser_id, match.loser_next_match_slot) + clear_from_next(match.loser_next_match, match.loser_next_match_slot) diff --git a/frontend/src/components/Bracket/BracketView.jsx b/frontend/src/components/Bracket/BracketView.jsx index b0de91a..cdf9322 100644 --- a/frontend/src/components/Bracket/BracketView.jsx +++ b/frontend/src/components/Bracket/BracketView.jsx @@ -4,7 +4,6 @@ import { useEffect, useRef, useState } from 'react'; import MatchCard from "./MatchCard"; import Podium from './Podium'; - export default function BracketView({ matches, onMatchClick }) { const containerRef = useRef(null); const [lines, setLines] = useState([]); @@ -14,23 +13,58 @@ export default function BracketView({ matches, onMatchClick }) { if (!containerRef.current) return; const container = containerRef.current.getBoundingClientRect(); const newLines = []; + matches.forEach(m => { - if (!m.winner_next_match_id) return; - const sEl = document.getElementById(`match-${m.id}`); - const eEl = document.getElementById(`match-${m.winner_next_match_id}`); - if (sEl && eEl) { - const r1 = sEl.getBoundingClientRect(), r2 = eEl.getBoundingClientRect(); - const sx = r1.right - container.left, sy = r1.top + r1.height / 2 - container.top; - const ex = r2.left - container.left, ey = r2.top + r2.height / 2 - container.top; - const c1 = sx + (ex - sx) / 2; - newLines.push(); + if (m.winner_next_match_id) { + const sEl = document.getElementById(`match-${m.id}`); + const eEl = document.getElementById(`match-${m.winner_next_match_id}`); + if (sEl && eEl) { + const r1 = sEl.getBoundingClientRect(), r2 = eEl.getBoundingClientRect(); + const sx = r1.right - container.left, sy = r1.top + r1.height / 2 - container.top; + const ex = r2.left - container.left, ey = r2.top + r2.height / 2 - container.top; + const c1 = sx + (ex - sx) / 2; + newLines.push( + + ); + } + } + + if (m.loser_next_match_id) { + const targetMatch = matches.find(x => x.id === m.loser_next_match_id); + if (targetMatch && targetMatch.bracket === 'Finals') { + const sEl = document.getElementById(`match-${m.id}`); + const eEl = document.getElementById(`match-${targetMatch.id}`); + if (sEl && eEl) { + const r1 = sEl.getBoundingClientRect(), r2 = eEl.getBoundingClientRect(); + const sx = r1.right - container.left, sy = r1.top + r1.height / 2 - container.top; + const ex = r2.left - container.left, ey = r2.top + r2.height / 2 - container.top; + const c1 = sx + (ex - sx) / 2; + newLines.push( + + ); + } + } } }); setLines(newLines); }; + const t = setTimeout(draw, 100); window.addEventListener('resize', draw); - return () => { clearTimeout(t); window.removeEventListener('resize', draw); }; + + const handlePrint = () => { draw(); setTimeout(draw, 100); }; + const mql = window.matchMedia('print'); + mql.addEventListener('change', handlePrint); + window.addEventListener('beforeprint', handlePrint); + window.addEventListener('afterprint', draw); + + return () => { + clearTimeout(t); + window.removeEventListener('resize', draw); + mql.removeEventListener('change', handlePrint); + window.removeEventListener('beforeprint', handlePrint); + window.removeEventListener('afterprint', draw); + }; }, [matches]); const renderRound = (list) => { @@ -66,43 +100,64 @@ export default function BracketView({ matches, onMatchClick }) { }); }; + const isDoubleElim = matches.some(m => m.bracket === 'Loser'); const wb = matches.filter(m => m.bracket === 'Winner'); const lb = matches.filter(m => m.bracket === 'Loser'); const finals = matches.filter(m => m.bracket === 'Finals'); + let displayWb = [...wb]; + let displayFinals = [...finals]; + + if (!isDoubleElim && displayWb.length > 0) { + const maxRound = Math.max(...displayWb.map(m => m.round)); + const gfIndex = displayWb.findIndex(m => m.round === maxRound); + + if (gfIndex !== -1) { + const gfMatch = displayWb.splice(gfIndex, 1)[0]; + displayFinals.unshift(gfMatch); + } + } + return ( -
+
+ +
- + {lines}
-
Winners Bracket
-
{renderRound(wb)}
+
Winners Bracket
+
{renderRound(displayWb)}
- {matches.some(m => m.bracket === 'Loser') && ( -
-
Losers Bracket
-
{renderRound(lb)}
+ {isDoubleElim && ( +
+
Losers Bracket
+
{renderRound(lb)}
)}
- {matches.some(m => m.bracket === 'Finals') && ( - <> -
Championship
- {finals.map(m => )} - + {displayFinals.length > 0 && ( +
+
+ Championship +
+ {displayFinals.map(m => )} +
)} -
-
); diff --git a/frontend/src/components/Bracket/MatchCard.jsx b/frontend/src/components/Bracket/MatchCard.jsx index 48b965f..367c326 100644 --- a/frontend/src/components/Bracket/MatchCard.jsx +++ b/frontend/src/components/Bracket/MatchCard.jsx @@ -1,7 +1,7 @@ // frontend/src/components/Bracket/MatchCard.jsx import { Check } from 'lucide-react'; -import { stringToColor } from '../../utils/helpers'; +import { printName, stringToColor } from '../../utils/helpers'; export default function MatchCard({ match, onClick }) { const isFinished = match.status === "Finished"; @@ -11,7 +11,6 @@ export default function MatchCard({ match, onClick }) { if (isFinished) borderClass = 'border-orange-500 ring-2 ring-orange-500/10'; const canInteract = match.hasTeams; - const cursorClass = canInteract ? 'cursor-pointer hover:shadow-md hover:-translate-y-0.5' : 'cursor-default opacity-100'; @@ -20,44 +19,44 @@ export default function MatchCard({ match, onClick }) {
canInteract && onClick(match)} - className={`w-64 bg-white dark:bg-zinc-900 rounded-lg border ${borderClass} shadow-sm transition-all duration-200 relative z-10 flex flex-col ${cursorClass}`} + className={`w-64 bg-white dark:bg-zinc-900 print:!bg-white rounded-lg border ${borderClass} print:!border-zinc-400 print:!shadow-none shadow-sm transition-all duration-200 print:transition-none relative z-10 flex flex-col ${cursorClass}`} > -
+
- # {match.number} + # {match.number} {match.time && ( - + {match.court} )}
{isFinished ? ( - + ) : ( - {match.time || 'TBD'} + {match.time || 'TBD'} )}
-
+
{[ - { - n: match.p1, - s: match.p1_sets, - win: match.winner_team_id !== null && match.winner_team_id === match.p1_team_id, - real: match.p1_team_id - }, - { - n: match.p2, - s: match.p2_sets, - win: match.winner_team_id !== null && match.winner_team_id === match.p2_team_id, - real: match.p2_team_id - } + { n: match.p1, s: match.p1_sets, win: match.winner_team_id !== null && match.winner_team_id === match.p1_team_id, real: match.p1_is_real }, + { n: match.p2, s: match.p2_sets, win: match.winner_team_id !== null && match.winner_team_id === match.p2_team_id, real: match.p2_is_real } ].map((p, i) => ( -
- {p.n} - +
+ + {p.n} + + + {printName(p.n)} + + + {p.s} + + + {isFinished ? p.s : ''} +
))}
diff --git a/frontend/src/components/Bracket/Podium.jsx b/frontend/src/components/Bracket/Podium.jsx index 5042576..7236833 100644 --- a/frontend/src/components/Bracket/Podium.jsx +++ b/frontend/src/components/Bracket/Podium.jsx @@ -1,10 +1,10 @@ // frontend/src/components/Bracket/Podium.jsx -import React from 'react'; import { Trophy } from 'lucide-react'; export default function Podium({ matches }) { const isDoubleElim = matches.some(m => m.bracket === 'Loser'); + const wb = matches.filter(m => m.bracket === 'Winner').sort((a, b) => a.round - b.round); const lb = matches.filter(m => m.bracket === 'Loser').sort((a, b) => a.round - b.round); const finals = matches.filter(m => m.bracket === 'Finals').sort((a, b) => a.round - b.round); @@ -28,21 +28,23 @@ export default function Podium({ matches }) { { rank: 3, label: "3rd", team: "TBD", isReal: false, color: "bg-amber-600 text-amber-50 dark:text-amber-50 shadow-amber-600/50", hidden: false } ]; + // --- CALCULATE 3RD PLACE --- if (tpMatch) { if (tpMatch.isFinished && tpMatch.winnerName) { - if (isDoubleElim) { - podium[2].team = tpMatch.winnerName === tpMatch.p1 ? tpMatch.p2 : tpMatch.p1; - } else { - podium[2].team = tpMatch.winnerName; - } + // If match is done, grab the actual team name + podium[2].team = isDoubleElim + ? (tpMatch.winnerName === tpMatch.p1 ? tpMatch.p2 : tpMatch.p1) // DE: Loser of LB Final + : tpMatch.winnerName; // SE: Winner of 3rd Place Match podium[2].isReal = true; } else { + // Set the exact string expected by the print formatter podium[2].team = isDoubleElim ? `Loser of #${tpMatch.number}` : `Winner of #${tpMatch.number}`; } } else { podium[2].hidden = true; } + // --- CALCULATE 1ST & 2ND PLACE --- if (resetMatch && resetMatch.isFinished && resetMatch.winnerName) { podium[0].team = resetMatch.winnerName; podium[0].isReal = true; @@ -50,39 +52,56 @@ export default function Podium({ matches }) { podium[1].isReal = true; } else if (gfMatch) { if (gfMatch.isFinished && gfMatch.winnerName) { + // Has the loser bracket champ won, forcing a reset? const isResetForced = resetMatch && resetMatch.hasTeams; if (!isResetForced) { + // GF Winner is 1st, GF Loser is 2nd podium[0].team = gfMatch.winnerName; podium[0].isReal = true; podium[1].team = gfMatch.winnerName === gfMatch.p1 ? gfMatch.p2 : gfMatch.p1; podium[1].isReal = true; } else { + // Reset forced, wait for the final match podium[0].team = `Winner of #${resetMatch.number}`; podium[1].team = `Loser of #${resetMatch.number}`; } } else { + // GF not finished yet podium[0].team = `Winner of #${gfMatch.number}`; podium[1].team = `Loser of #${gfMatch.number}`; } } + // Helper for rendering print strings (Takes "Winner of #7" -> "W#7: _______") + const printName = (name) => { + if (!name) return ''; + if (name.startsWith('Winner of #')) return name.replace('Winner of #', 'W#') + ': _______'; + if (name.startsWith('Loser of #')) return name.replace('Loser of #', 'L#') + ': _______'; + return name; + }; + return ( -
-
-

- Final Standings +
+
+

+ Final Standings

{podium.filter(p => !p.hidden).map(p => (
-
+
{p.rank}
-
+ {/* Web Label */} +
{p.team}
+ {/* Print Label */} +
+ {printName(p.team)} +
))}
diff --git a/frontend/src/components/Dashboard/DashCard.jsx b/frontend/src/components/Dashboard/DashCard.jsx new file mode 100644 index 0000000..e7d19d7 --- /dev/null +++ b/frontend/src/components/Dashboard/DashCard.jsx @@ -0,0 +1,48 @@ +// frontend/src/components/Dashboard/DashCard.jsx + +import { MapPin, SlidersHorizontal, Users } from 'lucide-react'; + +export default function DashCard({ t, isAdmin, onSelect, onEdit }) { + return ( +
onSelect(t.id)} + className="block bg-white dark:bg-zinc-900 p-6 rounded-xl shadow-sm border border-zinc-200 dark:border-zinc-800 relative group hover:shadow-md hover:scale-[1.02] transition-all duration-200 will-change-transform transform-gpu cursor-pointer" + > +
+
+

+ {t.name} +

+
+ {t.timestamp ? new Date(t.timestamp).toLocaleDateString() : 'TBD'} + {t.timestamp ? new Date(t.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''} +
+
+ + {t.type} + +
+ +
+
+ + {t.team_count} Teams +
+
+ + {t.court_count} Courts +
+ + {isAdmin && ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/Forms/TournamentForm.jsx b/frontend/src/components/Forms/TournamentForm.jsx index 9872eb1..bba81fc 100644 --- a/frontend/src/components/Forms/TournamentForm.jsx +++ b/frontend/src/components/Forms/TournamentForm.jsx @@ -63,7 +63,6 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }) { try { if (tournamentId) { - // UPDATE: Dispatch the 3 separated PATCH endpoints concurrently const basePayload = { name, code, @@ -78,7 +77,6 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }) { api.patch(`/tournaments/${tournamentId}/courts`, courts) ]); } else { - // CREATE: Send the full monolithic payload to POST /tournaments const fullPayload = { name, code, @@ -121,7 +119,6 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }) { } return ( - // Add a key mapped to the ID so React completely rebuilds the form when data loads
{error && (
@@ -169,7 +166,7 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }) {
- + - +
+
+ +
-
+
- {/* Dark Mode FAB */} -
+
+ ) : m.isReady && ( + + )} +
+ +
+
+ {isFinished ? m.p1_sets : ''} +
+ - +
+ {isFinished ? m.p2_sets : ''} +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/Schedule/ScheduleView.jsx b/frontend/src/components/Schedule/ScheduleView.jsx index 2dedec7..9d88d25 100644 --- a/frontend/src/components/Schedule/ScheduleView.jsx +++ b/frontend/src/components/Schedule/ScheduleView.jsx @@ -1,8 +1,8 @@ // frontend/src/components/Schedule/ScheduleView.jsx -import { CheckCircle, Pencil, Plus, Search, Trophy } from 'lucide-react'; -import React, { useState } from 'react'; -import { stringToColor } from '../../utils/helpers'; +import { Search } from 'lucide-react'; +import { useState } from 'react'; +import ScheduleRow from './ScheduleRow'; export default function ScheduleView({ schedule, onMatchClick }) { const [filter, setFilter] = useState(""); @@ -12,26 +12,29 @@ export default function ScheduleView({ schedule, onMatchClick }) { return c.length > max.length ? c : max; }, "Court"); - const badgeWidth = Math.max(100, longestCourt.length * 9); + const badgeWidth = Math.max(80, longestCourt.length * 10); const filteredAndSorted = schedule - .filter(m => (m.p1 + m.p2 + m.number).toLowerCase().includes(filter.toLowerCase())) + .filter(m => (m.p1 + m.p2 + m.number + `Match #${m.number}`).toLowerCase().includes(filter.toLowerCase())) .sort((a, b) => { const timeA = a.start_time || a.timestamp || a.time || ""; const timeB = b.start_time || b.timestamp || b.time || ""; - - if (timeA !== timeB) { - return timeA.localeCompare(timeB); - } - + if (timeA !== timeB) return timeA.localeCompare(timeB); const courtA = a.court || ""; const courtB = b.court || ""; return courtA.localeCompare(courtB); }); return ( -
-
+
+ + +
-
-
- {filteredAndSorted.map(m => { - const courtColor = stringToColor(m.court); - const isFinished = m.isFinished; +
- return ( -
-
- {/* UNIFORM WIDTH METADATA COLUMN */} -
-
{m.time}
-
- {m.court} -
-
#{m.number}
-
+
+
-
-
- {[{ n: m.p1, win: m.winnerName === m.p1, real: m.p1_is_real }, - { n: m.p2, win: m.winnerName === m.p2, real: m.p2_is_real }].map((p, i) => ( - -
- {p.win && } - {p.n} -
- {i === 0 && VS} -
- ))} -
-
Match #{m.number}
-
-
+
+ Tournament Schedule +
-
- {isFinished ? ( - - ) : m.isReady && ( - - )} -
-
- ); - })}
diff --git a/frontend/src/components/Tournament/ScoreModal.jsx b/frontend/src/components/Tournament/ScoreModal.jsx index 861f36e..2306763 100644 --- a/frontend/src/components/Tournament/ScoreModal.jsx +++ b/frontend/src/components/Tournament/ScoreModal.jsx @@ -1,7 +1,7 @@ // frontend/src/components/Tournament/ScoreModal.jsx import React, { useState } from 'react'; -import { Eraser, Clock, MapPin } from 'lucide-react'; +import { Eraser, Clock, MapPin, Trophy } from 'lucide-react'; import { stringToColor } from '../../utils/helpers'; import Modal from '../UI/Modal'; @@ -139,7 +139,7 @@ export default function ScoreModal({ isOpen, onClose, match, isAdmin, onSubmit, if (!isOpen || !match) return null; return ( - + +
-

{title}

+

+ {Icon && } + {title} +

diff --git a/frontend/src/index.css b/frontend/src/index.css index 486e40c..abda2c2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,8 @@ /* frontend/src/index.css */ @import "tailwindcss"; -@custom-variant dark (&:where(.dark, .dark *)); \ No newline at end of file +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + --text-tiny: 0.675rem; +} \ No newline at end of file diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index d17b292..b5b690d 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1,56 +1,13 @@ // frontend/src/pages/Dashboard.jsx -import { Calendar, ChevronDown, ChevronUp, History, Plus, SlidersHorizontal, Users, MapPin, Clock } from 'lucide-react'; +import { Calendar, ChevronDown, ChevronUp, History, Plus } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useNavigate, useOutletContext } from 'react-router-dom'; +import DashCard from '../components/Dashboard/DashCard'; import TournamentForm from '../components/Forms/TournamentForm'; import Modal from '../components/UI/Modal'; import api, { WS_URL } from '../services/api'; - -// --- RESTORED DASHCARD DESIGN --- -const DashCard = ({ t, isAdmin, onSelect, onEdit }) => ( -
onSelect(t.id)} - className="block bg-white dark:bg-zinc-900 p-6 rounded-xl shadow-sm border border-zinc-200 dark:border-zinc-800 relative group hover:shadow-md hover:scale-[1.02] transition-all duration-200 will-change-transform transform-gpu cursor-pointer" - > -
-
-

- {t.name} -

-
- {/* Parse date safely */} - {t.timestamp ? new Date(t.timestamp).toLocaleDateString() : 'TBD'} - {t.timestamp ? new Date(t.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''} -
-
- - {t.type} - -
- -
-
- - {t.team_count} Teams -
-
- - {t.court_count} Courts -
- - {isAdmin && ( - - )} -
-
-); +import { SlidersHorizontal, PlusCircle } from 'lucide-react'; export default function Dashboard() { const { setNavTitle, setNavSubtitle, isAdmin, showSettings, setShowSettings } = useOutletContext(); @@ -140,8 +97,11 @@ export default function Dashboard() { {groups.live.length > 0 && (
-

-
Live Events +

+ + + + Live Events

{groups.live.map(t => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)} @@ -149,8 +109,8 @@ export default function Dashboard() {

)} -
-

+
+

Upcoming

{groups.future.length > 0 ? ( @@ -160,7 +120,7 @@ export default function Dashboard() {

{groups.future.length > 4 && (
- @@ -173,11 +133,11 @@ export default function Dashboard() { {groups.past.length > 0 && (
{showPast && ( -
+
{groups.past.map(t => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)}
)} @@ -185,7 +145,7 @@ export default function Dashboard() { )}
- { setShowSettings(false); setEditTarget(null); }} title={editTarget ? 'Modify Event' : 'Initialize Event'}> + { setShowSettings(false); setEditTarget(null); }} title={editTarget ? 'Tournament Settings' : 'New Tournament'} icon={editTarget ? SlidersHorizontal : PlusCircle}>
-

System Access

+

Admin Access

Enter administrative credentials

{error && ( @@ -48,12 +48,12 @@ export default function Login() {
- - + +
- +
@@ -68,7 +68,9 @@ export default function Login() {
- Back to Dashboard + + Back to Dashboard +
diff --git a/frontend/src/pages/Tournament.jsx b/frontend/src/pages/Tournament.jsx index 633e6e5..fe53e60 100644 --- a/frontend/src/pages/Tournament.jsx +++ b/frontend/src/pages/Tournament.jsx @@ -1,6 +1,6 @@ // frontend/src/pages/Tournament.jsx -import { CalendarDays, Loader2, Network, Settings } from 'lucide-react'; +import { CalendarDays, Loader2, Network, SlidersHorizontal } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { useOutletContext, useParams } from 'react-router-dom'; import BracketView from '../components/Bracket/BracketView'; @@ -53,6 +53,7 @@ export default function Tournament() { const hasTeams = !!(m.p1_team_id && m.p2_team_id); const isFinished = m.status === "Finished"; const isReady = hasTeams && !isFinished; + const winnerName = m.winner_team_id ? teamMap[m.winner_team_id] : null; return { ...m, @@ -61,6 +62,7 @@ export default function Tournament() { number: m.match_number, p1, p2, + winnerName, p1_is_real: !!m.p1_team_id, p2_is_real: !!m.p2_team_id, isReady, @@ -109,8 +111,8 @@ export default function Tournament() { if (loading) return
; return ( -
-
+
+
- {isAdmin && } + {isAdmin && }
-
+
{view === 'bracket' ? : @@ -137,11 +139,7 @@ export default function Tournament() { /> )} setShowSettings(false)} title="Edit Tournament"> - { setShowSettings(false); fetchData(); }} - onDelete={handleDeleteTournament} - /> + { setShowSettings(false); fetchData(); }} onDelete={handleDeleteTournament} />
); diff --git a/frontend/src/utils/helpers.js b/frontend/src/utils/helpers.js index 6cedc3d..b78c7e7 100644 --- a/frontend/src/utils/helpers.js +++ b/frontend/src/utils/helpers.js @@ -9,4 +9,11 @@ export const stringToColor = (str) => { const combined = normalized + salt; for (let i = 0; i < combined.length; i++) hash = combined.charCodeAt(i) + ((hash << 5) - hash); return COURT_COLORS[Math.abs(hash) % COURT_COLORS.length]; +}; + +export const printName = (name) => { + if (!name) return ''; + if (name.startsWith('Winner of #')) return name.replace('Winner of #', 'W') + ': _______________'; + if (name.startsWith('Loser of #')) return name.replace('Loser of #', 'L') + ': _______________'; + return name; }; \ No newline at end of file