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
This commit is contained in:
136
backend/app/routers/dashboard.py
Normal file
136
backend/app/routers/dashboard.py
Normal file
@@ -0,0 +1,136 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user