From 63853f22de1b7105eeab65b77937b1d3eca5be53 Mon Sep 17 00:00:00 2001 From: yohlo Date: Mon, 9 Feb 2026 23:36:04 -0600 Subject: [PATCH] regional teams --- .../_authed/admin/tournaments/run.$id.tsx | 1 + src/components/team-avatar.tsx | 139 ++++++++++++++++++ .../matches/components/match-card.tsx | 56 ++----- src/features/teams/components/team-card.tsx | 7 +- src/features/teams/components/team-list.tsx | 11 +- .../components/edit-enrolled-teams.tsx | 28 ++-- .../components/seed-tournament.tsx | 14 +- src/lib/pocketbase/services/matches.ts | 6 +- src/lib/pocketbase/services/players.ts | 2 +- src/lib/pocketbase/services/teams.ts | 2 +- 10 files changed, 181 insertions(+), 85 deletions(-) create mode 100644 src/components/team-avatar.tsx diff --git a/src/app/routes/_authed/admin/tournaments/run.$id.tsx b/src/app/routes/_authed/admin/tournaments/run.$id.tsx index 25034a1..3de4ccf 100644 --- a/src/app/routes/_authed/admin/tournaments/run.$id.tsx +++ b/src/app/routes/_authed/admin/tournaments/run.$id.tsx @@ -86,6 +86,7 @@ function RouteComponent() { )} diff --git a/src/components/team-avatar.tsx b/src/components/team-avatar.tsx new file mode 100644 index 0000000..a639936 --- /dev/null +++ b/src/components/team-avatar.tsx @@ -0,0 +1,139 @@ +import { Box, AvatarGroup } from "@mantine/core"; +import { CrownIcon } from "@phosphor-icons/react"; +import { TeamInfo } from "@/features/teams/types"; +import Avatar from "./avatar"; +import PlayerAvatar from "./player-avatar"; + +interface TeamAvatarProps { + team: TeamInfo; + size?: number; + radius?: string | number; + withBorder?: boolean; + disableFullscreen?: boolean; + contain?: boolean; + style?: React.CSSProperties; + winner?: boolean; + isRegional?: boolean; +} + +const TeamAvatar = ({ + team, + size = 35, + radius = "sm", + withBorder = true, + disableFullscreen = false, + contain = false, + style, + winner = false, + isRegional, +}: TeamAvatarProps) => { + const hasNoLogo = !team.logo; + const hasTwoPlayers = team.players?.length === 2; + + let shouldShowPlayerAvatars = false; + + if (isRegional !== undefined) { + shouldShowPlayerAvatars = isRegional && hasTwoPlayers && hasNoLogo; + } else { + const tournaments = (team as any).tournaments; + const hasTournaments = tournaments && tournaments.length > 0; + const allTournamentsAreRegional = hasTournaments && tournaments.every((t: any) => t.regional === true); + + shouldShowPlayerAvatars = hasTwoPlayers && hasNoLogo && (allTournamentsAreRegional || !hasTournaments); + } + + if (shouldShowPlayerAvatars && team.players?.length === 2) { + const playerSize = size * 0.6; + const crownSize = Math.max(12, size * 0.35); + + return ( + + + + + {winner && ( + + + + )} + + + + {winner && ( + + + + )} + + + + ); + } + + const crownSize = Math.max(14, size * 0.4); + + return ( + + + {winner && ( + + + + )} + + ); +}; + +export default TeamAvatar; diff --git a/src/features/matches/components/match-card.tsx b/src/features/matches/components/match-card.tsx index 827942c..37d3ebe 100644 --- a/src/features/matches/components/match-card.tsx +++ b/src/features/matches/components/match-card.tsx @@ -1,8 +1,8 @@ import { Text, Group, Stack, Paper, Indicator, Box, Tooltip, ActionIcon } from "@mantine/core"; -import { CrownIcon, FootballHelmetIcon } from "@phosphor-icons/react"; +import { FootballHelmetIcon } from "@phosphor-icons/react"; import { useNavigate } from "@tanstack/react-router"; import { Match } from "../types"; -import Avatar from "@/components/avatar"; +import TeamAvatar from "@/components/team-avatar"; import EmojiBar from "@/features/reactions/components/emoji-bar"; import { Suspense } from "react"; import { useSheet } from "@/hooks/use-sheet"; @@ -113,32 +113,16 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => { - - {isHomeWin && ( - - - - )} { - - {isAwayWin && ( - - - - )} { > - { key={`team-list-${team.id}`} p="xs" icon={ - } style={{ cursor: "pointer" }} diff --git a/src/features/tournaments/components/edit-enrolled-teams.tsx b/src/features/tournaments/components/edit-enrolled-teams.tsx index 02f0d80..1fd105e 100644 --- a/src/features/tournaments/components/edit-enrolled-teams.tsx +++ b/src/features/tournaments/components/edit-enrolled-teams.tsx @@ -11,7 +11,7 @@ import { useState, useCallback, useMemo, memo } from "react"; import { useTournament, useUnenrolledTeams } from "../queries"; import useEnrollTeam from "../hooks/use-enroll-team"; import useUnenrollTeam from "../hooks/use-unenroll-team"; -import Avatar from "@/components/avatar"; +import TeamAvatar from "@/components/team-avatar"; import { Team, TeamInfo } from "@/features/teams/types"; interface EditEnrolledTeamsProps { @@ -22,9 +22,10 @@ interface TeamItemProps { team: TeamInfo; onUnenroll: (teamId: string) => void; disabled: boolean; + isRegional?: boolean; } -const TeamItem = memo(({ team, onUnenroll, disabled }: TeamItemProps) => { +const TeamItem = memo(({ team, onUnenroll, disabled, isRegional }: TeamItemProps) => { const playerNames = useMemo( () => team.players?.map((p) => `${p.first_name} ${p.last_name}`).join(", ") || @@ -34,15 +35,11 @@ const TeamItem = memo(({ team, onUnenroll, disabled }: TeamItemProps) => { return ( - @@ -73,6 +70,8 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => { const { data: unenrolledTeams = [], isLoading: unenrolledLoading } = useUnenrolledTeams(tournamentId); + const isRegional = tournament?.regional; + const { mutate: enrollTeam, isPending: isEnrolling } = useEnrollTeam(); const { mutate: unenrollTeam, isPending: isUnenrolling } = useUnenrollTeam(); @@ -107,15 +106,11 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => { const team = option.data; return ( - {team.name} @@ -174,6 +169,7 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => { team={team} onUnenroll={handleUnenrollTeam} disabled={isUnenrolling} + isRegional={isRegional} /> ))} diff --git a/src/features/tournaments/components/seed-tournament.tsx b/src/features/tournaments/components/seed-tournament.tsx index 8cafb19..f82a89c 100644 --- a/src/features/tournaments/components/seed-tournament.tsx +++ b/src/features/tournaments/components/seed-tournament.tsx @@ -13,7 +13,7 @@ import { DotsNineIcon } from "@phosphor-icons/react"; import { useServerMutation } from "@/lib/tanstack-query/hooks/use-server-mutation"; import { generateTournamentBracket } from "../../matches/server"; import { TeamInfo } from "@/features/teams/types"; -import Avatar from "@/components/avatar"; +import TeamAvatar from "@/components/team-avatar"; import { useBracketPreview } from "@/features/bracket/queries"; import { BracketData } from "@/features/bracket/types"; import BracketView from "@/features/bracket/components/bracket-view"; @@ -23,11 +23,13 @@ import { tournamentKeys } from "../queries"; interface SeedTournamentProps { tournamentId: string; teams: TeamInfo[]; + isRegional?: boolean; } const SeedTournament: React.FC = ({ tournamentId, teams, + isRegional, }) => { const [orderedTeams, setOrderedTeams] = useState(teams); const { data: bracketPreview } = useBracketPreview(teams.length); @@ -171,15 +173,11 @@ const SeedTournament: React.FC = ({ }} /> - diff --git a/src/lib/pocketbase/services/matches.ts b/src/lib/pocketbase/services/matches.ts index 9bcb325..72c101d 100644 --- a/src/lib/pocketbase/services/matches.ts +++ b/src/lib/pocketbase/services/matches.ts @@ -7,7 +7,7 @@ export function createMatchesService(pb: PocketBase) { return { async getMatch(id: string): Promise { const result = await pb.collection("matches").getOne(id, { - expand: "tournament, home, away", + expand: "tournament, home, away, home.players, away.players", }); return transformMatch(result); }, @@ -19,7 +19,7 @@ export function createMatchesService(pb: PocketBase) { const result = await pb.collection("matches").getFullList({ filter: `tournament="${match.tournament.id}" && (home_from_lid = ${match.lid} || away_from_lid = ${match.lid}) && bye = false`, - expand: "tournament, home, away", + expand: "tournament, home, away, home.players, away.players", }); const winnerMatch = result.find(m => (m.home_from_lid === match.lid && !m.home_from_loser) || (m.away_from_lid === match.lid && !m.away_from_loser)); @@ -50,7 +50,7 @@ export function createMatchesService(pb: PocketBase) { async updateMatch(id: string, data: Partial): Promise { logger.info("PocketBase | Updating match", { id, data }); const result = await pb.collection("matches").update(id, data, { - expand: 'home, away, tournament' + expand: 'home, away, tournament, home.players, away.players' }); return transformMatch(result); }, diff --git a/src/lib/pocketbase/services/players.ts b/src/lib/pocketbase/services/players.ts index 2d4c638..e95120e 100644 --- a/src/lib/pocketbase/services/players.ts +++ b/src/lib/pocketbase/services/players.ts @@ -126,7 +126,7 @@ export function createPlayersService(pb: PocketBase) { const result = await pb.collection("matches").getFullList({ filter: `(${teamFilter}) && (status = "ended" || status = "started")`, sort: "-created", - expand: "tournament,home,away", + expand: "tournament,home,away,home.players,away.players", }); return result.map((match) => transformMatch(match)); diff --git a/src/lib/pocketbase/services/teams.ts b/src/lib/pocketbase/services/teams.ts index e041932..4b1aac5 100644 --- a/src/lib/pocketbase/services/teams.ts +++ b/src/lib/pocketbase/services/teams.ts @@ -105,7 +105,7 @@ export function createTeamsService(pb: PocketBase) { const result = await pb.collection("matches").getFullList({ filter: `(${teamFilter}) && (status = "ended" || status = "started")`, sort: "-start_time", - expand: "tournament,home,away", + expand: "tournament,home,away,home.players,away.players", }); return result.map((match) => transformMatch(match));