mirror of
https://github.com/evroon/bracket.git
synced 2026-06-11 10:15:19 -04:00
Multi users and teams creation (#342)
fixes https://github.com/evroon/bracket/issues/292
This commit is contained in:
@@ -23,7 +23,12 @@ class Player(BaseModelORM):
|
||||
|
||||
|
||||
class PlayerBody(BaseModelORM):
|
||||
name: str = Field(..., max_length=30)
|
||||
name: str = Field(..., min_length=1, max_length=30)
|
||||
active: bool
|
||||
|
||||
|
||||
class PlayerMultiBody(BaseModelORM):
|
||||
names: str
|
||||
active: bool
|
||||
|
||||
|
||||
|
||||
@@ -70,9 +70,14 @@ class FullTeamWithPlayers(TeamWithPlayers, Team):
|
||||
|
||||
|
||||
class TeamBody(BaseModelORM):
|
||||
name: str = Field(..., max_length=30)
|
||||
name: str = Field(..., min_length=1, max_length=30)
|
||||
active: bool
|
||||
player_ids: list[int] = Field(..., unique_items=True)
|
||||
|
||||
|
||||
class TeamMultiBody(BaseModelORM):
|
||||
names: str = Field(..., min_length=1, max_length=30)
|
||||
active: bool
|
||||
player_ids: list[int]
|
||||
|
||||
|
||||
class TeamToInsert(BaseModelORM):
|
||||
|
||||
@@ -4,7 +4,8 @@ from fastapi import APIRouter, Depends
|
||||
from heliclockter import datetime_utc
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.models.db.player import Player, PlayerBody, PlayerToInsert
|
||||
from bracket.models.db.player import Player, PlayerBody, PlayerMultiBody, PlayerToInsert
|
||||
from bracket.models.db.players import START_ELO
|
||||
from bracket.models.db.user import UserPublic
|
||||
from bracket.routes.auth import user_authenticated_for_tournament
|
||||
from bracket.routes.models import PlayersResponse, SinglePlayerResponse, SuccessResponse
|
||||
@@ -66,30 +67,37 @@ async def delete_player(
|
||||
return SuccessResponse()
|
||||
|
||||
|
||||
@router.post("/tournaments/{tournament_id}/players", response_model=SinglePlayerResponse)
|
||||
async def create_player(
|
||||
@router.post("/tournaments/{tournament_id}/players", response_model=SuccessResponse)
|
||||
async def create_single_player(
|
||||
player_body: PlayerBody,
|
||||
tournament_id: int,
|
||||
_: UserPublic = Depends(user_authenticated_for_tournament),
|
||||
) -> SinglePlayerResponse:
|
||||
last_record_id = await database.execute(
|
||||
) -> SuccessResponse:
|
||||
await insert_player(player_body, tournament_id)
|
||||
return SuccessResponse()
|
||||
|
||||
|
||||
async def insert_player(player_body: PlayerBody, tournament_id: int) -> None:
|
||||
await database.execute(
|
||||
query=players.insert(),
|
||||
values=PlayerToInsert(
|
||||
**player_body.dict(),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
elo_score=Decimal('0.0'),
|
||||
elo_score=Decimal(START_ELO),
|
||||
swiss_score=Decimal('0.0'),
|
||||
).dict(),
|
||||
)
|
||||
return SinglePlayerResponse(
|
||||
data=assert_some(
|
||||
await fetch_one_parsed(
|
||||
database,
|
||||
Player,
|
||||
players.select().where(
|
||||
players.c.id == last_record_id and players.c.tournament_id == tournament_id
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/tournaments/{tournament_id}/players_multi", response_model=SuccessResponse)
|
||||
async def create_multiple_players(
|
||||
player_body: PlayerMultiBody,
|
||||
tournament_id: int,
|
||||
_: UserPublic = Depends(user_authenticated_for_tournament),
|
||||
) -> SuccessResponse:
|
||||
player_names = [player.strip() for player in player_body.names.split('\n') if len(player) > 0]
|
||||
for player_name in player_names:
|
||||
await insert_player(PlayerBody(name=player_name, active=player_body.active), tournament_id)
|
||||
|
||||
return SuccessResponse()
|
||||
|
||||
@@ -4,7 +4,7 @@ from starlette import status
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.logic.ranking.elo import recalculate_ranking_for_tournament_id
|
||||
from bracket.models.db.team import FullTeamWithPlayers, Team, TeamBody, TeamToInsert
|
||||
from bracket.models.db.team import FullTeamWithPlayers, Team, TeamBody, TeamMultiBody, TeamToInsert
|
||||
from bracket.models.db.user import UserPublic
|
||||
from bracket.routes.auth import (
|
||||
user_authenticated_for_tournament,
|
||||
@@ -133,3 +133,24 @@ async def create_team(
|
||||
team_result = await get_team_by_id(last_record_id, tournament_id)
|
||||
assert team_result is not None
|
||||
return SingleTeamResponse(data=team_result)
|
||||
|
||||
|
||||
@router.post("/tournaments/{tournament_id}/teams_multi", response_model=SuccessResponse)
|
||||
async def create_multiple_teams(
|
||||
team_body: TeamMultiBody,
|
||||
tournament_id: int,
|
||||
_: UserPublic = Depends(user_authenticated_for_tournament),
|
||||
) -> SuccessResponse:
|
||||
team_names = [team.strip() for team in team_body.names.split('\n') if len(team) > 0]
|
||||
for team_name in team_names:
|
||||
await database.execute(
|
||||
query=teams.insert(),
|
||||
values=TeamToInsert(
|
||||
name=team_name,
|
||||
active=team_body.active,
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
).dict(),
|
||||
)
|
||||
|
||||
return SuccessResponse()
|
||||
|
||||
@@ -41,10 +41,21 @@ async def test_create_player(
|
||||
) -> None:
|
||||
body = {'name': 'Some new name', 'active': True}
|
||||
response = await send_tournament_request(HTTPMethod.POST, 'players', auth_context, json=body)
|
||||
assert response['data']['name'] == body['name']
|
||||
assert response['success'] is True
|
||||
await assert_row_count_and_clear(players, 1)
|
||||
|
||||
|
||||
async def test_create_players(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
body = {'names': 'Player x\nPlayer y', 'active': True}
|
||||
response = await send_tournament_request(
|
||||
HTTPMethod.POST, 'players_multi', auth_context, json=body
|
||||
)
|
||||
assert response['success'] is True
|
||||
await assert_row_count_and_clear(players, 2)
|
||||
|
||||
|
||||
async def test_delete_player(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
|
||||
@@ -43,6 +43,17 @@ async def test_create_team(
|
||||
await assert_row_count_and_clear(teams, 1)
|
||||
|
||||
|
||||
async def test_create_teams(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
body = {'names': 'Team -1\nTeam -2', 'active': True}
|
||||
response = await send_tournament_request(
|
||||
HTTPMethod.POST, 'teams_multi', auth_context, None, body
|
||||
)
|
||||
assert response['success'] is True
|
||||
await assert_row_count_and_clear(teams, 2)
|
||||
|
||||
|
||||
async def test_delete_team(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
|
||||
@@ -2,12 +2,7 @@ import { Button } from '@mantine/core';
|
||||
|
||||
export default function SaveButton(props: any) {
|
||||
return (
|
||||
<Button
|
||||
color="green"
|
||||
size="md"
|
||||
style={{ marginBottom: 10, marginRight: 10, marginLeft: 10 }}
|
||||
{...props}
|
||||
>
|
||||
<Button color="green" size="md" mb="10px" mx="10px" {...props}>
|
||||
{props.title}
|
||||
</Button>
|
||||
);
|
||||
|
||||
25
frontend/src/components/forms/player_create_csv_input.tsx
Normal file
25
frontend/src/components/forms/player_create_csv_input.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Textarea } from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import React from 'react';
|
||||
|
||||
export function MultiPlayersInput({ form }: { form: UseFormReturnType<any> }) {
|
||||
return (
|
||||
<Textarea
|
||||
label="Add multiple players. Put every player on a separate line"
|
||||
placeholder="Player 1"
|
||||
minRows={10}
|
||||
{...form.getInputProps('names')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MultiTeamsInput({ form }: { form: UseFormReturnType<any> }) {
|
||||
return (
|
||||
<Textarea
|
||||
label="Add multiple teams. Put every team on a separate line"
|
||||
placeholder="Team 1"
|
||||
minRows={10}
|
||||
{...form.getInputProps('names')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
148
frontend/src/components/modals/player_create_modal.tsx
Normal file
148
frontend/src/components/modals/player_create_modal.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { Button, Checkbox, Group, Modal, Tabs, TextInput } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { IconUser, IconUserPlus, IconUsers } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { createMultiplePlayers, createPlayer } from '../../services/player';
|
||||
import SaveButton from '../buttons/save';
|
||||
import { MultiPlayersInput } from '../forms/player_create_csv_input';
|
||||
|
||||
function MultiPlayerTab({
|
||||
tournament_id,
|
||||
swrPlayersResponse,
|
||||
setOpened,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
swrPlayersResponse: SWRResponse;
|
||||
setOpened: any;
|
||||
}) {
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
names: '',
|
||||
active: true,
|
||||
},
|
||||
|
||||
validate: {
|
||||
names: (value) => (value.length > 0 ? null : 'Enter at least one player'),
|
||||
},
|
||||
});
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
await createMultiplePlayers(tournament_id, values.names, values.active);
|
||||
await swrPlayersResponse.mutate(null);
|
||||
setOpened(false);
|
||||
})}
|
||||
>
|
||||
<MultiPlayersInput form={form} />
|
||||
|
||||
<Checkbox
|
||||
mt="md"
|
||||
label="These players are active"
|
||||
{...form.getInputProps('active', { type: 'checkbox' })}
|
||||
/>
|
||||
<Button fullWidth style={{ marginTop: 10 }} color="green" type="submit">
|
||||
Save players
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function SinglePlayerTab({
|
||||
tournament_id,
|
||||
swrPlayersResponse,
|
||||
setOpened,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
swrPlayersResponse: SWRResponse;
|
||||
setOpened: any;
|
||||
}) {
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
name: '',
|
||||
active: true,
|
||||
player_ids: [],
|
||||
},
|
||||
validate: {
|
||||
name: (value) => (value.length > 0 ? null : 'Name too short'),
|
||||
},
|
||||
});
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
await createPlayer(tournament_id, values.name, values.active);
|
||||
await swrPlayersResponse.mutate(null);
|
||||
setOpened(false);
|
||||
})}
|
||||
>
|
||||
<TextInput
|
||||
withAsterisk
|
||||
label="Name"
|
||||
placeholder="Best Player Ever"
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
mt="md"
|
||||
label="This player is active"
|
||||
{...form.getInputProps('active', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<Button fullWidth style={{ marginTop: 10 }} color="green" type="submit">
|
||||
Save player
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PlayerCreateModal({
|
||||
tournament_id,
|
||||
swrPlayersResponse,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
swrPlayersResponse: SWRResponse;
|
||||
}) {
|
||||
const [opened, setOpened] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Modal opened={opened} onClose={() => setOpened(false)} title="Create Player">
|
||||
<Tabs defaultValue="single">
|
||||
<Tabs.List position="center" grow>
|
||||
<Tabs.Tab value="single" icon={<IconUser size="0.8rem" />}>
|
||||
Single player
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="multi" icon={<IconUsers size="0.8rem" />}>
|
||||
Multiple players
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="single" pt="xs">
|
||||
<SinglePlayerTab
|
||||
swrPlayersResponse={swrPlayersResponse}
|
||||
tournament_id={tournament_id}
|
||||
setOpened={setOpened}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="multi" pt="xs">
|
||||
<MultiPlayerTab
|
||||
swrPlayersResponse={swrPlayersResponse}
|
||||
tournament_id={tournament_id}
|
||||
setOpened={setOpened}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
|
||||
<Group position="right">
|
||||
<SaveButton
|
||||
onClick={() => setOpened(true)}
|
||||
leftIcon={<IconUserPlus size={24} />}
|
||||
title="Add Player"
|
||||
mt="1.5rem"
|
||||
/>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +1,31 @@
|
||||
import { Button, Checkbox, Group, Modal, TextInput } from '@mantine/core';
|
||||
import { Button, Checkbox, Modal, TextInput } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { BiEditAlt } from '@react-icons/all-files/bi/BiEditAlt';
|
||||
import { GoPlus } from '@react-icons/all-files/go/GoPlus';
|
||||
import { useState } from 'react';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { Player } from '../../interfaces/player';
|
||||
import { createPlayer, updatePlayer } from '../../services/player';
|
||||
import SaveButton from '../buttons/save';
|
||||
import { updatePlayer } from '../../services/player';
|
||||
|
||||
export default function PlayerModal({
|
||||
export default function PlayerUpdateModal({
|
||||
tournament_id,
|
||||
player,
|
||||
swrPlayersResponse,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
player: Player | null;
|
||||
player: Player;
|
||||
swrPlayersResponse: SWRResponse;
|
||||
}) {
|
||||
const is_create_form = player == null;
|
||||
const operation_text = is_create_form ? 'Create Player' : 'Edit Player';
|
||||
const icon = is_create_form ? <GoPlus size={20} /> : <BiEditAlt size={20} />;
|
||||
const [opened, setOpened] = useState(false);
|
||||
const modalOpenButton = is_create_form ? (
|
||||
<Group position="right">
|
||||
<SaveButton
|
||||
onClick={() => setOpened(true)}
|
||||
leftIcon={<GoPlus size={24} />}
|
||||
title={operation_text}
|
||||
/>
|
||||
</Group>
|
||||
) : (
|
||||
const modalOpenButton = (
|
||||
<Button
|
||||
color="green"
|
||||
size="xs"
|
||||
style={{ marginRight: 10 }}
|
||||
onClick={() => setOpened(true)}
|
||||
leftIcon={icon}
|
||||
leftIcon={<BiEditAlt size={20} />}
|
||||
>
|
||||
{operation_text}
|
||||
Edit Player
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -47,7 +34,6 @@ export default function PlayerModal({
|
||||
name: player == null ? '' : player.name,
|
||||
active: player == null ? true : player.active,
|
||||
},
|
||||
|
||||
validate: {
|
||||
name: (value) => (value.length > 0 ? null : 'Name too short'),
|
||||
},
|
||||
@@ -55,13 +41,12 @@ export default function PlayerModal({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal opened={opened} onClose={() => setOpened(false)} title={operation_text}>
|
||||
<Modal opened={opened} onClose={() => setOpened(false)} title="Edit Player">
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
if (is_create_form) await createPlayer(tournament_id, values.name, values.active, null);
|
||||
else await updatePlayer(tournament_id, player.id, values.name, values.active, null);
|
||||
await updatePlayer(tournament_id, player.id, values.name, values.active, null);
|
||||
await swrPlayersResponse.mutate(null);
|
||||
// setOpened(false);
|
||||
setOpened(false);
|
||||
})}
|
||||
>
|
||||
<TextInput
|
||||
@@ -84,7 +84,7 @@ export function Spotlight() {
|
||||
actions={allActions}
|
||||
searchIcon={<IconSearch size="1.2rem" />}
|
||||
searchPlaceholder="Search..."
|
||||
shortcut="mod + k"
|
||||
shortcut={['mod + k', 'mod + y', '/']}
|
||||
nothingFoundMessage="Nothing found..."
|
||||
/>
|
||||
);
|
||||
|
||||
164
frontend/src/components/modals/team_create_modal.tsx
Normal file
164
frontend/src/components/modals/team_create_modal.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import { Button, Checkbox, Group, Modal, MultiSelect, Tabs, TextInput } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { IconUser, IconUsers, IconUsersPlus } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { Player } from '../../interfaces/player';
|
||||
import { getPlayers } from '../../services/adapter';
|
||||
import { createTeam, createTeams } from '../../services/team';
|
||||
import SaveButton from '../buttons/save';
|
||||
import { MultiTeamsInput } from '../forms/player_create_csv_input';
|
||||
|
||||
function MultiTeamTab({
|
||||
tournament_id,
|
||||
swrTeamsResponse,
|
||||
setOpened,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
swrTeamsResponse: SWRResponse;
|
||||
setOpened: any;
|
||||
}) {
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
names: '',
|
||||
active: true,
|
||||
},
|
||||
|
||||
validate: {
|
||||
names: (value) => (value.length > 0 ? null : 'Enter at least one team'),
|
||||
},
|
||||
});
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
await createTeams(tournament_id, values.names, values.active);
|
||||
await swrTeamsResponse.mutate(null);
|
||||
setOpened(false);
|
||||
})}
|
||||
>
|
||||
<MultiTeamsInput form={form} />
|
||||
|
||||
<Checkbox
|
||||
mt="md"
|
||||
label="These teams are active"
|
||||
{...form.getInputProps('active', { type: 'checkbox' })}
|
||||
/>
|
||||
<Button fullWidth style={{ marginTop: 10 }} color="green" type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function SingleTeamTab({
|
||||
tournament_id,
|
||||
swrTeamsResponse,
|
||||
setOpened,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
swrTeamsResponse: SWRResponse;
|
||||
setOpened: any;
|
||||
}) {
|
||||
const { data } = getPlayers(tournament_id, false);
|
||||
const players: Player[] = data != null ? data.data : [];
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
name: '',
|
||||
active: true,
|
||||
player_ids: [],
|
||||
},
|
||||
validate: {
|
||||
name: (value) => (value.length > 0 ? null : 'Name too short'),
|
||||
},
|
||||
});
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
await createTeam(tournament_id, values.name, values.active, values.player_ids);
|
||||
await swrTeamsResponse.mutate(null);
|
||||
setOpened(false);
|
||||
})}
|
||||
>
|
||||
<TextInput
|
||||
withAsterisk
|
||||
label="Name"
|
||||
placeholder="Best Team Ever"
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
mt="md"
|
||||
label="This team is active"
|
||||
{...form.getInputProps('active', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
data={players.map((p) => ({ value: `${p.id}`, label: p.name }))}
|
||||
label="Team members"
|
||||
placeholder="Pick all that you like"
|
||||
dropdownPosition="bottom"
|
||||
maxDropdownHeight={160}
|
||||
searchable
|
||||
mb="12rem"
|
||||
mt={12}
|
||||
limit={25}
|
||||
{...form.getInputProps('player_ids')}
|
||||
/>
|
||||
<Button fullWidth style={{ marginTop: 10 }} color="green" type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TeamCreateModal({
|
||||
tournament_id,
|
||||
swrTeamsResponse,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
swrTeamsResponse: SWRResponse;
|
||||
}) {
|
||||
const [opened, setOpened] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Modal opened={opened} onClose={() => setOpened(false)} title="Create Team">
|
||||
<Tabs defaultValue="single">
|
||||
<Tabs.List position="center" grow>
|
||||
<Tabs.Tab value="single" icon={<IconUser size="0.8rem" />}>
|
||||
Single team
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="multi" icon={<IconUsers size="0.8rem" />}>
|
||||
Multiple teams
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="single" pt="xs">
|
||||
<SingleTeamTab
|
||||
swrTeamsResponse={swrTeamsResponse}
|
||||
tournament_id={tournament_id}
|
||||
setOpened={setOpened}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="multi" pt="xs">
|
||||
<MultiTeamTab
|
||||
swrTeamsResponse={swrTeamsResponse}
|
||||
tournament_id={tournament_id}
|
||||
setOpened={setOpened}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
|
||||
<Group position="right">
|
||||
<SaveButton
|
||||
onClick={() => setOpened(true)}
|
||||
leftIcon={<IconUsersPlus size={24} />}
|
||||
title="Add Team"
|
||||
mt="1.5rem"
|
||||
/>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,58 +1,32 @@
|
||||
import { Button, Checkbox, Group, Modal, MultiSelect, TextInput } from '@mantine/core';
|
||||
import { Button, Checkbox, Modal, MultiSelect, TextInput } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { BiEditAlt } from '@react-icons/all-files/bi/BiEditAlt';
|
||||
import { GoPlus } from '@react-icons/all-files/go/GoPlus';
|
||||
import { useState } from 'react';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import { Player } from '../../interfaces/player';
|
||||
import { TeamInterface } from '../../interfaces/team';
|
||||
import { getPlayers } from '../../services/adapter';
|
||||
import { createTeam, updateTeam } from '../../services/team';
|
||||
import SaveButton from '../buttons/save';
|
||||
import { updateTeam } from '../../services/team';
|
||||
|
||||
export default function TeamModal({
|
||||
export default function TeamUpdateModal({
|
||||
tournament_id,
|
||||
team,
|
||||
swrTeamsResponse,
|
||||
}: {
|
||||
tournament_id: number;
|
||||
team: TeamInterface | null;
|
||||
team: TeamInterface;
|
||||
swrTeamsResponse: SWRResponse;
|
||||
}) {
|
||||
const { data } = getPlayers(tournament_id, false);
|
||||
const players: Player[] = data != null ? data.data : [];
|
||||
|
||||
const is_create_form = team == null;
|
||||
const operation_text = is_create_form ? 'Create Team' : 'Edit Team';
|
||||
const icon = is_create_form ? <GoPlus size={20} /> : <BiEditAlt size={20} />;
|
||||
const [opened, setOpened] = useState(false);
|
||||
const modalOpenButton = is_create_form ? (
|
||||
<Group position="right">
|
||||
<SaveButton
|
||||
onClick={() => setOpened(true)}
|
||||
leftIcon={<GoPlus size={24} />}
|
||||
title={operation_text}
|
||||
style={{ marginTop: '1rem' }}
|
||||
/>
|
||||
</Group>
|
||||
) : (
|
||||
<Button
|
||||
color="green"
|
||||
size="xs"
|
||||
style={{ marginRight: 10 }}
|
||||
onClick={() => setOpened(true)}
|
||||
leftIcon={icon}
|
||||
>
|
||||
{operation_text}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
name: team == null ? '' : team.name,
|
||||
active: team == null ? true : team.active,
|
||||
player_ids: team == null ? [] : team.players.map((player) => `${player.id}`),
|
||||
name: team.name,
|
||||
active: team.active,
|
||||
player_ids: team.players.map((player) => `${player.id}`),
|
||||
},
|
||||
|
||||
validate: {
|
||||
@@ -62,21 +36,12 @@ export default function TeamModal({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal opened={opened} onClose={() => setOpened(false)} title={operation_text}>
|
||||
<Modal opened={opened} onClose={() => setOpened(false)} title="Edit Team">
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
if (is_create_form) {
|
||||
await createTeam(tournament_id, values.name, values.active, values.player_ids);
|
||||
} else {
|
||||
await updateTeam(
|
||||
tournament_id,
|
||||
team.id,
|
||||
values.name,
|
||||
values.active,
|
||||
values.player_ids
|
||||
);
|
||||
}
|
||||
swrTeamsResponse.mutate(null);
|
||||
await updateTeam(tournament_id, team.id, values.name, values.active, values.player_ids);
|
||||
|
||||
await swrTeamsResponse.mutate(null);
|
||||
setOpened(false);
|
||||
})}
|
||||
>
|
||||
@@ -112,7 +77,15 @@ export default function TeamModal({
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
{modalOpenButton}
|
||||
<Button
|
||||
color="green"
|
||||
size="xs"
|
||||
style={{ marginRight: 10 }}
|
||||
onClick={() => setOpened(true)}
|
||||
leftIcon={<BiEditAlt size={20} />}
|
||||
>
|
||||
Edit Team
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
getLossColor,
|
||||
getWinColor,
|
||||
} from '../info/player_statistics';
|
||||
import PlayerModal from '../modals/player_modal';
|
||||
import PlayerUpdateModal from '../modals/player_update_modal';
|
||||
import { DateTime } from '../utils/datetime';
|
||||
import { EmptyTableInfo } from '../utils/empty_table_info';
|
||||
import RequestErrorAlert from '../utils/error_alert';
|
||||
@@ -94,7 +94,7 @@ export default function PlayersTable({
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<PlayerModal
|
||||
<PlayerUpdateModal
|
||||
swrPlayersResponse={swrPlayersResponse}
|
||||
tournament_id={tournamentData.id}
|
||||
player={player}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { TournamentMinimal } from '../../interfaces/tournament';
|
||||
import { deleteTeam } from '../../services/team';
|
||||
import DeleteButton from '../buttons/delete';
|
||||
import PlayerList from '../info/player_list';
|
||||
import TeamModal from '../modals/team_modal';
|
||||
import TeamUpdateModal from '../modals/team_update_modal';
|
||||
import { DateTime } from '../utils/datetime';
|
||||
import { EmptyTableInfo } from '../utils/empty_table_info';
|
||||
import RequestErrorAlert from '../utils/error_alert';
|
||||
@@ -43,7 +43,7 @@ export default function TeamsTable({
|
||||
<td>{team.swiss_score.toFixed(1)}</td>
|
||||
<td>{team.elo_score.toFixed(0)}</td>
|
||||
<td>
|
||||
<TeamModal
|
||||
<TeamUpdateModal
|
||||
tournament_id={tournamentData.id}
|
||||
team={team}
|
||||
swrTeamsResponse={swrTeamsResponse}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Grid, Title } from '@mantine/core';
|
||||
|
||||
import PlayerModal from '../../../components/modals/player_modal';
|
||||
import PlayerCreateModal from '../../../components/modals/player_create_modal';
|
||||
import PlayersTable from '../../../components/tables/players';
|
||||
import { getTournamentIdFromRouter } from '../../../components/utils/util';
|
||||
import { getPlayers } from '../../../services/adapter';
|
||||
@@ -16,10 +16,9 @@ export default function Players() {
|
||||
<Title>Players</Title>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={3}>
|
||||
<PlayerModal
|
||||
<PlayerCreateModal
|
||||
swrPlayersResponse={swrPlayersResponse}
|
||||
tournament_id={tournamentData.id}
|
||||
player={null}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Grid, Group, Select, Title } from '@mantine/core';
|
||||
import React, { useState } from 'react';
|
||||
import { SWRResponse } from 'swr';
|
||||
|
||||
import TeamModal from '../../../components/modals/team_modal';
|
||||
import TeamCreateModal from '../../../components/modals/team_create_modal';
|
||||
import TeamsTable from '../../../components/tables/teams';
|
||||
import { getTournamentIdFromRouter, responseIsValid } from '../../../components/utils/util';
|
||||
import { StageItemWithRounds } from '../../../interfaces/stage_item';
|
||||
@@ -75,10 +75,9 @@ export default function Teams() {
|
||||
groupStageItems={groupStageItems}
|
||||
setFilteredStageItemId={setFilteredStageItemId}
|
||||
/>
|
||||
<TeamModal
|
||||
<TeamCreateModal
|
||||
swrTeamsResponse={swrTeamsResponse}
|
||||
tournament_id={tournamentData.id}
|
||||
team={null}
|
||||
/>
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { createAxios, handleRequestError } from './adapter';
|
||||
|
||||
export async function createPlayer(
|
||||
tournament_id: number,
|
||||
name: string,
|
||||
active: boolean,
|
||||
team_id: string | null
|
||||
) {
|
||||
export async function createPlayer(tournament_id: number, name: string, active: boolean) {
|
||||
return createAxios()
|
||||
.post(`tournaments/${tournament_id}/players`, {
|
||||
name,
|
||||
active,
|
||||
team_id,
|
||||
})
|
||||
.post(`tournaments/${tournament_id}/players`, { name, active })
|
||||
.catch((response: any) => handleRequestError(response));
|
||||
}
|
||||
|
||||
export async function createMultiplePlayers(tournament_id: number, names: string, active: boolean) {
|
||||
return createAxios()
|
||||
.post(`tournaments/${tournament_id}/players_multi`, { names, active })
|
||||
.catch((response: any) => handleRequestError(response));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ export async function createTeam(
|
||||
});
|
||||
}
|
||||
|
||||
export async function createTeams(tournament_id: number, names: string, active: boolean) {
|
||||
return createAxios().post(`tournaments/${tournament_id}/teams_multi`, { names, active });
|
||||
}
|
||||
|
||||
export async function deleteTeam(tournament_id: number, team_id: number) {
|
||||
await createAxios()
|
||||
.delete(`tournaments/${tournament_id}/teams/${team_id}`)
|
||||
|
||||
Reference in New Issue
Block a user