diff --git a/backend/bracket/models/db/match.py b/backend/bracket/models/db/match.py index 32d4f3224..898918419 100644 --- a/backend/bracket/models/db/match.py +++ b/backend/bracket/models/db/match.py @@ -24,6 +24,7 @@ class MatchBaseInsertable(BaseModelORM): court_id: CourtId | None = None stage_item_input1_conflict: bool stage_item_input2_conflict: bool + played: bool = False @property def end_time(self) -> datetime_utc: @@ -60,9 +61,7 @@ class MatchWithDetails(Match): court: Court | None = None -def get_match_hash( - stage_item_input1_id: StageItemInputId | None, stage_item_input2_id: StageItemInputId | None -) -> str: +def get_match_hash(stage_item_input1_id: StageItemInputId | None, stage_item_input2_id: StageItemInputId | None) -> str: return f"{stage_item_input1_id}-{stage_item_input2_id}" @@ -93,6 +92,7 @@ class MatchBody(BaseModelORM): court_id: CourtId | None = None custom_duration_minutes: int | None = None custom_margin_minutes: int | None = None + played: bool = False class MatchCreateBodyFrontend(BaseModelORM): diff --git a/backend/bracket/routes/matches.py b/backend/bracket/routes/matches.py index 5076085e6..988008f5e 100644 --- a/backend/bracket/routes/matches.py +++ b/backend/bracket/routes/matches.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, Depends, HTTPException from starlette import status +from bracket.logger import get_logger from bracket.logic.planning.conflicts import handle_conflicts from bracket.logic.planning.matches import ( get_scheduled_matches, @@ -8,14 +9,9 @@ reorder_matches_for_court, schedule_all_unscheduled_matches, ) -from bracket.logic.ranking.calculation import ( - recalculate_ranking_for_stage_item, -) +from bracket.logic.ranking.calculation import recalculate_ranking_for_stage_item from bracket.logic.ranking.elimination import update_inputs_in_subsequent_elimination_rounds -from bracket.logic.scheduling.upcoming_matches import ( - get_draft_round_in_stage_item, - get_upcoming_matches_for_swiss, -) +from bracket.logic.scheduling.upcoming_matches import get_draft_round_in_stage_item, get_upcoming_matches_for_swiss from bracket.models.db.match import ( Match, MatchBody, @@ -41,6 +37,7 @@ from bracket.utils.types import assert_some router = APIRouter() +logger = get_logger("bracket") @router.get( @@ -68,9 +65,7 @@ async def get_matches_to_schedule( if len(courts) <= len(draft_round.matches): return UpcomingMatchesResponse(data=[]) - return UpcomingMatchesResponse( - data=get_upcoming_matches_for_swiss(match_filter, stage_item, draft_round) - ) + return UpcomingMatchesResponse(data=get_upcoming_matches_for_swiss(match_filter, stage_item, draft_round)) @router.delete("/tournaments/{tournament_id}/matches/{match_id}", response_model=SuccessResponse) @@ -136,9 +131,7 @@ async def schedule_matches( return SuccessResponse() -@router.post( - "/tournaments/{tournament_id}/matches/{match_id}/reschedule", response_model=SuccessResponse -) +@router.post("/tournaments/{tournament_id}/matches/{match_id}/reschedule", response_model=SuccessResponse) async def reschedule_match( tournament_id: TournamentId, match_id: MatchId, @@ -164,6 +157,8 @@ async def update_match_by_id( await check_foreign_keys_belong_to_tournament(match_body, tournament_id) tournament = await sql_get_tournament(tournament_id) + logger.info(f"Updating match {match_id} with body: {match_body.model_dump()}") + await sql_update_match(match_id, match_body, tournament) round_ = await get_round_by_id(tournament_id, match.round_id) diff --git a/backend/bracket/schema.py b/backend/bracket/schema.py index 926357de3..784b37ae8 100644 --- a/backend/bracket/schema.py +++ b/backend/bracket/schema.py @@ -139,6 +139,7 @@ Column("stage_item_input1_score", Integer, nullable=False), Column("stage_item_input2_score", Integer, nullable=False), Column("position_in_schedule", Integer, nullable=True), + Column("played", Boolean, nullable=False), ) teams = Table( diff --git a/backend/bracket/sql/matches.py b/backend/bracket/sql/matches.py index 39adecc59..c82a36545 100644 --- a/backend/bracket/sql/matches.py +++ b/backend/bracket/sql/matches.py @@ -53,7 +53,8 @@ async def sql_create_match(match: MatchCreateBody) -> Match: stage_item_input2_score, stage_item_input1_conflict, stage_item_input2_conflict, - created + created, + played ) VALUES ( :round_id, @@ -70,7 +71,8 @@ async def sql_create_match(match: MatchCreateBody) -> Match: 0, false, false, - NOW() + NOW(), + false ) RETURNING * """ @@ -92,20 +94,17 @@ async def sql_update_match(match_id: MatchId, match: MatchBody, tournament: Tour custom_duration_minutes = :custom_duration_minutes, custom_margin_minutes = :custom_margin_minutes, duration_minutes = :duration_minutes, - margin_minutes = :margin_minutes + margin_minutes = :margin_minutes, + played = :played WHERE matches.id = :match_id RETURNING * """ duration_minutes = ( - match.custom_duration_minutes - if match.custom_duration_minutes is not None - else tournament.duration_minutes + match.custom_duration_minutes if match.custom_duration_minutes is not None else tournament.duration_minutes ) margin_minutes = ( - match.custom_margin_minutes - if match.custom_margin_minutes is not None - else tournament.margin_minutes + match.custom_margin_minutes if match.custom_margin_minutes is not None else tournament.margin_minutes ) await database.execute( query=query, @@ -189,15 +188,9 @@ async def sql_reschedule_match_and_determine_duration_and_margin( tournament: Tournament, ) -> None: duration_minutes = ( - tournament.duration_minutes - if match.custom_duration_minutes is None - else match.custom_duration_minutes - ) - margin_minutes = ( - tournament.margin_minutes - if match.custom_margin_minutes is None - else match.custom_margin_minutes + tournament.duration_minutes if match.custom_duration_minutes is None else match.custom_duration_minutes ) + margin_minutes = tournament.margin_minutes if match.custom_margin_minutes is None else match.custom_margin_minutes await sql_reschedule_match( match.id, court_id, @@ -226,9 +219,7 @@ async def sql_get_match(match_id: MatchId) -> Match: return Match.model_validate(dict(result._mapping)) -async def clear_scores_for_matches_in_stage_item( - tournament_id: TournamentId, stage_item_id: StageItemId -) -> None: +async def clear_scores_for_matches_in_stage_item(tournament_id: TournamentId, stage_item_id: StageItemId) -> None: query = """ UPDATE matches SET stage_item_input1_score = 0, diff --git a/frontend/public/locales/de/common.json b/frontend/public/locales/de/common.json index a3da641bc..1c52e55e0 100644 --- a/frontend/public/locales/de/common.json +++ b/frontend/public/locales/de/common.json @@ -272,5 +272,7 @@ "win_distribution_text_draws": "zeichnet", "win_distribution_text_losses": "Niederlagen", "win_distribution_text_win": "Siege", - "win_points_input_label": "Punkte für einen Sieg" + "win_points_input_label": "Punkte für einen Sieg", + "results_tab_upcoming": "Bevorstehende Spiele", + "results_tab_past": "Vergangene Spiele" } \ No newline at end of file diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 620157a64..0f1186efe 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -272,5 +272,7 @@ "win_distribution_text_draws": "draws", "win_distribution_text_losses": "losses", "win_distribution_text_win": "wins", - "win_points_input_label": "Points for a win" + "win_points_input_label": "Points for a win", + "results_tab_upcoming": "Upcoming Matches", + "results_tab_past": "Past Matches" } \ No newline at end of file diff --git a/frontend/src/components/dashboard/layout.tsx b/frontend/src/components/dashboard/layout.tsx index 1ca39abf3..a993dcfee 100644 --- a/frontend/src/components/dashboard/layout.tsx +++ b/frontend/src/components/dashboard/layout.tsx @@ -86,6 +86,7 @@ export function DoubleHeader({ tournamentData }: { tournamentData: Tournament }) const mainLinks = [ { link: `/tournaments/${endpoint}/dashboard`, label: 'Matches' }, + { link: `/tournaments/${endpoint}/dashboard/results`, label: 'Results' }, { link: `/tournaments/${endpoint}/dashboard/standings`, label: 'Standings' }, ]; diff --git a/frontend/src/components/modals/match_modal.tsx b/frontend/src/components/modals/match_modal.tsx index 75fb2d733..976b6a5bb 100644 --- a/frontend/src/components/modals/match_modal.tsx +++ b/frontend/src/components/modals/match_modal.tsx @@ -106,6 +106,7 @@ function MatchModalForm({ court_id: match.court_id, custom_duration_minutes: customDurationEnabled ? values.custom_duration_minutes : null, custom_margin_minutes: customMarginEnabled ? values.custom_margin_minutes : null, + played: true, }; await updateMatch(tournamentData.id, match.id, updatedMatch); await swrStagesResponse.mutate(); diff --git a/frontend/src/interfaces/match.tsx b/frontend/src/interfaces/match.tsx index a0bcc343f..df0ae06f1 100644 --- a/frontend/src/interfaces/match.tsx +++ b/frontend/src/interfaces/match.tsx @@ -32,6 +32,7 @@ export interface MatchBodyInterface { court_id: number | null; custom_duration_minutes: number | null; custom_margin_minutes: number | null; + played: boolean; } export interface MatchRescheduleInterface { diff --git a/frontend/src/pages/tournaments/[id]/dashboard/index.tsx b/frontend/src/pages/tournaments/[id]/dashboard/index.tsx index 4b78d7ac4..cdbda1205 100644 --- a/frontend/src/pages/tournaments/[id]/dashboard/index.tsx +++ b/frontend/src/pages/tournaments/[id]/dashboard/index.tsx @@ -132,6 +132,7 @@ export function Schedule({ const matches: any[] = Object.values(matchesLookup); const sortedMatches = matches .filter((m1: any) => m1.match.start_time != null) + .filter((m1: any) => m1.match.played === false) .sort( (m1: any, m2: any) => compareDateTime(m1.match.start_time, m2.match.start_time) || @@ -143,20 +144,6 @@ export function Schedule({ for (let c = 0; c < sortedMatches.length; c += 1) { const data = sortedMatches[c]; - if (c < 1 || sortedMatches[c - 1].match.start_time) { - const startTime = formatTime(data.match.start_time); - - if (c < 1 || startTime !== formatTime(sortedMatches[c - 1].match.start_time)) { - rows.push( -