"use client" import { useState, useRef, useEffect } from "react" import { useRouter } from "next/navigation" import Navigation from "@/components/Navigation" // Interface for a single manual match entry interface ManualMatchEntry { id: number round: string table: string team1P1: string team1P2: string team2P1: string team2P2: string team1Score: string team2Score: string isCasual: boolean } export default function UploadMatchesPage() { const router = useRouter() const [activeTab, setActiveTab] = useState<"csv" | "manual">("csv") // CSV Upload state const [selectedFile, setSelectedFile] = useState(null) const [selectedTournament, setSelectedTournament] = useState("") const [csvError, setCsvError] = useState("") const [csvSuccess, setCsvSuccess] = useState("") const [isCsvLoading, setIsCsvLoading] = useState(false) const fileInputRef = useRef(null) // Manual Entry state const [manualTournament, setManualTournament] = useState("") const [manualMatches, setManualMatches] = useState([ { id: Date.now(), round: "", table: "", team1P1: "", team1P2: "", team2P1: "", team2P2: "", team1Score: "", team2Score: "", isCasual: false, }, ]) const [manualError, setManualError] = useState("") const [manualSuccess, setManualSuccess] = useState("") const [isManualLoading, setIsManualLoading] = useState(false) // Player list for autocomplete const [players, setPlayers] = useState<{ id: number; name: string }[]>([]) const [tournaments, setTournaments] = useState<{ id: number; name: string }[]>([]) // Fetch tournaments and players on mount useEffect(() => { // Fetch tournaments fetch(`${window.location.origin}/api/tournaments`) .then((res) => res.json()) .then((data) => { if (data.tournaments) { setTournaments(data.tournaments) if (data.tournaments.length > 0) { const recentId = data.tournaments[0].id.toString() setSelectedTournament(recentId) setManualTournament(recentId) } } }) .catch((err) => console.error("Failed to fetch tournaments:", err)) // Fetch players fetch(`${window.location.origin}/api/players`) .then((res) => res.json()) .then((data) => { if (data.players) { setPlayers(data.players) } }) .catch((err) => console.error("Failed to fetch players:", err)) }, []) const createDefaultTournament = async (): Promise<{ id: number; name: string } | null> => { try { const response = await fetch(`${window.location.origin}/api/tournaments`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: `Tournament ${new Date().toLocaleDateString()}`, format: "round_robin", eventDate: new Date().toISOString(), }), }) if (response.ok) { const data = await response.json() if (data.tournament) { const newTournaments = [data.tournament] setTournaments(newTournaments) setSelectedTournament(data.tournament.id.toString()) setManualTournament(data.tournament.id.toString()) return data.tournament } } return null } catch (err) { console.error("Failed to create default tournament:", err) return null } } // CSV Upload handlers const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) { if (!file.name.endsWith(".csv")) { setCsvError("Please upload a CSV file") return } setSelectedFile(file) setCsvError("") } } const handleCsvSubmit = async (e: React.FormEvent) => { e.preventDefault() setCsvError("") setCsvSuccess("") if (!selectedFile) { setCsvError("Please select a CSV file to upload") return } setIsCsvLoading(true) try { let tournamentId = selectedTournament // If no tournament is selected, create a default one if (!tournamentId) { const newTournament = await createDefaultTournament() if (newTournament) { tournamentId = newTournament.id.toString() } else { throw new Error("Failed to create default tournament") } } const formData = new FormData() formData.append("csvFile", selectedFile) formData.append("eventId", tournamentId) const response = await fetch(`${window.location.origin}/api/matches/upload`, { method: "POST", body: formData, }) if (!response.ok) { try { const errorData = await response.json() throw new Error(errorData.error || "Failed to upload CSV") } catch (jsonError) { if (jsonError instanceof Error && jsonError.message !== "Failed to upload CSV") { throw jsonError } throw new Error(`Failed to upload CSV: ${response.status} ${response.statusText}`) } } const data = await response.json() setCsvSuccess( `Successfully imported ${data.importedCount} matches. ` + `${data.errorCount || 0} errors occurred.` + (data.errors ? `\n\nErrors:\n${data.errors.join("\n")}` : "") ) // Reset form setSelectedFile(null) if (fileInputRef.current) { fileInputRef.current.value = "" } } catch (err) { if (err instanceof Error) { setCsvError(err.message) } else { setCsvError("An unknown error occurred") } } finally { setIsCsvLoading(false) } } // Manual Entry handlers const addMatchEntry = () => { const newEntry: ManualMatchEntry = { id: Date.now(), round: "", table: "", team1P1: "", team1P2: "", team2P1: "", team2P2: "", team1Score: "", team2Score: "", isCasual: false, } setManualMatches([...manualMatches, newEntry]) } const removeMatchEntry = (id: number) => { if (manualMatches.length > 1) { setManualMatches(manualMatches.filter((m) => m.id !== id)) } } const updateMatchEntry = (id: number, field: keyof ManualMatchEntry, value: string | boolean) => { setManualMatches( manualMatches.map((m) => (m.id === id ? { ...m, [field]: value } : m)) ) } const handleManualSubmit = async (e: React.FormEvent) => { e.preventDefault() setManualError("") setManualSuccess("") // Validate at least one match has required data const validMatches = manualMatches.filter( (m) => m.team1P1 && m.team1P2 && m.team2P1 && m.team2P2 && m.team1Score && m.team2Score ) if (validMatches.length === 0) { setManualError("Please enter at least one match with all player names and scores") return } setIsManualLoading(true) try { let tournamentId = manualTournament // If no tournament is selected, create a default one if (!tournamentId) { const newTournament = await createDefaultTournament() if (newTournament) { tournamentId = newTournament.id.toString() } else { throw new Error("Failed to create default tournament") } } const matchesData = validMatches.map((m) => ({ eventId: parseInt(tournamentId), round: m.round ? parseInt(m.round) : undefined, table: m.table || undefined, team1P1Name: m.team1P1, team1P2Name: m.team1P2, team2P1Name: m.team2P1, team2P2Name: m.team2P2, team1Score: parseInt(m.team1Score), team2Score: parseInt(m.team2Score), isCasual: m.isCasual, })) const response = await fetch(`${window.location.origin}/api/matches/bulk`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ matches: matchesData }), }) if (!response.ok) { try { const errorData = await response.json() throw new Error(errorData.error || "Failed to create matches") } catch (jsonError) { if (jsonError instanceof Error && jsonError.message !== "Failed to create matches") { throw jsonError } throw new Error(`Failed to create matches: ${response.status} ${response.statusText}`) } } const data = await response.json() setManualSuccess( `Successfully created ${data.importedCount} matches. ` + `${data.errorCount || 0} errors occurred.` + (data.errors ? `\n\nErrors:\n${data.errors.join("\n")}` : "") ) // Reset form setManualMatches([ { id: Date.now(), round: "", table: "", team1P1: "", team1P2: "", team2P1: "", team2P2: "", team1Score: "", team2Score: "", isCasual: false, }, ]) } catch (err) { if (err instanceof Error) { setManualError(err.message) } else { setManualError("An unknown error occurred") } } finally { setIsManualLoading(false) } } return (

