import { memo, useCallback, useMemo } from "react"; import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { Checkbox } from "~/components/ui/checkbox"; import { Popover, PopoverTrigger, PopoverContent, } from "~/components/ui/popover"; import { Sheet, SheetTrigger, SheetContent, SheetTitle, SheetFooter, SheetClose, } from "~/components/ui/sheet"; import { ListPlus, ListX, ChevronDown } from "lucide-react"; function getParticipantState( participant: { id: string; sport: { id: string } }, draftedParticipantIds: Set, queueMap: Map, eligibility: { eligibleSportIds: Set; ineligibleReasons: Record; } | null ) { const isDrafted = draftedParticipantIds.has(participant.id); const isInQueue = queueMap.has(participant.id); const isEligible = eligibility ? eligibility.eligibleSportIds.has(participant.sport.id) : true; const ineligibleReason = eligibility?.ineligibleReasons[participant.sport.id]; return { isDrafted, isInQueue, isEligible, ineligibleReason }; } interface SportFilterContentProps { sportsForDropdown: Array<{ name: string; isDrafted: boolean }>; sportFilterSet: Set; hideCompletedSports: boolean; hasTeam: boolean; variant: "sheet" | "popover"; onToggleSport: (sport: string, checked: boolean | "indeterminate") => void; onHideCompletedSportsChange: (hide: boolean) => void; } function SportFilterContent({ sportsForDropdown, sportFilterSet, hideCompletedSports, hasTeam, variant, onToggleSport, onHideCompletedSportsChange, }: SportFilterContentProps) { const isSheet = variant === "sheet"; const idPrefix = isSheet ? "sheet-sport-filter-" : "popover-sport-filter-"; const listClass = isSheet ? "overflow-y-auto flex-1 px-4" : "max-h-64 overflow-y-auto p-2 space-y-0.5"; const itemBaseClass = isSheet ? "flex items-center gap-4 py-4 border-b last:border-b-0 cursor-pointer text-base" : "flex items-center gap-2 px-2 py-2 rounded-sm hover:bg-accent cursor-pointer text-sm"; const toggleLabelClass = isSheet ? "flex items-center gap-4 py-2 cursor-pointer text-base" : "flex items-center gap-2 px-4 py-3 cursor-pointer text-sm"; return ( <>
{sportsForDropdown .filter(({ isDrafted }) => !isDrafted || !hideCompletedSports) .map(({ name, isDrafted }) => { const checkboxId = `${idPrefix}${name.replace(/\s+/g, "-").toLowerCase()}`; return ( ); })}
{hasTeam && ( isSheet ? (
) : ( <>
) )} ); } interface AvailableParticipantsSectionProps { participants: Array<{ id: string; name: string; sport: { id: string; name: string; }; }>; searchQuery: string; sportFilters: string[]; hideDrafted: boolean; hideIneligible: boolean; hideCompletedSports: boolean; userDraftedSportNames: Set; uniqueSports: string[]; draftedParticipantIds: Set; queue: Array<{ id: string; participantId: string }>; eligibility: { eligibleSportIds: Set; ineligibleReasons: Record; } | null; canPick: boolean; hasTeam: boolean; onSearchChange: (query: string) => void; onSportFiltersChange: (sports: string[]) => void; onHideDraftedChange: (hide: boolean) => void; onHideIneligibleChange: (hide: boolean) => void; onHideCompletedSportsChange: (hide: boolean) => void; onMakePick: (participantId: string) => void; onAddToQueue: (participantId: string) => void; onRemoveFromQueue: (queueId: string) => void; } export const AvailableParticipantsSection = memo(function AvailableParticipantsSection({ participants, searchQuery, sportFilters, hideDrafted, hideIneligible, hideCompletedSports, userDraftedSportNames, uniqueSports, draftedParticipantIds, queue, eligibility, canPick, hasTeam, onSearchChange, onSportFiltersChange, onHideDraftedChange, onHideIneligibleChange, onHideCompletedSportsChange, onMakePick, onAddToQueue, onRemoveFromQueue, }: AvailableParticipantsSectionProps) { const queueMap = useMemo( () => new Map(queue.map((item) => [item.participantId, item.id])), [queue] ); const sportFilterSet = useMemo(() => new Set(sportFilters), [sportFilters]); const sportsForDropdown = useMemo(() => { return uniqueSports.map((s) => ({ name: s, isDrafted: hasTeam && userDraftedSportNames.has(s), })); }, [uniqueSports, userDraftedSportNames, hasTeam]); const triggerText = useMemo( () => sportFilters.length === 0 ? "All Sports" : sportFilters.length === 1 ? sportFilters[0] : `${sportFilters.length} sports`, [sportFilters] ); const triggerAriaLabel = useMemo( () => sportFilters.length === 0 ? "Filter by sport: All Sports" : sportFilters.length === 1 ? `Filter by sport: ${sportFilters[0]}` : `Filter by sport: ${sportFilters.length} sports selected`, [sportFilters] ); const handleToggleSport = useCallback( (sport: string, checked: boolean | "indeterminate") => { if (checked === true) { onSportFiltersChange([...sportFilters, sport]); } else { onSportFiltersChange(sportFilters.filter((s) => s !== sport)); } }, [sportFilters, onSportFiltersChange] ); const handleReset = useCallback(() => { onSportFiltersChange([]); onHideCompletedSportsChange(false); }, [onSportFiltersChange, onHideCompletedSportsChange]); const emptyMessage = useMemo(() => { if (participants.length > 0) return null; const active: string[] = []; if (hideDrafted) active.push("drafted players"); if (hideIneligible && eligibility) active.push("ineligible players"); if (hideCompletedSports) active.push("drafted sports"); if (sportFilters.length > 0) active.push("other sports"); if (active.length === 0) return "No participants found."; return `No participants found. Try showing ${active.join(", ")}.`; }, [participants.length, hideDrafted, hideIneligible, hideCompletedSports, eligibility, sportFilters]); const hasActiveFilters = sportFilters.length > 0 || hideCompletedSports; return (
{/* Search Input */} onSearchChange(e.target.value)} className="w-full px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground" />
{/* Sport Filter Multi-Select */}
{/* Mobile: bottom sheet */}
Filter by sport {hasActiveFilters && ( )}
{/* Desktop: popover */}
{hasActiveFilters && (
)}
{/* Show/Hide Drafted Toggle */} {/* Show/Hide Ineligible Toggle - only show if user has a team */} {hasTeam && eligibility && ( )}
{/* Mobile Card List - visible on mobile only */}
{participants.length === 0 ? (
{emptyMessage}
) : ( participants.map((participant) => { const { isDrafted, isInQueue, isEligible, ineligibleReason } = getParticipantState(participant, draftedParticipantIds, queueMap, eligibility); return (
{participant.name}
{participant.sport.name} {isDrafted && ( Drafted )} {!isDrafted && !isEligible && ( Ineligible )} {isInQueue && !isDrafted && isEligible && ( Queued )}
{hasTeam && !isDrafted && (
{!isInQueue ? ( ) : ( )}
)}
); }) )}
{/* Desktop Table - hidden on mobile */}
{hasTeam && ( )} {participants.length === 0 ? ( ) : ( participants.map((participant) => { const { isDrafted, isInQueue, isEligible, ineligibleReason } = getParticipantState(participant, draftedParticipantIds, queueMap, eligibility); return ( {hasTeam && ( )} ); }) )}
Participant SportQueue
{emptyMessage}
{participant.name} {isDrafted && ( Drafted )} {!isDrafted && !isEligible && ( Ineligible )} {isInQueue && !isDrafted && isEligible && ( Queued )}
{participant.sport.name}
{!isDrafted && ( <> {/* Queue icon button */} {!isInQueue ? ( ) : ( )} {/* Draft button - always visible, disabled when not your turn */} )}
); });