Minor bugfixes connected to the WO and dropped out functions.
Test and Release / test-backend (push) Failing after 37s
Test and Release / test-frontend (push) Successful in 29s
Test and Release / build-docker (backend) (push) Skipped
Test and Release / build-docker (frontend) (push) Skipped

This commit is contained in:
WilliamSoderberg committed 2026-08-29 21:01:02 +02:00 Verified
1 parent 364895fc84
commit 4f5d071eeb
4 files changed
+87 -26

No files matched your search

+14 -3
View File
@@ -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
+20
View File
@@ -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}
+2 -2
View File
@@ -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 && (