Removed some ghost bracket, hidden rest match and better rendering and score submitting
This commit is contained in:
@@ -4,6 +4,7 @@ from datetime import timedelta
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from .constants import BracketTypes, MatchStatus, TournamentTypes
|
from .constants import BracketTypes, MatchStatus, TournamentTypes
|
||||||
@@ -190,8 +191,11 @@ def advance_winner(db: Session, match: models.Match, winner_id: int):
|
|||||||
reset_match = match.winner_next_match
|
reset_match = match.winner_next_match
|
||||||
if reset_match.winner_team_id:
|
if reset_match.winner_team_id:
|
||||||
undo_advancement(db, reset_match)
|
undo_advancement(db, reset_match)
|
||||||
|
|
||||||
reset_match.winner_team_id = None
|
reset_match.winner_team_id = None
|
||||||
reset_match.sets = []
|
reset_match.sets = []
|
||||||
|
flag_modified(reset_match, "sets")
|
||||||
|
|
||||||
reset_match.p1_team_id = None
|
reset_match.p1_team_id = None
|
||||||
reset_match.p2_team_id = None
|
reset_match.p2_team_id = None
|
||||||
reset_match.status = MatchStatus.SCHEDULED
|
reset_match.status = MatchStatus.SCHEDULED
|
||||||
@@ -205,6 +209,18 @@ def advance_winner(db: Session, match: models.Match, winner_id: int):
|
|||||||
):
|
):
|
||||||
if not next_match or target_slot is None:
|
if not next_match or target_slot is None:
|
||||||
return
|
return
|
||||||
|
current_team = (
|
||||||
|
next_match.p1_team_id if target_slot == 0 else next_match.p2_team_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if current_team and current_team != team_id:
|
||||||
|
if next_match.winner_team_id:
|
||||||
|
undo_advancement(db, next_match)
|
||||||
|
next_match.winner_team_id = None
|
||||||
|
next_match.sets = []
|
||||||
|
flag_modified(next_match, "sets")
|
||||||
|
next_match.status = MatchStatus.SCHEDULED
|
||||||
|
|
||||||
if target_slot == 0:
|
if target_slot == 0:
|
||||||
next_match.p1_team_id = team_id
|
next_match.p1_team_id = team_id
|
||||||
elif target_slot == 1:
|
elif target_slot == 1:
|
||||||
@@ -239,8 +255,10 @@ def undo_advancement(db: Session, match: models.Match):
|
|||||||
|
|
||||||
if next_match.winner_team_id:
|
if next_match.winner_team_id:
|
||||||
undo_advancement(db, next_match)
|
undo_advancement(db, next_match)
|
||||||
|
|
||||||
next_match.winner_team_id = None
|
next_match.winner_team_id = None
|
||||||
next_match.sets = []
|
next_match.sets = []
|
||||||
|
flag_modified(next_match, "sets")
|
||||||
|
|
||||||
if target_slot == 0:
|
if target_slot == 0:
|
||||||
next_match.p1_team_id = None
|
next_match.p1_team_id = None
|
||||||
|
|||||||
@@ -42,13 +42,17 @@ async def report_score(
|
|||||||
if not report.sets:
|
if not report.sets:
|
||||||
raise HTTPException(400, "No sets submitted")
|
raise HTTPException(400, "No sets submitted")
|
||||||
|
|
||||||
|
if match.winner_team_id:
|
||||||
|
logic.undo_advancement(db, match)
|
||||||
|
|
||||||
_apply_score(match, report.sets)
|
_apply_score(match, report.sets)
|
||||||
flag_modified(match, "sets")
|
flag_modified(match, "sets")
|
||||||
db.commit()
|
|
||||||
|
|
||||||
if match.winner_team_id:
|
if match.winner_team_id:
|
||||||
logic.advance_winner(db, match, match.winner_team_id)
|
logic.advance_winner(db, match, match.winner_team_id)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
await send_ws_update(id)
|
await send_ws_update(id)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
|
|
||||||
@@ -77,11 +81,12 @@ async def edit_score(
|
|||||||
_apply_score(match, report.sets)
|
_apply_score(match, report.sets)
|
||||||
|
|
||||||
flag_modified(match, "sets")
|
flag_modified(match, "sets")
|
||||||
db.commit()
|
|
||||||
|
|
||||||
if match.winner_team_id:
|
if match.winner_team_id:
|
||||||
logic.advance_winner(db, match, match.winner_team_id)
|
logic.advance_winner(db, match, match.winner_team_id)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
await send_ws_update(id)
|
await send_ws_update(id)
|
||||||
return SUCCESS
|
return SUCCESS
|
||||||
|
|
||||||
@@ -107,7 +112,10 @@ async def clear_score(
|
|||||||
logic.undo_advancement(db, match)
|
logic.undo_advancement(db, match)
|
||||||
|
|
||||||
match.winner_team_id = None
|
match.winner_team_id = None
|
||||||
|
if match.p1_team_id and match.p2_team_id:
|
||||||
match.status = MatchStatus.PENDING
|
match.status = MatchStatus.PENDING
|
||||||
|
else:
|
||||||
|
match.status = MatchStatus.SCHEDULED
|
||||||
match.sets = []
|
match.sets = []
|
||||||
|
|
||||||
flag_modified(match, "sets")
|
flag_modified(match, "sets")
|
||||||
|
|||||||
@@ -70,28 +70,10 @@ export default function BracketView({ matches, onMatchClick }) {
|
|||||||
const renderRound = (list) => {
|
const renderRound = (list) => {
|
||||||
const rounds = {};
|
const rounds = {};
|
||||||
list.forEach(m => (rounds[m.round] = rounds[m.round] || []).push(m));
|
list.forEach(m => (rounds[m.round] = rounds[m.round] || []).push(m));
|
||||||
|
|
||||||
const roundKeys = Object.keys(rounds).sort((a, b) => Number(a) - Number(b));
|
const roundKeys = Object.keys(rounds).sort((a, b) => Number(a) - Number(b));
|
||||||
let prevRoundMap = new Map();
|
return roundKeys.map((r) => {
|
||||||
|
|
||||||
return roundKeys.map((r, rIdx) => {
|
|
||||||
let matchesInRound = rounds[r];
|
let matchesInRound = rounds[r];
|
||||||
if (rIdx === 0) {
|
|
||||||
matchesInRound.sort((a, b) => a.number - b.number);
|
matchesInRound.sort((a, b) => a.number - b.number);
|
||||||
} else {
|
|
||||||
matchesInRound.sort((a, b) => {
|
|
||||||
const getSourceAvg = (match) => {
|
|
||||||
const sources = list.filter(x => x.winner_next_match_id === match.id);
|
|
||||||
if (sources.length === 0) return 9999;
|
|
||||||
const indices = sources.map(s => prevRoundMap.get(s.id)).filter(i => i !== undefined);
|
|
||||||
if (indices.length === 0) return 9999;
|
|
||||||
return indices.reduce((sum, val) => sum + val, 0) / indices.length;
|
|
||||||
};
|
|
||||||
return getSourceAvg(a) - getSourceAvg(b);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
matchesInRound.forEach((m, idx) => prevRoundMap.set(m.id, idx));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={r} className="flex flex-col gap-10 z-10 w-64 shrink-0 justify-around">
|
<div key={r} className="flex flex-col gap-10 z-10 w-64 shrink-0 justify-around">
|
||||||
{matchesInRound.map(m => <MatchCard key={m.id} match={m} onClick={onMatchClick} />)}
|
{matchesInRound.map(m => <MatchCard key={m.id} match={m} onClick={onMatchClick} />)}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// frontend/src/components/Tournament/ScoreModal.jsx
|
// frontend/src/components/Tournament/ScoreModal.jsx
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import { Clock, Eraser, MapPin, Trophy } from 'lucide-react';
|
||||||
import { Eraser, Clock, MapPin, Trophy } from 'lucide-react';
|
import { useState } from 'react';
|
||||||
import { stringToColor } from '../../utils/helpers';
|
|
||||||
import Modal from '../UI/Modal';
|
import Modal from '../UI/Modal';
|
||||||
|
|
||||||
const ScoreForm = ({ match, isAdmin, onSubmit, onClear }) => {
|
const ScoreForm = ({ match, isAdmin, onSubmit, onClear }) => {
|
||||||
|
|||||||
@@ -29,6 +29,17 @@ export default function Tournament() {
|
|||||||
const processMatches = (rawMatches, courts, teams) => {
|
const processMatches = (rawMatches, courts, teams) => {
|
||||||
if (!rawMatches) return [];
|
if (!rawMatches) return [];
|
||||||
|
|
||||||
|
const gf1 = rawMatches.find(m =>
|
||||||
|
m.bracket_type === 'Finals' &&
|
||||||
|
m.winner_next_match_id &&
|
||||||
|
m.winner_next_match_id === m.loser_next_match_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let skippedResetMatchId = null;
|
||||||
|
if (gf1 && gf1.status === 'Finished' && gf1.winner_team_id === gf1.p1_team_id) {
|
||||||
|
skippedResetMatchId = gf1.winner_next_match_id;
|
||||||
|
}
|
||||||
|
|
||||||
const courtMap = Object.fromEntries(courts.map(c => [c.id, c.name]));
|
const courtMap = Object.fromEntries(courts.map(c => [c.id, c.name]));
|
||||||
const teamMap = Object.fromEntries(teams.map(t => [t.id, t.name]));
|
const teamMap = Object.fromEntries(teams.map(t => [t.id, t.name]));
|
||||||
|
|
||||||
@@ -43,7 +54,9 @@ export default function Tournament() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return rawMatches.map(m => {
|
return rawMatches
|
||||||
|
.filter(m => m.id !== skippedResetMatchId)
|
||||||
|
.map(m => {
|
||||||
const sources = incoming[m.id] || [];
|
const sources = incoming[m.id] || [];
|
||||||
|
|
||||||
let sourceIndex = 0;
|
let sourceIndex = 0;
|
||||||
@@ -133,9 +146,19 @@ export default function Tournament() {
|
|||||||
|
|
||||||
{scoreMatch && (
|
{scoreMatch && (
|
||||||
<ScoreModal
|
<ScoreModal
|
||||||
isOpen={!!scoreMatch} onClose={() => setScoreMatch(null)} match={scoreMatch} isAdmin={isAdmin}
|
isOpen={!!scoreMatch}
|
||||||
onClear={async (mid, c) => { await api.delete(`/tournaments/${id}/matches/${mid}/score?code=${encodeURIComponent(c || '')}`); setScoreMatch(null); }}
|
onClose={() => setScoreMatch(null)}
|
||||||
onSubmit={async (mid, s, c) => { await api.post(`/tournaments/${id}/matches/${mid}/score`, { sets: s, code: c }); setScoreMatch(null); }}
|
match={scoreMatch}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
onClear={async (mid, c) => {
|
||||||
|
await api.delete(`/tournaments/${id}/matches/${mid}/score?code=${encodeURIComponent(c || '')}`);
|
||||||
|
setScoreMatch(null);
|
||||||
|
}}
|
||||||
|
onSubmit={async (mid, s, c) => {
|
||||||
|
const method = scoreMatch.isFinished ? 'patch' : 'post';
|
||||||
|
await api[method](`/tournaments/${id}/matches/${mid}/score`, { sets: s, code: c });
|
||||||
|
setScoreMatch(null);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Modal isOpen={showSettings} onClose={() => setShowSettings(false)} title="Edit Tournament">
|
<Modal isOpen={showSettings} onClose={() => setShowSettings(false)} title="Edit Tournament">
|
||||||
|
|||||||
Reference in New Issue
Block a user