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) => (
))}
{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 && (
)}
{/* 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 (
);
}