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"
|
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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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