From e136e5b913e6b6ab463e7504281d505c9ebffe4e Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:20:09 +0100 Subject: [PATCH 01/20] Add custom scheduling modal to frontend --- .../components/modals/match_update_modal.tsx | 76 +++++++++++++++++++ .../src/pages/tournaments/[id]/schedule.tsx | 28 ++++++- 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/modals/match_update_modal.tsx diff --git a/frontend/src/components/modals/match_update_modal.tsx b/frontend/src/components/modals/match_update_modal.tsx new file mode 100644 index 000000000..711bb4459 --- /dev/null +++ b/frontend/src/components/modals/match_update_modal.tsx @@ -0,0 +1,76 @@ +import { ActionIcon, Button, Modal, NumberInput } from '@mantine/core'; +import { BiEditAlt } from '@react-icons/all-files/bi/BiEditAlt'; +import { useTranslation } from 'next-i18next'; +import { useState } from 'react'; +import { SWRResponse } from 'swr'; +import { useForm } from '@mantine/form'; +import { MatchInterface } from '../../interfaces/match'; +import { updateMatch } from '../../services/match'; +import { requestSucceeded } from '../../services/adapter'; + +export default function MatchUpdateModal({ + tournament_id, + match, + swrMatchResponse, +}: { + tournament_id: number; + match: MatchInterface; + swrMatchResponse: SWRResponse; +}) { + const { t } = useTranslation(); + const [opened, setOpened] = useState(false); + + const form = useForm({ + initialValues: { + custom_duration_minutes: match.custom_duration_minutes, + custom_margin_minutes: match.custom_margin_minutes, + }, + }); + + return ( + <> + setOpened(false)} title={t('edit_match_title')}> +
{ + const result = await updateMatch( + tournament_id, + match.id, + { ...match, ...values } + ); + + if (requestSucceeded(result)) { + await swrMatchResponse.mutate(); + setOpened(false); + } + })} + > + + + + + + +
+ + setOpened(true)} + > + + + + ); +} diff --git a/frontend/src/pages/tournaments/[id]/schedule.tsx b/frontend/src/pages/tournaments/[id]/schedule.tsx index caec58fc5..a156ca30d 100644 --- a/frontend/src/pages/tournaments/[id]/schedule.tsx +++ b/frontend/src/pages/tournaments/[id]/schedule.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'next-i18next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import React from 'react'; +import { SWRResponse } from 'swr'; import { NoContent } from '../../../components/no_content/empty_table_info'; import { Time } from '../../../components/utils/datetime'; import { Translator } from '../../../components/utils/types'; @@ -21,19 +22,24 @@ import { } from '../../../services/lookups'; import { rescheduleMatch, scheduleMatches } from '../../../services/match'; import TournamentLayout from '../_tournament_layout'; +import MatchUpdateModal from '../../../components/modals/match_update_modal'; function ScheduleRow({ index, match, stageItemsLookup, matchesLookup, + swrStagesResponse, }: { index: number; match: MatchInterface; stageItemsLookup: any; matchesLookup: any; + swrStagesResponse: SWRResponse; }) { const stageItemColor = stringToColour(`${matchesLookup[match.id].stageItem.id}`); + // No need to trickle down the tournament_id, as this component is only used in the SchedulePage + const { tournamentData } = getTournamentIdFromRouter(); return ( @@ -53,10 +59,17 @@ function ScheduleRow({ {formatMatchTeam2(stageItemsLookup, matchesLookup, match)} - - - {match.start_time != null ? + + + + {match.start_time != null ? + + {matchesLookup[match.id].stageItem.name} @@ -75,11 +88,13 @@ function ScheduleColumn({ matches, stageItemsLookup, matchesLookup, + swrStagesResponse, }: { court: Court; matches: MatchInterface[]; stageItemsLookup: any; matchesLookup: any; + swrStagesResponse: SWRResponse; }) { const { t } = useTranslation(); const rows = matches.map((match: MatchInterface, index: number) => ( @@ -89,6 +104,7 @@ function ScheduleColumn({ matchesLookup={matchesLookup} match={match} key={match.id} + swrStagesResponse={swrStagesResponse} /> )); @@ -125,11 +141,13 @@ function Schedule({ stageItemsLookup, matchesLookup, schedule, + swrStagesResponse, }: { t: Translator; stageItemsLookup: any; matchesLookup: any; schedule: { court: Court; matches: MatchInterface[] }[]; + swrStagesResponse: SWRResponse; }) { const columns = schedule.map((item) => ( )); @@ -213,6 +232,7 @@ export default function SchedulePage() { schedule={data} stageItemsLookup={stageItemsLookup} matchesLookup={matchesLookup} + swrStagesResponse={swrStagesResponse} /> From 2b4015ea2b3855ee322306863795b83d7e87c1a9 Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:23:04 +0100 Subject: [PATCH 02/20] Use DateTime instead of Time and mod size --- frontend/src/pages/tournaments/[id]/schedule.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/tournaments/[id]/schedule.tsx b/frontend/src/pages/tournaments/[id]/schedule.tsx index a156ca30d..70c17fc20 100644 --- a/frontend/src/pages/tournaments/[id]/schedule.tsx +++ b/frontend/src/pages/tournaments/[id]/schedule.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { SWRResponse } from 'swr'; import { NoContent } from '../../../components/no_content/empty_table_info'; -import { Time } from '../../../components/utils/datetime'; +import { DateTime } from '../../../components/utils/datetime'; import { Translator } from '../../../components/utils/types'; import { getTournamentIdFromRouter, responseIsValid } from '../../../components/utils/util'; import { Court } from '../../../interfaces/court'; @@ -62,7 +62,7 @@ function ScheduleRow({ - {match.start_time != null ? {(provided) => (
-
+

{court.name}

