predictions
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { Box, Container, Flex, Skeleton, Stack } from "@mantine/core";
|
||||
|
||||
export function BracketPending() {
|
||||
const columns = [4, 2, 1];
|
||||
|
||||
return (
|
||||
<Container size="md" px={0}>
|
||||
<Box
|
||||
p={0}
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
backgroundImage: `radial-gradient(circle, var(--mantine-color-default-border) 1px, transparent 1px)`,
|
||||
backgroundSize: "16px 16px",
|
||||
backgroundPosition: "0 0, 8px 8px",
|
||||
minHeight: "70dvh",
|
||||
}}
|
||||
>
|
||||
<Skeleton height={18} width={140} radius="sm" m={16} />
|
||||
<Flex gap="xl" px={16} align="stretch">
|
||||
{columns.map((count, columnIndex) => (
|
||||
<Stack
|
||||
key={`bracket-pending-round-${columnIndex}`}
|
||||
gap="xl"
|
||||
justify="space-around"
|
||||
style={{ opacity: 1 - columnIndex * 0.25 }}
|
||||
>
|
||||
{Array.from({ length: count }).map((_, matchIndex) => (
|
||||
<Skeleton
|
||||
key={`bracket-pending-match-${columnIndex}-${matchIndex}`}
|
||||
height={84}
|
||||
width={220}
|
||||
radius="md"
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -13,9 +13,11 @@ interface BracketViewProps {
|
||||
num_groups: number;
|
||||
advance_per_group: number;
|
||||
};
|
||||
renderMatch?: (match: Match) => React.ReactNode;
|
||||
bottomOffset?: number;
|
||||
}
|
||||
|
||||
const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls, groupConfig }) => {
|
||||
const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls, groupConfig, renderMatch, bottomOffset }) => {
|
||||
const height = useAppShellHeight();
|
||||
const orders = useMemo(() => {
|
||||
const map: Record<number, number> = {};
|
||||
@@ -37,16 +39,17 @@ const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls, groupC
|
||||
<Text fw={600} size="md" m={16}>
|
||||
Winners Bracket
|
||||
</Text>
|
||||
<Bracket rounds={bracket.winners} orders={orders} showControls={showControls} groupConfig={groupConfig} />
|
||||
<Bracket rounds={bracket.winners} orders={orders} showControls={showControls} groupConfig={groupConfig} renderMatch={renderMatch} />
|
||||
</div>
|
||||
{bracket.losers && bracket.losers.length > 0 && bracket.losers.some(round => round.length > 0) && (
|
||||
<div>
|
||||
<Text fw={600} size="md" m={16}>
|
||||
Losers Bracket
|
||||
</Text>
|
||||
<Bracket rounds={bracket.losers} orders={orders} showControls={showControls} groupConfig={groupConfig} />
|
||||
<Bracket rounds={bracket.losers} orders={orders} showControls={showControls} groupConfig={groupConfig} renderMatch={renderMatch} />
|
||||
</div>
|
||||
)}
|
||||
{bottomOffset ? <div style={{ height: bottomOffset }} /> : null}
|
||||
</ScrollArea>
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ interface BracketProps {
|
||||
num_groups: number;
|
||||
advance_per_group: number;
|
||||
};
|
||||
renderMatch?: (match: Match) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const Bracket: React.FC<BracketProps> = ({
|
||||
@@ -18,6 +19,7 @@ export const Bracket: React.FC<BracketProps> = ({
|
||||
orders,
|
||||
showControls,
|
||||
groupConfig,
|
||||
renderMatch,
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
@@ -133,12 +135,16 @@ export const Bracket: React.FC<BracketProps> = ({
|
||||
<div key={match.lid}></div>
|
||||
) : (
|
||||
<div key={match.lid}>
|
||||
<MatchCard
|
||||
match={match}
|
||||
orders={orders}
|
||||
showControls={showControls}
|
||||
groupConfig={groupConfig}
|
||||
/>
|
||||
{renderMatch ? (
|
||||
renderMatch(match)
|
||||
) : (
|
||||
<MatchCard
|
||||
match={match}
|
||||
orders={orders}
|
||||
showControls={showControls}
|
||||
groupConfig={groupConfig}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { TeamInfo } from "@/features/teams/types";
|
||||
import AnimatedScore from "@/features/matches/components/animated-score";
|
||||
import classes from "./match-slot.module.css";
|
||||
|
||||
export type MatchSlotState = "winner" | "correct" | "incorrect";
|
||||
|
||||
interface MatchSlotProps {
|
||||
from?: number;
|
||||
from_loser?: boolean;
|
||||
@@ -14,6 +16,7 @@ interface MatchSlotProps {
|
||||
cups?: number;
|
||||
isWinner?: boolean;
|
||||
groupLabel?: string;
|
||||
state?: MatchSlotState;
|
||||
}
|
||||
|
||||
export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
@@ -23,7 +26,8 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
seed,
|
||||
cups,
|
||||
isWinner,
|
||||
groupLabel
|
||||
groupLabel,
|
||||
state,
|
||||
}) => {
|
||||
const teamId = team?.id;
|
||||
const previousTeamIdRef = useRef<string | undefined>(teamId);
|
||||
@@ -37,11 +41,19 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
}
|
||||
}, [teamId]);
|
||||
|
||||
const slotState: MatchSlotState | undefined =
|
||||
state ?? (isWinner ? "winner" : undefined);
|
||||
const highlighted = slotState === "winner" || slotState === "correct";
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align="stretch"
|
||||
style={{
|
||||
backgroundColor: isWinner ? 'var(--mantine-color-green-light)' : 'transparent',
|
||||
backgroundColor: highlighted
|
||||
? 'var(--mantine-color-green-light)'
|
||||
: slotState === "incorrect"
|
||||
? 'var(--mantine-color-red-light)'
|
||||
: 'transparent',
|
||||
borderRadius: 'var(--mantine-radius-sm)',
|
||||
transition: 'background-color 200ms ease',
|
||||
}}
|
||||
@@ -60,11 +72,12 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
<Text
|
||||
size={team.name.length > 12 ? (team.name.length > 18 ? '10px' : '11px') : 'xs'}
|
||||
truncate
|
||||
c={slotState === "incorrect" ? "dimmed" : undefined}
|
||||
style={{ minWidth: 0, flex: 1, lineHeight: "12px" }}
|
||||
>
|
||||
{team.name}
|
||||
</Text>
|
||||
{isWinner && (
|
||||
{highlighted && (
|
||||
<CrownIcon
|
||||
size={14}
|
||||
weight="fill"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { BracketData } from "../types";
|
||||
|
||||
export const groupMatchesIntoBracket = (matches?: Match[]): BracketData => {
|
||||
if (!matches || matches.length === 0) {
|
||||
return { winners: [], losers: [] };
|
||||
}
|
||||
|
||||
const winnersMap = new Map<number, Match[]>();
|
||||
const losersMap = new Map<number, Match[]>();
|
||||
|
||||
matches
|
||||
.filter((match) => match.round !== -1)
|
||||
.sort((a, b) => a.lid - b.lid)
|
||||
.forEach((match) => {
|
||||
if (!match.is_losers_bracket) {
|
||||
if (!winnersMap.has(match.round)) {
|
||||
winnersMap.set(match.round, []);
|
||||
}
|
||||
winnersMap.get(match.round)!.push(match);
|
||||
} else {
|
||||
if (!losersMap.has(match.round)) {
|
||||
losersMap.set(match.round, []);
|
||||
}
|
||||
losersMap.get(match.round)!.push(match);
|
||||
}
|
||||
});
|
||||
|
||||
const winners = Array.from(winnersMap.entries())
|
||||
.sort(([a], [b]) => a - b)
|
||||
.map(([, matches]) => matches);
|
||||
|
||||
const losers = Array.from(losersMap.entries())
|
||||
.sort(([a], [b]) => a - b)
|
||||
.map(([, matches]) => matches);
|
||||
return { winners, losers };
|
||||
};
|
||||
Reference in New Issue
Block a user