mirror of
https://github.com/evroon/bracket.git
synced 2026-06-11 02:04:33 -04:00
Fix handling of available inputs (#993)
This commit is contained in:
@@ -18,7 +18,7 @@ from bracket.models.db.team import FullTeamWithPlayers
|
||||
from bracket.models.db.util import StageWithStageItems
|
||||
from bracket.sql.rounds import get_next_round_name, sql_create_round
|
||||
from bracket.sql.stage_items import get_stage_item
|
||||
from bracket.utils.id_types import StageId, TournamentId
|
||||
from bracket.utils.id_types import StageId, StageItemId, TournamentId
|
||||
from tests.integration_tests.mocks import MOCK_NOW
|
||||
|
||||
|
||||
@@ -71,10 +71,9 @@ async def build_matches_for_stage_item(stage_item: StageItem, tournament_id: Tou
|
||||
|
||||
|
||||
def determine_available_inputs(
|
||||
stage_id: StageId,
|
||||
teams: list[FullTeamWithPlayers],
|
||||
stages: list[StageWithStageItems],
|
||||
) -> list[StageItemInputOptionTentative | StageItemInputOptionFinal]:
|
||||
) -> dict[StageId, list[StageItemInputOptionTentative | StageItemInputOptionFinal]]:
|
||||
"""
|
||||
Returns available inputs for the given stage.
|
||||
|
||||
@@ -83,35 +82,41 @@ def determine_available_inputs(
|
||||
- Previous ROUND_ROBIN stage items
|
||||
"""
|
||||
results_team_ids = {team.id: False for team in teams}
|
||||
results_tentative = []
|
||||
results_tentative: dict[tuple[StageItemId, int], StageItemInputOptionTentative] = {}
|
||||
results = {}
|
||||
|
||||
for stage in stages:
|
||||
# First, set options that are used in this round to have `already_taken=True`
|
||||
for stage_item in stage.stage_items:
|
||||
item_team_id_inputs = [
|
||||
input.team_id for input in stage_item.inputs if input.team_id is not None
|
||||
]
|
||||
for input_ in item_team_id_inputs:
|
||||
if input_ in results_team_ids:
|
||||
if stage_id != stage.id:
|
||||
results_team_ids.pop(input_)
|
||||
else:
|
||||
results_team_ids[input_] = True
|
||||
for input_ in stage_item.inputs:
|
||||
if input_.team_id is not None and input_.team_id in results_team_ids:
|
||||
results_team_ids[input_.team_id] = True
|
||||
|
||||
if stage_id == stage.id:
|
||||
break
|
||||
if (
|
||||
input_.winner_from_stage_item_id is not None
|
||||
and input_.winner_position is not None
|
||||
and (key := (input_.winner_from_stage_item_id, input_.winner_position))
|
||||
in results_tentative
|
||||
):
|
||||
results_tentative[key].already_taken = True
|
||||
|
||||
# Store results for this stage
|
||||
results_final = [
|
||||
StageItemInputOptionFinal(team_id=team_id, already_taken=taken)
|
||||
for team_id, taken in results_team_ids.items()
|
||||
]
|
||||
results[stage.id] = results_final + list(results_tentative.values())
|
||||
|
||||
# Then, add inputs from non-elimination stage items that can be used in the next stage.
|
||||
for stage_item in stage.stage_items:
|
||||
for winner_position in range(1, 5):
|
||||
results_tentative.append(
|
||||
StageItemInputOptionTentative(
|
||||
winner_from_stage_item_id=stage_item.id,
|
||||
winner_position=winner_position,
|
||||
already_taken=False,
|
||||
if stage_item.type in {StageType.ROUND_ROBIN, StageType.SWISS}:
|
||||
for winner_position in range(1, 5):
|
||||
results_tentative[(stage_item.id, winner_position)] = (
|
||||
StageItemInputOptionTentative(
|
||||
winner_from_stage_item_id=stage_item.id,
|
||||
winner_position=winner_position,
|
||||
already_taken=False,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
results_final = [
|
||||
StageItemInputOptionFinal(team_id=team_id, already_taken=taken)
|
||||
for team_id, taken in results_team_ids.items()
|
||||
]
|
||||
return results_final + results_tentative
|
||||
return results
|
||||
|
||||
@@ -145,10 +145,7 @@ async def get_available_inputs(
|
||||
) -> StageItemInputOptionsResponse:
|
||||
stages = await get_full_tournament_details(tournament_id)
|
||||
teams = await get_teams_with_members(tournament_id)
|
||||
available_inputs = {
|
||||
stage.id: determine_available_inputs(stage.id, teams, stages) for stage in stages
|
||||
}
|
||||
return StageItemInputOptionsResponse(data=available_inputs)
|
||||
return StageItemInputOptionsResponse(data=determine_available_inputs(teams, stages))
|
||||
|
||||
|
||||
@router.get("/tournaments/{tournament_id}/next_stage_rankings")
|
||||
|
||||
@@ -84,8 +84,12 @@ export function RoundsGridCols({
|
||||
return <NoRoundsAlert readOnly={readOnly} />;
|
||||
}
|
||||
|
||||
const items = stageItem.rounds
|
||||
let result: React.JSX.Element[] | React.JSX.Element = stageItem.rounds
|
||||
.sort((r1: any, r2: any) => (r1.name > r2.name ? 1 : -1))
|
||||
.filter(
|
||||
(round: RoundInterface) =>
|
||||
round.matches.length > 0 || displaySettings.matchVisibility === 'all'
|
||||
)
|
||||
.map((round: RoundInterface) => (
|
||||
<Round
|
||||
key={round.id}
|
||||
@@ -98,24 +102,34 @@ export function RoundsGridCols({
|
||||
/>
|
||||
));
|
||||
|
||||
let result: React.JSX.Element[] | React.JSX.Element = items;
|
||||
|
||||
if (result.length < 1) {
|
||||
result = (
|
||||
<Container mt="1rem">
|
||||
<Stack align="center">
|
||||
<NoContent title={t('no_round_description')} />
|
||||
<AddRoundButton
|
||||
t={t}
|
||||
tournamentData={tournamentData}
|
||||
stageItem={stageItem}
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
size="lg"
|
||||
/>
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
if (stageItem.rounds.length < 1) {
|
||||
result = (
|
||||
<Container mt="1rem">
|
||||
<Stack align="center">
|
||||
<NoContent title={t('no_round_description')} />
|
||||
{stageItem.rounds.length < 1 && (
|
||||
<AddRoundButton
|
||||
t={t}
|
||||
tournamentData={tournamentData}
|
||||
stageItem={stageItem}
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
result = (
|
||||
<Container mt="1rem">
|
||||
<Stack align="center">
|
||||
<NoContent title={t('no_round_found_title')} />
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const hideAddRoundButton = tournamentData == null || readOnly;
|
||||
|
||||
@@ -70,10 +70,6 @@ export default function Round({
|
||||
/>
|
||||
);
|
||||
|
||||
if (matches.length < 1 && displaySettings.matchVisibility !== 'all') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = (
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Alert, Container, Text, Title } from '@mantine/core';
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import { MdOutlineConstruction } from 'react-icons/md';
|
||||
import { HiMiniWrenchScrewdriver } from 'react-icons/hi2';
|
||||
|
||||
import classes from './empty_table_info.module.css';
|
||||
|
||||
@@ -39,7 +39,7 @@ export function NoContent({
|
||||
}) {
|
||||
return (
|
||||
<Container mt="md">
|
||||
<div className={classes.label}>{icon || <MdOutlineConstruction />}</div>
|
||||
<div className={classes.label}>{icon || <HiMiniWrenchScrewdriver />}</div>
|
||||
<Title className={classes.title}>{title}</Title>
|
||||
<Text size="lg" ta="center" className={classes.description}>
|
||||
{description}
|
||||
|
||||
@@ -8,7 +8,6 @@ export interface StageItemWithRounds {
|
||||
name: string;
|
||||
type_name: string;
|
||||
team_count: number;
|
||||
is_active: boolean;
|
||||
rounds: RoundInterface[];
|
||||
inputs: StageItemInput[];
|
||||
stage_id: number;
|
||||
|
||||
Reference in New Issue
Block a user