278 lines
11 KiB
TypeScript
278 lines
11 KiB
TypeScript
|
|
import { Button } from "~/components/ui/button";
|
||
|
|
import { Badge } from "~/components/ui/badge";
|
||
|
|
|
||
|
|
interface AvailableParticipantsSectionProps {
|
||
|
|
participants: Array<{
|
||
|
|
id: string;
|
||
|
|
name: string;
|
||
|
|
expectedValue: number;
|
||
|
|
sport: {
|
||
|
|
id: string;
|
||
|
|
name: string;
|
||
|
|
};
|
||
|
|
}>;
|
||
|
|
searchQuery: string;
|
||
|
|
sportFilter: string;
|
||
|
|
hideDrafted: boolean;
|
||
|
|
uniqueSports: string[];
|
||
|
|
draftedParticipantIds: Set<string>;
|
||
|
|
queue: Array<{ id: string; participantId: string }>;
|
||
|
|
eligibility: {
|
||
|
|
eligibleSportIds: Set<string>;
|
||
|
|
ineligibleReasons: Record<string, string>;
|
||
|
|
} | null;
|
||
|
|
canPick: boolean;
|
||
|
|
hasTeam: boolean;
|
||
|
|
onSearchChange: (query: string) => void;
|
||
|
|
onSportFilterChange: (sport: string) => void;
|
||
|
|
onHideDraftedChange: (hide: boolean) => void;
|
||
|
|
onMakePick: (participantId: string) => void;
|
||
|
|
onAddToQueue: (participantId: string) => void;
|
||
|
|
onRemoveFromQueue: (queueId: string) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function AvailableParticipantsSection({
|
||
|
|
participants,
|
||
|
|
searchQuery,
|
||
|
|
sportFilter,
|
||
|
|
hideDrafted,
|
||
|
|
uniqueSports,
|
||
|
|
draftedParticipantIds,
|
||
|
|
queue,
|
||
|
|
eligibility,
|
||
|
|
canPick,
|
||
|
|
hasTeam,
|
||
|
|
onSearchChange,
|
||
|
|
onSportFilterChange,
|
||
|
|
onHideDraftedChange,
|
||
|
|
onMakePick,
|
||
|
|
onAddToQueue,
|
||
|
|
onRemoveFromQueue,
|
||
|
|
}: AvailableParticipantsSectionProps) {
|
||
|
|
return (
|
||
|
|
<div className="flex flex-col h-full">
|
||
|
|
<div className="mb-4 flex-shrink-0">
|
||
|
|
<h3 className="text-lg font-semibold mb-3">Available Participants</h3>
|
||
|
|
|
||
|
|
<div className="space-y-2">
|
||
|
|
{/* Search Input */}
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
placeholder="Search participants..."
|
||
|
|
value={searchQuery}
|
||
|
|
onChange={(e) => onSearchChange(e.target.value)}
|
||
|
|
className="w-full px-3 py-2 border rounded-md text-sm"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div className="flex gap-3">
|
||
|
|
{/* Sport Filter Dropdown */}
|
||
|
|
<select
|
||
|
|
value={sportFilter}
|
||
|
|
onChange={(e) => onSportFilterChange(e.target.value)}
|
||
|
|
className="flex-1 px-3 py-2 border rounded-md text-sm"
|
||
|
|
>
|
||
|
|
<option value="all">All Sports</option>
|
||
|
|
{uniqueSports.map((sport) => (
|
||
|
|
<option key={sport} value={sport}>
|
||
|
|
{sport}
|
||
|
|
</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
|
||
|
|
{/* Show/Hide Drafted Toggle */}
|
||
|
|
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md">
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={!hideDrafted}
|
||
|
|
onChange={(e) => onHideDraftedChange(!e.target.checked)}
|
||
|
|
className="rounded"
|
||
|
|
/>
|
||
|
|
<span>Show Drafted</span>
|
||
|
|
</label>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Table */}
|
||
|
|
<div className="border rounded-lg overflow-hidden flex-1 flex flex-col min-h-0">
|
||
|
|
<div className="overflow-y-auto flex-1">
|
||
|
|
<table className="w-full text-sm">
|
||
|
|
<thead className="bg-muted sticky top-0">
|
||
|
|
<tr>
|
||
|
|
<th className="text-left p-3 font-semibold">Participant</th>
|
||
|
|
<th className="text-left p-3 font-semibold">Sport</th>
|
||
|
|
<th className="text-right p-3 font-semibold">EV</th>
|
||
|
|
{hasTeam && (
|
||
|
|
<th className="text-right p-3 font-semibold">Queue</th>
|
||
|
|
)}
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
{participants.length === 0 ? (
|
||
|
|
<tr>
|
||
|
|
<td
|
||
|
|
colSpan={hasTeam ? 4 : 3}
|
||
|
|
className="text-center py-8 text-muted-foreground"
|
||
|
|
>
|
||
|
|
No participants found
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
) : (
|
||
|
|
participants.map((participant) => {
|
||
|
|
const isDrafted = draftedParticipantIds.has(participant.id);
|
||
|
|
const isInQueue = queue.some(
|
||
|
|
(item) => item.participantId === participant.id
|
||
|
|
);
|
||
|
|
|
||
|
|
// Check eligibility
|
||
|
|
const isEligible = eligibility
|
||
|
|
? eligibility.eligibleSportIds.has(participant.sport.id)
|
||
|
|
: true;
|
||
|
|
const ineligibleReason =
|
||
|
|
eligibility?.ineligibleReasons[participant.sport.id];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<tr
|
||
|
|
key={participant.id}
|
||
|
|
className={`border-t transition-colors ${
|
||
|
|
isDrafted
|
||
|
|
? "bg-muted/50 opacity-60"
|
||
|
|
: !isEligible
|
||
|
|
? "bg-red-50/30 dark:bg-red-950/20 opacity-75"
|
||
|
|
: "hover:bg-muted/50"
|
||
|
|
}`}
|
||
|
|
title={
|
||
|
|
!isEligible && !isDrafted ? ineligibleReason : undefined
|
||
|
|
}
|
||
|
|
>
|
||
|
|
<td className="p-3">
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<span
|
||
|
|
className={`font-medium ${!isEligible && !isDrafted ? "text-muted-foreground" : ""}`}
|
||
|
|
>
|
||
|
|
{participant.name}
|
||
|
|
</span>
|
||
|
|
{isDrafted && (
|
||
|
|
<Badge variant="secondary" className="text-xs">
|
||
|
|
Drafted
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
{!isDrafted && !isEligible && (
|
||
|
|
<Badge
|
||
|
|
variant="destructive"
|
||
|
|
className="text-xs"
|
||
|
|
title={ineligibleReason}
|
||
|
|
>
|
||
|
|
Ineligible
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
{isInQueue && !isDrafted && isEligible && (
|
||
|
|
<Badge variant="default" className="text-xs">
|
||
|
|
Queued
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td className="p-3 text-muted-foreground">
|
||
|
|
{participant.sport.name}
|
||
|
|
</td>
|
||
|
|
<td className="p-3 text-right font-mono font-semibold">
|
||
|
|
{participant.expectedValue}
|
||
|
|
</td>
|
||
|
|
{hasTeam && (
|
||
|
|
<td className="p-3 text-right">
|
||
|
|
<div className="flex gap-2 justify-end items-center">
|
||
|
|
{/* Pick button - only show if it's your turn and participant not drafted */}
|
||
|
|
{canPick && !isDrafted && (
|
||
|
|
<>
|
||
|
|
<Button
|
||
|
|
variant="default"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => onMakePick(participant.id)}
|
||
|
|
disabled={!isEligible}
|
||
|
|
title={
|
||
|
|
!isEligible ? ineligibleReason : undefined
|
||
|
|
}
|
||
|
|
>
|
||
|
|
Pick
|
||
|
|
</Button>
|
||
|
|
{/* Queue icon button next to Pick */}
|
||
|
|
{!isInQueue ? (
|
||
|
|
<Button
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => onAddToQueue(participant.id)}
|
||
|
|
title={
|
||
|
|
!isEligible
|
||
|
|
? ineligibleReason
|
||
|
|
: "Add to queue"
|
||
|
|
}
|
||
|
|
disabled={!isEligible}
|
||
|
|
>
|
||
|
|
<span className="text-lg">+</span>
|
||
|
|
</Button>
|
||
|
|
) : (
|
||
|
|
<Button
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => {
|
||
|
|
const queueItem = queue.find(
|
||
|
|
(item) =>
|
||
|
|
item.participantId === participant.id
|
||
|
|
);
|
||
|
|
if (queueItem) {
|
||
|
|
onRemoveFromQueue(queueItem.id);
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
title="Remove from queue"
|
||
|
|
>
|
||
|
|
<span className="text-lg">✓</span>
|
||
|
|
</Button>
|
||
|
|
)}
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Queue buttons - only show if not your turn */}
|
||
|
|
{!canPick && !isDrafted && !isInQueue && (
|
||
|
|
<Button
|
||
|
|
variant="outline"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => onAddToQueue(participant.id)}
|
||
|
|
disabled={!isEligible}
|
||
|
|
title={!isEligible ? ineligibleReason : undefined}
|
||
|
|
>
|
||
|
|
Add to Queue
|
||
|
|
</Button>
|
||
|
|
)}
|
||
|
|
{!canPick && !isDrafted && isInQueue && (
|
||
|
|
<Button
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => {
|
||
|
|
const queueItem = queue.find(
|
||
|
|
(item) =>
|
||
|
|
item.participantId === participant.id
|
||
|
|
);
|
||
|
|
if (queueItem) {
|
||
|
|
onRemoveFromQueue(queueItem.id);
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
Remove
|
||
|
|
</Button>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
)}
|
||
|
|
</tr>
|
||
|
|
);
|
||
|
|
})
|
||
|
|
)}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|