diff --git a/src/components/TeamsSection.tsx b/src/components/TeamsSection.tsx new file mode 100644 index 0000000..eeee568 --- /dev/null +++ b/src/components/TeamsSection.tsx @@ -0,0 +1,413 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" + +interface Player { + id: number + name: string + currentElo: number +} + +interface Team { + id: number + teamName: string | null + player1: Player + player2: Player +} + +interface TeamsSectionProps { + tournamentId: number + teams: Team[] + participants: Player[] + teamDurability: string + partnerRotation: string + allowByes: boolean +} + +type TeamDurabilityOption = 'permanent' | 'variable' | 'per_round' +type PartnerRotationOption = 'none' | 'minimize_repeat' | 'maximize_even' | 'elo_based' + +export default function TeamsSection({ + tournamentId, + teams: initialTeams, + participants, + teamDurability: initialTeamDurability, + partnerRotation: initialPartnerRotation, + allowByes: initialAllowByes, +}: TeamsSectionProps) { + const router = useRouter() + const [teams, setTeams] = useState(initialTeams) + const [teamDurability, setTeamDurability] = useState(initialTeamDurability as TeamDurabilityOption) + const [partnerRotation, setPartnerRotation] = useState(initialPartnerRotation as PartnerRotationOption) + const [allowByes, setAllowByes] = useState(initialAllowByes) + const [isGenerating, setIsGenerating] = useState(false) + const [error, setError] = useState("") + const [success, setSuccess] = useState("") + + const handleSaveConfig = async () => { + setError("") + setSuccess("") + setIsGenerating(true) + + try { + const response = await fetch(`/api/tournaments/${tournamentId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + teamDurability, + partnerRotation, + allowByes, + }), + }) + + if (!response.ok) { + try { + const errorData = await response.json() + setError(errorData.error || "Failed to save configuration") + } catch { + setError(`Failed to save configuration: ${response.status} ${response.statusText}`) + } + return + } + + setSuccess("Configuration saved successfully!") + } catch (err) { + if (err instanceof Error) { + setError(err.message) + } else { + setError("An unknown error occurred") + } + } finally { + setIsGenerating(false) + } + } + + const handleGenerateTeams = async () => { + if (participants.length < 2) { + setError("At least 2 participants are required to generate teams") + return + } + + if (participants.length % 2 !== 0 && !allowByes) { + setError("Odd number of participants. Enable 'Allow Byes' to proceed.") + return + } + + setError("") + setSuccess("") + setIsGenerating(true) + + try { + const response = await fetch(`/api/tournaments/${tournamentId}/teams/generate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + teamDurability, + partnerRotation, + allowByes, + }), + }) + + if (!response.ok) { + try { + const errorData = await response.json() + setError(errorData.error || "Failed to generate teams") + } catch { + setError(`Failed to generate teams: ${response.status} ${response.statusText}`) + } + return + } + + const data = await response.json() + setTeams(data.teams) + setSuccess(`Successfully generated ${data.teams.length} teams!`) + router.refresh() + } catch (err) { + if (err instanceof Error) { + setError(err.message) + } else { + setError("An unknown error occurred") + } + } finally { + setIsGenerating(false) + } + } + + const handleDeleteTeams = async () => { + if (!confirm("Are you sure you want to delete all teams? This will also delete the schedule.")) { + return + } + + setError("") + setSuccess("") + setIsGenerating(true) + + try { + // First delete schedule if exists + await fetch(`/api/tournaments/${tournamentId}/schedule`, { + method: "DELETE", + }) + + // Then delete teams + const response = await fetch(`/api/tournaments/${tournamentId}/teams`, { + method: "DELETE", + }) + + if (!response.ok) { + try { + const errorData = await response.json() + setError(errorData.error || "Failed to delete teams") + } catch { + setError(`Failed to delete teams: ${response.status} ${response.statusText}`) + } + return + } + + setTeams([]) + setSuccess("Teams deleted successfully!") + router.refresh() + } catch (err) { + if (err instanceof Error) { + setError(err.message) + } else { + setError("An unknown error occurred") + } + } finally { + setIsGenerating(false) + } + } + + return ( +
+

+ Teams ({teams.length}) +

+ + {/* Configuration Panel */} +
+

Team Configuration

+ + {/* Team Durability */} +
+ +
+ + + +
+

+ {teamDurability === 'permanent' + ? 'Teams are fixed for the entire tournament. Each team plays together in all rounds.' + : teamDurability === 'variable' + ? 'Teams are generated per round based on configuration. Partners rotate each round.' + : 'Teams are created fresh for each round. No persistent teams.'} +

+
+ + {/* Partner Rotation (for variable/per_round teams) */} + {(teamDurability === 'variable' || teamDurability === 'per_round') && ( +
+ +
+ + + + +
+

+ {partnerRotation === 'none' + ? 'Partners are randomly assigned each round.' + : partnerRotation === 'minimize_repeat' + ? 'Algorithm minimizes how often players partner together.' + : partnerRotation === 'maximize_even' + ? 'Algorithm pairs teams to maximize competitive balance.' + : 'Strongest player paired with weakest player each round.'} +

+
+ )} + + {/* Allow Byes (for odd participant counts) */} +
+ +

+ When enabled, one player will have a bye each round if there's an odd number of participants. +

+
+ + {/* Action Buttons */} +
+ +
+
+ + {/* Error/Success Messages */} + {error && ( +
+
{error}
+
+ )} + + {success && ( +
+
{success}
+
+ )} + + {/* Team Generation Controls */} +
+ + + {teams.length > 0 && ( + + )} +
+ + {/* Teams Display */} + {teams.length > 0 ? ( +
+ {teams.map((team) => ( +
+
+

+ {team.player1.name} + {team.player2.name} +

+

+ {team.teamName || `Team ${team.id}`} +

+
+
+

+ ELO: {team.player1.currentElo} + {team.player2.currentElo} = {team.player1.currentElo + team.player2.currentElo} +

+
+
+ ))} +
+ ) : ( +

No teams created yet. Configure options above and click Generate Teams.

+ )} + + {/* Participants Summary */} +
+

+ Available Participants ({participants.length}) +

+
+ {participants.map((player) => ( +
+ {player.name} ({player.currentElo}) +
+ ))} +
+
+
+ ) +}