- SeasonStandings: highlight rows for the logged-in user's drafted participants with electric accent (in-points) or muted (outside points) left border + star icon - Loader: derive userParticipantIds from current user's teams and draft picks - EventSchedule: hide type badge for schedule_event; show "Results Pending" only when event is past and not yet marked complete (matches admin page logic) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
725 lines
31 KiB
TypeScript
725 lines
31 KiB
TypeScript
import { Form, Link } from "react-router";
|
||
import type { Route } from "./+types/admin.sports-seasons.$id.events.$eventId";
|
||
import { loader, action } from "./admin.sports-seasons.$id.events.$eventId.server";
|
||
import { Button } from "~/components/ui/button";
|
||
import { Input } from "~/components/ui/input";
|
||
import { Label } from "~/components/ui/label";
|
||
import {
|
||
Card,
|
||
CardContent,
|
||
CardDescription,
|
||
CardHeader,
|
||
CardTitle,
|
||
} from "~/components/ui/card";
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "~/components/ui/select";
|
||
import { Badge } from "~/components/ui/badge";
|
||
import { ArrowLeft, Trophy, CheckCircle2, Pencil, Trash2, Brackets, Save } from "lucide-react";
|
||
import { getEventTypeLabel } from "~/models/scoring-event";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "~/components/ui/table";
|
||
import { useState } from "react";
|
||
|
||
export { loader, action };
|
||
|
||
export default function EventResults({
|
||
loaderData,
|
||
actionData,
|
||
}: Route.ComponentProps) {
|
||
const { sportsSeason, event, participants, results, participantResults, seasonResults, qpConfig, qpStandings } = loaderData;
|
||
const [hasChanges, setHasChanges] = useState(false);
|
||
|
||
// Create a map of participants with results for easy lookup
|
||
const participantResultsMap = new Map(
|
||
results.map((r: { participant: { id: string } }) => [r.participant.id, r])
|
||
);
|
||
|
||
// Get participants without results
|
||
const participantsWithoutResults = participants.filter(
|
||
(p: { id: string }) => !participantResultsMap.has(p.id)
|
||
);
|
||
|
||
// Sort results by placement
|
||
const sortedResults = [...results].sort((a, b) => (a.placement || 999) - (b.placement || 999));
|
||
|
||
// Create a map of season results for easy lookup
|
||
const seasonResultsMap = seasonResults
|
||
? new Map(
|
||
seasonResults.map((r: { participantId: string }) => [r.participantId, r])
|
||
)
|
||
: new Map();
|
||
|
||
// Non-scoring events have a simple view — edit name/date and toggle updated status
|
||
if (event.eventType === "schedule_event") {
|
||
const today = new Date().toISOString().split("T")[0];
|
||
const isPast = event.eventDate && event.eventDate < today;
|
||
|
||
return (
|
||
<div className="p-8">
|
||
<div className="max-w-2xl">
|
||
<div className="mb-6">
|
||
<Button variant="ghost" size="sm" asChild className="mb-2">
|
||
<Link to={`/admin/sports-seasons/${sportsSeason.id}/events`}>
|
||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||
Back to Events
|
||
</Link>
|
||
</Button>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold">{event.name}</h1>
|
||
<p className="text-muted-foreground mt-1">
|
||
{sportsSeason.sport.name} — {sportsSeason.name} • Non-Scoring
|
||
</p>
|
||
</div>
|
||
{event.isComplete ? (
|
||
<Badge variant="default" className="bg-emerald-500">
|
||
<CheckCircle2 className="mr-1 h-3 w-3" />
|
||
Updated
|
||
</Badge>
|
||
) : (
|
||
<Badge variant="outline">
|
||
{isPast ? "Results Pending" : "Upcoming"}
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{actionData?.error && (
|
||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm mb-4">
|
||
{actionData.error}
|
||
</div>
|
||
)}
|
||
{actionData?.success && (
|
||
<div className="bg-emerald-500/15 text-emerald-400 px-4 py-3 rounded-md text-sm mb-4">
|
||
{actionData.success}
|
||
</div>
|
||
)}
|
||
|
||
<div className="space-y-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Edit Event</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Form method="post" className="space-y-4">
|
||
<input type="hidden" name="intent" value="update-event" />
|
||
<div className="space-y-2">
|
||
<Label htmlFor="name">Name</Label>
|
||
<Input
|
||
id="name"
|
||
name="name"
|
||
defaultValue={event.name}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="eventDate">Date</Label>
|
||
<Input
|
||
id="eventDate"
|
||
name="eventDate"
|
||
type="date"
|
||
defaultValue={event.eventDate ?? ""}
|
||
/>
|
||
</div>
|
||
<Button type="submit" variant="outline">
|
||
<Save className="mr-2 h-4 w-4" />
|
||
Save Changes
|
||
</Button>
|
||
</Form>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Scoring Status</CardTitle>
|
||
<CardDescription>
|
||
{event.isComplete
|
||
? "This event has been noted as updated in standings."
|
||
: isPast
|
||
? "This event has passed — mark it once standings have been updated."
|
||
: "This event hasn't occurred yet."}
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{event.isComplete ? (
|
||
<Form method="post">
|
||
<input type="hidden" name="intent" value="uncomplete" />
|
||
<Button type="submit" variant="outline" size="sm">
|
||
Mark as Not Updated
|
||
</Button>
|
||
</Form>
|
||
) : (
|
||
<Form method="post">
|
||
<input type="hidden" name="intent" value="complete" />
|
||
<Button type="submit" size="sm">
|
||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||
Mark as Updated
|
||
</Button>
|
||
</Form>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="p-8">
|
||
<div className="max-w-4xl">
|
||
<div className="mb-6">
|
||
<Button variant="ghost" size="sm" asChild className="mb-2">
|
||
<Link to={`/admin/sports-seasons/${sportsSeason.id}/events`}>
|
||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||
Back to Events
|
||
</Link>
|
||
</Button>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold">{event.name}</h1>
|
||
<p className="text-muted-foreground mt-1">
|
||
{sportsSeason.sport.name} - {sportsSeason.name} •{" "}
|
||
{getEventTypeLabel(event.eventType)}
|
||
{event.playoffRound && ` • ${event.playoffRound}`}
|
||
</p>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
{event.eventType === "playoff_game" && (
|
||
<Button variant="outline" asChild>
|
||
<Link to={`/admin/sports-seasons/${sportsSeason.id}/events/${event.id}/bracket`}>
|
||
<Brackets className="mr-2 h-4 w-4" />
|
||
Manage Bracket
|
||
</Link>
|
||
</Button>
|
||
)}
|
||
{event.isComplete ? (
|
||
<Badge variant="default" className="bg-emerald-500">
|
||
<CheckCircle2 className="mr-1 h-3 w-3" />
|
||
Completed
|
||
</Badge>
|
||
) : (
|
||
<Badge variant="secondary">In Progress</Badge>
|
||
)}
|
||
{event.isQualifyingEvent && (
|
||
<Badge variant="outline" className="border-amber-500 text-amber-600">
|
||
Qualifying Event
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
{/* Debug info and manual QP processing for completed major tournaments */}
|
||
{event.isComplete && event.eventType === "major_tournament" && (
|
||
<Card className="border-electric/30">
|
||
<CardHeader>
|
||
<CardTitle className="text-electric">
|
||
Event Processing Status
|
||
</CardTitle>
|
||
<CardDescription>
|
||
<div className="space-y-1 font-mono text-xs">
|
||
<div>Event Type: {event.eventType}</div>
|
||
<div>Is Qualifying Event: {event.isQualifyingEvent ? 'Yes' : 'No'}</div>
|
||
<div>Sports Season Pattern: {sportsSeason.scoringPattern || 'not set'}</div>
|
||
<div>Results Count: {results.length}</div>
|
||
</div>
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3">
|
||
{!event.isQualifyingEvent && (
|
||
<div className="space-y-2">
|
||
<p className="text-sm text-muted-foreground">
|
||
This event needs to be marked as a qualifying event to award qualifying points.
|
||
</p>
|
||
<Form method="post">
|
||
<input type="hidden" name="intent" value="mark-qualifying" />
|
||
<Button type="submit" variant="outline">
|
||
Mark as Qualifying Event
|
||
</Button>
|
||
</Form>
|
||
</div>
|
||
)}
|
||
{event.isQualifyingEvent && (
|
||
<Form method="post">
|
||
<input type="hidden" name="intent" value="process-qp" />
|
||
<Button type="submit" className="bg-blue-600 hover:bg-blue-700">
|
||
<Trophy className="mr-2 h-4 w-4" />
|
||
Process/Reprocess Qualifying Points
|
||
</Button>
|
||
</Form>
|
||
)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
{actionData?.error && (
|
||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||
{actionData.error}
|
||
</div>
|
||
)}
|
||
|
||
{actionData?.success && (
|
||
<div className="bg-emerald-500/15 text-emerald-400 px-4 py-3 rounded-md text-sm">
|
||
{actionData.success}
|
||
</div>
|
||
)}
|
||
|
||
{/* Season Standings Table for final_standings events */}
|
||
{event.eventType === "final_standings" && !event.isComplete && (
|
||
<>
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Season Standings Tracker</CardTitle>
|
||
<CardDescription>
|
||
Enter championship points for each participant. Positions are automatically calculated based on points (highest = 1st).
|
||
When the season ends, mark this event as complete to assign fantasy points to the top 8.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Form method="post" className="space-y-4">
|
||
<input type="hidden" name="intent" value="update-standings" />
|
||
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>Participant</TableHead>
|
||
<TableHead className="text-right">Championship Points</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{participants.map((participant: { id: string; name: string }) => {
|
||
const result = seasonResultsMap.get(participant.id);
|
||
const currentPoints = result?.currentPoints || "";
|
||
|
||
return (
|
||
<TableRow key={participant.id}>
|
||
<TableCell className="font-medium">
|
||
{participant.name}
|
||
</TableCell>
|
||
<TableCell className="text-right">
|
||
<Input
|
||
type="number"
|
||
name={`points-${participant.id}`}
|
||
defaultValue={currentPoints}
|
||
placeholder="e.g., 250"
|
||
step="0.01"
|
||
min="0"
|
||
className="w-32 ml-auto"
|
||
onChange={() => setHasChanges(true)}
|
||
/>
|
||
</TableCell>
|
||
</TableRow>
|
||
);
|
||
})}
|
||
</TableBody>
|
||
</Table>
|
||
|
||
<div className="flex justify-between items-center pt-4">
|
||
<p className="text-sm text-muted-foreground">
|
||
{hasChanges
|
||
? "You have unsaved changes"
|
||
: "Update standings after each race/event during the season"}
|
||
</p>
|
||
<Button type="submit">
|
||
<Save className="mr-2 h-4 w-4" />
|
||
Save Standings
|
||
</Button>
|
||
</div>
|
||
</Form>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Complete Season Button */}
|
||
<Card className="border-amber-accent/30">
|
||
<CardHeader>
|
||
<CardTitle className="text-amber-accent">
|
||
<Trophy className="inline mr-2 h-5 w-5" />
|
||
Finalize Season
|
||
</CardTitle>
|
||
<CardDescription>
|
||
When the season is complete, mark this event as complete to:
|
||
<ul className="list-disc list-inside mt-2 space-y-1">
|
||
<li>Convert the top 8 participants (by position) to fantasy placements 1-8</li>
|
||
<li>Award fantasy points based on league scoring rules</li>
|
||
<li>Update all league standings</li>
|
||
</ul>
|
||
<p className="mt-2 font-semibold text-orange-600 dark:text-orange-400">
|
||
⚠️ Make sure all standings are saved before completing
|
||
</p>
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Form method="post">
|
||
<input type="hidden" name="intent" value="complete" />
|
||
<Button type="submit" variant="default" className="bg-orange-600 hover:bg-orange-700">
|
||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||
Mark Season Complete & Assign Fantasy Points
|
||
</Button>
|
||
</Form>
|
||
</CardContent>
|
||
</Card>
|
||
</>
|
||
)}
|
||
|
||
{/* Regular Result Entry for other event types */}
|
||
{event.eventType !== "final_standings" && !event.isComplete && (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Add Result</CardTitle>
|
||
<CardDescription>
|
||
Enter the placement for a participant in this event
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Form method="post" className="space-y-4">
|
||
<input type="hidden" name="intent" value="add-result" />
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="participantId">Participant</Label>
|
||
<Select name="participantId" required>
|
||
<SelectTrigger id="participantId">
|
||
<SelectValue placeholder="Select participant" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{participantsWithoutResults.length === 0 ? (
|
||
<div className="p-2 text-sm text-muted-foreground">
|
||
All participants have results
|
||
</div>
|
||
) : (
|
||
participantsWithoutResults.map((participant: { id: string; name: string }) => (
|
||
<SelectItem
|
||
key={participant.id}
|
||
value={participant.id}
|
||
>
|
||
{participant.name}
|
||
</SelectItem>
|
||
))
|
||
)}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="placement">Placement</Label>
|
||
<Input
|
||
id="placement"
|
||
name="placement"
|
||
type="number"
|
||
min="1"
|
||
max="100"
|
||
placeholder="e.g., 1"
|
||
required
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<Button
|
||
type="submit"
|
||
className="w-full"
|
||
disabled={participantsWithoutResults.length === 0}
|
||
>
|
||
<Trophy className="mr-2 h-4 w-4" />
|
||
Add Result
|
||
</Button>
|
||
</Form>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Manual QP Processing Button (for existing events) */}
|
||
{event.isComplete &&
|
||
event.eventType === "major_tournament" &&
|
||
sportsSeason.scoringPattern === "qualifying_points" &&
|
||
results.length > 0 && (
|
||
<Card className="border-amber-200 dark:border-amber-800">
|
||
<CardHeader>
|
||
<CardTitle className="text-amber-600 dark:text-amber-400">
|
||
Process Qualifying Points
|
||
</CardTitle>
|
||
<CardDescription>
|
||
This event is complete but qualifying points haven't been awarded yet.
|
||
Click below to award QP based on the results entered.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Form method="post">
|
||
<input type="hidden" name="intent" value="process-qp" />
|
||
<Button type="submit" className="bg-amber-600 hover:bg-amber-700">
|
||
<Trophy className="mr-2 h-4 w-4" />
|
||
Award Qualifying Points Now
|
||
</Button>
|
||
</Form>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Current Results - Hide for playoff events since they use the bracket */}
|
||
{event.eventType !== "playoff_game" && (
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<CardTitle>Results</CardTitle>
|
||
<CardDescription>
|
||
{results.length} of {participants.length} participants have
|
||
results
|
||
</CardDescription>
|
||
</div>
|
||
{!event.isComplete && results.length > 0 && event.eventType !== "final_standings" && (
|
||
<Form method="post">
|
||
<input type="hidden" name="intent" value="complete" />
|
||
<Button type="submit" variant="outline" size="sm">
|
||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||
Mark Event Complete
|
||
</Button>
|
||
</Form>
|
||
)}
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{sortedResults.length === 0 ? (
|
||
<p className="text-sm text-muted-foreground">
|
||
No results added yet. Add results using the form above.
|
||
</p>
|
||
) : (
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead className="w-24">Placement</TableHead>
|
||
<TableHead>Participant</TableHead>
|
||
{event.isQualifyingEvent && event.isComplete && (
|
||
<TableHead className="w-32 text-right">QP Awarded</TableHead>
|
||
)}
|
||
{!event.isComplete && <TableHead className="w-32">Actions</TableHead>}
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{sortedResults.map((result) => (
|
||
<TableRow key={result.id}>
|
||
<TableCell>
|
||
<div className="flex items-center gap-2">
|
||
{result.placement === 1 && (
|
||
<span className="text-xl">🥇</span>
|
||
)}
|
||
{result.placement === 2 && (
|
||
<span className="text-xl">🥈</span>
|
||
)}
|
||
{result.placement === 3 && (
|
||
<span className="text-xl">🥉</span>
|
||
)}
|
||
<span className="font-semibold">
|
||
{result.placement}
|
||
</span>
|
||
</div>
|
||
</TableCell>
|
||
<TableCell>{result.participant.name}</TableCell>
|
||
{event.isQualifyingEvent && event.isComplete && (
|
||
<TableCell className="text-right">
|
||
{result.qualifyingPointsAwarded ? (
|
||
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
||
{parseFloat(result.qualifyingPointsAwarded).toFixed(2)} QP
|
||
</span>
|
||
) : (
|
||
<span className="text-muted-foreground">0 QP</span>
|
||
)}
|
||
</TableCell>
|
||
)}
|
||
{!event.isComplete && (
|
||
<TableCell>
|
||
<div className="flex items-center gap-2">
|
||
<Form method="post" className="inline">
|
||
<input type="hidden" name="intent" value="update-result" />
|
||
<input type="hidden" name="resultId" value={result.id} />
|
||
<Input
|
||
type="number"
|
||
name="placement"
|
||
defaultValue={result.placement || ""}
|
||
min="1"
|
||
max="100"
|
||
className="w-16 h-8 text-sm"
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter') {
|
||
e.currentTarget.form?.requestSubmit();
|
||
}
|
||
}}
|
||
/>
|
||
<Button type="submit" size="sm" variant="ghost" className="h-8 w-8 p-0">
|
||
<Pencil className="h-3 w-3" />
|
||
</Button>
|
||
</Form>
|
||
<Form method="post" className="inline">
|
||
<input type="hidden" name="intent" value="delete-result" />
|
||
<input type="hidden" name="resultId" value={result.id} />
|
||
<Button
|
||
type="submit"
|
||
size="sm"
|
||
variant="ghost"
|
||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||
onClick={(e) => {
|
||
if (!confirm(`Delete result for ${result.participant.name}?`)) {
|
||
e.preventDefault();
|
||
}
|
||
}}
|
||
>
|
||
<Trash2 className="h-3 w-3" />
|
||
</Button>
|
||
</Form>
|
||
</div>
|
||
</TableCell>
|
||
)}
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Bracket Explanation Card for Playoff Events */}
|
||
{event.eventType === "playoff_game" && !participantResults?.length && (
|
||
<Card className="border-electric/30 bg-electric/10">
|
||
<CardHeader>
|
||
<CardTitle className="text-electric">
|
||
<Brackets className="inline mr-2 h-5 w-5" />
|
||
Bracket Event
|
||
</CardTitle>
|
||
<CardDescription className="text-electric">
|
||
This is a bracket/playoff event. Results are managed through the bracket interface.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-3 text-sm">
|
||
<p>To complete this event:</p>
|
||
<ol className="list-decimal list-inside space-y-2 ml-2">
|
||
<li>Click "Manage Bracket" above to set match winners</li>
|
||
<li>Once all matches are complete, click "Finalize Bracket"</li>
|
||
<li>Fantasy placements and points will appear below automatically</li>
|
||
</ol>
|
||
<div className="pt-3">
|
||
<Button variant="outline" asChild>
|
||
<Link to={`/admin/sports-seasons/${sportsSeason.id}/events/${event.id}/bracket`}>
|
||
<Brackets className="mr-2 h-4 w-4" />
|
||
Go to Bracket Manager
|
||
</Link>
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Participant Results with Fantasy Points */}
|
||
{participantResults && participantResults.length > 0 && (
|
||
<Card className={event.eventType === "playoff_game" ? "border-emerald-500/30" : ""}>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<CardTitle className={event.eventType === "playoff_game" ? "text-emerald-400" : ""}>
|
||
{event.eventType === "playoff_game" ? (
|
||
<>
|
||
<Trophy className="inline mr-2 h-5 w-5" />
|
||
Fantasy Points Awarded (from Bracket)
|
||
</>
|
||
) : (
|
||
"Fantasy Points Awarded"
|
||
)}
|
||
</CardTitle>
|
||
<CardDescription>
|
||
{event.eventType === "playoff_game"
|
||
? `${participantResults.length} participants assigned placements from bracket results`
|
||
: "Points calculated from bracket placements (sorted by position)"
|
||
}
|
||
</CardDescription>
|
||
</div>
|
||
{event.eventType === "playoff_game" && event.isComplete && (
|
||
<Badge variant="default" className="bg-emerald-500">
|
||
<CheckCircle2 className="mr-1 h-3 w-3" />
|
||
Bracket Finalized
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead className="w-32">Final Position</TableHead>
|
||
<TableHead>Participant</TableHead>
|
||
<TableHead className="w-32 text-right">Fantasy Points</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{[...participantResults]
|
||
.sort((a: any, b: any) => {
|
||
const posA = a.finalPosition ?? 999;
|
||
const posB = b.finalPosition ?? 999;
|
||
return posA - posB;
|
||
})
|
||
.map((result: any) => (
|
||
<TableRow key={result.id}>
|
||
<TableCell>
|
||
<div className="flex items-center gap-2">
|
||
{result.finalPosition === 1 && (
|
||
<span className="text-xl">🥇</span>
|
||
)}
|
||
{result.finalPosition === 2 && (
|
||
<span className="text-xl">🥈</span>
|
||
)}
|
||
{result.finalPosition === 3 && (
|
||
<span className="text-xl">🥉</span>
|
||
)}
|
||
<span className="font-semibold">
|
||
{result.finalPosition === 0 ? (
|
||
<span className="text-muted-foreground">Early Elimination</span>
|
||
) : (
|
||
`${result.finalPosition}${
|
||
result.finalPosition === 1
|
||
? "st"
|
||
: result.finalPosition === 2
|
||
? "nd"
|
||
: result.finalPosition === 3
|
||
? "rd"
|
||
: "th"
|
||
}`
|
||
)}
|
||
</span>
|
||
</div>
|
||
</TableCell>
|
||
<TableCell>{result.participant.name}</TableCell>
|
||
<TableCell className="text-right font-semibold">
|
||
{result.qualifyingPoints ? (
|
||
<span className={result.qualifyingPoints === "0.00" ? "text-muted-foreground" : "text-emerald-400"}>
|
||
{parseFloat(result.qualifyingPoints).toFixed(2)} pts
|
||
</span>
|
||
) : (
|
||
<span className="text-muted-foreground">-</span>
|
||
)}
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|