Files
servesync-demo/backend/app/routers/dashboard.py
Nemo 4421f61c24 feat: Rebuild as Court Manager Dashboard v2
- 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
2026-03-19 07:35:51 +08:00

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,
}