import { useState, useMemo } from 'react'; import { Users, UserPlus, Search, X } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import type { EventInvitation, Connection } from '@/types'; // ── Status display helpers ── const STATUS_CONFIG = { accepted: { label: 'Going', dotClass: 'bg-green-400', textClass: 'text-green-400' }, tentative: { label: 'Tentative', dotClass: 'bg-amber-400', textClass: 'text-amber-400' }, declined: { label: 'Declined', dotClass: 'bg-red-400', textClass: 'text-red-400' }, pending: { label: 'Pending', dotClass: 'bg-neutral-500', textClass: 'text-muted-foreground' }, } as const; function StatusBadge({ status }: { status: string }) { const config = STATUS_CONFIG[status as keyof typeof STATUS_CONFIG] ?? STATUS_CONFIG.pending; return (
{config.label}
); } function AvatarCircle({ name }: { name: string }) { const letter = name?.charAt(0)?.toUpperCase() || '?'; return (
{letter}
); } // ── View Mode: InviteeList ── interface InviteeListProps { invitees: EventInvitation[]; isRecurringChild?: boolean; } export function InviteeList({ invitees, isRecurringChild }: InviteeListProps) { if (invitees.length === 0) return null; const goingCount = invitees.filter((i) => i.status === 'accepted').length; const countLabel = goingCount > 0 ? `${goingCount} going` : null; return (
Invitees
{countLabel && ( {countLabel} )}
{invitees.map((inv) => (
{inv.invitee_name}
))}
{isRecurringChild && (

Status shown for this occurrence

)}
); } // ── Edit Mode: InviteSearch ── interface InviteSearchProps { connections: Connection[]; existingInviteeIds: Set; onInvite: (userIds: number[]) => void; isInviting: boolean; } export function InviteSearch({ connections, existingInviteeIds, onInvite, isInviting }: InviteSearchProps) { const [search, setSearch] = useState(''); const [selectedIds, setSelectedIds] = useState([]); const searchResults = useMemo(() => { if (!search.trim()) return []; const q = search.toLowerCase(); return connections .filter((c) => !existingInviteeIds.has(c.connected_user_id) && !selectedIds.includes(c.connected_user_id) && ( (c.connected_preferred_name?.toLowerCase().includes(q)) || c.connected_umbral_name.toLowerCase().includes(q) ) ) .slice(0, 6); }, [search, connections, existingInviteeIds, selectedIds]); const selectedConnections = connections.filter((c) => selectedIds.includes(c.connected_user_id)); const handleAdd = (userId: number) => { setSelectedIds((prev) => [...prev, userId]); setSearch(''); }; const handleRemove = (userId: number) => { setSelectedIds((prev) => prev.filter((id) => id !== userId)); }; const handleSend = () => { if (selectedIds.length === 0) return; onInvite(selectedIds); setSelectedIds([]); }; return (
Invite People
setSearch(e.target.value)} onBlur={() => setTimeout(() => setSearch(''), 150)} placeholder="Search connections..." className="h-8 pl-8 text-xs" /> {search.trim() && searchResults.length > 0 && (
{searchResults.map((conn) => ( ))}
)} {search.trim() && searchResults.length === 0 && (

No connections found

)}
{/* Selected invitees */} {selectedConnections.length > 0 && (
{selectedConnections.map((conn) => (
{conn.connected_preferred_name || conn.connected_umbral_name}
))}
)}
); } // ── RSVP Buttons (for invitee view) ── interface RsvpButtonsProps { currentStatus: string; onRespond: (status: 'accepted' | 'tentative' | 'declined') => void; isResponding: boolean; } export function RsvpButtons({ currentStatus, onRespond, isResponding }: RsvpButtonsProps) { return (
); }