- Complete rewrite for single Court Manager user - Feature 1: Player Management (CRUD, ELO, skill level) - Feature 2: Court Reservation (timeline, override) - Feature 3: Match Event Builder (3-step wizard) - Step 1: Event setup with player selection + stage calculator - Step 2: Stage configurator (Open/Skill/RR/Tournament) - Step 3: Auto bracket generation - Feature 4: Live Court View (main dashboard, 2x2 grid) - Event Control: score entry, bracket advancement, court assignment - Screen view: TV display per court (/screen/:id) - Seed: 8 players, 4 courts, 1 completed + 1 active event - Routes: / players courts events events/new events/:id screen/:id
137 lines
5.2 KiB
Python
137 lines
5.2 KiB
Python
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from datetime import datetime
|
|
from ..database import get_db
|
|
from ..models import Court, Match, Event, Stage, Player, CourtReservation
|
|
|
|
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
|
|
|
|
|
|
@router.get("/")
|
|
def get_dashboard(db: Session = Depends(get_db)):
|
|
courts = db.query(Court).all()
|
|
courts_data = []
|
|
|
|
for court in courts:
|
|
current_match = db.query(Match).filter(
|
|
Match.court_id == court.id,
|
|
Match.status == "in_progress"
|
|
).first()
|
|
|
|
match_data = None
|
|
elapsed = None
|
|
if current_match:
|
|
p1 = db.query(Player).filter(Player.id == current_match.player1_id).first() if current_match.player1_id else None
|
|
p2 = db.query(Player).filter(Player.id == current_match.player2_id).first() if current_match.player2_id else None
|
|
if current_match.started_at:
|
|
elapsed = int((datetime.utcnow() - current_match.started_at).total_seconds())
|
|
match_data = {
|
|
"id": current_match.id,
|
|
"player1_id": current_match.player1_id,
|
|
"player2_id": current_match.player2_id,
|
|
"player1_name": p1.name if p1 else None,
|
|
"player2_name": p2.name if p2 else None,
|
|
"score1": current_match.score1,
|
|
"score2": current_match.score2,
|
|
"status": current_match.status,
|
|
"elapsed_seconds": elapsed,
|
|
"stage_id": current_match.stage_id,
|
|
}
|
|
|
|
# Next reservation
|
|
now = datetime.utcnow()
|
|
next_reservation = db.query(CourtReservation).filter(
|
|
CourtReservation.court_id == court.id,
|
|
CourtReservation.start_time > now
|
|
).order_by(CourtReservation.start_time).first()
|
|
|
|
courts_data.append({
|
|
"id": court.id,
|
|
"name": court.name,
|
|
"court_type": court.court_type,
|
|
"status": court.status,
|
|
"current_match": match_data,
|
|
"elapsed_seconds": elapsed,
|
|
"next_reservation": {
|
|
"start_time": next_reservation.start_time,
|
|
"group_name": next_reservation.group_name,
|
|
} if next_reservation else None,
|
|
})
|
|
|
|
# Upcoming scheduled matches (not yet in progress)
|
|
upcoming = db.query(Match).filter(
|
|
Match.status == "scheduled",
|
|
Match.player1_id.isnot(None),
|
|
Match.player2_id.isnot(None)
|
|
).limit(10).all()
|
|
|
|
upcoming_data = []
|
|
for m in upcoming:
|
|
p1 = db.query(Player).filter(Player.id == m.player1_id).first()
|
|
p2 = db.query(Player).filter(Player.id == m.player2_id).first()
|
|
stage = db.query(Stage).filter(Stage.id == m.stage_id).first()
|
|
event = db.query(Event).filter(Event.id == stage.event_id).first() if stage else None
|
|
upcoming_data.append({
|
|
"id": m.id,
|
|
"player1_name": p1.name if p1 else None,
|
|
"player2_name": p2.name if p2 else None,
|
|
"round_number": m.round_number,
|
|
"event_name": event.name if event else None,
|
|
"stage_name": stage.name if stage else None,
|
|
})
|
|
|
|
# Active event summary
|
|
active_event = db.query(Event).filter(Event.status == "active").first()
|
|
active_event_data = None
|
|
if active_event:
|
|
active_stage = None
|
|
if active_event.current_stage_id:
|
|
active_stage = db.query(Stage).filter(Stage.id == active_event.current_stage_id).first()
|
|
active_event_data = {
|
|
"id": active_event.id,
|
|
"name": active_event.name,
|
|
"format": active_event.format,
|
|
"current_stage": active_stage.name if active_stage else None,
|
|
"current_stage_id": active_event.current_stage_id,
|
|
}
|
|
|
|
return {
|
|
"courts": courts_data,
|
|
"upcoming_matches": upcoming_data,
|
|
"active_event": active_event_data,
|
|
}
|
|
|
|
|
|
@router.get("/screen/{court_id}")
|
|
def screen_view(court_id: int, db: Session = Depends(get_db)):
|
|
"""TV/screen display for a specific court"""
|
|
court = db.query(Court).filter(Court.id == court_id).first()
|
|
if not court:
|
|
return {"error": "Court not found"}
|
|
|
|
current_match = db.query(Match).filter(
|
|
Match.court_id == court_id,
|
|
Match.status == "in_progress"
|
|
).first()
|
|
|
|
match_data = None
|
|
if current_match:
|
|
p1 = db.query(Player).filter(Player.id == current_match.player1_id).first()
|
|
p2 = db.query(Player).filter(Player.id == current_match.player2_id).first()
|
|
elapsed = None
|
|
if current_match.started_at:
|
|
elapsed = int((datetime.utcnow() - current_match.started_at).total_seconds())
|
|
match_data = {
|
|
"id": current_match.id,
|
|
"player1_name": p1.name if p1 else "TBD",
|
|
"player2_name": p2.name if p2 else "TBD",
|
|
"score1": current_match.score1 if current_match.score1 is not None else 0,
|
|
"score2": current_match.score2 if current_match.score2 is not None else 0,
|
|
"elapsed_seconds": elapsed,
|
|
}
|
|
|
|
return {
|
|
"court": {"id": court.id, "name": court.name, "status": court.status},
|
|
"current_match": match_data,
|
|
}
|