import { Form, useActionData, useLoaderData, useOutletContext, } from "@remix-run/react"; import clsx from "clsx"; import { Image } from "~/components/Image"; import { SubmitButton } from "~/components/SubmitButton"; import { useTranslation } from "~/hooks/useTranslation"; import type { ModeShort, StageId } from "~/modules/in-game-lists"; import type { TournamentMapListMap } from "~/modules/tournament-map-list-generator"; import { modeImageUrl, stageImageUrl } from "~/utils/urls"; import type { TournamentMatchLoaderData } from "../routes/to.$id.matches.$mid"; import { HACKY_resolvePoolCode, mapCountPlayedInSetWithCertainty, resolveHostingTeam, resolveRoomPass, } from "../tournament-bracket-utils"; import { ScoreReporterRosters } from "./ScoreReporterRosters"; import type { SerializeFrom } from "@remix-run/node"; import type { Unpacked } from "~/utils/types"; import type { TournamentLoaderTeam, TournamentLoaderData, } from "~/features/tournament"; import { canAdminTournament } from "~/permissions"; import { useUser } from "~/modules/auth"; import { useIsMounted } from "~/hooks/useIsMounted"; import { databaseTimestampToDate } from "~/utils/dates"; export type Result = Unpacked< SerializeFrom["results"] >; export function ScoreReporter({ teams, currentStageWithMode, modes, selectedResultIndex, setSelectedResultIndex, result, type, }: { teams: [TournamentLoaderTeam, TournamentLoaderTeam]; currentStageWithMode: TournamentMapListMap; modes: ModeShort[]; selectedResultIndex?: number; // if this is set it means the component is being used in presentation manner setSelectedResultIndex?: (index: number) => void; result?: Result; type: "EDIT" | "MEMBER" | "OTHER"; }) { const isMounted = useIsMounted(); const actionData = useActionData<{ error?: "locked" }>(); const user = useUser(); const parentRouteData = useOutletContext(); const data = useLoaderData(); const scoreOne = data.match.opponentOne?.score ?? 0; const scoreTwo = data.match.opponentTwo?.score ?? 0; const currentPosition = scoreOne + scoreTwo; const presentational = Boolean(setSelectedResultIndex); const showFullInfos = !presentational && (type === "EDIT" || type === "MEMBER"); const roundInfos = [ showFullInfos ? ( <> {resolveHostingTeam(teams).name} hosts ) : null, showFullInfos ? ( <> Pass {resolveRoomPass(data.match.id)} ) : null, showFullInfos ? ( <> Pool {HACKY_resolvePoolCode(parentRouteData.event)} ) : null, <> {scoreOne}-{scoreTwo} {" "} (Best of {data.match.bestOf}) , ]; const matchIsLockedError = actionData?.error === "locked"; return (
{currentPosition > 0 && !presentational && type === "EDIT" && (
Undo last score
)} {canAdminTournament({ user, event: parentRouteData.event }) && presentational && !matchIsLockedError && (
Reopen match
)} {matchIsLockedError && (
Match is locked
)}
{type === "EDIT" || presentational ? ( ) : null} {result ? (
{isMounted ? databaseTimestampToDate(result.createdAt).toLocaleString() : "t"}
) : null}
); } function FancyStageBanner({ stage, infos, children, teams, }: { stage: TournamentMapListMap; infos?: (JSX.Element | null)[]; children?: React.ReactNode; teams: [TournamentLoaderTeam, TournamentLoaderTeam]; }) { const { t } = useTranslation(["game-misc", "tournament"]); const stageNameToBannerImageUrl = (stageId: StageId) => { return stageImageUrl(stageId) + ".png"; }; const style = { "--_tournament-bg-url": `url("${stageNameToBannerImageUrl( stage.stageId )}")`, }; const pickInfoText = () => { if (stage.source === teams[0].id) return t("tournament:pickInfo.team", { number: 1 }); if (stage.source === teams[1].id) return t("tournament:pickInfo.team", { number: 2 }); if (stage.source === "TIEBREAKER") return t("tournament:pickInfo.tiebreaker"); if (stage.source === "BOTH") return t("tournament:pickInfo.both"); if (stage.source === "DEFAULT") return t("tournament:pickInfo.default"); console.error(`Unknown source: ${String(stage.source)}`); return ""; }; return ( <>

{t(`game-misc:MODE_LONG_${stage.mode}`)} on{" "} {t(`game-misc:STAGE_${stage.stageId}`)}

{pickInfoText()}

{children}
{infos && (
{infos.filter(Boolean).map((info, i) => (
{info}
))}
)} ); } function ModeProgressIndicator({ modes, scores, bestOf, selectedResultIndex, setSelectedResultIndex, }: { modes: ModeShort[]; scores: [number, number]; bestOf: number; selectedResultIndex?: number; setSelectedResultIndex?: (index: number) => void; }) { const data = useLoaderData(); const { t } = useTranslation(["game-misc"]); const maxIndexThatWillBePlayedForSure = mapCountPlayedInSetWithCertainty({ bestOf, scores }) - 1; // TODO: this should be button when we click on it return (
{modes.map((mode, i) => { return ( {t(`game-misc:MODE_LONG_${mode}`)} setSelectedResultIndex?.(i)} /> ); })}
); } function ActionSectionWrapper({ children, icon, ...rest }: { children: React.ReactNode; icon?: "warning" | "info" | "success" | "error"; "justify-center"?: boolean; "data-cy"?: string; }) { // todo: flex-dir: column on mobile const style = icon ? { "--action-section-icon-color": `var(--theme-${icon})`, } : undefined; return (
{children}
); }