Minor bugfixes connected to the WO and dropped out functions.
This commit is contained in:
1 parent
364895fc84
commit
4f5d071eeb
4 files changed
+87
-26
No files matched your search
+14
-3
@@ -2,7 +2,7 @@
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from fastapi import HTTPException # <--- Add this at the top with your imports
|
||||
from fastapi import HTTPException
|
||||
|
||||
from . import logic, models, schemas
|
||||
|
||||
@@ -28,7 +28,12 @@ def create_tournament(db: Session, data: schemas.TournamentCreate):
|
||||
duration=data.duration,
|
||||
type=data.type,
|
||||
)
|
||||
new_t.teams = [models.Team(name=n) for n in data.teams]
|
||||
|
||||
new_t.teams = [
|
||||
models.Team(name=nt.name, dropped_out=nt.dropped_out)
|
||||
for nt in data.teams
|
||||
]
|
||||
|
||||
new_t.courts = db.query(models.Court).filter(models.Court.id.in_(data.courts)).all()
|
||||
|
||||
db.add(new_t)
|
||||
@@ -160,9 +165,15 @@ def update_tournament_teams(db: Session, tournament_id: str, new_teams: list[sch
|
||||
db.delete(team)
|
||||
db.flush()
|
||||
|
||||
t.teams = [models.Team(name=nt.name, tournament_id=t.id) for nt in new_teams]
|
||||
t.teams = [
|
||||
models.Team(name=nt.name, tournament_id=t.id, dropped_out=nt.dropped_out)
|
||||
for nt in new_teams
|
||||
]
|
||||
db.flush()
|
||||
_rebuild_structure(db, t)
|
||||
for m in t.matches:
|
||||
logic.check_and_apply_walkover(db, m)
|
||||
|
||||
db.commit()
|
||||
db.refresh(t)
|
||||
return t
|
||||
|
||||
@@ -148,14 +148,34 @@ def update_schedule_times(db: Session, t: models.Tournament):
|
||||
court_timers = {c.id: start_of_day for c in all_courts}
|
||||
planned_finish_times = {}
|
||||
unscheduled: list[models.Match] = []
|
||||
tournament_has_started = {
|
||||
t_item.id: any(m.status == MatchStatus.FINISHED for m in t_item.matches)
|
||||
for t_item in tournaments
|
||||
}
|
||||
|
||||
for m in all_matches:
|
||||
is_locked = False
|
||||
t_started = tournament_has_started[m.tournament_id]
|
||||
|
||||
if m.status == MatchStatus.FINISHED and m.start_time and m.court_id:
|
||||
is_locked = True
|
||||
|
||||
elif m.status == MatchStatus.PENDING and m.start_time and m.court_id:
|
||||
if t_started:
|
||||
is_locked = True
|
||||
|
||||
elif m.tournament_id != t.id and m.start_time and m.court_id:
|
||||
if t_started:
|
||||
is_locked = True
|
||||
|
||||
if is_locked and m.start_time and m.court_id:
|
||||
fin = m.start_time + timedelta(minutes=m.tournament.duration)
|
||||
planned_finish_times[m.id] = fin
|
||||
if court_timers.get(m.court_id, start_of_day) < fin:
|
||||
court_timers[m.court_id] = fin
|
||||
else:
|
||||
m.start_time = None
|
||||
m.court_id = None
|
||||
unscheduled.append(m)
|
||||
|
||||
tournament_match_counts = {t_item.id: 0 for t_item in tournaments}
|
||||
|
||||
@@ -13,7 +13,7 @@ class TeamSchema(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class TeamUpdate(BaseModel):
|
||||
id: int
|
||||
id: int | str
|
||||
name: str
|
||||
dropped_out: bool = False
|
||||
|
||||
@@ -76,7 +76,7 @@ class TournamentCreate(BaseModel):
|
||||
type: TournamentTypes
|
||||
timestamp: datetime
|
||||
duration: int
|
||||
teams: list[str]
|
||||
teams: list[TeamUpdate]
|
||||
courts: list[int]
|
||||
|
||||
|
||||
|
||||
@@ -169,7 +169,20 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
onSuccess();
|
||||
} catch (err: any) {
|
||||
console.error("Save error:", err);
|
||||
const msg = err?.response?.data?.detail || err?.detail || err?.message || "An error occurred while saving.";
|
||||
let msg = "An error occurred while saving.";
|
||||
if (err?.response?.data?.detail) {
|
||||
const detail = err.response.data.detail;
|
||||
if (Array.isArray(detail)) {
|
||||
msg = detail.map((d: any) => `${d.loc[d.loc.length - 1]}: ${d.msg}`).join(' | ');
|
||||
} else if (typeof detail === 'string') {
|
||||
msg = detail;
|
||||
} else {
|
||||
msg = JSON.stringify(detail);
|
||||
}
|
||||
} else if (err?.message) {
|
||||
msg = err.message;
|
||||
}
|
||||
|
||||
setError(msg);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -292,26 +305,43 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
}`}
|
||||
/>
|
||||
|
||||
{hasStarted ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleDropOut(t.id)}
|
||||
className={`p-1 rounded transition opacity-0 group-hover:opacity-100 focus:opacity-100 ${t.dropped_out ? 'text-red-500 bg-red-50 dark:bg-red-900/30 opacity-100' : 'text-zinc-300 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30'
|
||||
}`}
|
||||
title={t.dropped_out ? "Restore Team" : "Mark as Dropped Out"}
|
||||
>
|
||||
<UserMinus size={14} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeTeam(t.id)}
|
||||
className="p-1 text-zinc-300 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 rounded transition opacity-0 group-hover:opacity-100 focus:opacity-100"
|
||||
title="Remove Team"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
)}
|
||||
<div className="flex items-center gap-1">
|
||||
{/* 1. If dropped out, ALWAYS show Restore */}
|
||||
{t.dropped_out && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleDropOut(t.id)}
|
||||
className="p-1 rounded text-red-500 bg-red-50 dark:bg-red-900/30 opacity-100 transition focus:opacity-100"
|
||||
title="Restore Team"
|
||||
>
|
||||
<UserMinus size={14} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 2. If started and active, show Drop Out */}
|
||||
{hasStarted && !t.dropped_out && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleDropOut(t.id)}
|
||||
className="p-1 text-zinc-300 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 rounded transition opacity-0 group-hover:opacity-100 focus:opacity-100"
|
||||
title="Mark as Dropped Out"
|
||||
>
|
||||
<UserMinus size={14} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 3. If NOT started, ALWAYS allow Deletion */}
|
||||
{!hasStarted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeTeam(t.id)}
|
||||
className="p-1 text-zinc-300 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 rounded transition opacity-0 group-hover:opacity-100 focus:opacity-100"
|
||||
title="Remove Team entirely"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{teams.length === 0 && (
|
||||
|
||||
Reference in new issue
Block a user