import { useState } from 'react'
import { Plus, Pin, Trash2, MessageSquare, History, X } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { ChatListItem } from '@/types/assistant-chat'
interface ChatSidebarProps {
chats: ChatListItem[]
activeChatId: string | null
onSelectChat: (id: string) => void
onNewChat: () => void
onDeleteChat: (id: string) => void
onTogglePin: (id: string, pinned: boolean) => void
mobileOpen?: boolean
onMobileClose?: () => void
collapsed?: boolean
onToggleCollapse?: () => void
}
export function ChatSidebar({
chats,
activeChatId,
onSelectChat,
onNewChat,
onDeleteChat,
onTogglePin,
mobileOpen = false,
onMobileClose,
collapsed = false,
onToggleCollapse,
}: ChatSidebarProps) {
const pinnedChats = chats.filter(c => c.pinned)
const unpinnedChats = chats.filter(c => !c.pinned)
const handleSelectChat = (id: string) => {
onSelectChat(id)
onMobileClose?.()
}
const handleNewChat = () => {
onNewChat()
onMobileClose?.()
}
// When collapsed on desktop, render nothing — parent renders the top bar
if (collapsed && !mobileOpen) {
return null
}
return (
<>
{/* Mobile overlay */}
{mobileOpen && (
)}
{/* Header */}
New Chat
{onToggleCollapse && (
)}
{/* Chat list */}
{pinnedChats.length > 0 && (
Pinned
)}
{pinnedChats.map(chat => (
handleSelectChat(chat.id)}
onDelete={() => onDeleteChat(chat.id)}
onTogglePin={() => onTogglePin(chat.id, !chat.pinned)}
/>
))}
{pinnedChats.length > 0 && unpinnedChats.length > 0 && (
)}
{unpinnedChats.map(chat => (
handleSelectChat(chat.id)}
onDelete={() => onDeleteChat(chat.id)}
onTogglePin={() => onTogglePin(chat.id, !chat.pinned)}
/>
))}
{chats.length === 0 && (
No conversations yet
)}
>
)
}
/** Collapsed top bar — rendered by the parent page above the chat area */
export function ChatSidebarCollapsedBar({
chats,
activeChatId,
onNewChat,
onExpand,
}: {
chats: ChatListItem[]
activeChatId: string | null
onNewChat: () => void
onExpand: () => void
}) {
return (
New
History
{chats.length > 0 && (
{chats.length}
)}
{activeChatId && (
{chats.find(c => c.id === activeChatId)?.title}
)}
)
}
function ChatItem({
chat,
isActive,
onSelect,
onDelete,
onTogglePin,
}: {
chat: ChatListItem
isActive: boolean
onSelect: () => void
onDelete: () => void
onTogglePin: () => void
}) {
const [confirming, setConfirming] = useState(false)
return (
e.stopPropagation() : onSelect}
className={cn(
'group flex items-center gap-2 px-3 py-2.5 mx-1.5 rounded-lg cursor-pointer transition-colors',
confirming
? 'bg-danger-dim border border-danger/20'
: isActive
? 'bg-accent-dim text-foreground'
: 'text-muted-foreground hover:bg-input hover:text-foreground'
)}
>
{confirming ? (
Delete?
{ e.stopPropagation(); onDelete(); setConfirming(false) }}
className="text-[0.6875rem] font-medium text-danger hover:text-danger px-1.5 py-0.5 rounded bg-danger/15 hover:bg-danger/25 transition-colors"
>
Yes
{ e.stopPropagation(); setConfirming(false) }}
className="text-[0.6875rem] text-muted-foreground hover:text-foreground px-1.5 py-0.5"
>
No
) : (
<>
{chat.title}
{chat.psa_ticket_id && (
#{chat.psa_ticket_id}
)}
{(chat.status === 'escalated' || chat.status === 'requesting_escalation') && (
Escalated
)}
{/* Secondary line: problem snippet when the title doesn't already
carry it, otherwise the message count. Keeps untitled
sessions from collapsing into identical-looking rows. */}
{chat.problem_summary && chat.problem_summary !== chat.title ? (
{chat.problem_summary}
) : (
{chat.message_count} messages
)}
>
)}
{!confirming && (
{ e.stopPropagation(); onTogglePin() }}
className="p-1 rounded hover:bg-elevated"
title={chat.pinned ? 'Unpin' : 'Pin'}
>
{ e.stopPropagation(); setConfirming(true) }}
className="p-1 rounded hover:bg-elevated text-muted-foreground hover:text-danger"
title="Delete"
>
)}
)
}