import ReactDOM from 'react-dom' import { useState, useEffect, useRef, useCallback } from 'react' import { Pencil, Users, Check } from 'lucide-react' import type { BudgetItemMember } from '../../types' export interface TripMember { id: number username: string avatar_url?: string | null } // ── Chip with custom tooltip ───────────────────────────────────────────────── interface ChipWithTooltipProps { label: string avatarUrl: string | null size?: number paid?: boolean onClick?: () => void } export function ChipWithTooltip({ label, avatarUrl, size = 20, paid, onClick }: ChipWithTooltipProps) { const [hover, setHover] = useState(false) const [pos, setPos] = useState({ top: 0, left: 0 }) const ref = useRef(null) const onEnter = () => { if (ref.current) { const rect = ref.current.getBoundingClientRect() setPos({ top: rect.top - 6, left: rect.left + rect.width / 2 }) } setHover(true) } const borderColor = paid ? '#22c55e' : 'var(--border-primary)' const bg = paid ? 'rgba(34,197,94,0.15)' : 'var(--bg-tertiary)' return ( <>
setHover(false)} onClick={onClick} style={{ width: size, height: size, borderRadius: '50%', border: `2px solid ${borderColor}`, background: bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: size * 0.4, fontWeight: 700, color: paid ? '#16a34a' : 'var(--text-muted)', overflow: 'hidden', flexShrink: 0, cursor: onClick ? 'pointer' : 'default', transition: 'border-color 0.15s, background 0.15s', }}> {avatarUrl ? : label?.[0]?.toUpperCase() }
{hover && ReactDOM.createPortal(
{label} {paid && ( Paid )}
, document.body )} ) } // ── Budget Member Chips (for Persons column) ──────────────────────────────── interface BudgetMemberChipsProps { members?: BudgetItemMember[] tripMembers?: TripMember[] onSetMembers: (memberIds: number[]) => void onTogglePaid?: (userId: number, paid: boolean) => void compact?: boolean readOnly?: boolean } export default function BudgetMemberChips({ members = [], tripMembers = [], onSetMembers, onTogglePaid, compact = true, readOnly = false }: BudgetMemberChipsProps) { const chipSize = compact ? 20 : 30 const btnSize = compact ? 18 : 28 const iconSize = compact ? (members.length > 0 ? 8 : 9) : (members.length > 0 ? 12 : 14) const [showDropdown, setShowDropdown] = useState(false) const [dropPos, setDropPos] = useState({ top: 0, left: 0 }) const btnRef = useRef(null) const dropRef = useRef(null) const openDropdown = useCallback(() => { if (btnRef.current) { const rect = btnRef.current.getBoundingClientRect() setDropPos({ top: rect.bottom + 4, left: rect.left + rect.width / 2 }) } setShowDropdown(v => !v) }, []) useEffect(() => { if (!showDropdown) return const close = (e: MouseEvent) => { if (dropRef.current && dropRef.current.contains(e.target as Node)) return if (btnRef.current && btnRef.current.contains(e.target as Node)) return setShowDropdown(false) } document.addEventListener('mousedown', close) return () => document.removeEventListener('mousedown', close) }, [showDropdown]) const memberIds = members.map(m => m.user_id) const toggleMember = (userId: number) => { const newIds = memberIds.includes(userId) ? memberIds.filter(id => id !== userId) : [...memberIds, userId] onSetMembers(newIds) } return (
{members.map(m => ( onTogglePaid(m.user_id, !m.paid) : undefined} /> ))} {!readOnly && ( )} {showDropdown && ReactDOM.createPortal(
{tripMembers.map(tm => { const isActive = memberIds.includes(tm.id) return ( ) })}
, document.body )}
) }