feat: add ScheduleDisplay component and wire up schedule page with Generator

This commit is contained in:
2026-04-26 22:03:21 -07:00
parent 8f7ca1362a
commit 799f5e1c63
4 changed files with 138 additions and 13 deletions
@@ -11,7 +11,7 @@ Feature: Tournament Schedule
Then I should see "Schedule" Then I should see "Schedule"
And I should see the "Generate Schedule" button 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 Scenario: Tournament admin generates round-robin schedule
Given I am logged in as a tournament admin Given I am logged in as a tournament admin
And a tournament exists with 4 teams 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 1 matchups
And I should see round 2 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 Scenario: Tournament admin views schedule with bye rounds
Given I am logged in as a tournament admin Given I am logged in as a tournament admin
And a tournament exists with 5 teams And a tournament exists with 5 teams
@@ -30,7 +30,7 @@ Feature: Tournament Schedule
Then I should see a bye round for one team Then I should see a bye round for one team
And each team should play every other team exactly once 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 Scenario: Tournament admin clicks on a matchup to enter results
Given I am logged in as a tournament admin Given I am logged in as a tournament admin
And a tournament has a generated schedule And a tournament has a generated schedule
-2
View File
@@ -212,8 +212,6 @@ help:
clean: clean:
@echo "Cleaning project..." @echo "Cleaning project..."
rm -rf node_modules .next dist rm -rf node_modules .next dist
@echo "Cleaning Docker artifacts..."
docker system prune -f
# Generate Prisma client # Generate Prisma client
prisma-generate: prisma-generate:
@@ -2,6 +2,8 @@ import { prisma } from "@/lib/prisma"
import Navigation from "@/components/Navigation" import Navigation from "@/components/Navigation"
import Link from "next/link" import Link from "next/link"
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { ScheduleGenerator } from "@/components/ScheduleGenerator"
import { ScheduleDisplay } from "@/components/ScheduleDisplay"
interface PageProps { interface PageProps {
params: Promise<{ params: Promise<{
@@ -27,6 +29,21 @@ export default async function TournamentSchedulePage({ params }: PageProps) {
player: true, 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() notFound()
} }
const teamCount = tournament.participants.length
const existingRounds = tournament.rounds.length
return ( return (
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
<Navigation /> <Navigation />
@@ -53,19 +73,28 @@ export default async function TournamentSchedulePage({ params }: PageProps) {
Schedule - {tournament.name} Schedule - {tournament.name}
</h1> </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"> <div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">
Tournament Schedule Tournament Schedule
</h2> </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> </div>
<p className="text-gray-500"> {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. No schedule has been generated yet. Click "Generate Schedule" to create round matchups.
</p> </p>
)}
<div className="mt-6 pt-6 border-t border-gray-200">
<ScheduleGenerator
tournamentId={tournamentId}
teamCount={teamCount}
existingRounds={existingRounds}
/>
</div>
</div> </div>
</div> </div>
</main> </main>
+98
View File
@@ -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>
)
}