import { useState, useEffect, useMemo } from 'react' import { useAuthStore } from '../../store/authStore' import { useTranslation } from '../../i18n' import { MessageCircle, StickyNote, Link2, BarChart3, Sparkles } from 'lucide-react' import CollabChat from './CollabChat' import CollabNotes from './CollabNotes' import CollabPolls from './CollabPolls' import WhatsNextWidget from './WhatsNextWidget' import CollabLinks from './CollabLinks' import CollabPanelHead from './CollabPanelHead' function useIsDesktop(breakpoint = 1024) { const [isDesktop, setIsDesktop] = useState(window.innerWidth >= breakpoint) useEffect(() => { const check = () => setIsDesktop(window.innerWidth >= breakpoint) window.addEventListener('resize', check) return () => window.removeEventListener('resize', check) }, [breakpoint]) return isDesktop } const cardClass = 'flex flex-col bg-surface-secondary rounded-2xl border border-edge-faint overflow-hidden min-h-0' interface TripMember { id: number username: string avatar_url?: string | null } interface CollabFeatures { chat: boolean notes: boolean links?: boolean polls: boolean whatsnext: boolean } interface CollabPanelProps { tripId: number tripMembers?: TripMember[] collabFeatures?: CollabFeatures } const ALL_TABS = [ { id: 'chat', featureKey: 'chat' as const, labelKey: 'collab.tabs.chat', fallback: 'Chat', icon: MessageCircle }, { id: 'notes', featureKey: 'notes' as const, labelKey: 'collab.tabs.notes', fallback: 'Notes', icon: StickyNote }, { id: 'links', featureKey: 'links' as const, labelKey: 'collab.tabs.links', fallback: 'Links', icon: Link2 }, { id: 'polls', featureKey: 'polls' as const, labelKey: 'collab.tabs.polls', fallback: 'Polls', icon: BarChart3 }, { id: 'next', featureKey: 'whatsnext' as const, labelKey: 'collab.whatsNext.title', fallback: "What's Next", icon: Sparkles }, ] export default function CollabPanel({ tripId, tripMembers = [], collabFeatures }: CollabPanelProps) { const { user } = useAuthStore() const { t } = useTranslation() const isDesktop = useIsDesktop() // Older server/admin configs predate the Links feature; merge defaults so a // missing `links` key does not silently hide the new panel. const features = { chat: true, notes: true, links: true, polls: true, whatsnext: true, ...(collabFeatures || {}) } const tabs = useMemo(() => ALL_TABS.filter(tab => features[tab.featureKey]).map(tab => ({ ...tab, label: t(tab.labelKey) || tab.fallback, })), [features, t]) const [mobileTab, setMobileTab] = useState(() => tabs[0]?.id || 'chat') // If active tab gets disabled, switch to first available useEffect(() => { if (tabs.length > 0 && !tabs.some(t => t.id === mobileTab)) { setMobileTab(tabs[0].id) } }, [tabs, mobileTab]) const chatOn = features.chat const rightPanels = [ features.notes && 'notes', features.links && 'links', features.polls && 'polls', features.whatsnext && 'whatsnext', ].filter(Boolean) as string[] // One place decides what a panel id renders. It was spelled out at five call // sites, and the layout branch below dropped a panel precisely because one of // those copies did not list it. const renderPanel = (p: string) => ( <> {p === 'notes' && } {p === 'links' && } {p === 'polls' && } {p === 'whatsnext' && } ) // The chat carries the same head band as the panels beside it. const chatPanel = ( <>
) const panelRow = (ids: string[]) => (
{ids.map(p =>
{renderPanel(p)}
)}
) if (tabs.length === 0) return null if (isDesktop) { // Chat always 380px fixed when on. Right panels share remaining space. // If chat off, all panels share full width equally. if (chatOn && rightPanels.length === 0) { // Only chat return (
{chatPanel}
) } if (chatOn) { // Chat left (380px) + right panels return (
{chatPanel}
{rightPanels.length <= 2 && panelRow(rightPanels)} {rightPanels.length >= 3 && (() => { // Two rows, split by kind rather than by a pair being present: // the old condition needed notes AND links together, so turning // the new Links feature off dropped Notes out of the layout // entirely. Each row renders only when it has something in it. const written = rightPanels.filter(p => p === 'notes' || p === 'links') const rest = rightPanels.filter(p => p !== 'notes' && p !== 'links') return <> {written.length > 0 && panelRow(written)} {rest.length > 0 && panelRow(rest)} })()}
) } // Chat off — remaining panels share full width const panels = rightPanels return (
{panels.map(p => (
{renderPanel(p)}
))}
) } // Mobile: tab bar + single panel (only enabled tabs) return (
{tabs.map(tab => { const active = mobileTab === tab.id return ( ) })}
{mobileTab === 'chat' && features.chat && } {mobileTab === 'notes' && features.notes && } {mobileTab === 'links' && features.links && } {mobileTab === 'polls' && features.polls && } {mobileTab === 'next' && features.whatsnext && }
) }