import { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Plus,
MessageSquare,
Bookmark,
Trash2,
X,
Database,
ChevronRight,
Sparkles,
Search,
PanelLeftClose,
PanelLeft,
Settings,
ArrowRight,
Hash
} from 'lucide-react';
import useChatStore from '../../store/useChatStore';
const TABLE_GROUPS = {
'accounts': 'Core SaaS',
'contacts': 'Core SaaS',
'workspaces': 'Core SaaS',
'workspace_users': 'Core SaaS',
'departments': 'HR & Staff',
'employees': 'HR & Staff',
'plans': 'Billing',
'products': 'Billing',
'feature_catalog': 'Billing',
'subscriptions': 'Billing',
'invoices': 'Billing',
'payments': 'Billing',
'opportunities': 'Sales',
'support_tickets': 'Support & Ops',
'ticket_events': 'Support & Ops',
'incidents': 'Support & Ops',
'product_usage_daily': 'Support & Ops',
'query_audit_log': 'Support & Ops',
};
const MOCK_COLUMNS = {
departments: ['id', 'name', 'manager_id', 'location'],
employees: ['id', 'first_name', 'last_name', 'email', 'department_id', 'salary', 'hire_date'],
plans: ['id', 'name', 'price', 'billing_interval', 'features'],
products: ['id', 'name', 'sku', 'price', 'status'],
feature_catalog: ['id', 'name', 'code', 'status'],
accounts: ['id', 'name', 'domain', 'industry', 'owner_id', 'created_at'],
contacts: ['id', 'account_id', 'first_name', 'last_name', 'email', 'phone'],
workspaces: ['id', 'account_id', 'name', 'slug', 'created_at'],
workspace_users: ['workspace_id', 'user_id', 'role', 'joined_at'],
subscriptions: ['id', 'account_id', 'plan_id', 'status', 'start_date', 'end_date'],
invoices: ['id', 'subscription_id', 'amount', 'due_date', 'status'],
payments: ['id', 'invoice_id', 'amount', 'payment_method', 'status', 'created_at'],
opportunities: ['id', 'account_id', 'name', 'amount', 'stage', 'closed_at'],
product_usage_daily: ['date', 'workspace_id', 'queries_run', 'active_users'],
support_tickets: ['id', 'account_id', 'subject', 'status', 'priority', 'created_at'],
ticket_events: ['id', 'ticket_id', 'event_type', 'created_at'],
incidents: ['id', 'title', 'severity', 'status', 'created_at'],
query_audit_log: ['id', 'user_id', 'query_text', 'execution_time_ms', 'status'],
};
function SectionHeader({ title, count, collapsed, onToggle, isSidebarCollapsed }) {
if (isSidebarCollapsed) return
;
return (
);
}
function EmptyChats({ onNewChat }) {
return (
No conversations
Start your first AI analysis
);
}
export default function Sidebar({ open, onClose }) {
const chats = useChatStore(s => s.chats);
const activeChatId = useChatStore(s => s.activeChatId);
const savedQueries = useChatStore(s => s.savedQueries);
const health = useChatStore(s => s.health);
const schemaTables = useChatStore(s => s.schemaTables);
const selectedSchema = useChatStore(s => s.selectedSchema);
const newChat = useChatStore(s => s.newChat);
const selectChat = useChatStore(s => s.selectChat);
const deleteChat = useChatStore(s => s.deleteChat);
const setSelectedSchema = useChatStore(s => s.setSelectedSchema);
const sidebarCollapsed = useChatStore(s => s.sidebarCollapsed);
const setSidebarCollapsed = useChatStore(s => s.setSidebarCollapsed);
const [searchQuery, setSearchQuery] = useState('');
const [hoveredChat, setHoveredChat] = useState(null);
const [schemaCollapsed, setSchemaCollapsed] = useState(false);
const [savedCollapsed, setSavedCollapsed] = useState(false);
const searchInputRef = useRef(null);
// Global Ctrl+K shortcut to focus search
useEffect(() => {
const handleKeyDown = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
if (sidebarCollapsed) {
setSidebarCollapsed(false);
}
setTimeout(() => searchInputRef.current?.focus(), 50);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [sidebarCollapsed, setSidebarCollapsed]);
const handleSavedQuery = (query) => {
window.dispatchEvent(new CustomEvent('plainsql:submit', { detail: { query } }));
if (open) onClose?.();
};
const handleNewChat = () => {
newChat();
if (open) onClose?.();
};
// Filter logic
const filteredTables = schemaTables.filter(t =>
t.toLowerCase().includes(searchQuery.toLowerCase())
);
const filteredChats = chats.filter(c =>
c.title.toLowerCase().includes(searchQuery.toLowerCase())
);
const filteredSaved = savedQueries.filter(q =>
q.toLowerCase().includes(searchQuery.toLowerCase())
);
// Group schema tables
const groupedTables = {};
filteredTables.forEach(t => {
const groupName = TABLE_GROUPS[t] || 'Other';
if (!groupedTables[groupName]) groupedTables[groupName] = [];
groupedTables[groupName].push(t);
});
return (
<>
{/* Mobile scrim */}
{open && (
)}
{/* Brand + Mobile close */}
sidebarCollapsed && setSidebarCollapsed(false)}
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 shadow-lg cursor-pointer bg-glow border-glow"
style={{ background: 'linear-gradient(135deg, var(--brand), var(--brand-cyan))' }}
>
S
{!sidebarCollapsed && (
PlainSQL
AI Data Platform
)}
{!sidebarCollapsed && (
{/* New chat */}
{/* Mobile close button */}
)}
{/* Search Input Area */}
{!sidebarCollapsed ? (
setSearchQuery(e.target.value)}
className="bg-transparent border-0 outline-none text-xs w-full text-white placeholder-t4"
/>
{searchQuery ? (
) : (
⌘K
)}
) : (
)}
{/* Scrollable body */}
{/* New Chat Button (Standard size) */}
{!sidebarCollapsed ? (
) : (
)}
{/* Schema Explorer */}
{!sidebarCollapsed ? (
<>
setSchemaCollapsed(v => !v)}
/>
{!schemaCollapsed && (
{/* All tables selector */}
{/* Grouped Table Explorer */}
{Object.entries(groupedTables).map(([group, tables]) => (
{group}
{tables.map(t => {
const isSel = selectedSchema === t;
const cols = MOCK_COLUMNS[t]?.length || 4;
return (
);
})}
))}
)}
>
) : (
)}
{/* History */}
{!sidebarCollapsed ? (
<>
{filteredChats.length === 0 && searchQuery && (
No results match query
)}
{filteredChats.length === 0 && !searchQuery && (
)}
{filteredChats.map(chat => {
const isActive = chat.id === activeChatId;
const queryCount = chat.messages.filter(m => m.role === 'user').length;
return (
setHoveredChat(chat.id)}
onHoverEnd={() => setHoveredChat(null)}
onClick={() => { selectChat(chat.id); if (open) onClose?.(); }}
className={`
nav-item relative group flex items-center gap-2.5 px-3 py-2 cursor-pointer
${isActive ? 'active text-white font-medium' : 'text-t2'}
`}
>
{chat.title}
{queryCount > 0 && hoveredChat !== chat.id && (
{queryCount}
)}
{hoveredChat === chat.id && (
)}
);
})}
>
) : (
{filteredChats.slice(0, 8).map(chat => {
const isActive = chat.id === activeChatId;
return (
);
})}
)}
{/* Saved queries */}
{!sidebarCollapsed ? (
<>
setSavedCollapsed(v => !v)}
/>
{!savedCollapsed && (
{filteredSaved.map((q, i) => (
))}
)}
>
) : (
)}
{/* Footer — Profile, Settings, and Sidebar Collapse Trigger */}
{/* Health status indicator */}
{!sidebarCollapsed ? (
{health.status === 'healthy' ? 'All systems active' :
health.status === 'degraded' ? 'Degraded service' : 'Connecting...'}
{health.latency && (
{health.latency}ms
)}
) : (
)}
{/* Profile row */}
{!sidebarCollapsed ? (
LC
Lalit Chaudhari
Developer
) : (
LC
)}
>
);
}