diff --git a/app/hooks/useTournamentRounds/index.ts b/app/hooks/useTournamentRounds/index.ts index df02d432f..2947245fd 100644 --- a/app/hooks/useTournamentRounds/index.ts +++ b/app/hooks/useTournamentRounds/index.ts @@ -14,39 +14,41 @@ export function useTournamentRounds(args: UseTournamentRoundsArgs) { switch (action.type) { case "START_EDITING_ROUND": { const newState = clone(oldState); - newState[action.data.side] = newState[action.data.side].map( - (round, i) => - i === action.data.index - ? { ...round, editing: true, newMapList: [...round.mapList] } - : round + newState.bracket[action.data.side] = newState.bracket[ + action.data.side + ].map((round, i) => + i === action.data.index + ? { ...round, editing: true, newMapList: [...round.mapList] } + : round ); - return newState; + return calculateRoundsBeingEditedAndAdjustState(newState); } case "CANCEL_EDITING_ROUND": { const newState = clone(oldState); - newState[action.data.side] = newState[action.data.side].map( - (round, i) => - i === action.data.index ? { ...round, editing: false } : round + newState.bracket[action.data.side] = newState.bracket[ + action.data.side + ].map((round, i) => + i === action.data.index ? { ...round, editing: false } : round ); - return newState; + return calculateRoundsBeingEditedAndAdjustState(newState); } case "SAVE_ROUND": { const newState = clone(oldState); - newState[action.data.side] = newState[action.data.side].map( - (round, i) => { - if (i !== action.data.index) return round; - invariant(round.newMapList, "round.newMapList is undefined"); - return { ...round, editing: false, mapList: round.newMapList }; - } - ); + newState.bracket[action.data.side] = newState.bracket[ + action.data.side + ].map((round, i) => { + if (i !== action.data.index) return round; + invariant(round.newMapList, "round.newMapList is undefined"); + return { ...round, editing: false, mapList: round.newMapList }; + }); - return newState; + return calculateRoundsBeingEditedAndAdjustState(newState); } case "EDIT_STAGE": { const newState = clone(oldState); - const roundToEdit = newState[action.data.side].find( + const roundToEdit = newState.bracket[action.data.side].find( (_, i) => i === action.data.index ); invariant( @@ -64,43 +66,70 @@ export function useTournamentRounds(args: UseTournamentRoundsArgs) { return newState; } case "REGENERATE_MAP_LIST": { - return regenMapList(oldState); + return regenMapList({ oldState }); } case "SET_ROUND_BEST_OF": { const newState = clone(oldState); - newState[action.data.side][action.data.index].bestOf = + newState.bracket[action.data.side][action.data.index].bestOf = action.data.newBestOf; - return regenMapList(newState); + return regenMapList({ oldState, newState }); + } + case "SHOW_ALERT": { + return { ...oldState, showAlert: true }; } default: { return oldState; } } }, - args.initialState + { bracket: args.initialState, showAlert: false } ); - function regenMapList( - state: UseTournamentRoundsState - ): UseTournamentRoundsState { + function regenMapList({ + oldState, + newState: _newState, + }: { + oldState: UseTournamentRoundsState; + newState?: UseTournamentRoundsState; + }): UseTournamentRoundsState { + const newState = _newState ?? oldState; const newMapLists = generateMapListForRounds({ mapPool: args.mapPool, rounds: { - winners: state.winners.map((r) => r.bestOf), - losers: state.losers.map((r) => r.bestOf), + winners: newState.bracket.winners.map((r) => r.bestOf), + losers: newState.bracket.losers.map((r) => r.bestOf), }, }); return { - winners: state.winners.map((round, i) => ({ - ...round, - mapList: newMapLists.winners[i], - })), - losers: state.losers.map((round, i) => ({ - ...round, - mapList: newMapLists.losers[i], - })), + showAlert: oldState.showAlert, + bracket: { + winners: newState.bracket.winners.map((round, i) => ({ + ...round, + mapList: newMapLists.winners[i], + })), + losers: newState.bracket.losers.map((round, i) => ({ + ...round, + mapList: newMapLists.losers[i], + })), + }, + }; + } + + function calculateRoundsBeingEditedAndAdjustState( + newState: UseTournamentRoundsState + ): UseTournamentRoundsState { + const beingEditedCount = [newState.bracket.winners, newState.bracket.losers] + .flat() + .reduce((acc, cur) => { + return acc + (cur.editing ? 1 : 0); + }, 0); + + return { + ...newState, + showAlert: beingEditedCount === 0 ? false : newState.showAlert, + actionButtonsDisabled: beingEditedCount > 0 ? true : false, }; } } diff --git a/app/hooks/useTournamentRounds/types.ts b/app/hooks/useTournamentRounds/types.ts index a22aca4cb..206c964a2 100644 --- a/app/hooks/useTournamentRounds/types.ts +++ b/app/hooks/useTournamentRounds/types.ts @@ -6,18 +6,22 @@ import type { } from "~/core/tournament/bracket"; import { MyReducerAction } from "~/utils"; -export type UseTournamentRoundsState = EliminationBracket< - { - bestOf: BestOf; - name: string; - mapList: Stage[]; - newMapList?: Stage[]; - editing?: boolean; - }[] ->; +export type UseTournamentRoundsState = { + bracket: EliminationBracket< + { + bestOf: BestOf; + name: string; + mapList: Stage[]; + newMapList?: Stage[]; + editing?: boolean; + }[] + >; + showAlert: boolean; + actionButtonsDisabled?: boolean; +}; export interface UseTournamentRoundsArgs { - initialState: UseTournamentRoundsState; + initialState: UseTournamentRoundsState["bracket"]; mapPool: Stage[]; } @@ -47,20 +51,5 @@ export type UseTournamentRoundsAction = | MyReducerAction< "SET_ROUND_BEST_OF", { newBestOf: BestOf; side: EliminationBracketSide; index: number } - >; - -// export type Action = -// | { -// type: "SET_PLAYER"; -// name: string; -// number: number; -// } -// | { type: "CREATE_FIRST_MATCH" } -// | { type: "SET_WINNER"; winner: "alpha" | "bravo" } -// | { -// type: "SET_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS"; -// amountOfRoundsWithSameTeams: number; -// } -// | { type: "UNDO_LATEST_MATCH" } -// | { type: "RESET" } -// | { type: "SET_NO_PLACING_TO_SAME_TEAM"; id: number; checked: boolean }; + > + | MyReducerAction<"SHOW_ALERT">; diff --git a/app/routes/to/$organization.$tournament/start.tsx b/app/routes/to/$organization.$tournament/start.tsx index c42fce449..c44380a15 100644 --- a/app/routes/to/$organization.$tournament/start.tsx +++ b/app/routes/to/$organization.$tournament/start.tsx @@ -3,6 +3,7 @@ import classNames from "classnames"; import type { LinksFunction, LoaderFunction } from "remix"; import { json, useLoaderData } from "remix"; import invariant from "tiny-invariant"; +import { Alert } from "~/components/Alert"; import { Button } from "~/components/Button"; import { modesShort, modesShortToLong } from "~/constants"; import { eliminationBracket } from "~/core/tournament/algorithms"; @@ -19,7 +20,6 @@ import type { import { findTournamentByNameForUrl } from "~/services/tournament"; import startBracketTabStylesUrl from "~/styles/tournament-start.css"; -// TODO: some warning if trying to continue and some round card is editing // TODO: error if not admin AND keep the links available export const links: LinksFunction = () => { @@ -61,45 +61,78 @@ export const loader: LoaderFunction = async ({ params }) => { // TODO: handle warning if check-in has not concluded export default function StartBracketTab() { const args = useLoaderData(); - const [rounds, dispatch] = useTournamentRounds(args); + const [{ bracket, showAlert, actionButtonsDisabled }, dispatch] = + useTournamentRounds(args); return (
- +
{args.initialState.losers.length > 0 && ( )}
- +
); } function ActionButtons({ dispatch, + disabled, + showAlert, + position, }: { dispatch: React.Dispatch; + disabled?: boolean; + showAlert: boolean; + position: "top" | "bottom"; }) { return ( -
- - -
+ <> + {position === "bottom" && showAlert && ( + + Save or cancel the round being edited to continue or regenerate maps + + )} +
+ + +
+ {position === "top" && showAlert && ( + + Save or cancel the round being edited to continue or regenerate maps + + )} + ); } @@ -110,7 +143,7 @@ function RoundsCollection({ }: { side: EliminationBracketSide; dispatch: React.Dispatch; - rounds: UseTournamentRoundsState["winners"]; + rounds: UseTournamentRoundsState["bracket"]["winners"]; }) { const { mapPool } = useLoaderData(); @@ -205,7 +238,7 @@ function RoundsCollection({ ); })} -
+
{round.editing ? ( <>