"use client" import { useState, useEffect, useMemo } from "react" import { useRouter } from "next/navigation" import Navigation from "@/components/Navigation" import { expectedRounds, expectedMatchups } from "@/lib/schedule-generator" interface Player { id: number name: string currentElo: number } interface TournamentFormData { name: string description: string eventDate: string format: string tournamentType: 'individual' | 'team' participants: number[] } type PairingMethod = 'elo' | 'manual' | 'random' export default function NewTournamentPage() { const router = useRouter() const [step, setStep] = useState(1) const [formData, setFormData] = useState({ name: "", description: "", eventDate: "", format: "round_robin", tournamentType: "individual", participants: [], }) const [error, setError] = useState("") const [isLoading, setIsLoading] = useState(false) // Player search state const [searchQuery, setSearchQuery] = useState("") const [searchResults, setSearchResults] = useState([]) const [selectedPlayers, setSelectedPlayers] = useState([]) const [isSearching, setIsSearching] = useState(false) // Sorting state const [sortConfig, setSortConfig] = useState<{ key: 'name' | 'currentElo'; direction: 'asc' | 'desc' }>({ key: 'name', direction: 'asc' }) // Team pairing state const [pairingMethod, setPairingMethod] = useState('elo') // Dynamic round preview calculation const scheduleInfo = useMemo(() => { if (formData.tournamentType === 'team') { const teamCount = Math.floor(selectedPlayers.length / 2) if (teamCount < 2) return null return { rounds: expectedRounds(teamCount), matchups: expectedMatchups(teamCount), teams: teamCount, playerCount: selectedPlayers.length, } } else { // Individual: players form teams of 2 for Euchre const teamCount = Math.floor(selectedPlayers.length / 2) if (teamCount < 2) return null return { rounds: expectedRounds(teamCount), matchups: expectedMatchups(teamCount), teams: teamCount, playerCount: selectedPlayers.length, } } }, [selectedPlayers.length, formData.tournamentType]) // Minimum players by format const getMinPlayers = () => { if (formData.tournamentType === 'team') { return 4 // At least 2 teams } // Individual tournaments still need pairs for Euchre return 4 // At least 2 teams of 2 } // Search for players as user types useEffect(() => { const searchPlayers = async () => { if (searchQuery.length < 2) { setSearchResults([]) return } setIsSearching(true) try { const response = await fetch(`/api/players/search?q=${encodeURIComponent(searchQuery)}`) if (response.ok) { const data = await response.json() const availablePlayers = data.players.filter( (p: Player) => !selectedPlayers.find(sp => sp.id === p.id) ) setSearchResults(availablePlayers) } } catch (err) { console.error("Search failed:", err) } finally { setIsSearching(false) } } const debounceTimer = setTimeout(searchPlayers, 300) return () => clearTimeout(debounceTimer) }, [searchQuery, selectedPlayers]) const addPlayer = (player: Player) => { setSelectedPlayers([...selectedPlayers, player]) setSearchQuery("") setSearchResults([]) } const removePlayer = (playerId: number) => { setSelectedPlayers(selectedPlayers.filter(p => p.id !== playerId)) } const handleSort = (key: 'name' | 'currentElo') => { setSortConfig(prevConfig => ({ key, direction: prevConfig.key === key && prevConfig.direction === 'asc' ? 'desc' : 'asc' })) } const getSortedPlayers = () => { const sorted = [...selectedPlayers].sort((a, b) => { if (sortConfig.key === 'name') { return sortConfig.direction === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) } else { return sortConfig.direction === 'asc' ? a.currentElo - b.currentElo : b.currentElo - a.currentElo } }) return sorted } // Generate team pairings based on method const getTeamPairings = () => { const sorted = [...selectedPlayers] if (pairingMethod === 'elo') { sorted.sort((a, b) => b.currentElo - a.currentElo) const teams: { player1: Player; player2: Player }[] = [] for (let i = 0; i < sorted.length - 1; i += 2) { teams.push({ player1: sorted[i], player2: sorted[i + 1] }) } return teams } else if (pairingMethod === 'random') { // Shuffle using Fisher-Yates for (let i = sorted.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [sorted[i], sorted[j]] = [sorted[j], sorted[i]] } const teams: { player1: Player; player2: Player }[] = [] for (let i = 0; i < sorted.length - 1; i += 2) { teams.push({ player1: sorted[i], player2: sorted[i + 1] }) } return teams } else { // Manual: just use current order const teams: { player1: Player; player2: Player }[] = [] for (let i = 0; i < sorted.length - 1; i += 2) { teams.push({ player1: sorted[i], player2: sorted[i + 1] }) } return teams } } const handleChange = (e: React.ChangeEvent) => { setFormData({ ...formData, [e.target.name]: e.target.value, }) } const nextStep = () => { if (step === 1) { if (!formData.name.trim()) { setError("Tournament name is required") return } } setError("") setStep(step + 1) } const prevStep = () => { setStep(step - 1) setError("") } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError("") setIsLoading(true) try { const minPlayers = getMinPlayers() if (selectedPlayers.length < minPlayers) { setError(`At least ${minPlayers} participants are required (${minPlayers / 2} teams)`) setIsLoading(false) return } if (selectedPlayers.length % 2 !== 0) { setError("An even number of participants is required to form teams") setIsLoading(false) return } // Create the tournament const tournamentResponse = await fetch("/api/tournaments", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: formData.name, description: formData.description, eventDate: formData.eventDate || null, format: formData.format, tournamentType: formData.tournamentType, }), }) const tournamentData = await tournamentResponse.json() if (!tournamentResponse.ok) { throw new Error(tournamentData.error || "Failed to create tournament") } const tournamentId = tournamentData.tournament.id // Add participants to the tournament if (selectedPlayers.length > 0) { await fetch(`/api/tournaments/${tournamentId}/participants`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ playerIds: selectedPlayers.map(p => p.id), }), }) } // Auto-generate schedule for round_robin if (formData.format === 'round_robin') { await fetch(`/api/tournaments/${tournamentId}/schedule`, { method: "POST", headers: { "Content-Type": "application/json", }, }) } // Redirect to schedule view router.push(`/admin/tournaments/${tournamentId}/schedule`) } catch (err: unknown) { const message = err instanceof Error ? err.message : "An unexpected error occurred"; setError(message) } finally { setIsLoading(false) } } return (

Create New Tournament

{/* Step Indicator */}
= 1 ? 'bg-green-600 text-white' : 'bg-gray-300 text-gray-600'}`}> 1
= 2 ? 'bg-green-600' : 'bg-gray-300'}`} />
= 2 ? 'bg-green-600 text-white' : 'bg-gray-300 text-gray-600'}`}> 2
Tournament Details Participants
{error && (
{error}
)} {/* Step 1: Tournament Details */} {step === 1 && ( <>