mirror of
https://github.com/evroon/bracket.git
synced 2026-01-27 23:52:04 -05:00
Improve UX of swiss items (#981)
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"at_least_two_team_validation": "Need at least two teams",
|
||||
"auto_assign_courts_label": "Automatically assign courts to matches",
|
||||
"auto_create_matches_button": "Add new matches automatically",
|
||||
"courts_filled_badge": "courts filled",
|
||||
"back_home_nav": "Take me back to home page",
|
||||
"back_to_login_nav": "Back to login page",
|
||||
"checkbox_status_checked": "Checked",
|
||||
@@ -45,7 +46,9 @@
|
||||
"clubs_spotlight_description": "View, add or delete clubs",
|
||||
"clubs_title": "clubs",
|
||||
"copied_dashboard_url_button": "Copied Dashboard URL",
|
||||
"copied_url_button": "Copied URL",
|
||||
"copy_dashboard_url_button": "Copy Dashboard URL",
|
||||
"copy_url_button": "Copy URL",
|
||||
"could_not_find_any_alert": "Could not find any",
|
||||
"court_name_input_placeholder": "Best Court Ever",
|
||||
"courts_title": "courts",
|
||||
@@ -145,8 +148,6 @@
|
||||
"multiple_teams_input_label": "Add multiple teams. Put every team on a separate line",
|
||||
"multiple_teams_input_placeholder": "Team 1",
|
||||
"name_field_text": "name",
|
||||
"name_filter_options_player": "Player names",
|
||||
"name_filter_options_team": "Team names",
|
||||
"name_input_label": "Name",
|
||||
"name_input_placeholder": "Your name",
|
||||
"name_table_header": "Name",
|
||||
@@ -156,7 +157,9 @@
|
||||
"next_matches_badge": "Next matches",
|
||||
"next_stage_button": "Next Stage",
|
||||
"no_courts_description": "No courts have been created yet. First, create the tournament structure by adding stages and stage items. Then, create courts here and schedule matches on these courts.",
|
||||
"no_courts_description_swiss": "No courts have been created yet. First add courts before managing a Swiss stage item.",
|
||||
"no_courts_title": "No courts yet",
|
||||
"go_to_courts_page": "Go to courts page",
|
||||
"no_matches_description": "First, add matches by creating stages and stage items. Then, schedule them using the button in the topright corner.",
|
||||
"no_matches_title": "No matches scheduled yet",
|
||||
"no_players_title": "No players yet",
|
||||
@@ -244,6 +247,8 @@
|
||||
"tournament_title": "tournament",
|
||||
"tournaments_title": "tournaments",
|
||||
"upcoming_matches_empty_table_info": "upcoming matches",
|
||||
"no_more_matches_title": "No more matches to schedule",
|
||||
"all_matches_scheduled_description": "Matches have been scheduled on all courts in this round. Add a new round or add a new court for more matches.",
|
||||
"upload_placeholder_team": "Drop a file here to upload as team logo.",
|
||||
"upload_placeholder_tournament": "Drop a file here to upload as tournament logo.",
|
||||
"uppercase_required": "Includes uppercase letter",
|
||||
|
||||
@@ -1,30 +1,97 @@
|
||||
import { Alert, Button, Container, Grid, Group, Skeleton } from '@mantine/core';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Center,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
Group,
|
||||
Progress,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Switch,
|
||||
} from '@mantine/core';
|
||||
import { GoPlus } from '@react-icons/all-files/go/GoPlus';
|
||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import { MdOutlineAutoFixHigh } from 'react-icons/md';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { BracketDisplaySettings } from '../../interfaces/brackets';
|
||||
import { SchedulerSettings } from '../../interfaces/match';
|
||||
import { RoundInterface } from '../../interfaces/round';
|
||||
import { StageItemWithRounds, stageItemIsHandledAutomatically } from '../../interfaces/stage_item';
|
||||
import { TournamentMinimal } from '../../interfaces/tournament';
|
||||
import { Tournament, TournamentMinimal } from '../../interfaces/tournament';
|
||||
import { createRound } from '../../services/round';
|
||||
import { AutoCreateMatchesButton } from '../buttons/create_matches_auto';
|
||||
import ActivateNextRoundModal from '../modals/activate_next_round_modal';
|
||||
import { NoContent } from '../no_content/empty_table_info';
|
||||
import { Translator } from '../utils/types';
|
||||
import { responseIsValid } from '../utils/util';
|
||||
import Round from './round';
|
||||
|
||||
function getRoundsGridCols(
|
||||
t: Translator,
|
||||
stageItem: StageItemWithRounds,
|
||||
tournamentData: TournamentMinimal,
|
||||
swrStagesResponse: SWRResponse,
|
||||
swrUpcomingMatchesResponse: SWRResponse | null,
|
||||
readOnly: boolean,
|
||||
displaySettings: BracketDisplaySettings
|
||||
) {
|
||||
let rounds: React.JSX.Element[] | React.JSX.Element = stageItem.rounds
|
||||
function AddRoundButton({
|
||||
t,
|
||||
tournamentData,
|
||||
stageItem,
|
||||
swrStagesResponse,
|
||||
size,
|
||||
}: {
|
||||
t: Translator;
|
||||
tournamentData: TournamentMinimal;
|
||||
stageItem: StageItemWithRounds;
|
||||
swrStagesResponse: SWRResponse;
|
||||
size: 'md' | 'lg';
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
color="green"
|
||||
size={size}
|
||||
leftSection={<GoPlus size={24} />}
|
||||
variant="outline"
|
||||
onClick={async () => {
|
||||
await createRound(tournamentData.id, stageItem.id);
|
||||
await swrStagesResponse.mutate();
|
||||
}}
|
||||
>
|
||||
{t('add_round_button')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function RoundsGridCols({
|
||||
stageItem,
|
||||
tournamentData,
|
||||
swrStagesResponse,
|
||||
swrCourtsResponse,
|
||||
swrUpcomingMatchesResponse,
|
||||
schedulerSettings,
|
||||
readOnly,
|
||||
displaySettings,
|
||||
draftRound,
|
||||
}: {
|
||||
stageItem: StageItemWithRounds;
|
||||
tournamentData: Tournament;
|
||||
swrStagesResponse: SWRResponse;
|
||||
swrCourtsResponse: SWRResponse;
|
||||
swrUpcomingMatchesResponse: SWRResponse;
|
||||
schedulerSettings: SchedulerSettings;
|
||||
readOnly: boolean;
|
||||
displaySettings: BracketDisplaySettings;
|
||||
draftRound: RoundInterface;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (swrStagesResponse.isLoading) {
|
||||
return <LoadingSkeleton />;
|
||||
}
|
||||
if (!responseIsValid(swrStagesResponse)) {
|
||||
return <NoRoundsAlert readOnly={readOnly} />;
|
||||
}
|
||||
|
||||
const items = stageItem.rounds
|
||||
.sort((r1: any, r2: any) => (r1.name > r2.name ? 1 : -1))
|
||||
.map((round: RoundInterface) => (
|
||||
<Round
|
||||
@@ -39,42 +106,92 @@ function getRoundsGridCols(
|
||||
/>
|
||||
));
|
||||
|
||||
if (rounds.length < 1) {
|
||||
rounds = (
|
||||
<Alert
|
||||
icon={<IconAlertCircle size={16} />}
|
||||
title={t('no_round_title')}
|
||||
color="blue"
|
||||
radius="lg"
|
||||
>
|
||||
{t('no_round_description')}
|
||||
</Alert>
|
||||
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}
|
||||
size="lg"
|
||||
/>
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const hideAddRoundButton =
|
||||
tournamentData == null || readOnly || stageItemIsHandledAutomatically(stageItem);
|
||||
|
||||
const courtsCount = swrCourtsResponse.data?.data?.length || 0;
|
||||
const scheduledMatchesCount = draftRound?.matches.length;
|
||||
|
||||
return (
|
||||
<React.Fragment key={stageItem.id}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Grid grow>
|
||||
<Grid.Col span={6}></Grid.Col>
|
||||
<Grid.Col span={6} mb="2rem">
|
||||
<Group>
|
||||
<Center>
|
||||
{scheduledMatchesCount == null ? null : (
|
||||
<>
|
||||
<Stack gap="6px">
|
||||
<>
|
||||
{scheduledMatchesCount} / {courtsCount} {t('courts_filled_badge')}
|
||||
</>
|
||||
<Progress
|
||||
value={(scheduledMatchesCount * 100) / courtsCount}
|
||||
miw="12rem"
|
||||
striped
|
||||
color="indigo"
|
||||
/>
|
||||
</Stack>
|
||||
<Divider orientation="vertical" mx="1rem" />
|
||||
</>
|
||||
)}
|
||||
<Switch
|
||||
size="md"
|
||||
onLabel={<MdOutlineAutoFixHigh size={16} />}
|
||||
offLabel={<IoOptions size={16} />}
|
||||
checked={displaySettings.showManualSchedulingOptions === 'false'}
|
||||
label={
|
||||
displaySettings.showManualSchedulingOptions === 'true' ? 'Manual' : 'Automatic'
|
||||
}
|
||||
color="indigo"
|
||||
onChange={(event) => {
|
||||
displaySettings.setShowManualSchedulingOptions(
|
||||
event.currentTarget.checked ? 'false' : 'true'
|
||||
);
|
||||
}}
|
||||
miw="9rem"
|
||||
/>
|
||||
<Divider orientation="vertical" mx="1rem" />
|
||||
<AutoCreateMatchesButton
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
tournamentData={tournamentData}
|
||||
stageItemId={stageItem.id}
|
||||
schedulerSettings={schedulerSettings}
|
||||
displaySettings={displaySettings}
|
||||
/>
|
||||
</Center>
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<Group justify="right">
|
||||
{hideAddRoundButton ? null : (
|
||||
<Button
|
||||
color="green"
|
||||
<AddRoundButton
|
||||
t={t}
|
||||
tournamentData={tournamentData}
|
||||
stageItem={stageItem}
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
size="md"
|
||||
leftSection={<GoPlus size={24} />}
|
||||
variant="outline"
|
||||
onClick={async () => {
|
||||
await createRound(tournamentData.id, stageItem.id);
|
||||
await swrStagesResponse.mutate();
|
||||
}}
|
||||
>
|
||||
{t('add_round_button')}
|
||||
</Button>
|
||||
/>
|
||||
)}
|
||||
{hideAddRoundButton ? null : (
|
||||
<ActivateNextRoundModal
|
||||
@@ -87,7 +204,7 @@ function getRoundsGridCols(
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</div>
|
||||
<Group align="top">{rounds}</Group>
|
||||
<Group align="top">{result}</Group>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -132,44 +249,3 @@ function LoadingSkeleton() {
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Brackets({
|
||||
tournamentData,
|
||||
swrStagesResponse,
|
||||
swrUpcomingMatchesResponse,
|
||||
readOnly,
|
||||
displaySettings,
|
||||
stageItem,
|
||||
}: {
|
||||
tournamentData: TournamentMinimal;
|
||||
swrStagesResponse: SWRResponse;
|
||||
swrUpcomingMatchesResponse: SWRResponse | null;
|
||||
readOnly: boolean;
|
||||
displaySettings: BracketDisplaySettings;
|
||||
stageItem: StageItemWithRounds;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (swrStagesResponse.isLoading) {
|
||||
return <LoadingSkeleton />;
|
||||
}
|
||||
if (!swrStagesResponse.isLoading && !responseIsValid(swrStagesResponse)) {
|
||||
return <NoRoundsAlert readOnly={readOnly} />;
|
||||
}
|
||||
|
||||
if (swrStagesResponse.isLoading) {
|
||||
return <LoadingSkeleton />;
|
||||
}
|
||||
|
||||
const rounds = getRoundsGridCols(
|
||||
t,
|
||||
stageItem,
|
||||
tournamentData,
|
||||
swrStagesResponse,
|
||||
swrUpcomingMatchesResponse,
|
||||
readOnly,
|
||||
displaySettings
|
||||
);
|
||||
|
||||
return <div>{rounds}</div>;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import assert from 'assert';
|
||||
import React, { useState } from 'react';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { BracketDisplaySettings } from '../../interfaces/brackets';
|
||||
import {
|
||||
MatchInterface,
|
||||
formatMatchInput1,
|
||||
@@ -47,7 +46,6 @@ export default function Match({
|
||||
match,
|
||||
readOnly,
|
||||
dynamicSchedule,
|
||||
displaySettings,
|
||||
}: {
|
||||
swrStagesResponse: SWRResponse;
|
||||
swrUpcomingMatchesResponse: SWRResponse | null;
|
||||
@@ -55,7 +53,6 @@ export default function Match({
|
||||
match: MatchInterface;
|
||||
readOnly: boolean;
|
||||
dynamicSchedule: boolean;
|
||||
displaySettings?: BracketDisplaySettings | null;
|
||||
}) {
|
||||
// const { classes } = useStyles();
|
||||
const theme = useMantineTheme();
|
||||
@@ -63,8 +60,6 @@ export default function Match({
|
||||
// backgroundColor: theme.colorScheme === 'dark' ? theme.colors.green[9] : theme.colors.green[4],
|
||||
backgroundColor: theme.colors.green[9],
|
||||
};
|
||||
const showTeamMemberNames =
|
||||
displaySettings != null && displaySettings.teamNamesDisplay === 'player-names';
|
||||
|
||||
const stageItemsLookup = getStageItemLookup(swrStagesResponse);
|
||||
const matchesLookup = getMatchLookup(swrStagesResponse);
|
||||
@@ -74,22 +69,8 @@ export default function Match({
|
||||
const team2_style =
|
||||
match.stage_item_input1_score < match.stage_item_input2_score ? winner_style : {};
|
||||
|
||||
const team1_players = match.stage_item_input1?.team
|
||||
? match.stage_item_input1.team.players.map((player) => player.name).join(', ')
|
||||
: '';
|
||||
const team2_players = match.stage_item_input2?.team
|
||||
? match.stage_item_input2.team.players.map((player) => player.name).join(', ')
|
||||
: '';
|
||||
|
||||
const team1_players_label = team1_players === '' ? 'No players' : team1_players;
|
||||
const team2_players_label = team2_players === '' ? 'No players' : team2_players;
|
||||
|
||||
const team1_label = showTeamMemberNames
|
||||
? team1_players_label
|
||||
: formatMatchInput1(stageItemsLookup, matchesLookup, match);
|
||||
const team2_label = showTeamMemberNames
|
||||
? team2_players_label
|
||||
: formatMatchInput2(stageItemsLookup, matchesLookup, match);
|
||||
const team1_label = formatMatchInput1(stageItemsLookup, matchesLookup, match);
|
||||
const team2_label = formatMatchInput2(stageItemsLookup, matchesLookup, match);
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ export default function Round({
|
||||
match={match}
|
||||
readOnly={readOnly}
|
||||
dynamicSchedule={dynamicSchedule}
|
||||
displaySettings={displaySettings}
|
||||
/>
|
||||
));
|
||||
const active_round_style = round.is_active
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Menu,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
useCombobox,
|
||||
useMantineColorScheme,
|
||||
useMantineTheme,
|
||||
@@ -282,44 +283,56 @@ function StageItemRow({
|
||||
setOpened={setOpened}
|
||||
rankings={rankings}
|
||||
/>
|
||||
<Menu withinPortal position="bottom-end" shadow="sm">
|
||||
<Menu.Target>
|
||||
<ActionIcon variant="transparent" color="gray">
|
||||
<IconDots size="1.25rem" />
|
||||
<Group gap="0rem">
|
||||
<Tooltip label={t('handle_swiss_system')}>
|
||||
<ActionIcon
|
||||
variant="transparent"
|
||||
color="gray"
|
||||
component={Link}
|
||||
href={`/tournaments/${tournament.id}/stages/swiss/${stageItem.id}`}
|
||||
>
|
||||
<BiSolidWrench size="1.25rem" />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
</Tooltip>
|
||||
<Menu withinPortal position="bottom-end" shadow="sm">
|
||||
<Menu.Target>
|
||||
<ActionIcon variant="transparent" color="gray">
|
||||
<IconDots size="1.25rem" />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
leftSection={<IconPencil size="1.5rem" />}
|
||||
onClick={() => {
|
||||
setOpened(true);
|
||||
}}
|
||||
>
|
||||
{t('edit_stage_item_label')}
|
||||
</Menu.Item>
|
||||
{stageItem.type === 'SWISS' ? (
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
leftSection={<BiSolidWrench size="1.5rem" />}
|
||||
component={Link}
|
||||
href={`/tournaments/${tournament.id}/swiss/${stageItem.id}`}
|
||||
leftSection={<IconPencil size="1.5rem" />}
|
||||
onClick={() => {
|
||||
setOpened(true);
|
||||
}}
|
||||
>
|
||||
{t('handle_swiss_system')}
|
||||
{t('edit_stage_item_label')}
|
||||
</Menu.Item>
|
||||
) : null}
|
||||
<Menu.Item
|
||||
leftSection={<IconTrash size="1.5rem" />}
|
||||
onClick={async () => {
|
||||
await deleteStageItem(tournament.id, stageItem.id);
|
||||
await swrStagesResponse.mutate();
|
||||
await swrAvailableInputsResponse.mutate();
|
||||
}}
|
||||
color="red"
|
||||
>
|
||||
{t('delete_button')}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
{stageItem.type === 'SWISS' ? (
|
||||
<Menu.Item
|
||||
leftSection={<BiSolidWrench size="1.5rem" />}
|
||||
component={Link}
|
||||
href={`/tournaments/${tournament.id}/swiss/${stageItem.id}`}
|
||||
>
|
||||
{t('handle_swiss_system')}
|
||||
</Menu.Item>
|
||||
) : null}
|
||||
<Menu.Item
|
||||
leftSection={<IconTrash size="1.5rem" />}
|
||||
onClick={async () => {
|
||||
await deleteStageItem(tournament.id, stageItem.id);
|
||||
await swrStagesResponse.mutate();
|
||||
await swrAvailableInputsResponse.mutate();
|
||||
}}
|
||||
color="red"
|
||||
>
|
||||
{t('delete_button')}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card.Section>
|
||||
{inputs}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Button } from '@mantine/core';
|
||||
import { IconTool } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import { MdOutlineAutoFixHigh } from 'react-icons/md';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { BracketDisplaySettings } from '../../interfaces/brackets';
|
||||
import { SchedulerSettings } from '../../interfaces/match';
|
||||
import { Tournament } from '../../interfaces/tournament';
|
||||
import { createMatchesAuto } from '../../services/round';
|
||||
@@ -14,21 +15,22 @@ export function AutoCreateMatchesButton({
|
||||
swrUpcomingMatchesResponse,
|
||||
stageItemId,
|
||||
schedulerSettings,
|
||||
displaySettings,
|
||||
}: {
|
||||
schedulerSettings: SchedulerSettings;
|
||||
stageItemId: number;
|
||||
tournamentData: Tournament;
|
||||
swrStagesResponse: SWRResponse;
|
||||
swrUpcomingMatchesResponse: SWRResponse;
|
||||
displaySettings: BracketDisplaySettings;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Button
|
||||
size="md"
|
||||
mt="1rem"
|
||||
mb="1rem"
|
||||
color="indigo"
|
||||
leftSection={<IconTool size={24} />}
|
||||
leftSection={<MdOutlineAutoFixHigh size={24} />}
|
||||
disabled={displaySettings.showManualSchedulingOptions === 'true'}
|
||||
onClick={async () => {
|
||||
await createMatchesAuto(
|
||||
tournamentData.id,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import QRCode from 'react-qr-code';
|
||||
|
||||
import { Tournament, getTournamentEndpoint } from '../../interfaces/tournament';
|
||||
import { Tournament } from '../../interfaces/tournament';
|
||||
import { getBaseApiUrl } from '../../services/adapter';
|
||||
import { getBaseURL } from '../utils/util';
|
||||
import classes from './layout.module.css';
|
||||
@@ -80,7 +80,7 @@ export function TournamentTitle({ tournamentDataFull }: { tournamentDataFull: To
|
||||
|
||||
export function DoubleHeader({ tournamentData }: { tournamentData: Tournament }) {
|
||||
const router = useRouter();
|
||||
const endpoint = getTournamentEndpoint(tournamentData);
|
||||
const endpoint = tournamentData.dashboard_endpoint;
|
||||
const pathName = router.pathname.replace('[id]', endpoint).replace(/\/+$/, '');
|
||||
|
||||
const mainLinks = [
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
import { Text } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import { BracketDisplaySettings } from '../../interfaces/brackets';
|
||||
import { TeamInterface } from '../../interfaces/team';
|
||||
import { truncateString } from '../utils/util';
|
||||
|
||||
export default function PlayerList({
|
||||
team,
|
||||
displaySettings,
|
||||
}: {
|
||||
team: TeamInterface;
|
||||
displaySettings?: BracketDisplaySettings | null;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
if (displaySettings != null && displaySettings.teamNamesDisplay === 'team-names') {
|
||||
return <span>{team.name}</span>;
|
||||
}
|
||||
if (team.players.length < 1) {
|
||||
return <i>{t('no_team_members_description')}</i>;
|
||||
}
|
||||
|
||||
const playerNames = team.players
|
||||
.map((player) => truncateString(player.name, 15))
|
||||
.sort()
|
||||
.join(', ');
|
||||
return (
|
||||
<Text span truncate="end">
|
||||
{playerNames}
|
||||
</Text>
|
||||
);
|
||||
export default function PlayerList({ team }: { team: TeamInterface }) {
|
||||
return <span>{team.name}</span>;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ function MainLink({ item, pathName }: { item: MainLinkProps; pathName: String })
|
||||
component={Link}
|
||||
href={item.link}
|
||||
className={classes.link}
|
||||
data-active={pathName === item.link || undefined}
|
||||
data-active={pathName.startsWith(item.link) || undefined}
|
||||
>
|
||||
<item.icon stroke={1.5} />
|
||||
</UnstyledButton>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Alert, Container, Text, Title } from '@mantine/core';
|
||||
import { AiOutlineHourglass } from '@react-icons/all-files/ai/AiOutlineHourglass';
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
@@ -29,32 +28,18 @@ export function EmptyTableInfo({
|
||||
);
|
||||
}
|
||||
|
||||
export function NoContent({ title, description }: { title: string; description?: string }) {
|
||||
return (
|
||||
<Container mt="md">
|
||||
<div className={classes.label}>
|
||||
<MdOutlineConstruction />
|
||||
</div>
|
||||
<Title className={classes.title}>{title}</Title>
|
||||
<Text size="lg" ta="center" className={classes.description}>
|
||||
{description}
|
||||
</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export function NoContentDashboard({
|
||||
export function NoContent({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Container mt="md">
|
||||
<div className={classes.label}>
|
||||
<AiOutlineHourglass />
|
||||
</div>
|
||||
<div className={classes.label}>{icon || <MdOutlineConstruction />}</div>
|
||||
<Title className={classes.title}>{title}</Title>
|
||||
<Text size="lg" ta="center" className={classes.description}>
|
||||
{description}
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
import { Divider, Grid, Group } from '@mantine/core';
|
||||
import { Divider } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { BracketDisplaySettings } from '../../interfaces/brackets';
|
||||
import { SchedulerSettings } from '../../interfaces/match';
|
||||
import { RoundInterface } from '../../interfaces/round';
|
||||
import { StageWithStageItems } from '../../interfaces/stage';
|
||||
import { StageItemWithRounds } from '../../interfaces/stage_item';
|
||||
import { Tournament } from '../../interfaces/tournament';
|
||||
import { AutoCreateMatchesButton } from '../buttons/create_matches_auto';
|
||||
import UpcomingMatchesTable from '../tables/upcoming_matches';
|
||||
import SwissSettings from './settings/ladder_fixed';
|
||||
|
||||
function SchedulingSystem({
|
||||
tournamentData,
|
||||
draftRound,
|
||||
swrRoundsResponse,
|
||||
swrStagesResponse,
|
||||
swrUpcomingMatchesResponse,
|
||||
displaySettings,
|
||||
}: {
|
||||
tournamentData: Tournament;
|
||||
draftRound: RoundInterface;
|
||||
swrRoundsResponse: SWRResponse;
|
||||
swrStagesResponse: SWRResponse;
|
||||
swrUpcomingMatchesResponse: SWRResponse;
|
||||
displaySettings: BracketDisplaySettings;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -31,9 +26,8 @@ function SchedulingSystem({
|
||||
<UpcomingMatchesTable
|
||||
round_id={draftRound.id}
|
||||
tournamentData={tournamentData}
|
||||
swrRoundsResponse={swrRoundsResponse}
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
displaySettings={displaySettings}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -41,50 +35,30 @@ function SchedulingSystem({
|
||||
|
||||
export default function Scheduler({
|
||||
activeStage,
|
||||
stageItem,
|
||||
tournamentData,
|
||||
draftRound,
|
||||
swrRoundsResponse,
|
||||
swrStagesResponse,
|
||||
swrUpcomingMatchesResponse,
|
||||
schedulerSettings,
|
||||
displaySettings,
|
||||
}: {
|
||||
activeStage: StageWithStageItems;
|
||||
stageItem: StageItemWithRounds;
|
||||
draftRound: RoundInterface;
|
||||
tournamentData: Tournament;
|
||||
swrRoundsResponse: SWRResponse;
|
||||
swrStagesResponse: SWRResponse;
|
||||
swrUpcomingMatchesResponse: SWRResponse;
|
||||
schedulerSettings: SchedulerSettings;
|
||||
displaySettings: BracketDisplaySettings;
|
||||
}) {
|
||||
return (
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<h2>
|
||||
Schedule new matches for <u>{draftRound.name}</u> in <u>{activeStage.name}</u>
|
||||
</h2>
|
||||
<Grid>
|
||||
<Grid.Col span="auto">
|
||||
<SwissSettings schedulerSettings={schedulerSettings} />
|
||||
</Grid.Col>
|
||||
<Grid.Col span="content">
|
||||
<Group justify="right">
|
||||
<AutoCreateMatchesButton
|
||||
swrStagesResponse={swrRoundsResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
tournamentData={tournamentData}
|
||||
stageItemId={stageItem.id}
|
||||
schedulerSettings={schedulerSettings}
|
||||
/>
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<SwissSettings schedulerSettings={schedulerSettings} />
|
||||
<SchedulingSystem
|
||||
draftRound={draftRound}
|
||||
tournamentData={tournamentData}
|
||||
swrRoundsResponse={swrRoundsResponse}
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
displaySettings={displaySettings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,29 +2,26 @@ import { Badge, Button, Table } from '@mantine/core';
|
||||
import { IconCalendarPlus, IconCheck } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import { FaCheck } from 'react-icons/fa6';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { BracketDisplaySettings } from '../../interfaces/brackets';
|
||||
import { MatchCreateBodyInterface, UpcomingMatchInterface } from '../../interfaces/match';
|
||||
import { Tournament } from '../../interfaces/tournament';
|
||||
import { createMatch } from '../../services/match';
|
||||
import PlayerList from '../info/player_list';
|
||||
import { EmptyTableInfo } from '../no_content/empty_table_info';
|
||||
import { NoContent } from '../no_content/empty_table_info';
|
||||
import RequestErrorAlert from '../utils/error_alert';
|
||||
import TableLayout, { ThNotSortable, ThSortable, getTableState, sortTableEntries } from './table';
|
||||
|
||||
export default function UpcomingMatchesTable({
|
||||
round_id,
|
||||
tournamentData,
|
||||
swrRoundsResponse,
|
||||
swrStagesResponse,
|
||||
swrUpcomingMatchesResponse,
|
||||
displaySettings,
|
||||
}: {
|
||||
round_id: number;
|
||||
tournamentData: Tournament;
|
||||
swrRoundsResponse: SWRResponse;
|
||||
swrStagesResponse: SWRResponse;
|
||||
swrUpcomingMatchesResponse: SWRResponse;
|
||||
displaySettings: BracketDisplaySettings;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const upcoming_matches: UpcomingMatchInterface[] =
|
||||
@@ -53,7 +50,7 @@ export default function UpcomingMatchesTable({
|
||||
|
||||
await createMatch(tournamentData.id, match_to_schedule);
|
||||
}
|
||||
await swrRoundsResponse.mutate();
|
||||
await swrStagesResponse.mutate();
|
||||
await swrUpcomingMatchesResponse.mutate();
|
||||
}
|
||||
|
||||
@@ -72,22 +69,8 @@ export default function UpcomingMatchesTable({
|
||||
</Badge>
|
||||
) : null}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{upcoming_match.stage_item_input1.team != null ? (
|
||||
<PlayerList
|
||||
team={upcoming_match.stage_item_input1.team}
|
||||
displaySettings={displaySettings}
|
||||
/>
|
||||
) : null}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{upcoming_match.stage_item_input2.team != null ? (
|
||||
<PlayerList
|
||||
team={upcoming_match.stage_item_input2.team}
|
||||
displaySettings={displaySettings}
|
||||
/>
|
||||
) : null}
|
||||
</Table.Td>
|
||||
<Table.Td>{upcoming_match.stage_item_input1.team?.name}</Table.Td>
|
||||
<Table.Td>{upcoming_match.stage_item_input2.team?.name}</Table.Td>
|
||||
<Table.Td>{Number(upcoming_match.elo_diff).toFixed(0)}</Table.Td>
|
||||
<Table.Td>{Number(upcoming_match.swiss_diff).toFixed(1)}</Table.Td>
|
||||
<Table.Td>
|
||||
@@ -105,7 +88,13 @@ export default function UpcomingMatchesTable({
|
||||
));
|
||||
|
||||
if (rows.length < 1) {
|
||||
return <EmptyTableInfo entity_name={t('upcoming_matches_empty_table_info')} />;
|
||||
return (
|
||||
<NoContent
|
||||
title={t('no_more_matches_title')}
|
||||
description={`${t('all_matches_scheduled_description')}`}
|
||||
icon={<FaCheck />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -36,10 +36,6 @@ export function getDefaultTimeRange(selectMultipleDates: boolean) {
|
||||
return [minDate, maxDate];
|
||||
}
|
||||
|
||||
export function onlyUnique(value: any, index: number, self: any) {
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
||||
export function getTournamentIdFromRouter() {
|
||||
const router = useRouter();
|
||||
const { id: idString }: any = router.query;
|
||||
|
||||
@@ -3,4 +3,6 @@ export interface BracketDisplaySettings {
|
||||
setMatchVisibility: React.Dispatch<any>;
|
||||
teamNamesDisplay: string;
|
||||
setTeamNamesDisplay: React.Dispatch<any>;
|
||||
showManualSchedulingOptions: string;
|
||||
setShowManualSchedulingOptions: React.Dispatch<any>;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,3 @@ export interface Tournament {
|
||||
export interface TournamentMinimal {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export function getTournamentEndpoint(tournament: Tournament) {
|
||||
return tournament.dashboard_endpoint != null ? tournament.dashboard_endpoint : `${tournament.id}`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Alert, Badge, Card, Center, Flex, Grid, Group, Stack, Text } from '@mantine/core';
|
||||
import { AiOutlineHourglass } from '@react-icons/all-files/ai/AiOutlineHourglass';
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
@@ -7,7 +8,7 @@ import React from 'react';
|
||||
|
||||
import { DashboardFooter } from '../../../../components/dashboard/footer';
|
||||
import { DoubleHeader, TournamentHeadTitle } from '../../../../components/dashboard/layout';
|
||||
import { NoContentDashboard } from '../../../../components/no_content/empty_table_info';
|
||||
import { NoContent } from '../../../../components/no_content/empty_table_info';
|
||||
import { Time, formatTime } from '../../../../components/utils/datetime';
|
||||
import { Translator } from '../../../../components/utils/types';
|
||||
import { responseIsValid } from '../../../../components/utils/util';
|
||||
@@ -162,7 +163,7 @@ export function Schedule({
|
||||
}
|
||||
|
||||
if (rows.length < 1) {
|
||||
return <NoContentDashboard title={t('no_matches_title')} description="" />;
|
||||
return <NoContent title={t('no_matches_title')} description="" icon={<AiOutlineHourglass />} />;
|
||||
}
|
||||
|
||||
const noItemsAlert =
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Container, Text } from '@mantine/core';
|
||||
import { AiOutlineHourglass } from '@react-icons/all-files/ai/AiOutlineHourglass';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import Head from 'next/head';
|
||||
@@ -8,7 +9,7 @@ import { SWRResponse } from 'swr';
|
||||
import NotFoundTitle from '../../../404';
|
||||
import { DashboardFooter } from '../../../../components/dashboard/footer';
|
||||
import { DoubleHeader, TournamentHeadTitle } from '../../../../components/dashboard/layout';
|
||||
import { NoContentDashboard } from '../../../../components/no_content/empty_table_info';
|
||||
import { NoContent } from '../../../../components/no_content/empty_table_info';
|
||||
import { StandingsTableForStageItem } from '../../../../components/tables/standings';
|
||||
import { TableSkeletonTwoColumns } from '../../../../components/utils/skeletons';
|
||||
import { responseIsValid } from '../../../../components/utils/util';
|
||||
@@ -43,9 +44,10 @@ function StandingsContent({ swrStagesResponse }: { swrStagesResponse: SWRRespons
|
||||
|
||||
if (rows.length < 1) {
|
||||
return (
|
||||
<NoContentDashboard
|
||||
<NoContent
|
||||
title={`${t('could_not_find_any_alert')} ${t('teams_title')}`}
|
||||
description=""
|
||||
icon={<AiOutlineHourglass />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@ import {
|
||||
Title,
|
||||
UnstyledButton,
|
||||
} from '@mantine/core';
|
||||
import { AiOutlineHourglass } from '@react-icons/all-files/ai/AiOutlineHourglass';
|
||||
import { IconAlertCircle } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import MatchModal from '../../../components/modals/match_modal';
|
||||
import { NoContentDashboard } from '../../../components/no_content/empty_table_info';
|
||||
import { NoContent } from '../../../components/no_content/empty_table_info';
|
||||
import { Time, formatTime } from '../../../components/utils/datetime';
|
||||
import { Translator } from '../../../components/utils/types';
|
||||
import { getTournamentIdFromRouter, responseIsValid } from '../../../components/utils/util';
|
||||
@@ -188,7 +189,11 @@ function Schedule({
|
||||
|
||||
if (rows.length < 1) {
|
||||
return (
|
||||
<NoContentDashboard title={t('no_matches_title')} description={t('no_matches_description')} />
|
||||
<NoContent
|
||||
title={t('no_matches_title')}
|
||||
description={t('no_matches_description')}
|
||||
icon={<AiOutlineHourglass />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { DateTimePicker } from '@mantine/dates';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { MdDelete } from '@react-icons/all-files/md/MdDelete';
|
||||
import { IconCalendar, IconCalendarTime } from '@tabler/icons-react';
|
||||
import { IconCalendar, IconCalendarTime, IconCopy } from '@tabler/icons-react';
|
||||
import assert from 'assert';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
@@ -28,7 +28,7 @@ import { DropzoneButton } from '../../../components/utils/file_upload';
|
||||
import { GenericSkeleton } from '../../../components/utils/skeletons';
|
||||
import { capitalize, getBaseURL, getTournamentIdFromRouter } from '../../../components/utils/util';
|
||||
import { Club } from '../../../interfaces/club';
|
||||
import { Tournament, getTournamentEndpoint } from '../../../interfaces/tournament';
|
||||
import { Tournament } from '../../../interfaces/tournament';
|
||||
import {
|
||||
getBaseApiUrl,
|
||||
getClubs,
|
||||
@@ -162,11 +162,32 @@ function GeneralTournamentForm({
|
||||
</Grid>
|
||||
</Fieldset>
|
||||
<Fieldset legend={t('dashboard_settings_title')} mt="lg" radius="md">
|
||||
<TextInput
|
||||
label={t('dashboard_link_label')}
|
||||
placeholder={t('dashboard_link_placeholder')}
|
||||
{...form.getInputProps('dashboard_endpoint')}
|
||||
/>
|
||||
<Text fz="sm">{t('dashboard_link_label')}</Text>
|
||||
<Grid>
|
||||
<Grid.Col span={{ sm: 9 }}>
|
||||
<TextInput
|
||||
placeholder={t('dashboard_link_placeholder')}
|
||||
{...form.getInputProps('dashboard_endpoint')}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ sm: 3 }}>
|
||||
<CopyButton
|
||||
value={`${getBaseURL()}/tournaments/${tournament.dashboard_endpoint}/dashboard`}
|
||||
>
|
||||
{({ copied, copy }) => (
|
||||
<Button
|
||||
disabled={form.values.dashboard_endpoint === ''}
|
||||
leftSection={<IconCopy size="1.1rem" stroke={1.5} />}
|
||||
fullWidth
|
||||
color={copied ? 'teal' : 'indigo'}
|
||||
onClick={copy}
|
||||
>
|
||||
{copied ? t('copied_url_button') : t('copy_url_button')}
|
||||
</Button>
|
||||
)}
|
||||
</CopyButton>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
<Checkbox
|
||||
mt="lg"
|
||||
@@ -212,17 +233,6 @@ function GeneralTournamentForm({
|
||||
{t('save_button')}
|
||||
</Button>
|
||||
|
||||
{tournament != null ? (
|
||||
<CopyButton
|
||||
value={`${getBaseURL()}/tournaments/${getTournamentEndpoint(tournament)}/dashboard`}
|
||||
>
|
||||
{({ copied, copy }) => (
|
||||
<Button fullWidth mt="sm" color={copied ? 'teal' : 'blue'} onClick={copy}>
|
||||
{copied ? t('copied_dashboard_url_button') : t('copy_dashboard_url_button')}
|
||||
</Button>
|
||||
)}
|
||||
</CopyButton>
|
||||
) : null}
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outline"
|
||||
|
||||
@@ -3,22 +3,22 @@ import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import React from 'react';
|
||||
|
||||
import Builder from '../../../components/builder/builder';
|
||||
import { CreateStageButtonLarge } from '../../../components/buttons/create_stage';
|
||||
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';
|
||||
import { Ranking } from '../../../interfaces/ranking';
|
||||
import { StageWithStageItems } from '../../../interfaces/stage';
|
||||
import Builder from '../../../../components/builder/builder';
|
||||
import { CreateStageButtonLarge } from '../../../../components/buttons/create_stage';
|
||||
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';
|
||||
import { Ranking } from '../../../../interfaces/ranking';
|
||||
import { StageWithStageItems } from '../../../../interfaces/stage';
|
||||
import {
|
||||
getAvailableStageItemInputs,
|
||||
getRankings,
|
||||
getStages,
|
||||
getTournamentById,
|
||||
} from '../../../services/adapter';
|
||||
import TournamentLayout from '../_tournament_layout';
|
||||
} from '../../../../services/adapter';
|
||||
import TournamentLayout from '../../_tournament_layout';
|
||||
|
||||
export default function StagesPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -1,34 +1,39 @@
|
||||
import { Button, Grid, Group, SegmentedControl, Title } from '@mantine/core';
|
||||
import { Button, Container, Grid, Group, SegmentedControl, Stack, Title } from '@mantine/core';
|
||||
import { IconExternalLink } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { LuNavigation } from 'react-icons/lu';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import NotFoundTitle from '../../../404';
|
||||
import Brackets from '../../../../components/brackets/brackets';
|
||||
import Scheduler from '../../../../components/scheduling/scheduling';
|
||||
import classes from '../../../../components/utility.module.css';
|
||||
import { useRouterQueryState } from '../../../../components/utils/query_parameters';
|
||||
import NotFoundTitle from '../../../../404';
|
||||
import { RoundsGridCols } from '../../../../../components/brackets/brackets';
|
||||
import { NoContent } from '../../../../../components/no_content/empty_table_info';
|
||||
import Scheduler from '../../../../../components/scheduling/scheduling';
|
||||
import classes from '../../../../../components/utility.module.css';
|
||||
import { useRouterQueryState } from '../../../../../components/utils/query_parameters';
|
||||
import { Translator } from '../../../../../components/utils/types';
|
||||
import {
|
||||
getStageItemIdFromRouter,
|
||||
getTournamentIdFromRouter,
|
||||
responseIsValid,
|
||||
} from '../../../../components/utils/util';
|
||||
import { BracketDisplaySettings } from '../../../../interfaces/brackets';
|
||||
import { SchedulerSettings } from '../../../../interfaces/match';
|
||||
import { RoundInterface } from '../../../../interfaces/round';
|
||||
import { getStageById } from '../../../../interfaces/stage';
|
||||
import { stageItemIsHandledAutomatically } from '../../../../interfaces/stage_item';
|
||||
import { getTournamentEndpoint } from '../../../../interfaces/tournament';
|
||||
} from '../../../../../components/utils/util';
|
||||
import { BracketDisplaySettings } from '../../../../../interfaces/brackets';
|
||||
import { SchedulerSettings } from '../../../../../interfaces/match';
|
||||
import { RoundInterface } from '../../../../../interfaces/round';
|
||||
import { getStageById } from '../../../../../interfaces/stage';
|
||||
import { stageItemIsHandledAutomatically } from '../../../../../interfaces/stage_item';
|
||||
import { Tournament } from '../../../../../interfaces/tournament';
|
||||
import {
|
||||
checkForAuthError,
|
||||
getCourts,
|
||||
getStages,
|
||||
getTournamentById,
|
||||
getUpcomingMatches,
|
||||
} from '../../../../services/adapter';
|
||||
import { getStageItemLookup } from '../../../../services/lookups';
|
||||
import TournamentLayout from '../../_tournament_layout';
|
||||
} from '../../../../../services/adapter';
|
||||
import { getStageItemLookup } from '../../../../../services/lookups';
|
||||
import TournamentLayout from '../../../_tournament_layout';
|
||||
|
||||
export const getServerSideProps = async ({ locale }: { locale: string }) => ({
|
||||
props: {
|
||||
@@ -36,6 +41,25 @@ export const getServerSideProps = async ({ locale }: { locale: string }) => ({
|
||||
},
|
||||
});
|
||||
|
||||
function NoCourtsButton({ t, tournamentData }: { t: Translator; tournamentData: Tournament }) {
|
||||
return (
|
||||
<Stack align="center">
|
||||
<NoContent title={t('no_courts_title')} description={t('no_courts_description_swiss')} />
|
||||
<Button
|
||||
color="green"
|
||||
size="lg"
|
||||
leftSection={<LuNavigation size={24} />}
|
||||
variant="outline"
|
||||
component={Link}
|
||||
className={classes.mobileLink}
|
||||
href={`/tournaments/${tournamentData.id}/schedule`}
|
||||
>
|
||||
{t('go_to_courts_page')}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TournamentPage() {
|
||||
const { id, tournamentData } = getTournamentIdFromRouter();
|
||||
const stageItemId = getStageItemIdFromRouter();
|
||||
@@ -44,17 +68,24 @@ export default function TournamentPage() {
|
||||
const swrTournamentResponse = getTournamentById(tournamentData.id);
|
||||
checkForAuthError(swrTournamentResponse);
|
||||
const swrStagesResponse: SWRResponse = getStages(id);
|
||||
const swrCourtsResponse = getCourts(tournamentData.id);
|
||||
const [onlyRecommended, setOnlyRecommended] = useRouterQueryState('only-recommended', 'true');
|
||||
const [eloThreshold, setEloThreshold] = useRouterQueryState('max-elo-diff', 100);
|
||||
const [iterations, setIterations] = useRouterQueryState('iterations', 1000);
|
||||
const [limit, setLimit] = useRouterQueryState('limit', 50);
|
||||
const [matchVisibility, setMatchVisibility] = useRouterQueryState('match-visibility', 'all');
|
||||
const [teamNamesDisplay, setTeamNamesDisplay] = useRouterQueryState('which-names', 'team-names');
|
||||
const [showAdvancedSchedulingOptions, setShowAdvancedSchedulingOptions] = useRouterQueryState(
|
||||
'advanced',
|
||||
'false'
|
||||
);
|
||||
const displaySettings: BracketDisplaySettings = {
|
||||
matchVisibility,
|
||||
setMatchVisibility,
|
||||
teamNamesDisplay,
|
||||
setTeamNamesDisplay,
|
||||
showManualSchedulingOptions: showAdvancedSchedulingOptions,
|
||||
setShowManualSchedulingOptions: setShowAdvancedSchedulingOptions,
|
||||
};
|
||||
|
||||
const schedulerSettings: SchedulerSettings = {
|
||||
@@ -81,7 +112,7 @@ export default function TournamentPage() {
|
||||
|
||||
if (activeStage != null && activeStage.stage_items != null) {
|
||||
const draftRounds = stageItem.rounds.filter(
|
||||
(round: RoundInterface) => round.stage_item_id === stageItemId
|
||||
(round: RoundInterface) => round.stage_item_id === stageItemId && round.is_draft
|
||||
);
|
||||
|
||||
if (draftRounds != null && draftRounds.length > 0) {
|
||||
@@ -96,17 +127,16 @@ export default function TournamentPage() {
|
||||
stageItem != null &&
|
||||
!stageItemIsHandledAutomatically(stageItem) &&
|
||||
activeStage != null &&
|
||||
displaySettings.showManualSchedulingOptions === 'true' &&
|
||||
swrUpcomingMatchesResponse != null ? (
|
||||
<>
|
||||
<Scheduler
|
||||
activeStage={activeStage}
|
||||
stageItem={stageItem}
|
||||
draftRound={draftRound}
|
||||
tournamentData={tournamentDataFull}
|
||||
swrRoundsResponse={swrStagesResponse}
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
schedulerSettings={schedulerSettings}
|
||||
displaySettings={displaySettings}
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
@@ -115,6 +145,18 @@ export default function TournamentPage() {
|
||||
return <NotFoundTitle />;
|
||||
}
|
||||
|
||||
if (!swrCourtsResponse.isLoading && swrCourtsResponse.data.data.length < 1) {
|
||||
return (
|
||||
<TournamentLayout tournament_id={tournamentData.id}>
|
||||
<Container mt="1rem">
|
||||
<Stack align="center">
|
||||
<NoCourtsButton t={t} tournamentData={tournamentDataFull} />
|
||||
</Stack>
|
||||
</Container>
|
||||
</TournamentLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TournamentLayout tournament_id={tournamentData.id}>
|
||||
<Grid grow>
|
||||
@@ -133,39 +175,37 @@ export default function TournamentPage() {
|
||||
{ label: t('match_filter_option_current'), value: 'present-only' },
|
||||
]}
|
||||
/>
|
||||
<SegmentedControl
|
||||
className={classes.fullWithMobile}
|
||||
value={teamNamesDisplay}
|
||||
onChange={setTeamNamesDisplay}
|
||||
data={[
|
||||
{ label: t('name_filter_options_team'), value: 'team-names' },
|
||||
{ label: t('name_filter_options_player'), value: 'player-names' },
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
className={classes.fullWithMobile}
|
||||
color="blue"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftSection={<IconExternalLink size={24} />}
|
||||
onClick={() => {
|
||||
const endpoint = getTournamentEndpoint(tournamentDataFull);
|
||||
window.open(`/tournaments/${endpoint}/dashboard`, '_ blank');
|
||||
}}
|
||||
>
|
||||
{t('view_dashboard_button')}
|
||||
</Button>
|
||||
{tournamentDataFull?.dashboard_endpoint && (
|
||||
<Button
|
||||
className={classes.fullWithMobile}
|
||||
color="blue"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftSection={<IconExternalLink size={24} />}
|
||||
onClick={() => {
|
||||
window.open(
|
||||
`/tournaments/${tournamentDataFull.dashboard_endpoint}/dashboard`,
|
||||
'_ blank'
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t('view_dashboard_button')}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<div style={{ marginTop: '1rem', marginLeft: '1rem', marginRight: '1rem' }}>
|
||||
<Brackets
|
||||
<RoundsGridCols
|
||||
tournamentData={tournamentDataFull}
|
||||
swrStagesResponse={swrStagesResponse}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
swrCourtsResponse={swrCourtsResponse}
|
||||
readOnly={false}
|
||||
stageItem={stageItem}
|
||||
displaySettings={displaySettings}
|
||||
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
|
||||
schedulerSettings={schedulerSettings}
|
||||
draftRound={draftRound}
|
||||
/>
|
||||
{scheduler}
|
||||
</div>
|
||||
Reference in New Issue
Block a user