match status

This commit is contained in:
yohlo
2025-09-11 14:04:05 -05:00
parent 22be6682dd
commit 8dfff139e1
8 changed files with 89 additions and 20 deletions

View File

@@ -0,0 +1,31 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// add field
collection.fields.addAt(21, new Field({
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"tbd",
"ready",
"started",
"ended"
]
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_2541054544")
// remove field
collection.fields.removeById("select2063623452")
return app.save(collection)
})

View File

@@ -1,5 +1,5 @@
import { createFileRoute, redirect, useRouter } from '@tanstack/react-router' import { createFileRoute, redirect, useRouter } from '@tanstack/react-router'
import { tournamentQueries } from '@/features/tournaments/queries' import { tournamentQueries, useTournament } from '@/features/tournaments/queries'
import { ensureServerQueryData } from '@/lib/tanstack-query/utils/ensure' import { ensureServerQueryData } from '@/lib/tanstack-query/utils/ensure'
import SeedTournament from '@/features/tournaments/components/seed-tournament' import SeedTournament from '@/features/tournaments/components/seed-tournament'
import { Container, Alert, Text } from '@mantine/core' import { Container, Alert, Text } from '@mantine/core'
@@ -32,8 +32,8 @@ export const Route = createFileRoute('/_authed/admin/tournaments/run/$id')({
}) })
function RouteComponent() { function RouteComponent() {
const { tournament } = Route.useRouteContext() const { id } = Route.useParams();
const router = useRouter() const { data: tournament } = useTournament(id)
const bracket: BracketData = useMemo(() => { const bracket: BracketData = useMemo(() => {
if (!tournament.matches || tournament.matches.length === 0) { if (!tournament.matches || tournament.matches.length === 0) {
@@ -67,18 +67,12 @@ function RouteComponent() {
return { winners, losers } return { winners, losers }
}, [tournament.matches]) }, [tournament.matches])
const handleSuccess = () => {
router.navigate({
to: '/admin/tournaments/$id',
params: { id: tournament.id }
})
}
const handleStartMatch = (match: Match) => { const handleStartMatch = (match: Match) => {
} }
console.log(tournament.matches)
return ( return (
<Container size="md"> <Container size="md">
{ {
@@ -88,7 +82,6 @@ function RouteComponent() {
<SeedTournament <SeedTournament
tournamentId={tournament.id} tournamentId={tournament.id}
teams={tournament.teams || []} teams={tournament.teams || []}
onSuccess={handleSuccess}
/>) />)
} }
</Container> </Container>

View File

@@ -18,7 +18,7 @@ function RouteComponent() {
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
const redirect = urlParams.get('redirect') const redirect = urlParams.get('redirect')
if (redirect) { if (redirect && !redirect.startsWith('/_serverFn')) {
window.location.href = decodeURIComponent(redirect) window.location.href = decodeURIComponent(redirect)
} else { } else {
window.location.href = '/' window.location.href = '/'

View File

@@ -35,8 +35,8 @@ export const MatchCard: React.FC<MatchCardProps> = ({
); );
const showToolbar = useMemo( const showToolbar = useMemo(
() => match.home && match.away && onStartMatch, () => match.status === "ready" && onStartMatch,
[match.home, match.away] [match.status, onStartMatch]
); );
const handleAnnounce = useCallback( const handleAnnounce = useCallback(

View File

@@ -58,6 +58,7 @@ export const generateTournamentBracket = createServerFn()
home_from_loser: match.home_from_loser || false, home_from_loser: match.home_from_loser || false,
away_from_loser: match.away_from_loser || false, away_from_loser: match.away_from_loser || false,
is_losers_bracket: false, is_losers_bracket: false,
status: "tbd",
tournament: tournamentId, tournament: tournamentId,
}; };
@@ -77,6 +78,10 @@ export const generateTournamentBracket = createServerFn()
} }
} }
if (matchInput.home && matchInput.away) {
matchInput.status = "ready"
}
matchInputs.push(matchInput); matchInputs.push(matchInput);
}); });
}); });
@@ -97,6 +102,7 @@ export const generateTournamentBracket = createServerFn()
home_from_loser: match.home_from_loser || false, home_from_loser: match.home_from_loser || false,
away_from_loser: match.away_from_loser || false, away_from_loser: match.away_from_loser || false,
is_losers_bracket: true, is_losers_bracket: true,
status: "tbd",
tournament: tournamentId, tournament: tournamentId,
}; };
@@ -135,7 +141,38 @@ export const startMatch = createServerFn()
} }
match = await pbAdmin.updateMatch(data, { match = await pbAdmin.updateMatch(data, {
start_time: new Date().toISOString() start_time: new Date().toISOString(),
status: "started"
});
return match;
}
));
const endMatchSchema = z.object({
matchId: z.string(),
home_cups: z.number(),
away_cups: z.number(),
ot_count: z.number()
});
export const endMatch = createServerFn()
.validator(endMatchSchema)
.middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data: { matchId, home_cups, away_cups, ot_count } }) =>
toServerResult(async () => {
logger.info('Ending match', matchId);
let match = await pbAdmin.getMatch(matchId);
if (!match) {
throw new Error('Match not found');
}
match = await pbAdmin.updateMatch(matchId, {
end_time: new Date().toISOString(),
status: "ended",
home_cups,
away_cups,
ot_count
}) })
} }
)); ));