{rows} {noItemsAlert} From b4dd0b761cdc689f771667ca521578369dec0c78 Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:46:10 +0100 Subject: [PATCH 03/20] i18n support --- frontend/src/components/modals/match_update_modal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/modals/match_update_modal.tsx b/frontend/src/components/modals/match_update_modal.tsx index 711bb4459..d737e1506 100644 --- a/frontend/src/components/modals/match_update_modal.tsx +++ b/frontend/src/components/modals/match_update_modal.tsx @@ -29,7 +29,7 @@ export default function MatchUpdateModal({ return ( <> - setOpened(false)} title={t('edit_match_title')}> + setOpened(false)} title={t('edit_match_modal_title')}>
{ const result = await updateMatch( @@ -45,13 +45,13 @@ export default function MatchUpdateModal({ })} > Date: Sun, 25 Feb 2024 12:46:36 +0100 Subject: [PATCH 04/20] Support removal of custom times --- frontend/src/components/modals/match_update_modal.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/modals/match_update_modal.tsx b/frontend/src/components/modals/match_update_modal.tsx index d737e1506..9e27b279f 100644 --- a/frontend/src/components/modals/match_update_modal.tsx +++ b/frontend/src/components/modals/match_update_modal.tsx @@ -35,7 +35,14 @@ export default function MatchUpdateModal({ const result = await updateMatch( tournament_id, match.id, - { ...match, ...values } + { ...match, + ...{ + // @ts-ignore + custom_duration_minutes: values.custom_duration_minutes === '' ? null : values.custom_duration_minutes, + // @ts-ignore + custom_margin_minutes: values.custom_margin_minutes === '' ? null : values.custom_margin_minutes, + }, + } ); if (requestSucceeded(result)) { From 2356e876cd413c2ea80daf6b77f1af4af4840c2e Mon Sep 17 00:00:00 2001 From: Babeuh Date: Sun, 25 Feb 2024 15:11:34 +0100 Subject: [PATCH 05/20] change: make update logic closer to other components --- .../components/modals/match_update_modal.tsx | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/modals/match_update_modal.tsx b/frontend/src/components/modals/match_update_modal.tsx index 9e27b279f..bd422e37c 100644 --- a/frontend/src/components/modals/match_update_modal.tsx +++ b/frontend/src/components/modals/match_update_modal.tsx @@ -32,23 +32,16 @@ export default function MatchUpdateModal({ setOpened(false)} title={t('edit_match_modal_title')}> { - const result = await updateMatch( - tournament_id, - match.id, - { ...match, - ...{ - // @ts-ignore - custom_duration_minutes: values.custom_duration_minutes === '' ? null : values.custom_duration_minutes, - // @ts-ignore - custom_margin_minutes: values.custom_margin_minutes === '' ? null : values.custom_margin_minutes, - }, - } - ); + const updatedMatch = { ...match, + // @ts-ignore + custom_duration_minutes: values.custom_duration_minutes === '' ? null : values.custom_duration_minutes, + // @ts-ignore + custom_margin_minutes: values.custom_margin_minutes === '' ? null : values.custom_margin_minutes, + }; - if (requestSucceeded(result)) { - await swrMatchResponse.mutate(); - setOpened(false); - } + await updateMatch(tournament_id, match.id, updatedMatch); + await swrMatchResponse.mutate(); + setOpened(false); })} > Date: Sun, 25 Feb 2024 16:12:15 +0100 Subject: [PATCH 06/20] fix: checks --- frontend/src/components/modals/match_update_modal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/modals/match_update_modal.tsx b/frontend/src/components/modals/match_update_modal.tsx index bd422e37c..4bd90203d 100644 --- a/frontend/src/components/modals/match_update_modal.tsx +++ b/frontend/src/components/modals/match_update_modal.tsx @@ -6,7 +6,6 @@ import { SWRResponse } from 'swr'; import { useForm } from '@mantine/form'; import { MatchInterface } from '../../interfaces/match'; import { updateMatch } from '../../services/match'; -import { requestSucceeded } from '../../services/adapter'; export default function MatchUpdateModal({ tournament_id, From 27da4f76dd30ff24ce1f312ef84db3f3be298d3f Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Tue, 5 Mar 2024 21:56:06 +0100 Subject: [PATCH 07/20] Improve match custom times component --- .../forms/common_custom_times_matches.tsx | 131 ++++++++++++++++++ .../src/components/modals/match_modal.tsx | 53 +------ .../components/modals/match_update_modal.tsx | 58 +++++--- frontend/src/components/utils/datetime.tsx | 16 ++- 4 files changed, 182 insertions(+), 76 deletions(-) create mode 100644 frontend/src/components/forms/common_custom_times_matches.tsx diff --git a/frontend/src/components/forms/common_custom_times_matches.tsx b/frontend/src/components/forms/common_custom_times_matches.tsx new file mode 100644 index 000000000..0b672242b --- /dev/null +++ b/frontend/src/components/forms/common_custom_times_matches.tsx @@ -0,0 +1,131 @@ +import { Center, Checkbox, Grid, Input, NumberInput, Text } from '@mantine/core'; +import { UseFormReturnType } from '@mantine/form'; +import { format, fromUnixTime, getUnixTime, parseISO } from 'date-fns'; +import { useTranslation } from 'next-i18next'; +import { useMemo } from 'react'; + +import { MatchInterface } from '../../interfaces/match'; + +interface CommonCustomTimeMatchesFormProps> { + customDurationEnabled: boolean; + setCustomDurationEnabled: (value: boolean) => void; + customMarginEnabled: boolean; + setCustomMarginEnabled: (value: boolean) => void; + match: MatchInterface; + form: UseFormReturnType< + ExtendedObj & { + custom_duration_minutes: number | null; + custom_margin_minutes: number | null; + }, + ( + values: ExtendedObj & { + custom_duration_minutes: number | null; + custom_margin_minutes: number | null; + } + ) => ExtendedObj & { + custom_duration_minutes: number | null; + custom_margin_minutes: number | null; + } + >; +} + +export default function CommonCustomTimeMatchesForm< + ExtendedValues extends Record = Record, +>({ + form, + customDurationEnabled, + customMarginEnabled, + match, + setCustomDurationEnabled, + setCustomMarginEnabled, +}: CommonCustomTimeMatchesFormProps) { + const { t } = useTranslation(); + + const matchDuration = useMemo(() => { + const value = customDurationEnabled + ? form.values.custom_duration_minutes + : match.duration_minutes; + return value ?? 0; + }, [customDurationEnabled, match.custom_duration_minutes, match.duration_minutes]); + + const matchMargin = useMemo(() => { + const value = customMarginEnabled ? form.values.custom_margin_minutes : match.margin_minutes; + return value ?? 0; + }, [customMarginEnabled, match.custom_margin_minutes, match.margin_minutes]); + + const endDatetime = useMemo(() => { + return fromUnixTime( + getUnixTime(parseISO(match.start_time)) + matchDuration * 60 + matchMargin * 60 + ); + }, [match.start_time, matchDuration, matchMargin]); + + return ( + <> + + {t('custom_match_margin_label')} + + + + {t('minutes')}} + placeholder={`${match.duration_minutes}`} + rightSectionWidth={92} + {...form.getInputProps('custom_duration_minutes')} + /> + + +
+ { + setCustomDurationEnabled(event.currentTarget.checked); + }} + /> +
+
+
+ + + {t('custom_match_margin_label')} + + + + {t('minutes')}} + rightSectionWidth={92} + {...form.getInputProps('custom_margin_minutes')} + /> + + +
+ { + setCustomMarginEnabled(event.currentTarget.checked); + }} + /> +
+
+
+ + + {/* */} + + + + ); +} diff --git a/frontend/src/components/modals/match_modal.tsx b/frontend/src/components/modals/match_modal.tsx index d46d3ec05..089a2240c 100644 --- a/frontend/src/components/modals/match_modal.tsx +++ b/frontend/src/components/modals/match_modal.tsx @@ -14,6 +14,7 @@ import { TournamentMinimal } from '../../interfaces/tournament'; import { getMatchLookup, getStageItemLookup } from '../../services/lookups'; import { deleteMatch, updateMatch } from '../../services/match'; import DeleteButton from '../buttons/delete'; +import CommonCustomTimeMatchesForm from '../forms/common_custom_times_matches'; function MatchDeleteButton({ tournamentData, @@ -131,57 +132,7 @@ export default function MatchModal({ /> - - {t('custom_match_duration_label')} - - - - {t('minutes')}} - placeholder={`${match.duration_minutes}`} - rightSectionWidth={92} - {...form.getInputProps('custom_duration_minutes')} - /> - - -
- { - setCustomDurationEnabled(event.currentTarget.checked); - }} - /> -
-
-
- - - {t('custom_match_margin_label')} - - - - {t('minutes')}} - rightSectionWidth={92} - {...form.getInputProps('custom_margin_minutes')} - /> - - -
- { - setCustomMarginEnabled(event.currentTarget.checked); - }} - /> -
-
-
+ + + + + + )} - + await updateMatch(tournament_id, previousMatch.id, updatedMatch); + await swrMatchResponse.mutate(); + }} + > + {t('calculate_label')} + From b39821bd4344d30cd5de8c779a7f90b238b8e609 Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Wed, 6 Mar 2024 13:38:47 +0100 Subject: [PATCH 11/20] Use DateTime instead for Match component --- frontend/src/components/brackets/match.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/brackets/match.tsx b/frontend/src/components/brackets/match.tsx index d3dfa91a2..118745597 100644 --- a/frontend/src/components/brackets/match.tsx +++ b/frontend/src/components/brackets/match.tsx @@ -1,4 +1,4 @@ -import { Center, Grid, UnstyledButton, useMantineTheme } from '@mantine/core'; +import { Center, Grid, UnstyledButton, useMantineTheme, Text } from '@mantine/core'; import assert from 'assert'; import React, { useState } from 'react'; import { SWRResponse } from 'swr'; @@ -13,7 +13,7 @@ import { import { TournamentMinimal } from '../../interfaces/tournament'; import { getMatchLookup, getStageItemLookup } from '../../services/lookups'; import MatchModal from '../modals/match_modal'; -import { Time } from '../utils/datetime'; +import { DateTime } from '../utils/datetime'; import classes from './match.module.css'; export function MatchBadge({ match, theme }: { match: MatchInterface; theme: any }) { @@ -30,10 +30,10 @@ export function MatchBadge({ match, theme }: { match: MatchInterface; theme: any }} >
- - {match.court?.name} |{' '} - {match.start_time != null ? +
+ {match.court?.name} + {match.start_time != null ? : null} +
From 6d10c1c94085f881c9fcdd215a900b97a109a322 Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Sun, 2 Jun 2024 14:48:27 +0200 Subject: [PATCH 12/20] Fix missing changes --- .../components/forms/common_custom_times_matches.tsx | 8 +------- frontend/src/components/modals/match_modal.tsx | 8 +++++++- frontend/src/components/modals/match_update_modal.tsx | 11 ++++++----- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/forms/common_custom_times_matches.tsx b/frontend/src/components/forms/common_custom_times_matches.tsx index 31396cdae..ab61be8d6 100644 --- a/frontend/src/components/forms/common_custom_times_matches.tsx +++ b/frontend/src/components/forms/common_custom_times_matches.tsx @@ -64,7 +64,7 @@ export default function CommonCustomTimeMatchesForm< {t('minutes')}} placeholder={`${match.duration_minutes}`} @@ -112,12 +112,6 @@ export default function CommonCustomTimeMatchesForm< - {/* */} - + { + setCustomDurationEnabled(val); + if (val === false) form.setFieldValue('custom_duration_minutes', null); + }} setCustomMarginEnabled={(val) => { + setCustomMarginEnabled(val); + if (val === false) form.setFieldValue('custom_margin_minutes', null); + }} />
diff --git a/frontend/src/components/forms/common_custom_times_matches.tsx b/frontend/src/components/forms/common_custom_times_matches.tsx index ea3265de4..b53e3c19b 100644 --- a/frontend/src/components/forms/common_custom_times_matches.tsx +++ b/frontend/src/components/forms/common_custom_times_matches.tsx @@ -53,9 +53,11 @@ export default function CommonCustomTimeMatchesForm< return value ?? 0; }, [customMarginEnabled, match.custom_margin_minutes, match.margin_minutes]); - const endDatetime = useMemo(() => fromUnixTime( - getUnixTime(parseISO(match.start_time)) + matchDuration * 60 + matchMargin * 60 - ), [match.start_time, matchDuration, matchMargin]); + const endDatetime = useMemo( + () => + fromUnixTime(getUnixTime(parseISO(match.start_time)) + matchDuration * 60 + matchMargin * 60), + [match.start_time, matchDuration, matchMargin] + ); return ( <> @@ -110,10 +112,7 @@ export default function CommonCustomTimeMatchesForm< - + {format(endDatetime, 'd LLLL yyyy HH:mm')} diff --git a/frontend/src/components/modals/match_update_modal.tsx b/frontend/src/components/modals/match_update_modal.tsx index 62c1fccc0..060fa68f0 100644 --- a/frontend/src/components/modals/match_update_modal.tsx +++ b/frontend/src/components/modals/match_update_modal.tsx @@ -1,11 +1,4 @@ -import { - ActionIcon, - Button, - Divider, - Grid, - Input, - Modal, -} from '@mantine/core'; +import { ActionIcon, Button, Divider, Grid, Input, Modal } from '@mantine/core'; import { DateTimePicker } from '@mantine/dates'; import { useForm } from '@mantine/form'; import { showNotification } from '@mantine/notifications'; diff --git a/frontend/src/pages/tournaments/[id]/schedule.tsx b/frontend/src/pages/tournaments/[id]/schedule.tsx index 3dead2cda..6cab51ce2 100644 --- a/frontend/src/pages/tournaments/[id]/schedule.tsx +++ b/frontend/src/pages/tournaments/[id]/schedule.tsx @@ -4,9 +4,10 @@ import { IconAlertCircle, IconCalendarPlus } from '@tabler/icons-react'; import { useTranslation } from 'next-i18next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import React, { useState } from 'react'; - import { SWRResponse } from 'swr'; + import MatchModal from '../../../components/modals/match_modal'; +import MatchUpdateModal from '../../../components/modals/match_update_modal'; import { NoContent } from '../../../components/no_content/empty_table_info'; import { DateTime } from '../../../components/utils/datetime'; import { Translator } from '../../../components/utils/types'; @@ -23,7 +24,6 @@ import { } from '../../../services/lookups'; import { rescheduleMatch, scheduleMatches } from '../../../services/match'; import TournamentLayout from '../_tournament_layout'; -import MatchUpdateModal from '../../../components/modals/match_update_modal'; function ScheduleRow({ index, @@ -78,7 +78,10 @@ function ScheduleRow({ previousMatch={previousMatch} /> - + {matchesLookup[match.id].stageItem.name} From def49feac09424590b1a3a6b13b4a78b841f938f Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Wed, 10 Jul 2024 23:22:27 +0200 Subject: [PATCH 15/20] Deprecate the use of my custom match update components Addresses https://github.com/evroon/bracket/pull/530#issuecomment-1962903307 --- .../src/components/modals/match_modal.tsx | 83 +++++++++++++++++-- .../src/pages/tournaments/[id]/schedule.tsx | 11 ++- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/modals/match_modal.tsx b/frontend/src/components/modals/match_modal.tsx index 801b2fbb1..a5109efcc 100644 --- a/frontend/src/components/modals/match_modal.tsx +++ b/frontend/src/components/modals/match_modal.tsx @@ -1,9 +1,10 @@ -import { Button, Divider, Modal, NumberInput } from '@mantine/core'; +import { Button, Center, Checkbox, Divider, Grid, Input, Modal, NumberInput, Text } from '@mantine/core'; import { useForm } from '@mantine/form'; import { useTranslation } from 'next-i18next'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { SWRResponse } from 'swr'; +import { format, fromUnixTime, getUnixTime, parseISO } from 'date-fns'; import { MatchBodyInterface, MatchInterface, @@ -14,7 +15,6 @@ import { TournamentMinimal } from '../../interfaces/tournament'; import { getMatchLookup, getStageItemLookup } from '../../services/lookups'; import { deleteMatch, updateMatch } from '../../services/match'; import DeleteButton from '../buttons/delete'; -import CommonCustomTimeMatchesForm from '../forms/common_custom_times_matches'; function MatchDeleteButton({ tournamentData, @@ -91,6 +91,24 @@ function MatchModalForm({ match.custom_margin_minutes != null ); + const matchDuration = useMemo(() => { + const value = customDurationEnabled + ? form.values.custom_duration_minutes + : match.duration_minutes; + return value ?? 0; + }, [customDurationEnabled, form.values.custom_duration_minutes, match.duration_minutes]); + + const matchMargin = useMemo(() => { + const value = customMarginEnabled ? form.values.custom_margin_minutes : match.margin_minutes; + return value ?? 0; + }, [customMarginEnabled, form.values.custom_margin_minutes, match.margin_minutes]); + + const endDatetime = useMemo( + () => + fromUnixTime(getUnixTime(parseISO(match.start_time)) + matchDuration * 60 + matchMargin * 60), + [match.start_time, matchDuration, matchMargin] + ); + const stageItemsLookup = getStageItemLookup(swrStagesResponse); const matchesLookup = getMatchLookup(swrStagesResponse); @@ -131,14 +149,69 @@ function MatchModalForm({ /> - + /> */} + + + {t('minutes')}} + placeholder={`${match.duration_minutes}`} + rightSectionWidth={92} + {...form.getInputProps('custom_duration_minutes')} + /> + + + +
+ { + setCustomDurationEnabled(event.currentTarget.checked); + }} + /> +
+
+
+ + + + {t('minutes')}} + rightSectionWidth={92} + {...form.getInputProps('custom_margin_minutes')} + /> + + + +
+ { + setCustomMarginEnabled(event.currentTarget.checked); + }} + /> +
+
+
+ + + + {format(endDatetime, 'd LLLL yyyy HH:mm')} + + + + + + )} + @@ -236,14 +300,9 @@ export default function MatchModal({ opened, setOpened, dynamicSchedule, -}: { - tournamentData: TournamentMinimal; - match: MatchInterface | null; - swrStagesResponse: SWRResponse; - swrUpcomingMatchesResponse: SWRResponse | null; - opened: boolean; - setOpened: any; - dynamicSchedule: boolean; + priorMatch, +}: MatchModalProps & { + opened: boolean }) { const { t } = useTranslation(); @@ -257,6 +316,7 @@ export default function MatchModal({ match={match} setOpened={setOpened} dynamicSchedule={dynamicSchedule} + priorMatch={priorMatch} /> diff --git a/frontend/src/components/modals/match_update_modal.tsx b/frontend/src/components/modals/match_update_modal.tsx deleted file mode 100644 index 060fa68f0..000000000 --- a/frontend/src/components/modals/match_update_modal.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { ActionIcon, Button, Divider, Grid, Input, Modal } from '@mantine/core'; -import { DateTimePicker } from '@mantine/dates'; -import { useForm } from '@mantine/form'; -import { showNotification } from '@mantine/notifications'; -import { BiEditAlt } from '@react-icons/all-files/bi/BiEditAlt'; -import { parseISO } from 'date-fns'; -import { useTranslation } from 'next-i18next'; -import { useState } from 'react'; -import { SWRResponse } from 'swr'; - -import { MatchInterface } from '../../interfaces/match'; -import { updateMatch } from '../../services/match'; -import CommonCustomTimeMatchesForm from '../forms/common_custom_times_matches'; - -export default function MatchUpdateModal({ - tournament_id, - match, - swrMatchResponse, - previousMatch, -}: { - tournament_id: number; - match: MatchInterface; - swrMatchResponse: SWRResponse; - previousMatch?: MatchInterface; -}) { - const { t } = useTranslation(); - const [opened, setOpened] = useState(false); - - const form = useForm({ - initialValues: { - custom_duration_minutes: match.custom_duration_minutes, - custom_margin_minutes: match.custom_margin_minutes, - }, - validate: { - custom_duration_minutes: (value) => - value == null || value >= 0 ? null : t('negative_match_duration_validation'), - custom_margin_minutes: (value) => - value == null || value >= 0 ? null : t('negative_match_margin_validation'), - }, - }); - - const [customDurationEnabled, setCustomDurationEnabled] = useState( - match.custom_duration_minutes != null - ); - const [customMarginEnabled, setCustomMarginEnabled] = useState( - match.custom_margin_minutes != null - ); - - const [date, setDate] = useState(null); - - return ( - <> - setOpened(false)} title={t('edit_match_modal_title')}> -
{ - const updatedMatch = { - ...match, - custom_duration_minutes: customDurationEnabled - ? values.custom_duration_minutes - : null, - custom_margin_minutes: customMarginEnabled ? values.custom_margin_minutes : null, - }; - - await updateMatch(tournament_id, match.id, updatedMatch); - await swrMatchResponse.mutate(); - setOpened(false); - })} - > - - - {previousMatch && ( - <> - - - - - - - - - - - - - )} - - - -
- - setOpened(true)}> - - - - ); -} diff --git a/frontend/src/pages/tournaments/[id]/schedule.tsx b/frontend/src/pages/tournaments/[id]/schedule.tsx index 6b7569d94..5f596de9f 100644 --- a/frontend/src/pages/tournaments/[id]/schedule.tsx +++ b/frontend/src/pages/tournaments/[id]/schedule.tsx @@ -3,8 +3,7 @@ import { ActionIcon, Alert, Badge, Button, Card, Grid, Group, Stack, Text, Title import { IconAlertCircle, IconCalendarPlus } from '@tabler/icons-react'; import { useTranslation } from 'next-i18next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import React, { useState } from 'react'; -import { SWRResponse } from 'swr'; +import React, { useCallback, useState } from 'react'; import { BiEditAlt } from 'react-icons/bi'; import MatchModal from '../../../components/modals/match_modal'; @@ -31,20 +30,15 @@ function ScheduleRow({ openMatchModal, stageItemsLookup, matchesLookup, - swrStagesResponse, previousMatch, }: { index: number; match: MatchInterface; - openMatchModal: any; + openMatchModal: (match: MatchInterface, priorMatch?: MatchInterface) => void; stageItemsLookup: any; matchesLookup: any; - swrStagesResponse: SWRResponse; previousMatch?: MatchInterface; }) { - // No need to trickle down the tournament_id, as this component is only used in the SchedulePage - const { tournamentData } = getTournamentIdFromRouter(); - return ( {(provided) => ( @@ -56,7 +50,7 @@ function ScheduleRow({ withBorder mt="md" onClick={() => { - openMatchModal(match); + openMatchModal(match, previousMatch); }} {...provided.dragHandleProps} > @@ -71,12 +65,6 @@ function ScheduleRow({ {match.start_time != null ? : null} - {/* */} @@ -103,14 +91,12 @@ function ScheduleColumn({ openMatchModal, stageItemsLookup, matchesLookup, - swrStagesResponse, }: { court: Court; matches: MatchInterface[]; openMatchModal: any; stageItemsLookup: any; matchesLookup: any; - swrStagesResponse: SWRResponse; }) { const { t } = useTranslation(); const rows = matches.map((match: MatchInterface, index: number) => ( @@ -121,7 +107,6 @@ function ScheduleColumn({ match={match} openMatchModal={openMatchModal} key={match.id} - swrStagesResponse={swrStagesResponse} previousMatch={index > 0 ? matches[index - 1] : undefined} /> )); @@ -159,14 +144,12 @@ function Schedule({ stageItemsLookup, matchesLookup, schedule, - swrStagesResponse, openMatchModal, }: { t: Translator; stageItemsLookup: any; matchesLookup: any; schedule: { court: Court; matches: MatchInterface[] }[]; - swrStagesResponse: SWRResponse; openMatchModal: CallableFunction; }) { const columns = schedule.map((item) => ( @@ -176,7 +159,6 @@ function Schedule({ key={item.court.id} court={item.court} matches={item.matches} - swrStagesResponse={swrStagesResponse} openMatchModal={openMatchModal} /> )); @@ -195,6 +177,7 @@ function Schedule({ export default function SchedulePage() { const [modalOpened, modalSetOpened] = useState(false); const [match, setMatch] = useState(null); + const [priorMatch, setPriorMatch] = useState(null); const { t } = useTranslation(); const { tournamentData } = getTournamentIdFromRouter(); @@ -211,16 +194,23 @@ export default function SchedulePage() { const data = responseIsValid(swrCourtsResponse) && responseIsValid(swrStagesResponse) - ? getScheduleData(swrCourtsResponse, matchesByCourtId) + ? getScheduleData( + swrCourtsResponse, + matchesByCourtId as ReturnType + ) : []; if (!responseIsValid(swrStagesResponse)) return null; if (!responseIsValid(swrCourtsResponse)) return null; - function openMatchModal(matchToOpen: MatchInterface) { + const openMatchModal = useCallback(( + matchToOpen: MatchInterface, + priorMatchToOpen?: MatchInterface + ) => { setMatch(matchToOpen); + setPriorMatch(priorMatchToOpen ?? null); modalSetOpened(true); - } + }, []); return ( @@ -233,6 +223,7 @@ export default function SchedulePage() { opened={modalOpened} setOpened={modalSetOpened} dynamicSchedule={false} + priorMatch={priorMatch} /> ) : null} @@ -275,7 +266,6 @@ export default function SchedulePage() { schedule={data} stageItemsLookup={stageItemsLookup} matchesLookup={matchesLookup} - swrStagesResponse={swrStagesResponse} openMatchModal={openMatchModal} /> From e03d188ce31c569fdaa173ab692ba40018c5540a Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Sat, 13 Jul 2024 11:49:25 +0200 Subject: [PATCH 17/20] Add typings --- frontend/src/components/utils/util.tsx | 18 +++++++++++------- frontend/src/services/lookups.tsx | 17 ++++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/utils/util.tsx b/frontend/src/components/utils/util.tsx index b57f502e5..08cff5770 100644 --- a/frontend/src/components/utils/util.tsx +++ b/frontend/src/components/utils/util.tsx @@ -73,13 +73,17 @@ export function getBaseURL() { return typeof window !== 'undefined' && window.location.origin ? window.location.origin : ''; } -export const groupBy = (keys: any) => (array: any) => - array.reduce((objectsByKeyValue: any, obj: any) => { - const value = keys.map((key: any) => obj[key]).join('-'); - // eslint-disable-next-line no-param-reassign - objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj); - return objectsByKeyValue; - }, {}); +export const groupBy = < + K extends PropertyKey = PropertyKey, + T extends Record = Record +>( + keys: K[], array: T[] +) => array.reduce((objectsByKeyValue: Record, obj) => { + const value = keys.map((key) => obj[key]).join(''); + // eslint-disable-next-line no-param-reassign + objectsByKeyValue[value] = (objectsByKeyValue[value] ?? []).concat(obj); + return objectsByKeyValue; +}, {}); export function truncateString(input: string, length: number) { if (input.length > length + 3) { diff --git a/frontend/src/services/lookups.tsx b/frontend/src/services/lookups.tsx index 039336ed5..520058f28 100644 --- a/frontend/src/services/lookups.tsx +++ b/frontend/src/services/lookups.tsx @@ -7,6 +7,7 @@ import { MatchInterface } from '../interfaces/match'; import { StageWithStageItems } from '../interfaces/stage'; import { TeamInterface } from '../interfaces/team'; import { getTeams } from './adapter'; +import { StageItemWithRounds } from '../interfaces/stage_item'; export function getTeamsLookup(tournamentId: number) { const swrTeamsResponse: SWRResponse = getTeams(tournamentId); @@ -18,8 +19,10 @@ export function getTeamsLookup(tournamentId: number) { return Object.fromEntries(swrTeamsResponse.data.data.teams.map((x: TeamInterface) => [x.id, x])); } -export function getStageItemLookup(swrStagesResponse: SWRResponse) { - let result: any[] = []; +export function getStageItemLookup( + swrStagesResponse: SWRResponse +) { + let result: [number, StageItemWithRounds][] = []; swrStagesResponse.data.data.map((stage: StageWithStageItems) => stage.stage_items.forEach((stage_item) => { @@ -30,7 +33,7 @@ export function getStageItemLookup(swrStagesResponse: SWRResponse) { } export function getStageItemList(swrStagesResponse: SWRResponse) { - let result: any[] = []; + let result: [StageItemWithRounds][] = []; swrStagesResponse.data.data.map((stage: StageWithStageItems) => stage.stage_items.forEach((stage_item) => { @@ -41,7 +44,7 @@ export function getStageItemList(swrStagesResponse: SWRResponse) { } export function getStageItemTeamIdsLookup(swrStagesResponse: SWRResponse) { - let result: any[] = []; + let result: [number, (number | null)[]][] = []; swrStagesResponse.data.data.map((stage: StageWithStageItems) => stage.stage_items.forEach((stageItem) => { @@ -79,7 +82,7 @@ export function getStageItemTeamsLookup( } export function getMatchLookup(swrStagesResponse: SWRResponse) { - let result: any[] = []; + let result: [number, { match: MatchInterface, stageItem: StageItemWithRounds }][] = []; swrStagesResponse.data.data.map((stage: StageWithStageItems) => stage.stage_items.forEach((stageItem) => { @@ -117,12 +120,12 @@ export function stringToColour(input: string) { export function getMatchLookupByCourt(swrStagesResponse: SWRResponse) { const matches = Object.values(getMatchLookup(swrStagesResponse)).map((x) => x.match); - return groupBy(['court_id'])(matches); + return groupBy(['court_id'], matches); } export function getScheduleData( swrCourtsResponse: SWRResponse, - matchesByCourtId: any + matchesByCourtId: ReturnType> ): { court: Court; matches: MatchInterface[] }[] { return swrCourtsResponse.data.data.map((court: Court) => ({ matches: (matchesByCourtId[court.id] || []) From 0564340339116c39fb26e0ff20729b9876a2a712 Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Sun, 14 Jul 2024 09:15:46 +0200 Subject: [PATCH 18/20] React more hooks were rendered than previous render --- frontend/src/pages/tournaments/[id]/schedule.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/tournaments/[id]/schedule.tsx b/frontend/src/pages/tournaments/[id]/schedule.tsx index 5f596de9f..27edd8215 100644 --- a/frontend/src/pages/tournaments/[id]/schedule.tsx +++ b/frontend/src/pages/tournaments/[id]/schedule.tsx @@ -200,17 +200,17 @@ export default function SchedulePage() { ) : []; - if (!responseIsValid(swrStagesResponse)) return null; - if (!responseIsValid(swrCourtsResponse)) return null; - const openMatchModal = useCallback(( matchToOpen: MatchInterface, - priorMatchToOpen?: MatchInterface + priorMatchToOpen: MatchInterface | null ) => { setMatch(matchToOpen); - setPriorMatch(priorMatchToOpen ?? null); + setPriorMatch(priorMatchToOpen); modalSetOpened(true); - }, []); + }, [setMatch, setPriorMatch, modalSetOpened]); + + if (!responseIsValid(swrStagesResponse)) return null; + if (!responseIsValid(swrCourtsResponse)) return null; return ( From b59676b228024b0ab3d9f513000d9842e9a433ea Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:01:49 +0200 Subject: [PATCH 19/20] More typings --- frontend/src/components/brackets/match.tsx | 1 + frontend/src/components/builder/builder.tsx | 14 ++--- .../src/components/modals/match_modal.tsx | 10 ++-- frontend/src/interfaces/stage.tsx | 3 +- frontend/src/interfaces/stage_item.tsx | 2 +- .../[id]/dashboard/present/courts.tsx | 2 +- .../src/pages/tournaments/[id]/results.tsx | 51 ++++++++++--------- .../src/pages/tournaments/[id]/schedule.tsx | 14 ++--- .../[id]/swiss/[stage_item_id].tsx | 22 ++++---- frontend/src/services/adapter.tsx | 2 +- frontend/src/services/lookups.tsx | 4 +- 11 files changed, 69 insertions(+), 56 deletions(-) diff --git a/frontend/src/components/brackets/match.tsx b/frontend/src/components/brackets/match.tsx index f301dd1c4..697e9d8f7 100644 --- a/frontend/src/components/brackets/match.tsx +++ b/frontend/src/components/brackets/match.tsx @@ -132,6 +132,7 @@ export default function Match({ opened={opened} setOpened={setOpened} dynamicSchedule={dynamicSchedule} + priorMatch={null} /> ); diff --git a/frontend/src/components/builder/builder.tsx b/frontend/src/components/builder/builder.tsx index 8ca895922..796b5b69c 100644 --- a/frontend/src/components/builder/builder.tsx +++ b/frontend/src/components/builder/builder.tsx @@ -28,12 +28,10 @@ function StageItemInputSectionLast({ lastInList, }: { input: StageItemInput; - team: TeamInterface | null; - teamStageItem: TeamInterface | null; + team: TeamInterface; + teamStageItem: StageItemWithRounds; lastInList: boolean; }) { - assert(team != null || teamStageItem != null); - const content = team ? team.name : // @ts-ignore @@ -53,7 +51,7 @@ function StageItemRow({ stageItem, swrStagesResponse, }: { - teamsMap: any; + teamsMap: NonNullable>; tournament: Tournament; stageItem: StageItemWithRounds; swrStagesResponse: SWRResponse; @@ -70,12 +68,14 @@ function StageItemRow({ ? stageItemsLookup[input.winner_from_stage_item_id] : null; + assert(team != null || teamStageItem != null); + return ( ); diff --git a/frontend/src/components/modals/match_modal.tsx b/frontend/src/components/modals/match_modal.tsx index 4d250c58b..dcc4552a9 100644 --- a/frontend/src/components/modals/match_modal.tsx +++ b/frontend/src/components/modals/match_modal.tsx @@ -20,13 +20,8 @@ import DeleteButton from '../buttons/delete'; interface MatchModalBaseProps { tournamentData: TournamentMinimal; - // match: MatchInterface | null; - // swrStagesResponse: SWRResponse; swrUpcomingMatchesResponse: SWRResponse | null; - // opened: boolean; - // setOpened: any; dynamicSchedule: boolean; - // priorMatch?: MatchInterface; } interface MatchModalProps extends MatchModalBaseProps { @@ -36,6 +31,11 @@ interface MatchModalProps extends MatchModalBaseProps { priorMatch: MatchInterface | null; } +/** + * A typical implementation for opening a match modal. Useful for other components, especially in pages. + */ +export type OpenMatchModalFn = (match: MatchInterface, priorMatch: MatchInterface | null) => void; + function MatchDeleteButton({ tournamentData, match, diff --git a/frontend/src/interfaces/stage.tsx b/frontend/src/interfaces/stage.tsx index 644251f33..d41c43dd2 100644 --- a/frontend/src/interfaces/stage.tsx +++ b/frontend/src/interfaces/stage.tsx @@ -12,5 +12,6 @@ export interface StageWithStageItems { } export function getStageById(swrStagesResponse: SWRResponse, stageId: number) { - return swrStagesResponse.data.data.filter((stage: StageWithStageItems) => stage.id === stageId); + return (swrStagesResponse.data.data as StageWithStageItems[]) + .filter((stage: StageWithStageItems) => stage.id === stageId); } diff --git a/frontend/src/interfaces/stage_item.tsx b/frontend/src/interfaces/stage_item.tsx index 46993c30b..57281e69a 100644 --- a/frontend/src/interfaces/stage_item.tsx +++ b/frontend/src/interfaces/stage_item.tsx @@ -3,7 +3,7 @@ import { StageItemInput } from './stage_item_input'; export interface StageItemWithRounds { id: number; - tournament_id: number; + stage_id: number; created: string; type: string; name: string; diff --git a/frontend/src/pages/tournaments/[id]/dashboard/present/courts.tsx b/frontend/src/pages/tournaments/[id]/dashboard/present/courts.tsx index 9a3470a6a..2c17bfe03 100644 --- a/frontend/src/pages/tournaments/[id]/dashboard/present/courts.tsx +++ b/frontend/src/pages/tournaments/[id]/dashboard/present/courts.tsx @@ -48,7 +48,7 @@ export default function CourtsPage() { const courts = responseIsValid(swrCourtsResponse) ? swrCourtsResponse.data.data : []; const matchesByCourtId = responseIsValid(swrStagesResponse) ? getMatchLookupByCourt(swrStagesResponse) - : []; + : {}; const rows = courts.map((court: Court) => { const matchesForCourt = matchesByCourtId[court.id] || []; diff --git a/frontend/src/pages/tournaments/[id]/results.tsx b/frontend/src/pages/tournaments/[id]/results.tsx index 312d06d57..d3e9b689a 100644 --- a/frontend/src/pages/tournaments/[id]/results.tsx +++ b/frontend/src/pages/tournaments/[id]/results.tsx @@ -14,9 +14,9 @@ import { import { IconAlertCircle } from '@tabler/icons-react'; import { useTranslation } from 'next-i18next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; -import MatchModal from '../../../components/modals/match_modal'; +import MatchModal, { OpenMatchModalFn } from '../../../components/modals/match_modal'; import { NoContentDashboard } from '../../../components/no_content/empty_table_info'; import { Time, formatTime } from '../../../components/utils/datetime'; import { Translator } from '../../../components/utils/types'; @@ -31,11 +31,13 @@ function ScheduleRow({ openMatchModal, stageItemsLookup, matchesLookup, + previousMatch, }: { - data: any; - openMatchModal: any; + data: { match: MatchInterface; stageItem: any }; + openMatchModal: OpenMatchModalFn; stageItemsLookup: any; matchesLookup: any; + previousMatch: MatchInterface | null; }) { const winColor = '#2a8f37'; const drawColor = '#656565'; @@ -62,14 +64,14 @@ function ScheduleRow({ mt="md" pt="0rem" onClick={() => { - openMatchModal(data.match); + openMatchModal(data.match, previousMatch); }} > - {data.match.court.name} + {data.match.court!.name} @@ -147,10 +149,10 @@ function Schedule({ }: { t: Translator; stageItemsLookup: any; - openMatchModal: CallableFunction; - matchesLookup: any; + openMatchModal: OpenMatchModalFn; + matchesLookup: ReturnType; }) { - const matches: any[] = Object.values(matchesLookup); + const matches = Object.values(matchesLookup); const sortedMatches = matches .filter((m1: any) => m1.match.start_time != null) .sort((m1: any, m2: any) => (m1.match.court?.name > m2.match.court?.name ? 1 : -1)) @@ -182,6 +184,7 @@ function Schedule({ openMatchModal={openMatchModal} stageItemsLookup={stageItemsLookup} matchesLookup={matchesLookup} + previousMatch={c > 0 ? sortedMatches[c - 1].match : null} /> ); } @@ -193,7 +196,7 @@ function Schedule({ } const noItemsAlert = - matchesLookup.length < 1 ? ( + Object.getOwnPropertyNames(matchesLookup).length < 1 ? ( } title={t('no_matches_title')} @@ -217,6 +220,7 @@ function Schedule({ export default function SchedulePage() { const [modalOpened, modalSetOpened] = useState(false); const [match, setMatch] = useState(null); + const [priorMatch, setPriorMatch] = useState(null); const { t } = useTranslation(); const { tournamentData } = getTournamentIdFromRouter(); @@ -226,22 +230,19 @@ export default function SchedulePage() { const stageItemsLookup = responseIsValid(swrStagesResponse) ? getStageItemLookup(swrStagesResponse) : []; - const matchesLookup = responseIsValid(swrStagesResponse) ? getMatchLookup(swrStagesResponse) : []; + const matchesLookup = responseIsValid(swrStagesResponse) ? getMatchLookup(swrStagesResponse) : {}; - if (!responseIsValid(swrStagesResponse)) return null; - if (!responseIsValid(swrCourtsResponse)) return null; - - function openMatchModal(matchToOpen: MatchInterface) { + const openMatchModal: OpenMatchModalFn = useCallback(( + matchToOpen: MatchInterface, + priorMatchToOpen: MatchInterface | null + ) => { setMatch(matchToOpen); + setPriorMatch(priorMatchToOpen); modalSetOpened(true); - } + }, [setMatch, setPriorMatch, modalSetOpened]); - function modalSetOpenedAndUpdateMatch(opened: boolean) { - if (!opened) { - setMatch(null); - } - modalSetOpened(opened); - } + if (!responseIsValid(swrStagesResponse)) return null; + if (!responseIsValid(swrCourtsResponse)) return null; return ( @@ -251,8 +252,12 @@ export default function SchedulePage() { tournamentData={tournamentData} match={match} opened={modalOpened} - setOpened={modalSetOpenedAndUpdateMatch} + setOpened={(openend) => { + if (openend === false) setMatch(null); + modalSetOpened(openend); + }} dynamicSchedule={false} + priorMatch={priorMatch} /> {t('results_title')}
diff --git a/frontend/src/pages/tournaments/[id]/schedule.tsx b/frontend/src/pages/tournaments/[id]/schedule.tsx index 27edd8215..d051c7ca0 100644 --- a/frontend/src/pages/tournaments/[id]/schedule.tsx +++ b/frontend/src/pages/tournaments/[id]/schedule.tsx @@ -6,7 +6,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import React, { useCallback, useState } from 'react'; import { BiEditAlt } from 'react-icons/bi'; -import MatchModal from '../../../components/modals/match_modal'; +import MatchModal, { OpenMatchModalFn } from '../../../components/modals/match_modal'; import { NoContent } from '../../../components/no_content/empty_table_info'; import { DateTime } from '../../../components/utils/datetime'; import { Translator } from '../../../components/utils/types'; @@ -34,10 +34,10 @@ function ScheduleRow({ }: { index: number; match: MatchInterface; - openMatchModal: (match: MatchInterface, priorMatch?: MatchInterface) => void; + openMatchModal: OpenMatchModalFn; stageItemsLookup: any; matchesLookup: any; - previousMatch?: MatchInterface; + previousMatch: MatchInterface | null; }) { return ( @@ -94,7 +94,7 @@ function ScheduleColumn({ }: { court: Court; matches: MatchInterface[]; - openMatchModal: any; + openMatchModal: OpenMatchModalFn; stageItemsLookup: any; matchesLookup: any; }) { @@ -107,7 +107,7 @@ function ScheduleColumn({ match={match} openMatchModal={openMatchModal} key={match.id} - previousMatch={index > 0 ? matches[index - 1] : undefined} + previousMatch={index > 0 ? matches[index - 1] : null} /> )); @@ -150,7 +150,7 @@ function Schedule({ stageItemsLookup: any; matchesLookup: any; schedule: { court: Court; matches: MatchInterface[] }[]; - openMatchModal: CallableFunction; + openMatchModal: OpenMatchModalFn; }) { const columns = schedule.map((item) => ( { diff --git a/frontend/src/pages/tournaments/[id]/swiss/[stage_item_id].tsx b/frontend/src/pages/tournaments/[id]/swiss/[stage_item_id].tsx index c819760cb..7bc015877 100644 --- a/frontend/src/pages/tournaments/[id]/swiss/[stage_item_id].tsx +++ b/frontend/src/pages/tournaments/[id]/swiss/[stage_item_id].tsx @@ -18,8 +18,8 @@ import { 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 { StageWithStageItems, getStageById } from '../../../../interfaces/stage'; +import { StageItemWithRounds, stageItemIsHandledAutomatically } from '../../../../interfaces/stage_item'; import { getTournamentEndpoint } from '../../../../interfaces/tournament'; import { checkForAuthError, @@ -71,12 +71,12 @@ export default function TournamentPage() { const tournamentDataFull = swrTournamentResponse.data != null ? swrTournamentResponse.data.data : null; - let activeStage = null; - let draftRound = null; - let stageItem = null; + let activeStage: StageWithStageItems | null = null; + let draftRound: RoundInterface | null = null; + let stageItem: StageItemWithRounds | null = null; - if (responseIsValid(swrStagesResponse) && stageItemId != null) { - stageItem = getStageItemLookup(swrStagesResponse)[stageItemId]; + if (responseIsValid(swrStagesResponse)) { + stageItem = getStageItemLookup(swrStagesResponse)[stageItemId] ?? null; [activeStage] = getStageById(swrStagesResponse, stageItem.stage_id); if (activeStage != null && activeStage.stage_items != null) { @@ -90,7 +90,11 @@ export default function TournamentPage() { } } - const swrUpcomingMatchesResponse = getUpcomingMatches(id, draftRound?.id, schedulerSettings); + const swrUpcomingMatchesResponse = getUpcomingMatches( + id, + draftRound?.id ?? null, + schedulerSettings + ); const scheduler = draftRound != null && stageItem != null && @@ -163,7 +167,7 @@ export default function TournamentPage() { swrStagesResponse={swrStagesResponse} swrUpcomingMatchesResponse={swrUpcomingMatchesResponse} readOnly={false} - stageItem={stageItem} + stageItem={stageItem!} // TODO: Actually check if stageItem exists before using it, remove this once proper checks are in place displaySettings={displaySettings} /> {scheduler} diff --git a/frontend/src/services/adapter.tsx b/frontend/src/services/adapter.tsx index 4e6015c6a..9c6739505 100644 --- a/frontend/src/services/adapter.tsx +++ b/frontend/src/services/adapter.tsx @@ -187,7 +187,7 @@ export function getUser(): SWRResponse { export function getUpcomingMatches( tournament_id: number, - round_id: number, + round_id: number | null, schedulerSettings: SchedulerSettings ): SWRResponse { return useSWR( diff --git a/frontend/src/services/lookups.tsx b/frontend/src/services/lookups.tsx index 520058f28..4a9254e1f 100644 --- a/frontend/src/services/lookups.tsx +++ b/frontend/src/services/lookups.tsx @@ -16,7 +16,9 @@ export function getTeamsLookup(tournamentId: number) { if (!isResponseValid) { return null; } - return Object.fromEntries(swrTeamsResponse.data.data.teams.map((x: TeamInterface) => [x.id, x])); + return Object.fromEntries( + (swrTeamsResponse.data.data.teams as TeamInterface[]).map((x: TeamInterface) => [x.id, x]) + ); } export function getStageItemLookup( From b28e06ee8f76e859e37fb14936f55ea9255c5b0d Mon Sep 17 00:00:00 2001 From: robigan <35210888+robigan@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:10:55 +0200 Subject: [PATCH 20/20] Prettier write --- .../src/components/modals/match_modal.tsx | 124 ++++++++++-------- frontend/src/components/utils/util.tsx | 18 +-- frontend/src/interfaces/stage.tsx | 5 +- .../src/pages/tournaments/[id]/results.tsx | 16 +-- .../src/pages/tournaments/[id]/schedule.tsx | 31 +++-- .../[id]/swiss/[stage_item_id].tsx | 5 +- frontend/src/services/lookups.tsx | 8 +- 7 files changed, 116 insertions(+), 91 deletions(-) diff --git a/frontend/src/components/modals/match_modal.tsx b/frontend/src/components/modals/match_modal.tsx index dcc4552a9..381c8e252 100644 --- a/frontend/src/components/modals/match_modal.tsx +++ b/frontend/src/components/modals/match_modal.tsx @@ -1,12 +1,22 @@ -import { Button, Center, Checkbox, Divider, Grid, Input, Modal, NumberInput, Text } from '@mantine/core'; +import { + Button, + Center, + Checkbox, + Divider, + Grid, + Input, + Modal, + NumberInput, + Text, +} from '@mantine/core'; +import { DateTimePicker } from '@mantine/dates'; import { useForm } from '@mantine/form'; +import { showNotification } from '@mantine/notifications'; +import { format, fromUnixTime, getUnixTime, parseISO } from 'date-fns'; import { useTranslation } from 'next-i18next'; import React, { useMemo, useState } from 'react'; import { SWRResponse } from 'swr'; -import { format, fromUnixTime, getUnixTime, parseISO } from 'date-fns'; -import { DateTimePicker } from '@mantine/dates'; -import { showNotification } from '@mantine/notifications'; import { MatchBodyInterface, MatchInterface, @@ -220,62 +230,62 @@ function MatchModalForm({ {priorMatch && ( - <> - + <> + - - - - - - - - - - - )} + await updateMatch(tournamentData.id, priorMatch.id, updatedMatch); + await swrStagesResponse.mutate(); + }} + > + {t('calculate_label')} + + + + + )}