diff --git a/backend/bracket/routes/stages.py b/backend/bracket/routes/stages.py
index 85d4701e..e8a83f97 100644
--- a/backend/bracket/routes/stages.py
+++ b/backend/bracket/routes/stages.py
@@ -49,7 +49,7 @@ async def delete_stage(
if len(stage.rounds) > 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
- detail="Round contains matches, delete those first",
+ detail="Stage contains rounds, please delete those first",
)
await sql_delete_stage(tournament_id, stage_id)
diff --git a/frontend/src/components/modals/stage_modal.tsx b/frontend/src/components/modals/stage_modal.tsx
new file mode 100644
index 00000000..0e1cf36d
--- /dev/null
+++ b/frontend/src/components/modals/stage_modal.tsx
@@ -0,0 +1,73 @@
+import { Button, Divider, Modal, Select } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { BiTrophy } from '@react-icons/all-files/bi/BiTrophy';
+import { useState } from 'react';
+import { SWRResponse } from 'swr';
+
+import { Tournament } from '../../interfaces/tournament';
+import { createStage } from '../../services/stage';
+import StagesTable from '../tables/stages';
+
+function CreateStageForm(
+ tournament: Tournament,
+ swrClubsResponse: SWRResponse,
+ setOpened: (value: ((prevState: boolean) => boolean) | boolean) => void
+) {
+ const form = useForm({
+ initialValues: { type: 'ROUND_ROBIN' },
+ validate: {},
+ });
+
+ return (
+
+ );
+}
+
+export default function StagesModal({
+ tournament,
+ swrStagesResponse,
+}: {
+ tournament: Tournament;
+ swrStagesResponse: SWRResponse;
+}) {
+ const [opened, setOpened] = useState(false);
+
+ return (
+ <>
+ setOpened(false)} title="Add or remove stages">
+
+ {CreateStageForm(tournament, swrStagesResponse, setOpened)}
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/components/modals/tournament_modal.tsx b/frontend/src/components/modals/tournament_modal.tsx
index 09e6f3dd..d9f6b5d9 100644
--- a/frontend/src/components/modals/tournament_modal.tsx
+++ b/frontend/src/components/modals/tournament_modal.tsx
@@ -1,4 +1,13 @@
-import { Button, Checkbox, Group, Image, Modal, Select, TextInput } from '@mantine/core';
+import {
+ Button,
+ Checkbox,
+ CopyButton,
+ Group,
+ Image,
+ Modal,
+ Select,
+ 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';
@@ -12,12 +21,112 @@ import { getBaseApiUrl, getClubs } from '../../services/adapter';
import { createTournament, updateTournament } from '../../services/tournament';
import SaveButton from '../buttons/save';
import { DropzoneButton } from '../utils/file_upload';
+import { getBaseURL } from '../utils/util';
export function TournamentLogo({ tournament }: { tournament: Tournament | null }) {
if (tournament == null || tournament.logo_path == null) return null;
return ;
}
+function GeneralTournamentForm({
+ setOpened,
+ is_create_form,
+ tournament,
+ swrTournamentsResponse,
+ clubs,
+}: {
+ setOpened: any;
+ is_create_form: boolean;
+ tournament: Tournament | null;
+ swrTournamentsResponse: SWRResponse;
+ clubs: Club[];
+}) {
+ const form = useForm({
+ initialValues: {
+ name: tournament == null ? '' : tournament.name,
+ club_id: tournament == null ? null : `${tournament.club_id}`,
+ dashboard_public: tournament == null ? true : tournament.dashboard_public,
+ players_can_be_in_multiple_teams:
+ tournament == null ? true : tournament.players_can_be_in_multiple_teams,
+ },
+
+ validate: {
+ name: (value) => (value.length > 0 ? null : 'Name too short'),
+ club_id: (value) => (value != null ? null : 'Please choose a club'),
+ },
+ });
+
+ return (
+
+ );
+}
+
export default function TournamentModal({
tournament,
swrTournamentsResponse,
@@ -53,82 +162,17 @@ export default function TournamentModal({
const swrClubsResponse: SWRResponse = getClubs();
const clubs: Club[] = swrClubsResponse.data != null ? swrClubsResponse.data.data : [];
- const form = useForm({
- initialValues: {
- name: tournament == null ? '' : tournament.name,
- club_id: tournament == null ? null : `${tournament.club_id}`,
- dashboard_public: tournament == null ? true : tournament.dashboard_public,
- players_can_be_in_multiple_teams:
- tournament == null ? true : tournament.players_can_be_in_multiple_teams,
- },
-
- validate: {
- name: (value) => (value.length > 0 ? null : 'Name too short'),
- club_id: (value) => (value != null ? null : 'Please choose a club'),
- },
- });
-
return (
<>
setOpened(false)} title={operation_text}>
-
+
-
{modalOpenButton}
>
);
diff --git a/frontend/src/components/tables/stages.tsx b/frontend/src/components/tables/stages.tsx
new file mode 100644
index 00000000..59b07ec2
--- /dev/null
+++ b/frontend/src/components/tables/stages.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { SWRResponse } from 'swr';
+
+import { StageInterface } from '../../interfaces/stage';
+import { Tournament } from '../../interfaces/tournament';
+import { deleteStage } from '../../services/stage';
+import DeleteButton from '../buttons/delete';
+import RequestErrorAlert from '../utils/error_alert';
+import TableLayout, { ThNotSortable, getTableState, sortTableEntries } from './table';
+
+export default function StagesTable({
+ tournament,
+ swrStagesResponse,
+}: {
+ tournament: Tournament;
+ swrStagesResponse: SWRResponse;
+}) {
+ const stages: StageInterface[] =
+ swrStagesResponse.data != null ? swrStagesResponse.data.data : [];
+ const tableState = getTableState('id');
+
+ if (swrStagesResponse.error) return ;
+
+ const rows = stages
+ .sort((s1: StageInterface, s2: StageInterface) => sortTableEntries(s1, s2, tableState))
+ .map((stage) => (
+
+ | {stage.type_name} |
+ {stage.status} |
+
+ {
+ await deleteStage(tournament.id, stage.id);
+ await swrStagesResponse.mutate(null);
+ }}
+ title="Delete Stage"
+ />
+ |
+
+ ));
+
+ return (
+
+
+
+ Title
+ Status
+ {null}
+
+
+ {rows}
+
+ );
+}
diff --git a/frontend/src/components/tables/table.tsx b/frontend/src/components/tables/table.tsx
index a432ee9b..eb393c8c 100644
--- a/frontend/src/components/tables/table.tsx
+++ b/frontend/src/components/tables/table.tsx
@@ -116,13 +116,7 @@ export default function TableLayout({ children }: any) {
return (
<>
-
+
diff --git a/frontend/src/components/utils/util.tsx b/frontend/src/components/utils/util.tsx
index c114a2c2..fe6da66e 100644
--- a/frontend/src/components/utils/util.tsx
+++ b/frontend/src/components/utils/util.tsx
@@ -47,3 +47,7 @@ export function getTournamentIdFromRouter() {
export function responseIsValid(response: SWRResponse) {
return response.data != null && response.data.data != null;
}
+
+export function getBaseURL() {
+ return typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
+}
diff --git a/frontend/src/interfaces/stage.tsx b/frontend/src/interfaces/stage.tsx
index 7792c08b..d5b1c15e 100644
--- a/frontend/src/interfaces/stage.tsx
+++ b/frontend/src/interfaces/stage.tsx
@@ -4,7 +4,6 @@ export interface StageInterface {
created: string;
type: string;
type_name: string;
- name: string;
status: string;
is_active: boolean;
rounds: StageInterface[];
diff --git a/frontend/src/pages/tournaments/[id].tsx b/frontend/src/pages/tournaments/[id].tsx
index a2e9c41a..acef9620 100644
--- a/frontend/src/pages/tournaments/[id].tsx
+++ b/frontend/src/pages/tournaments/[id].tsx
@@ -7,6 +7,7 @@ import { SWRResponse } from 'swr';
import NotFoundTitle from '../404';
import Brackets from '../../components/brackets/brackets';
import SaveButton from '../../components/buttons/save';
+import StagesModal from '../../components/modals/stage_modal';
import TournamentModal from '../../components/modals/tournament_modal';
import Scheduler from '../../components/scheduling/scheduler';
import StagesTab from '../../components/utils/stages_tab';
@@ -94,6 +95,11 @@ export default function TournamentPage() {
/>
) : null;
+ const stagesModal =
+ tournamentData != null ? (
+
+ ) : null;
+
return (
@@ -114,6 +120,7 @@ export default function TournamentPage() {
View dashboard
{tournamentModal}
+ {stagesModal}
{
await createRound(tournamentData.id);
diff --git a/frontend/src/services/stage.tsx b/frontend/src/services/stage.tsx
new file mode 100644
index 00000000..15992182
--- /dev/null
+++ b/frontend/src/services/stage.tsx
@@ -0,0 +1,13 @@
+import { createAxios, handleRequestError } from './adapter';
+
+export async function createStage(tournament_id: number, type: string) {
+ return createAxios()
+ .post(`tournaments/${tournament_id}/stages`, { type })
+ .catch((response: any) => handleRequestError(response));
+}
+
+export async function deleteStage(tournament_id: number, stage_id: number) {
+ return createAxios()
+ .delete(`tournaments/${tournament_id}/stages/${stage_id}`)
+ .catch((response: any) => handleRequestError(response));
+}