View File

@@ -2,6 +2,8 @@ import { z } from "zod";
import { TeamInfo } from "../teams/types"; import { TeamInfo } from "../teams/types";
import { TournamentInfo } from "../tournaments/types"; import { TournamentInfo } from "../tournaments/types";
export type MatchStatus = "tbd" | "ready" | "started" | "ended";
/** /**
* class TMatchSlot(BaseModel): * class TMatchSlot(BaseModel):
pass pass
@@ -52,6 +54,7 @@ export interface Match {
home_from_loser: boolean; home_from_loser: boolean;
away_from_loser: boolean; away_from_loser: boolean;
is_losers_bracket: boolean; is_losers_bracket: boolean;
status: MatchStatus;
tournament: TournamentInfo; tournament: TournamentInfo;
home?: TeamInfo; home?: TeamInfo;
away?: TeamInfo; away?: TeamInfo;
@@ -77,6 +80,7 @@ export const matchInputSchema = z.object({
home_from_loser: z.boolean().optional().default(false), home_from_loser: z.boolean().optional().default(false),
away_from_loser: z.boolean().optional().default(false), away_from_loser: z.boolean().optional().default(false),
is_losers_bracket: z.boolean().optional().default(false), is_losers_bracket: z.boolean().optional().default(false),
status: z.enum(["tbd", "ready", "started", "ended"]).optional().default("tbd"),
tournament: z.string().min(1), tournament: z.string().min(1),
home: z.string().min(1).optional(), home: z.string().min(1).optional(),
away: z.string().min(1).optional(), away: z.string().min(1).optional(),

View File

@@ -17,20 +17,21 @@ import Avatar from "@/components/avatar";
import { useBracketPreview } from "@/features/bracket/queries"; import { useBracketPreview } from "@/features/bracket/queries";
import { BracketData } from "@/features/bracket/types"; import { BracketData } from "@/features/bracket/types";
import BracketView from "@/features/bracket/components/bracket-view"; import BracketView from "@/features/bracket/components/bracket-view";
import { useQueryClient } from "@tanstack/react-query";
import { tournamentKeys } from "../queries";
interface SeedTournamentProps { interface SeedTournamentProps {
tournamentId: string; tournamentId: string;
teams: TeamInfo[]; teams: TeamInfo[];
onSuccess?: () => void;
} }
const SeedTournament: React.FC<SeedTournamentProps> = ({ const SeedTournament: React.FC<SeedTournamentProps> = ({
tournamentId, tournamentId,
teams, teams
onSuccess,
}) => { }) => {
const [orderedTeams, setOrderedTeams] = useState<TeamInfo[]>(teams); const [orderedTeams, setOrderedTeams] = useState<TeamInfo[]>(teams);
const { data: bracketPreview } = useBracketPreview(teams.length); const { data: bracketPreview } = useBracketPreview(teams.length);
const queryClient = useQueryClient()
const bracket: BracketData = useMemo( const bracket: BracketData = useMemo(
() => ({ () => ({
@@ -56,7 +57,9 @@ const SeedTournament: React.FC<SeedTournamentProps> = ({
mutationFn: generateTournamentBracket, mutationFn: generateTournamentBracket,
successMessage: "Tournament bracket generated successfully!", successMessage: "Tournament bracket generated successfully!",
onSuccess: () => { onSuccess: () => {
onSuccess?.(); queryClient.invalidateQueries({
queryKey: tournamentKeys.details(tournamentId)
})
}, },
}); });

View File

@@ -43,6 +43,7 @@ export const transformMatch = (record: any): Match => {
home_from_loser: record.home_from_loser, home_from_loser: record.home_from_loser,
away_from_loser: record.away_from_loser, away_from_loser: record.away_from_loser,
is_losers_bracket: record.is_losers_bracket, is_losers_bracket: record.is_losers_bracket,
status: record.status || "tbd",
tournament: transformTournamentInfo(record.expand?.tournament), tournament: transformTournamentInfo(record.expand?.tournament),
home: record.expand?.home ? transformTeamInfo(record.expand.home) : undefined, home: record.expand?.home ? transformTeamInfo(record.expand.home) : undefined,
away: record.expand?.away ? transformTeamInfo(record.expand.away) : undefined, away: record.expand?.away ? transformTeamInfo(record.expand.away) : undefined,