Seed page with rosters

This commit is contained in:
Kalle (Sendou)
2022-01-06 15:45:46 +02:00
parent 44e72a8a18
commit 4a2dfac884
3 changed files with 48 additions and 132 deletions

View File

@@ -27,11 +27,7 @@ import { Alert } from "~/components/Alert";
import { Button } from "~/components/Button";
import { Catcher } from "~/components/Catcher";
import { Draggable } from "~/components/Draggable";
import { TOURNAMENT_TEAM_ROSTER_MIN_SIZE } from "~/constants";
import { checkInHasStarted } from "~/core/tournament/utils";
import {
checkIn,
checkOut,
FindTournamentByNameForUrlI,
updateSeeds,
} from "~/services/tournament";
@@ -39,52 +35,22 @@ import seedsStylesUrl from "~/styles/tournament-seeds.css";
import { requireUser, Unpacked } from "~/utils";
import { useTimeoutState } from "~/utils/hooks";
enum Action {
UPDATE_SEEDS = "UPDATE_SEEDS",
CHECK_IN = "CHECK_IN",
CHECK_OUT = "CHECK_OUT",
}
export const action: ActionFunction = async ({ context, request }) => {
const data = Object.fromEntries(await request.formData());
invariant(typeof data._action === "string", "Invalid type for _action");
const user = requireUser(context);
switch (data._action) {
case Action.UPDATE_SEEDS: {
invariant(typeof data.seeds === "string", "Invalid type for seeds");
invariant(
typeof data.tournamentId === "string",
"Invalid type for tournamentId"
);
const newSeeds = JSON.parse(data.seeds);
invariant(typeof data.seeds === "string", "Invalid type for seeds");
invariant(
typeof data.tournamentId === "string",
"Invalid type for tournamentId"
);
const newSeeds = JSON.parse(data.seeds);
await updateSeeds({
tournamentId: data.tournamentId,
userId: user.id,
newSeeds,
});
break;
}
// TODO: broken
case Action.CHECK_IN: {
invariant(typeof data.teamId === "string", "Invalid type for seeds");
await checkIn({ teamId: data.teamId, userId: user.id });
break;
}
case Action.CHECK_OUT: {
invariant(typeof data.teamId === "string", "Invalid type for seeds");
await checkOut({ teamId: data.teamId, userId: user.id });
break;
}
default: {
throw new Response("Bad Request", { status: 400 });
}
}
await updateSeeds({
tournamentId: data.tournamentId,
userId: user.id,
newSeeds,
});
return new Response(undefined, { status: 200 });
};
@@ -97,8 +63,7 @@ export const links: LinksFunction = () => {
// TODO: error if not admin
export default function SeedsTab() {
const [, parentRoute] = useMatches();
const { id, teams, checkInStartTime } =
parentRoute.data as FindTournamentByNameForUrlI;
const { id, teams } = parentRoute.data as FindTournamentByNameForUrlI;
const transition = useTransition();
const [teamOrder, setTeamOrder] = React.useState(teams.map((t) => t.id));
const [activeTeam, setActiveTeam] = React.useState<Unpacked<
@@ -123,10 +88,7 @@ export default function SeedsTab() {
<div className="tournament__seeds__teams-container__header">Seed</div>
<div className="tournament__seeds__teams-container__header">Name</div>
<div className="tournament__seeds__teams-container__header">
{checkInHasStarted(checkInStartTime) ? "" : "Registered at"}
</div>
<div className="tournament__seeds__teams-container__header">
Roster size
Players
</div>
</li>
<DndContext
@@ -214,7 +176,7 @@ function SeedAlert({
return (
<Form method="post" className="tournament__seeds__form">
<input type="hidden" name="_action" value={Action.UPDATE_SEEDS} />
<input type="hidden" name="_action" value="UPDATE_SEEDS" />
<input type="hidden" name="tournamentId" value={tournamentId} />
<input type="hidden" name="seeds" value={JSON.stringify(teamOrder)} />
<Alert
@@ -244,90 +206,28 @@ function SeedAlert({
);
}
function RowContents({
const RowContents = React.memo(function RowContents({
team,
seed,
}: {
team: Unpacked<FindTournamentByNameForUrlI["teams"]>;
seed?: number;
}) {
const [, parentRoute] = useMatches();
const { checkInStartTime } = parentRoute.data as FindTournamentByNameForUrlI;
return (
<>
<div>{seed}</div>
<div>{team.name}</div>
<div>
{!checkInHasStarted(checkInStartTime) ? (
<>
{new Date(team.createdAt).toLocaleString("en-US", {
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
</>
) : team.checkedInTime ? (
<CheckOutButton teamId={team.id} />
) : team.members.length >= TOURNAMENT_TEAM_ROSTER_MIN_SIZE ? (
<CheckInButton teamId={team.id} />
) : null}
</div>
<div
className={clsx({
tournament__seeds__ok:
team.members.length >= TOURNAMENT_TEAM_ROSTER_MIN_SIZE,
tournament__seeds__problem:
team.members.length < TOURNAMENT_TEAM_ROSTER_MIN_SIZE,
})}
<div className="tournament__seeds__team-name">{team.name}</div>
{/* TODO: scrollbar overlaps on windows? */}
<ol
className="tournament__seeds__members-list"
// style={{ whiteSpace: "nowrap", overflowX: "auto" }}
>
{team.members.length}
</div>
{team.members.map((member) => (
<li key={member.member.id}>{member.member.discordName}</li>
))}
</ol>
</>
);
}
function CheckOutButton({ teamId }: { teamId: string }) {
const transition = useTransition();
return (
<Form
method="post"
className="tournament__action-section__button-container"
>
<input type="hidden" name="_action" value={Action.CHECK_OUT} />
<input type="hidden" name="teamId" value={teamId} />
<Button
tiny
variant="minimal-destructive"
loading={transition.state !== "idle"}
type="submit"
>
Check-out
</Button>
</Form>
);
}
function CheckInButton({ teamId }: { teamId: string }) {
const transition = useTransition();
return (
<Form
method="post"
className="tournament__action-section__button-container"
>
<input type="hidden" name="_action" value={Action.CHECK_OUT} />
<input type="hidden" name="teamId" value={teamId} />
<Button
tiny
variant="minimal-success"
loading={transition.state !== "idle"}
type="submit"
>
Check-in
</Button>
</Form>
);
}
});
export const CatchBoundary = Catcher;

View File

@@ -7,8 +7,8 @@
display: grid;
width: 100%;
align-items: center;
margin: var(--s-1);
padding: var(--s-2);
margin: var(--s-1);
border-radius: var(--rounded);
column-gap: var(--s-1);
font-size: var(--fonts-sm);

View File

@@ -22,8 +22,8 @@
padding: var(--s-3);
border-radius: var(--rounded);
column-gap: var(--s-1);
font-size: var(--fonts-sm);
grid-template-columns: 1fr 3fr 3fr 1fr;
font-size: var(--fonts-xs);
grid-template-columns: 3rem 10rem 1fr;
list-style: none;
row-gap: var(--s-1-5);
}
@@ -49,10 +49,26 @@
font-weight: var(--bold);
}
.tournament__seeds__ok {
color: var(--theme-success);
.tournament__seeds__team-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tournament__seeds__problem {
color: var(--theme-error);
.tournament__seeds__members-list {
display: flex;
padding: 0;
counter-reset: list;
}
.tournament__seeds__members-list > li {
list-style: none;
margin-inline-start: var(--s-3);
white-space: nowrap;
}
.tournament__seeds__members-list > li::before {
color: var(--theme-secondary);
content: counter(list) ") ";
counter-increment: list;
}