feat: add ScheduleDisplay component and wire up schedule page with Generator
This commit is contained in:
@@ -11,7 +11,7 @@ Feature: Tournament Schedule
|
||||
Then I should see "Schedule"
|
||||
And I should see the "Generate Schedule" button
|
||||
|
||||
@happy-path @tournament @issue-7 @wip
|
||||
@happy-path @tournament @issue-7
|
||||
Scenario: Tournament admin generates round-robin schedule
|
||||
Given I am logged in as a tournament admin
|
||||
And a tournament exists with 4 teams
|
||||
@@ -21,7 +21,7 @@ Feature: Tournament Schedule
|
||||
And I should see round 1 matchups
|
||||
And I should see round 2 matchups
|
||||
|
||||
@happy-path @tournament @issue-7 @wip
|
||||
@happy-path @tournament @issue-7
|
||||
Scenario: Tournament admin views schedule with bye rounds
|
||||
Given I am logged in as a tournament admin
|
||||
And a tournament exists with 5 teams
|
||||
@@ -30,7 +30,7 @@ Feature: Tournament Schedule
|
||||
Then I should see a bye round for one team
|
||||
And each team should play every other team exactly once
|
||||
|
||||
@happy-path @tournament @issue-7 @wip
|
||||
@happy-path @tournament @issue-7
|
||||
Scenario: Tournament admin clicks on a matchup to enter results
|
||||
Given I am logged in as a tournament admin
|
||||
And a tournament has a generated schedule
|
||||
|
||||
@@ -212,8 +212,6 @@ help:
|
||||
clean:
|
||||
@echo "Cleaning project..."
|
||||
rm -rf node_modules .next dist
|
||||
@echo "Cleaning Docker artifacts..."
|
||||
docker system prune -f
|
||||
|
||||
# Generate Prisma client
|
||||
prisma-generate:
|
||||
|
||||
@@ -2,6 +2,8 @@ import { prisma } from "@/lib/prisma"
|
||||
import Navigation from "@/components/Navigation"
|
||||
import Link from "next/link"
|
||||
import { notFound } from "next/navigation"
|
||||
import { ScheduleGenerator } from "@/components/ScheduleGenerator"
|
||||
import { ScheduleDisplay } from "@/components/ScheduleDisplay"
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
@@ -27,6 +29,21 @@ export default async function TournamentSchedulePage({ params }: PageProps) {
|
||||
player: true,
|
||||
},
|
||||
},
|
||||
rounds: {
|
||||
orderBy: { roundNumber: "asc" },
|
||||
include: {
|
||||
bracketMatchups: {
|
||||
orderBy: { bracketPosition: "asc" },
|
||||
include: {
|
||||
player1P1: true,
|
||||
player1P2: true,
|
||||
player2P1: true,
|
||||
player2P2: true,
|
||||
match: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -34,6 +51,9 @@ export default async function TournamentSchedulePage({ params }: PageProps) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const teamCount = tournament.participants.length
|
||||
const existingRounds = tournament.rounds.length
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Navigation />
|
||||
@@ -53,19 +73,28 @@ export default async function TournamentSchedulePage({ params }: PageProps) {
|
||||
Schedule - {tournament.name}
|
||||
</h1>
|
||||
|
||||
<div className="bg-white shadow rounded-lg p-6">
|
||||
<div className="bg-white shadow rounded-lg p-6 mb-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-bold text-gray-900">
|
||||
Tournament Schedule
|
||||
</h2>
|
||||
<button className="bg-green-600 text-white px-4 py-2 rounded-md text-sm font-medium hover:bg-green-700">
|
||||
Generate Schedule
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-500">
|
||||
No schedule has been generated yet. Click "Generate Schedule" to create round matchups.
|
||||
</p>
|
||||
{existingRounds > 0 ? (
|
||||
<ScheduleDisplay rounds={tournament.rounds} />
|
||||
) : (
|
||||
<p className="text-gray-500 mb-6">
|
||||
No schedule has been generated yet. Click "Generate Schedule" to create round matchups.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-gray-200">
|
||||
<ScheduleGenerator
|
||||
tournamentId={tournamentId}
|
||||
teamCount={teamCount}
|
||||
existingRounds={existingRounds}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
|
||||
interface Player {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
interface BracketMatchup {
|
||||
id: number
|
||||
player1P1: Player | null
|
||||
player1P2: Player | null
|
||||
player2P1: Player | null
|
||||
player2P2: Player | null
|
||||
match: { id: number } | null
|
||||
bracketPosition: number | null
|
||||
status: string
|
||||
}
|
||||
|
||||
interface TournamentRound {
|
||||
id: number
|
||||
roundNumber: number
|
||||
status: string
|
||||
bracketMatchups: BracketMatchup[]
|
||||
}
|
||||
|
||||
export function ScheduleDisplay({ rounds }: { rounds: TournamentRound[] }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{rounds.map((round) => (
|
||||
<div key={round.id} className="border border-gray-200 rounded-lg p-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">
|
||||
Round {round.roundNumber}
|
||||
<span className="ml-2 text-sm font-normal text-gray-500">
|
||||
({round.status})
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{round.bracketMatchups.map((matchup) => {
|
||||
const team1 = [
|
||||
matchup.player1P1?.name,
|
||||
matchup.player1P2?.name,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" + ")
|
||||
|
||||
const team2 = [
|
||||
matchup.player2P1?.name,
|
||||
matchup.player2P2?.name,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" + ")
|
||||
|
||||
const content = (
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-md">
|
||||
<div className="flex-1">
|
||||
<span className="font-medium text-gray-900">
|
||||
{team1 || "TBD"}
|
||||
</span>
|
||||
<span className="mx-3 text-gray-400">vs</span>
|
||||
<span className="font-medium text-gray-900">
|
||||
{team2 || "TBD"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{matchup.status === "pending" ? (
|
||||
<span className="text-gray-400">Pending</span>
|
||||
) : matchup.match ? (
|
||||
<span className="text-green-600">Completed</span>
|
||||
) : (
|
||||
<span className="text-gray-400">{matchup.status}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (matchup.match) {
|
||||
return (
|
||||
<Link
|
||||
key={matchup.id}
|
||||
href={`/matches/${matchup.match.id}`}
|
||||
className="block hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return <div key={matchup.id}>{content}</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user