312 lines
9.2 KiB
TypeScript
312 lines
9.2 KiB
TypeScript
import { useCallback, useState } from "react";
|
||
import { Alert, Button, Flex, Group, Paper, Stack, Text } from "@mantine/core";
|
||
import { CheckIcon, LightningIcon } from "@phosphor-icons/react";
|
||
import { Match } from "@/features/matches/types";
|
||
import { useAuth } from "@/contexts/auth-context";
|
||
import {
|
||
useReportMatchScore,
|
||
useConfirmMatchScore,
|
||
useClearMatchReport,
|
||
} from "@/features/matches/queries";
|
||
import { useSheet } from "@/hooks/use-sheet";
|
||
import Sheet from "@/components/sheet/sheet";
|
||
import { MatchForm } from "./match-form";
|
||
import toast from "@/lib/sonner";
|
||
import { Trans, useLingui } from "@lingui/react/macro";
|
||
|
||
const teamId = (t: Match["home"]): string | undefined =>
|
||
!t ? undefined : typeof t === "string" ? t : t.id;
|
||
|
||
const teamPlayerIds = (t: Match["home"]): string[] =>
|
||
t && typeof t !== "string" ? (t.players ?? []).map((p) => p.id) : [];
|
||
|
||
const teamName = (t: Match["home"]): string | undefined =>
|
||
t && typeof t !== "string" ? t.name : undefined;
|
||
|
||
interface MatchReportProps {
|
||
match: Match;
|
||
compact?: boolean;
|
||
}
|
||
|
||
export const MatchReport: React.FC<MatchReportProps> = ({ match, compact }) => {
|
||
const { user } = useAuth();
|
||
const formSheet = useSheet();
|
||
const { t } = useLingui();
|
||
const [acknowledgedReport, setAcknowledgedReport] = useState<string | null>(
|
||
null
|
||
);
|
||
|
||
const homeId = teamId(match.home);
|
||
const onHome = !!user?.id && teamPlayerIds(match.home).includes(user.id);
|
||
const onAway = !!user?.id && teamPlayerIds(match.away).includes(user.id);
|
||
const isParticipant = onHome || onAway;
|
||
|
||
const hasPending =
|
||
match.reported_by_team != null &&
|
||
match.reported_home_cups != null &&
|
||
match.reported_away_cups != null;
|
||
|
||
const reportedByHome = hasPending && match.reported_by_team === homeId;
|
||
const reportingTeam = hasPending
|
||
? reportedByHome
|
||
? match.home
|
||
: match.away
|
||
: undefined;
|
||
const opposingTeam = reportedByHome ? match.away : match.home;
|
||
const reportedByMine =
|
||
hasPending && ((reportedByHome && onHome) || (!reportedByHome && onAway));
|
||
|
||
const pendingSignature = hasPending
|
||
? [
|
||
match.reported_by_team,
|
||
match.reported_home_cups,
|
||
match.reported_away_cups,
|
||
match.reported_ot_count ?? 0,
|
||
].join(":")
|
||
: null;
|
||
|
||
const sheetTakenOver =
|
||
formSheet.isOpen &&
|
||
isParticipant &&
|
||
hasPending &&
|
||
!reportedByMine &&
|
||
pendingSignature !== acknowledgedReport;
|
||
|
||
const report = useReportMatchScore(user?.id, {
|
||
onSuccess: (data: any) => {
|
||
if (data?.finalized) {
|
||
toast.success(t`Score confirmed 🍻`);
|
||
if (data.downstreamReset) {
|
||
toast.error(
|
||
t`Downstream matches were reset because the correction changed who advances.`
|
||
);
|
||
}
|
||
if (data.groupEditAfterKnockout) {
|
||
toast.error(
|
||
t`Group result changed after the bracket was seeded — reseed the knockout stage if needed.`
|
||
);
|
||
}
|
||
}
|
||
},
|
||
});
|
||
|
||
const confirm = useConfirmMatchScore({
|
||
onSuccess: (data: any) => {
|
||
if (data?.downstreamReset) {
|
||
toast.error(
|
||
t`Downstream matches were reset because the correction changed who advances.`
|
||
);
|
||
}
|
||
if (data?.groupEditAfterKnockout) {
|
||
toast.error(
|
||
t`Group result changed after the bracket was seeded — reseed the knockout stage if needed.`
|
||
);
|
||
}
|
||
},
|
||
});
|
||
|
||
const clear = useClearMatchReport();
|
||
|
||
const submitReport = useCallback(
|
||
(data: { home_cups: number; away_cups: number; ot_count: number }) => {
|
||
report.mutate({ data: { ...data, matchId: match.id } });
|
||
formSheet.close();
|
||
toast.success(t`Score submitted — waiting for the other team`);
|
||
},
|
||
[report, match.id, formSheet.close, t]
|
||
);
|
||
|
||
const submitConfirm = useCallback(() => {
|
||
confirm.mutate({ data: { matchId: match.id } });
|
||
formSheet.close();
|
||
toast.success(t`Score confirmed 🍻`);
|
||
}, [confirm, match.id, formSheet.close, t]);
|
||
|
||
const submitClear = useCallback(() => {
|
||
clear.mutate({ data: { matchId: match.id } });
|
||
toast.success(t`Report cleared`);
|
||
}, [clear, match.id, t]);
|
||
|
||
const openForm = useCallback(() => {
|
||
setAcknowledgedReport(pendingSignature);
|
||
formSheet.open();
|
||
}, [pendingSignature, formSheet.open]);
|
||
|
||
const dismissTakeover = useCallback(() => {
|
||
setAcknowledgedReport(pendingSignature);
|
||
}, [pendingSignature]);
|
||
|
||
if (match.status !== "started") return null;
|
||
|
||
const scoreText = hasPending
|
||
? `${match.reported_home_cups}–${match.reported_away_cups}`
|
||
: "";
|
||
const size = compact ? "compact-xs" : "sm";
|
||
|
||
const formSheetEl = (
|
||
<Sheet
|
||
title={sheetTakenOver ? t`Confirm Score` : t`Report Score`}
|
||
{...formSheet.props}
|
||
>
|
||
{sheetTakenOver ? (
|
||
<Stack gap="md">
|
||
<Alert
|
||
variant="light"
|
||
color="yellow"
|
||
icon={<LightningIcon size={18} weight="fill" />}
|
||
title={t`${teamName(reportingTeam) ?? t`The other team`} just reported a score`}
|
||
>
|
||
<Trans>Does this look right?</Trans>
|
||
</Alert>
|
||
|
||
<Paper withBorder p="md">
|
||
<Group justify="center" gap="md" wrap="nowrap">
|
||
<Stack gap={2} align="center" style={{ flex: 1, minWidth: 0 }}>
|
||
<Text size="sm" fw={600} ta="center" lineClamp={1}>
|
||
{teamName(match.home) ?? t({ message: "Home", context: "match team" })}
|
||
</Text>
|
||
<Text fz={34} fw={700} lh={1.2}>
|
||
{match.reported_home_cups}
|
||
</Text>
|
||
</Stack>
|
||
<Text c="dimmed" fw={600}>
|
||
–
|
||
</Text>
|
||
<Stack gap={2} align="center" style={{ flex: 1, minWidth: 0 }}>
|
||
<Text size="sm" fw={600} ta="center" lineClamp={1}>
|
||
{teamName(match.away) ?? t({ message: "Away", context: "match team" })}
|
||
</Text>
|
||
<Text fz={34} fw={700} lh={1.2}>
|
||
{match.reported_away_cups}
|
||
</Text>
|
||
</Stack>
|
||
</Group>
|
||
</Paper>
|
||
|
||
<Stack gap="xs">
|
||
<Button
|
||
fullWidth
|
||
leftSection={<CheckIcon size={14} weight="bold" />}
|
||
onClick={submitConfirm}
|
||
loading={confirm.isPending}
|
||
>
|
||
<Trans>Confirm {scoreText}</Trans>
|
||
</Button>
|
||
<Button
|
||
fullWidth
|
||
variant="default"
|
||
onClick={dismissTakeover}
|
||
disabled={confirm.isPending}
|
||
>
|
||
<Trans>Not right — enter our score</Trans>
|
||
</Button>
|
||
</Stack>
|
||
</Stack>
|
||
) : (
|
||
<MatchForm
|
||
match={match}
|
||
onSubmit={submitReport}
|
||
onCancel={formSheet.close}
|
||
loading={report.isPending}
|
||
/>
|
||
)}
|
||
</Sheet>
|
||
);
|
||
|
||
if (isParticipant && !hasPending) {
|
||
return (
|
||
<>
|
||
<Button
|
||
size={size}
|
||
fullWidth={!compact}
|
||
onClick={openForm}
|
||
loading={report.isPending}
|
||
>
|
||
<Trans>Report Score</Trans>
|
||
</Button>
|
||
{formSheetEl}
|
||
</>
|
||
);
|
||
}
|
||
|
||
if (hasPending) {
|
||
if (reportedByMine) {
|
||
return (
|
||
<Flex
|
||
direction={compact ? "column" : "row"}
|
||
align={compact ? "stretch" : "center"}
|
||
justify="space-between"
|
||
gap={compact ? 4 : "xs"}
|
||
>
|
||
<Text size="xs" c="dimmed" lineClamp={2} style={{ minWidth: 0 }}>
|
||
<Trans>
|
||
Waiting for {teamName(opposingTeam) ?? t`the other team`} to confirm{" "}
|
||
·{" "}
|
||
<Text span fw={600} c="bright">
|
||
{scoreText}
|
||
</Text>
|
||
</Trans>
|
||
</Text>
|
||
<Button
|
||
size={size}
|
||
variant="subtle"
|
||
color="red"
|
||
onClick={submitClear}
|
||
loading={clear.isPending}
|
||
style={{ flexShrink: 0 }}
|
||
>
|
||
<Trans>Cancel</Trans>
|
||
</Button>
|
||
</Flex>
|
||
);
|
||
}
|
||
|
||
if (isParticipant) {
|
||
return (
|
||
<>
|
||
<Stack gap={6}>
|
||
<Text size="xs" c="dimmed" lineClamp={2}>
|
||
<Trans>
|
||
{teamName(reportingTeam) ?? t`Opponent`} reported{" "}
|
||
<Text span fw={600} c="bright">
|
||
{scoreText}
|
||
</Text>
|
||
</Trans>
|
||
</Text>
|
||
<Group gap="xs" wrap="nowrap">
|
||
<Button
|
||
size={size}
|
||
leftSection={<CheckIcon size={12} weight="bold" />}
|
||
onClick={submitConfirm}
|
||
loading={confirm.isPending}
|
||
style={{ flex: 1 }}
|
||
>
|
||
<Trans>Confirm</Trans>
|
||
</Button>
|
||
<Button
|
||
size={size}
|
||
variant="default"
|
||
onClick={openForm}
|
||
disabled={confirm.isPending}
|
||
>
|
||
<Trans>Not right</Trans>
|
||
</Button>
|
||
</Group>
|
||
</Stack>
|
||
{formSheetEl}
|
||
</>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Text size="xs" c="dimmed" lineClamp={2}>
|
||
<Trans>
|
||
Pending: {scoreText} · reported by {teamName(reportingTeam) ?? t`a team`}
|
||
</Trans>
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
return null;
|
||
};
|