Upload Match Results

{/* Tab Navigation */}
{/* CSV Upload Tab */} {activeTab === "csv" && (
{csvError && (
{csvError}
)} {csvSuccess && (
{csvSuccess}
)} {/* Tournament Selection */}
{/* File Upload */}

or drag and drop

CSV file with match results

{selectedFile && (

Selected: {selectedFile.name}

)}
{/* CSV Format Guide */}

CSV Format Requirements

  • Event #: Tournament ID (optional if selected above)
  • Round: Round number (1, 2, 3...)
  • Table: Table name
  • Seat 1: Player 1 name (Team 1)
  • Seat 3: Player 2 name (Team 1)
  • Odds Points: Score for Team 1
  • Seat 2: Player 1 name (Team 2)
  • Seat 4: Player 2 name (Team 2)
  • Evens Points: Score for Team 2
{/* Submit Button */}
)} {/* Manual Entry Tab */} {activeTab === "manual" && (
{manualError && (
{manualError}
)} {manualSuccess && (
{manualSuccess}
)} {/* Tournament Selection */}
{/* Manual Match Entries */}

Match Entries ({manualMatches.length})

{manualMatches.map((match, index) => (
Match {index + 1} {manualMatches.length > 1 && ( )}
{/* Round and Table */}
updateMatchEntry(match.id, "round", e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-1 px-2 text-sm focus:outline-none focus:ring-green-500 focus:border-green-500" placeholder="1" />
updateMatchEntry(match.id, "table", e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-1 px-2 text-sm focus:outline-none focus:ring-green-500 focus:border-green-500" placeholder="Table 1" />
{/* Team 1 */}
Team 1 updateMatchEntry(match.id, "team1Score", e.target.value)} className="w-16 text-center border border-blue-300 rounded py-1 px-2 text-sm" placeholder="Score" min="0" />
updateMatchEntry(match.id, "team1P1", e.target.value)} list="players-list" className="block w-full border border-blue-300 rounded-md shadow-sm py-1 px-2 text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Player 1" /> updateMatchEntry(match.id, "team1P2", e.target.value)} list="players-list" className="block w-full border border-blue-300 rounded-md shadow-sm py-1 px-2 text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="Player 2" />
{/* Team 2 */}
Team 2 updateMatchEntry(match.id, "team2Score", e.target.value)} className="w-16 text-center border border-red-300 rounded py-1 px-2 text-sm" placeholder="Score" min="0" />
updateMatchEntry(match.id, "team2P1", e.target.value)} list="players-list" className="block w-full border border-red-300 rounded-md shadow-sm py-1 px-2 text-sm focus:outline-none focus:ring-red-500 focus:border-red-500" placeholder="Player 1" /> updateMatchEntry(match.id, "team2P2", e.target.value)} list="players-list" className="block w-full border border-red-300 rounded-md shadow-sm py-1 px-2 text-sm focus:outline-none focus:ring-red-500 focus:border-red-500" placeholder="Player 2" />
{/* Casual Match Checkbox */}
updateMatchEntry(match.id, "isCasual", e.target.checked)} className="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded" />
))}
{/* Player autocomplete datalist */} {players.map((player) => ( {/* Submit Button */}
)} {/* Back Button */}
) }