Disable buttons on /start if round being edited

This commit is contained in:
Kalle (Sendou)
2021-12-18 08:57:13 +02:00
parent d4e5a64b84
commit c48bdea053
4 changed files with 139 additions and 82 deletions

View File

@@ -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,
};
}
}

View File

@@ -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">;

View File

@@ -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<UseTournamentRoundsArgs>();
const [rounds, dispatch] = useTournamentRounds(args);
const [{ bracket, showAlert, actionButtonsDisabled }, dispatch] =
useTournamentRounds(args);
return (
<div className="tournament__start__container">
<ActionButtons dispatch={dispatch} />
<ActionButtons
dispatch={dispatch}
disabled={actionButtonsDisabled}
showAlert={showAlert}
position="top"
/>
<div className="tournament__start__round-collections-container">
<RoundsCollection
side="winners"
dispatch={dispatch}
rounds={rounds.winners}
rounds={bracket.winners}
/>
{args.initialState.losers.length > 0 && (
<RoundsCollection
side="losers"
dispatch={dispatch}
rounds={rounds.losers}
rounds={bracket.losers}
/>
)}
</div>
<ActionButtons dispatch={dispatch} />
<ActionButtons
dispatch={dispatch}
disabled={actionButtonsDisabled}
showAlert={showAlert}
position="bottom"
/>
</div>
);
}
function ActionButtons({
dispatch,
disabled,
showAlert,
position,
}: {
dispatch: React.Dispatch<UseTournamentRoundsAction>;
disabled?: boolean;
showAlert: boolean;
position: "top" | "bottom";
}) {
return (
<div className="tournament__start__buttons-container">
<Button>Preview bracket</Button>
<Button
variant="outlined"
onClick={() => dispatch({ type: "REGENERATE_MAP_LIST" })}
>
Regenerate all maps
</Button>
</div>
<>
{position === "bottom" && showAlert && (
<Alert type="warning">
Save or cancel the round being edited to continue or regenerate maps
</Alert>
)}
<div className="tournament__start__action-buttons__container">
<Button>Preview bracket</Button>
<Button
variant="outlined"
onClick={() =>
disabled
? dispatch({ type: "SHOW_ALERT" })
: dispatch({ type: "REGENERATE_MAP_LIST" })
}
>
Regenerate all maps
</Button>
</div>
{position === "top" && showAlert && (
<Alert type="warning">
Save or cancel the round being edited to continue or regenerate maps
</Alert>
)}
</>
);
}
@@ -110,7 +143,7 @@ function RoundsCollection({
}: {
side: EliminationBracketSide;
dispatch: React.Dispatch<UseTournamentRoundsAction>;
rounds: UseTournamentRoundsState["winners"];
rounds: UseTournamentRoundsState["bracket"]["winners"];
}) {
const { mapPool } = useLoaderData<UseTournamentRoundsArgs>();
@@ -205,7 +238,7 @@ function RoundsCollection({
);
})}
</ol>
<div className="tournament__start__round-card-buttons-container">
<div className="tournament__start__round-card-buttons__container">
{round.editing ? (
<>
<Button

View File

@@ -11,12 +11,18 @@
gap: var(--s-6);
}
.tournament__start__buttons-container {
.tournament__start__action-buttons__container {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--s-1-5);
}
.tournament__start__action-buttons__info {
color: var(--theme-error);
font-size: var(--fonts-xs);
}
.tournament__start__round-collections-container {
display: flex;
flex-direction: column;
@@ -77,7 +83,7 @@
padding-inline: var(--s-1);
}
.tournament__start__round-card-buttons-container {
.tournament__start__round-card-buttons__container {
display: flex;
justify-content: center;
gap: var(--s-4);