diff --git a/backend/bracket/logic/ranking/elo.py b/backend/bracket/logic/ranking/elo.py index 8973be40..155da26e 100644 --- a/backend/bracket/logic/ranking/elo.py +++ b/backend/bracket/logic/ranking/elo.py @@ -96,7 +96,7 @@ def determine_ranking_for_stage_item( return team_x_stats -async def determine_team_ranking_for_stage_item( +def determine_team_ranking_for_stage_item( stage_item: StageItemWithRounds, ranking: Ranking, ) -> list[tuple[TeamId, TeamStatistics]]: diff --git a/backend/bracket/logic/scheduling/handle_stage_activation.py b/backend/bracket/logic/scheduling/handle_stage_activation.py index 60f5e76e..88cfd96c 100644 --- a/backend/bracket/logic/scheduling/handle_stage_activation.py +++ b/backend/bracket/logic/scheduling/handle_stage_activation.py @@ -3,9 +3,9 @@ from bracket.logic.ranking.elo import ( ) from bracket.logic.ranking.statistics import TeamStatistics from bracket.models.db.match import MatchWithDetails +from bracket.models.db.util import StageWithStageItems from bracket.sql.matches import sql_get_match, sql_update_team_ids_for_match from bracket.sql.rankings import get_ranking_for_stage_item -from bracket.sql.stage_items import get_stage_item from bracket.sql.stages import get_full_tournament_details from bracket.utils.id_types import MatchId, StageId, StageItemId, TeamId, TournamentId from bracket.utils.types import assert_some @@ -65,16 +65,12 @@ async def set_team_ids_for_match( await sql_update_team_ids_for_match(assert_some(match.id), team1_id, team2_id) -async def get_team_rankings_lookup(tournament_id: TournamentId) -> StageItemXTeamRanking: - stages = await get_full_tournament_details(tournament_id) - - stage_items = { - stage_item.id: assert_some(await get_stage_item(tournament_id, stage_item.id)) - for stage in stages - for stage_item in stage.stage_items - } +async def get_team_rankings_lookup_for_stage( + tournament_id: TournamentId, stage: StageWithStageItems +) -> StageItemXTeamRanking: + stage_items = {stage_item.id: stage_item for stage_item in stage.stage_items} return { - stage_item_id: await determine_team_ranking_for_stage_item( + stage_item_id: determine_team_ranking_for_stage_item( stage_item, assert_some(await get_ranking_for_stage_item(tournament_id, stage_item.id)), ) @@ -82,6 +78,16 @@ async def get_team_rankings_lookup(tournament_id: TournamentId) -> StageItemXTea } +async def get_team_rankings_lookup(tournament_id: TournamentId) -> StageItemXTeamRanking: + return { + stage_item_id: team_ranking + for stage in await get_full_tournament_details(tournament_id) + for stage_item_id, team_ranking in ( + await get_team_rankings_lookup_for_stage(tournament_id, stage) + ).items() + } + + async def update_matches_in_activated_stage(tournament_id: TournamentId, stage_id: StageId) -> None: [stage] = await get_full_tournament_details(tournament_id, stage_id=stage_id) stage_item_x_team_rankings = await get_team_rankings_lookup(tournament_id) diff --git a/backend/bracket/routes/stages.py b/backend/bracket/routes/stages.py index fa6b0eda..101a0eeb 100644 --- a/backend/bracket/routes/stages.py +++ b/backend/bracket/routes/stages.py @@ -2,11 +2,11 @@ from fastapi import APIRouter, Depends, HTTPException from starlette import status from bracket.database import database -from bracket.logic.ranking.elo import ( - determine_team_ranking_for_stage_item, -) from bracket.logic.scheduling.builder import determine_available_inputs -from bracket.logic.scheduling.handle_stage_activation import update_matches_in_activated_stage +from bracket.logic.scheduling.handle_stage_activation import ( + get_team_rankings_lookup_for_stage, + update_matches_in_activated_stage, +) from bracket.logic.subscriptions import check_requirement from bracket.models.db.stage import Stage, StageActivateBody, StageUpdateBody from bracket.models.db.user import UserPublic @@ -22,8 +22,6 @@ from bracket.routes.models import ( SuccessResponse, ) from bracket.routes.util import stage_dependency -from bracket.sql.rankings import get_ranking_for_stage_item -from bracket.sql.stage_items import get_stage_item from bracket.sql.stages import ( get_full_tournament_details, get_next_stage_in_tournament, @@ -33,7 +31,6 @@ from bracket.sql.stages import ( ) from bracket.sql.teams import get_teams_with_members from bracket.utils.id_types import StageId, TournamentId -from bracket.utils.types import assert_some router = APIRouter() @@ -158,17 +155,4 @@ async def get_rankings( Get the rankings for the stage items in this stage. """ [stage] = await get_full_tournament_details(tournament_id, stage_id=stage_id) - - stage_items = { - stage_item.id: assert_some(await get_stage_item(tournament_id, stage_item.id)) - for stage_item in stage.stage_items - } - stage_item_x_ranking = { - stage_item_id: await determine_team_ranking_for_stage_item( - stage_item, - assert_some(await get_ranking_for_stage_item(tournament_id, stage_item.id)), - ) - for stage_item_id, stage_item in stage_items.items() - } - - return StageRankingResponse(data=stage_item_x_ranking) + return StageRankingResponse(data=await get_team_rankings_lookup_for_stage(tournament_id, stage)) diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 35a084ff..52949012 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -7,6 +7,12 @@ "active_next_round_modal_choose_option_checked": "Adjust the start times of the next matches to start immediately(now). This will be done by modifying the margin times of the matches in the previous round.", "active_next_round_modal_choose_option_unchecked": "Use default timing (the next matches will be planned tightly after the matches of the active round end, taking margin into account)", "active_next_round_modal_description": "This will assign times and courts to matches of next round, which is the round after the current activated (green) round.", + "active_next_stage_modal_description": "This will start the next stage. Teams will be automatically assigned to the matches.", + "plan_next_stage_button": "Start the next stage", + "active_next_stage_modal_title": "Start the next stage", + "active_previous_stage_modal_description": "Are you sure you want to go back to the previous stage? Match results will be discarded.", + "plan_previous_stage_button": "Go back to the previous stage", + "active_previous_stage_modal_title": "Go back to the previous stage", "active_next_round_modal_title": "Assign times and courts to matches of next round", "active_player_checkbox_label": "This player is active", "active_players_checkbox_label": "These players are active", diff --git a/frontend/src/components/builder/builder.tsx b/frontend/src/components/builder/builder.tsx index 763205eb..69b66374 100644 --- a/frontend/src/components/builder/builder.tsx +++ b/frontend/src/components/builder/builder.tsx @@ -255,7 +255,7 @@ export default function Builder({ const button = ( -

+

diff --git a/frontend/src/components/buttons/next_stage_button.tsx b/frontend/src/components/buttons/next_stage_button.tsx deleted file mode 100644 index fc717b4b..00000000 --- a/frontend/src/components/buttons/next_stage_button.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Button } from '@mantine/core'; -import { IconSquareArrowLeft, IconSquareArrowRight } from '@tabler/icons-react'; -import { useTranslation } from 'next-i18next'; -import React from 'react'; - -import { activateNextStage } from '../../services/stage'; - -export function NextStageButton({ tournamentData, swrStagesResponse }: any) { - const { t } = useTranslation(); - return ( - - ); -} - -export function PreviousStageButton({ tournamentData, swrStagesResponse }: any) { - const { t } = useTranslation(); - - return ( - - ); -} diff --git a/frontend/src/components/modals/activate_next_stage_modal.tsx b/frontend/src/components/modals/activate_next_stage_modal.tsx new file mode 100644 index 00000000..9478813e --- /dev/null +++ b/frontend/src/components/modals/activate_next_stage_modal.tsx @@ -0,0 +1,69 @@ +import { Alert, Button, Modal } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { IconAlertCircle, IconSquareArrowRight } from '@tabler/icons-react'; +import { useTranslation } from 'next-i18next'; +import React, { useState } from 'react'; +import { SWRResponse } from 'swr'; + +import { activateNextStage } from '../../services/stage'; + +export default function ActivateNextStageModal({ + tournamentId, + swrStagesResponse, +}: { + tournamentId: number; + swrStagesResponse: SWRResponse; +}) { + const { t } = useTranslation(); + const [opened, setOpened] = useState(false); + + const form = useForm({ + initialValues: {}, + }); + + return ( + <> + setOpened(false)} + title={t('active_next_stage_modal_title')} + size="40rem" + > +
{ + await activateNextStage(tournamentId, 'next'); + swrStagesResponse.mutate(); + setOpened(false); + })} + > + } color="gray" radius="lg"> + {t('active_next_stage_modal_description')} + + + +
+
+ + + + ); +} diff --git a/frontend/src/components/modals/activate_previous_stage_modal.tsx b/frontend/src/components/modals/activate_previous_stage_modal.tsx new file mode 100644 index 00000000..0cd4e96b --- /dev/null +++ b/frontend/src/components/modals/activate_previous_stage_modal.tsx @@ -0,0 +1,69 @@ +import { Alert, Button, Modal } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { IconAlertCircle, IconSquareArrowLeft } from '@tabler/icons-react'; +import { useTranslation } from 'next-i18next'; +import React, { useState } from 'react'; +import { SWRResponse } from 'swr'; + +import { activateNextStage } from '../../services/stage'; + +export default function ActivatePreviousStageModal({ + tournamentId, + swrStagesResponse, +}: { + tournamentId: number; + swrStagesResponse: SWRResponse; +}) { + const { t } = useTranslation(); + const [opened, setOpened] = useState(false); + + const form = useForm({ + initialValues: {}, + }); + + return ( + <> + setOpened(false)} + title={t('active_previous_stage_modal_title')} + size="40rem" + > +
{ + await activateNextStage(tournamentId, 'previous'); + swrStagesResponse.mutate(); + setOpened(false); + })} + > + } color="orange" radius="lg"> + {t('active_previous_stage_modal_description')} + + + +
+
+ + + + ); +} diff --git a/frontend/src/pages/tournaments/[id]/stages.tsx b/frontend/src/pages/tournaments/[id]/stages.tsx index 0f502129..e89f328b 100644 --- a/frontend/src/pages/tournaments/[id]/stages.tsx +++ b/frontend/src/pages/tournaments/[id]/stages.tsx @@ -5,10 +5,8 @@ import React from 'react'; import Builder from '../../../components/builder/builder'; import { CreateStageButtonLarge } from '../../../components/buttons/create_stage'; -import { - NextStageButton, - PreviousStageButton, -} from '../../../components/buttons/next_stage_button'; +import ActivateNextStageModal from '../../../components/modals/activate_next_stage_modal'; +import ActivatePreviousStageModal from '../../../components/modals/activate_previous_stage_modal'; import { NoContent } from '../../../components/no_content/empty_table_info'; import { TableSkeletonTwoColumnsSmall } from '../../../components/utils/skeletons'; import { getTournamentIdFromRouter } from '../../../components/utils/util'; @@ -47,11 +45,14 @@ export default function StagesPage() { content = ( <> - + -