mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-24 23:19:39 -05:00
Bracket action panel initial
This commit is contained in:
parent
c06de36cb2
commit
ff5377fc1f
|
|
@ -4,7 +4,7 @@ import { useTimeoutState } from "~/utils/hooks";
|
|||
import { Button, ButtonProps } from "./Button";
|
||||
|
||||
export function SubmitButton(
|
||||
_props: ButtonProps & { actionType: string; successText: string }
|
||||
_props: ButtonProps & { actionType: string; successText?: string }
|
||||
) {
|
||||
const { actionType, successText, children, ...rest } = _props;
|
||||
const actionData = useActionData<{ ok?: string }>();
|
||||
|
|
@ -12,7 +12,7 @@ export function SubmitButton(
|
|||
const [showSuccess, setShowSuccess] = useTimeoutState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (actionData?.ok !== actionType) return;
|
||||
if (!successText || actionData?.ok !== actionType) return;
|
||||
|
||||
setShowSuccess(true);
|
||||
}, [actionData]);
|
||||
|
|
@ -30,10 +30,10 @@ export function SubmitButton(
|
|||
<Button
|
||||
type="submit"
|
||||
loading={isLoading()}
|
||||
variant={showSuccess ? "success" : rest.variant}
|
||||
variant={showSuccess && successText ? "success" : rest.variant}
|
||||
{...rest}
|
||||
>
|
||||
{showSuccess ? successText : children}
|
||||
{showSuccess && successText ? successText : children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useUser } from "~/utils/hooks";
|
|||
import { ActionSectionBeforeStartContent } from "./ActionSectionBeforeStartContent";
|
||||
|
||||
// TODO: warning when not registered but check in is open
|
||||
// TODO: rename, refactor
|
||||
export function ActionSection() {
|
||||
const tournament = useLoaderData<FindTournamentByNameForUrlI>();
|
||||
const user = useUser();
|
||||
|
|
@ -14,16 +15,10 @@ export function ActionSection() {
|
|||
)
|
||||
);
|
||||
|
||||
if (!ownTeam) {
|
||||
const tournamentHasStarted = tournament.brackets.some((b) => b.rounds.length);
|
||||
if (!ownTeam || tournamentHasStarted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
const tournamentStatus = "not-started"; // "ongoing" | "concluded"
|
||||
|
||||
if (tournamentStatus === "not-started") {
|
||||
return <ActionSectionBeforeStartContent ownTeam={ownTeam} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
return <ActionSectionBeforeStartContent ownTeam={ownTeam} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,15 @@ export function ActionSectionWrapper({
|
|||
...rest
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
icon: "warning" | "info" | "success" | "error";
|
||||
icon?: "warning" | "info" | "success" | "error";
|
||||
"data-cy"?: string;
|
||||
}) {
|
||||
// todo: flex-dir: column on mobile
|
||||
const style: MyCSSProperties = {
|
||||
"--action-section-icon-color": `var(--theme-${icon})`,
|
||||
};
|
||||
const style: MyCSSProperties | undefined = icon
|
||||
? {
|
||||
"--action-section-icon-color": `var(--theme-${icon})`,
|
||||
}
|
||||
: undefined;
|
||||
return (
|
||||
<section
|
||||
className="tournament__action-section"
|
||||
|
|
|
|||
102
app/components/tournament/BracketActions.tsx
Normal file
102
app/components/tournament/BracketActions.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import { useLoaderData, useMatches } from "remix";
|
||||
import invariant from "tiny-invariant";
|
||||
import type {
|
||||
BracketModified,
|
||||
FindTournamentByNameForUrlI,
|
||||
} from "~/services/tournament";
|
||||
import { useUser } from "~/utils/hooks";
|
||||
import { ActionSectionWrapper } from "./ActionSectionWrapper";
|
||||
|
||||
// 2) set up UI
|
||||
// - 1) Add *fc* 2) Host/join room with pass XXXX 3) Done
|
||||
|
||||
// 3) report score
|
||||
// - Show map played
|
||||
// - Select players who played with radio boxes if team.length > min_roster_length
|
||||
// - Report
|
||||
|
||||
export function BracketActions() {
|
||||
const data = useLoaderData<BracketModified>();
|
||||
const user = useUser();
|
||||
const [, parentRoute] = useMatches();
|
||||
const { teams } = parentRoute.data as FindTournamentByNameForUrlI;
|
||||
|
||||
const ownTeam = teams.find((team) =>
|
||||
team.members.some(({ member }) => member.id === user?.id)
|
||||
);
|
||||
|
||||
const tournamentIsOver = false;
|
||||
|
||||
if (tournamentIsOver || !ownTeam) return null;
|
||||
|
||||
const allMatches = [
|
||||
...data.winners.flatMap((round, roundI) =>
|
||||
round.matches.map((match) => ({
|
||||
...match,
|
||||
bestOf: round.bestOf,
|
||||
isFirstRound: roundI === 0,
|
||||
}))
|
||||
),
|
||||
...data.losers.flatMap((round) =>
|
||||
round.matches.map((match) => ({
|
||||
...match,
|
||||
bestOf: round.bestOf,
|
||||
isFirstRound: false,
|
||||
}))
|
||||
),
|
||||
];
|
||||
const currentMatch = allMatches.find((match) => {
|
||||
const hasBothParticipants = match.participants?.length === 2;
|
||||
const isOwnMatch = match.participants?.some((p) => p === ownTeam.name);
|
||||
|
||||
const higherCount = match.score
|
||||
? Math.max(match.score[0], match.score[1])
|
||||
: 0;
|
||||
const isNotOver = higherCount > match.bestOf / 2;
|
||||
|
||||
return hasBothParticipants && isOwnMatch && isNotOver;
|
||||
});
|
||||
|
||||
if (currentMatch) {
|
||||
return <ActionSectionWrapper>current match stuff</ActionSectionWrapper>;
|
||||
}
|
||||
|
||||
const nextMatch = allMatches.find((match) => {
|
||||
return (
|
||||
match.participants?.reduce(
|
||||
(acc, cur) => acc + (cur === null ? 1 : 0),
|
||||
0
|
||||
) === 1 &&
|
||||
match.participants.some((p) => p === ownTeam.name) &&
|
||||
!match.isFirstRound
|
||||
);
|
||||
});
|
||||
|
||||
invariant(nextMatch, "nextMatch is undefined");
|
||||
const matchWeAreWaitingFor = allMatches.find(
|
||||
(match) =>
|
||||
[match.winnerDestinationMatchId, match.loserDestinationMatchId].includes(
|
||||
nextMatch.id
|
||||
) && !match.participants?.includes(ownTeam.name)
|
||||
);
|
||||
invariant(matchWeAreWaitingFor, "matchWeAreWaitingFor is undefined");
|
||||
|
||||
if (matchWeAreWaitingFor.participants?.length !== 2) {
|
||||
return (
|
||||
<ActionSectionWrapper>
|
||||
Waiting on match number {matchWeAreWaitingFor.number} (missing teams)
|
||||
</ActionSectionWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionSectionWrapper>
|
||||
Waiting on <b>{matchWeAreWaitingFor.participants[0]}</b> vs.
|
||||
<b>{matchWeAreWaitingFor.participants[1]}</b>
|
||||
<i>
|
||||
{(matchWeAreWaitingFor.score ?? [0, 0]).join("-")} - Best of{" "}
|
||||
{matchWeAreWaitingFor.bestOf}
|
||||
</i>
|
||||
</ActionSectionWrapper>
|
||||
);
|
||||
}
|
||||
|
|
@ -374,7 +374,6 @@ function resolveSide(
|
|||
const matchNumbers = getWinnerDestinationMatchIdToMatchNumbers(rounds).get(
|
||||
destinationMatch.id
|
||||
);
|
||||
console.log(matchNumbers);
|
||||
const otherNumber = matchNumbers?.find((num) => num !== currentMatch.number);
|
||||
invariant(
|
||||
otherNumber,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export function findById(bracketId: string) {
|
|||
select: {
|
||||
id: true,
|
||||
position: true,
|
||||
winnerDestinationMatchId: true,
|
||||
loserDestinationMatchId: true,
|
||||
participants: {
|
||||
select: {
|
||||
team: {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from "~/services/tournament";
|
||||
import type { BracketModified } from "~/services/tournament";
|
||||
import { useUser } from "~/utils/hooks";
|
||||
import { BracketActions } from "~/components/tournament/BracketActions";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
|
|
@ -27,8 +28,8 @@ export const loader: LoaderFunction = async ({ params }) => {
|
|||
export default function BracketTabWrapper() {
|
||||
const data = useLoaderData<BracketModified>();
|
||||
const [, parentRoute] = useMatches();
|
||||
const user = useUser();
|
||||
const { teams } = parentRoute.data as FindTournamentByNameForUrlI;
|
||||
const user = useUser();
|
||||
|
||||
const ownTeam = teams.find((team) =>
|
||||
team.members.some(({ member }) => member.id === user?.id)
|
||||
|
|
@ -36,6 +37,7 @@ export default function BracketTabWrapper() {
|
|||
|
||||
return (
|
||||
<div className="tournament-bracket__container">
|
||||
<BracketActions />
|
||||
<EliminationBracket
|
||||
bracketSide={data.winners}
|
||||
ownTeamName={ownTeam?.name}
|
||||
|
|
|
|||
|
|
@ -292,7 +292,6 @@ export default function ManageRosterPage() {
|
|||
className="tournament__manage-roster__input__button"
|
||||
actionType="ADD_PLAYER"
|
||||
loadingText="Adding..."
|
||||
successText="Added!"
|
||||
data-cy="add-to-roster-button"
|
||||
>
|
||||
Add to roster
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@ function CheckOutButton({ teamId }: { teamId: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: error Cannot destructure property 'default' of 'routeModules[id]' as it is undefined.
|
||||
function CheckInButton({ teamId }: { teamId: string }) {
|
||||
const transition = useTransition();
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ type BracketModifiedSide = {
|
|||
number: number;
|
||||
score?: [upperTeamScore: number, lowerTeamScore: number];
|
||||
participants?: [upperTeamName: string | null, lowerTeamName: string | null];
|
||||
winnerDestinationMatchId: string | null;
|
||||
loserDestinationMatchId: string | null;
|
||||
}[];
|
||||
}[];
|
||||
|
||||
|
|
@ -199,6 +201,8 @@ function modifyRounds(
|
|||
return {
|
||||
number: match.position,
|
||||
id: match.id,
|
||||
winnerDestinationMatchId: match.winnerDestinationMatchId,
|
||||
loserDestinationMatchId: match.loserDestinationMatchId,
|
||||
score,
|
||||
participants,
|
||||
};
|
||||
|
|
@ -274,6 +278,8 @@ export async function createTournamentRounds({
|
|||
id: match.id,
|
||||
position: match.number,
|
||||
roundId: round.id,
|
||||
winnerDestinationMatchId: match.winnerDestinationMatchId,
|
||||
loserDestinationMatchId: match.loserDestinationMatchId,
|
||||
}));
|
||||
}),
|
||||
}),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user