import { useState, useEffect, useRef } from 'react' import { useNavigate, Link } from 'react-router-dom' import { Search, Plus, Loader2 } from 'lucide-react' import { treesApi } from '@/api/trees' import { sessionsApi } from '@/api/sessions' import type { TreeListItem } from '@/types' import type { Session } from '@/types/session' import { getTreeNavigatePath } from '@/lib/routing' import { usePermissions } from '@/hooks/usePermissions' import { QuickStats } from '@/components/dashboard/QuickStats' import { FiltersBar } from '@/components/dashboard/FiltersBar' import { SectionGroup } from '@/components/dashboard/SectionGroup' import { SessionsPanel } from '@/components/dashboard/SessionsPanel' import { TreeListItem as TreeListItemComponent } from '@/components/dashboard/TreeListItem' function timeAgo(dateStr: string): string { const now = Date.now() const then = new Date(dateStr).getTime() const diffMs = now - then const minutes = Math.floor(diffMs / 60000) if (minutes < 1) return 'just now' if (minutes < 60) return `${minutes}m ago` const hours = Math.floor(minutes / 60) if (hours < 24) return `${hours}h ago` const days = Math.floor(hours / 24) if (days === 1) return 'Yesterday' return `${days}d ago` } export function QuickStartPage() { const navigate = useNavigate() const { canCreateTrees } = usePermissions() const [query, setQuery] = useState('') const [searchResults, setSearchResults] = useState([]) const [isSearching, setIsSearching] = useState(false) const [showResults, setShowResults] = useState(false) const searchRef = useRef(null) const debounceRef = useRef | null>(null) const [trees, setTrees] = useState([]) const [activeSessions, setActiveSessions] = useState([]) const [allSessions, setAllSessions] = useState([]) const [isLoading, setIsLoading] = useState(true) const [activeFilter, setActiveFilter] = useState('all') // Load data on mount useEffect(() => { async function loadData() { try { const [treeList, active, recent] = await Promise.all([ treesApi.list({ sort_by: 'updated_at' }), sessionsApi.list({ completed: false, size: 5 }), sessionsApi.list({ size: 10 }), ]) setTrees(treeList) setActiveSessions(active) setAllSessions(recent) } catch (err) { console.error('Failed to load dashboard data:', err) } finally { setIsLoading(false) } } loadData() }, []) // Debounced search useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current) if (query.length < 2) { setSearchResults([]) setShowResults(false) setIsSearching(false) return } setIsSearching(true) setShowResults(true) debounceRef.current = setTimeout(async () => { try { const results = await treesApi.search(query, 8) setSearchResults(results) } catch { setSearchResults([]) } finally { setIsSearching(false) } }, 300) return () => { if (debounceRef.current) clearTimeout(debounceRef.current) } }, [query]) // Close dropdown on outside click useEffect(() => { function handleClick(e: MouseEvent) { if (searchRef.current && !searchRef.current.contains(e.target as Node)) { setShowResults(false) } } document.addEventListener('mousedown', handleClick) return () => document.removeEventListener('mousedown', handleClick) }, []) // Compute stats const totalTrees = trees.length const openSessions = activeSessions.length const todaySessions = allSessions.filter(s => { const d = new Date(s.started_at) const now = new Date() return d.toDateString() === now.toDateString() }).length const completedSessions = allSessions.filter(s => s.completed_at).length // Filter trees const filteredTrees = activeFilter === 'all' ? trees : activeFilter === 'recent' ? trees.slice(0, 10) : trees // Map sessions for SessionsPanel const recentSessionItems = allSessions.slice(0, 5).map(s => ({ id: s.id, treeName: s.tree_snapshot?.name || 'Unknown', status: (s.completed_at ? 'completed' : 'in_progress') as 'completed' | 'in_progress', ticketNumber: s.ticket_number || undefined, timeAgo: timeAgo(s.started_at), })) const filters = [ { id: 'all', label: 'All' }, { id: 'recent', label: 'Recently Used' }, { id: 'my', label: 'My Flows' }, { id: 'team', label: 'Team Flows' }, ] return (
{/* Page Header */}

Dashboard

Welcome back. Here's what's happening with your flows.

{canCreateTrees && ( Create Flow )}
{/* Quick Stats */} {/* Filters */} {/* Search (inline, not hero) */}
setQuery(e.target.value)} onFocus={() => query.length >= 2 && setShowResults(true)} placeholder="Search flows, sessions, tags…" className="w-full rounded-lg border border-border bg-card py-2.5 pl-9 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20" /> {showResults && (
{isSearching ? (
) : searchResults.length === 0 ? (
No results found
) : (
    {searchResults.map((tree) => (
  • ))}
)}
)}
{/* Recent Sessions */} {/* Tree/Flow List */} {isLoading ? (
) : ( {filteredTrees.slice(0, 20).map(tree => ( ))} {filteredTrees.length > 20 && ( View all {filteredTrees.length} flows → )} )}
) } export default QuickStartPage