← back to Letsbegin
components/RecentItemsFooter.tsx
279 lines
'use client'
import { useState, useEffect } from 'react'
import { Clock, FolderOpen, X, ChevronDown, ChevronUp, FileText } from 'lucide-react'
export interface RecentItem {
name: string
path: string
timestamp: number
type: 'project' | 'file'
prdData?: {
featureTitle?: string
problemStatement?: string
scope?: string
priority?: string
}
}
interface RecentItemsFooterProps {
onItemClick?: (item: RecentItem) => void
}
const STORAGE_KEY = 'letsbegin_recent_items'
const MAX_ITEMS = 10
export function addRecentItem(item: Omit<RecentItem, 'timestamp'>) {
if (typeof window === 'undefined') return
const stored = localStorage.getItem(STORAGE_KEY)
let items: RecentItem[] = stored ? JSON.parse(stored) : []
// Remove if already exists
items = items.filter(i => i.path !== item.path)
// Add to front with timestamp
items.unshift({
...item,
timestamp: Date.now()
})
// Keep only MAX_ITEMS
items = items.slice(0, MAX_ITEMS)
localStorage.setItem(STORAGE_KEY, JSON.stringify(items))
// Dispatch event for other components to listen
window.dispatchEvent(new CustomEvent('recentItemsUpdated'))
}
export function getRecentItems(): RecentItem[] {
if (typeof window === 'undefined') return []
const stored = localStorage.getItem(STORAGE_KEY)
return stored ? JSON.parse(stored) : []
}
export default function RecentItemsFooter({ onItemClick }: RecentItemsFooterProps) {
const [recentItems, setRecentItems] = useState<RecentItem[]>([])
const [isExpanded, setIsExpanded] = useState(false)
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set())
useEffect(() => {
// Load initial items
setRecentItems(getRecentItems())
// Listen for updates
const handleUpdate = () => {
setRecentItems(getRecentItems())
}
window.addEventListener('recentItemsUpdated', handleUpdate)
window.addEventListener('storage', handleUpdate)
return () => {
window.removeEventListener('recentItemsUpdated', handleUpdate)
window.removeEventListener('storage', handleUpdate)
}
}, [])
function removeItem(path: string, e: React.MouseEvent) {
e.stopPropagation()
const items = recentItems.filter(i => i.path !== path)
localStorage.setItem(STORAGE_KEY, JSON.stringify(items))
setRecentItems(items)
expandedItems.delete(path)
setExpandedItems(new Set(expandedItems))
}
function toggleItemExpand(path: string, e: React.MouseEvent) {
e.stopPropagation()
const newExpanded = new Set(expandedItems)
if (newExpanded.has(path)) {
newExpanded.delete(path)
} else {
newExpanded.add(path)
}
setExpandedItems(newExpanded)
}
function formatTime(timestamp: number): string {
const diff = Date.now() - timestamp
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(diff / 3600000)
if (minutes < 1) return 'Just now'
if (minutes < 60) return `${minutes}m ago`
if (hours < 24) return `${hours}h ago`
return new Date(timestamp).toLocaleDateString()
}
function formatFullTime(timestamp: number): string {
return new Date(timestamp).toLocaleString()
}
if (recentItems.length === 0) return null
return (
<footer className={`flex-shrink-0 border-t border-[var(--border)] bg-[var(--surface-alt)] transition-all ${isExpanded ? 'max-h-[400px]' : 'max-h-16'}`}>
{/* Header - always visible */}
<div className="px-4 py-2 flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={() => setIsExpanded(!isExpanded)}
className="flex items-center gap-1.5 text-xs text-[var(--foreground-tertiary)] hover:text-[var(--foreground)] transition-colors"
>
<Clock className="w-3 h-3" />
<span>Recent ({recentItems.length})</span>
{isExpanded ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" />}
</button>
{/* Quick access items (shown when collapsed) */}
{!isExpanded && (
<div className="flex items-center gap-2 overflow-x-auto">
{recentItems.slice(0, 5).map((item) => (
<button
key={item.path}
onClick={() => onItemClick?.(item)}
className="group flex items-center gap-2 px-2 py-1 rounded-md bg-[var(--surface)] border border-[var(--border)] hover:border-[var(--primary)] hover:bg-[var(--primary)]/5 transition-all text-xs"
>
<FolderOpen className="w-3 h-3 text-[var(--primary)]" />
<span className="text-[var(--foreground)] font-medium max-w-[100px] truncate">
{item.name}
</span>
</button>
))}
{recentItems.length > 5 && (
<span className="text-xs text-[var(--foreground-tertiary)]">
+{recentItems.length - 5} more
</span>
)}
</div>
)}
</div>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-xs px-2 py-1 rounded hover:bg-[var(--surface)] text-[var(--foreground-secondary)]"
>
{isExpanded ? 'Collapse' : 'Expand'}
</button>
</div>
{/* Expanded view with table */}
{isExpanded && (
<div className="px-4 pb-4 max-h-[340px] overflow-y-auto">
<table className="w-full text-sm">
<thead className="sticky top-0 bg-[var(--surface-alt)]">
<tr className="text-left text-xs text-[var(--foreground-tertiary)] border-b border-[var(--border)]">
<th className="pb-2 pr-3 w-8"></th>
<th className="pb-2 pr-3">Name</th>
<th className="pb-2 pr-3 hidden md:table-cell">Type</th>
<th className="pb-2 pr-3">Time</th>
<th className="pb-2 pr-3 hidden lg:table-cell">Path</th>
<th className="pb-2 w-20">Actions</th>
</tr>
</thead>
<tbody>
{recentItems.map((item, index) => {
const isItemExpanded = expandedItems.has(item.path)
return (
<>
<tr
key={item.path}
className="border-b border-[var(--border)]/50 hover:bg-[var(--surface)]/50 cursor-pointer"
onClick={() => onItemClick?.(item)}
>
<td className="py-2 pr-3">
<span className="text-[var(--foreground-tertiary)] text-xs">{index + 1}</span>
</td>
<td className="py-2 pr-3">
<div className="flex items-center gap-2">
{item.type === 'project' ? (
<FolderOpen className="w-4 h-4 text-[var(--primary)]" />
) : (
<FileText className="w-4 h-4 text-[var(--warning)]" />
)}
<span className="font-medium text-[var(--foreground)]">{item.name}</span>
</div>
</td>
<td className="py-2 pr-3 hidden md:table-cell">
<span className={`text-xs px-2 py-0.5 rounded-full ${
item.type === 'project'
? 'bg-[var(--primary)]/20 text-[var(--primary)]'
: 'bg-[var(--warning)]/20 text-[var(--warning)]'
}`}>
{item.type}
</span>
</td>
<td className="py-2 pr-3">
<span className="text-[var(--foreground-secondary)] text-xs">
{formatTime(item.timestamp)}
</span>
</td>
<td className="py-2 pr-3 hidden lg:table-cell">
<span className="text-[var(--foreground-tertiary)] text-xs font-mono max-w-[200px] truncate block">
{item.path}
</span>
</td>
<td className="py-2">
<div className="flex items-center gap-1">
<button
onClick={(e) => toggleItemExpand(item.path, e)}
className="p-1 rounded hover:bg-[var(--surface)] text-[var(--foreground-secondary)] hover:text-[var(--foreground)]"
title="Show details"
>
{isItemExpanded ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
</button>
<button
onClick={(e) => removeItem(item.path, e)}
className="p-1 rounded hover:bg-[var(--surface)] text-[var(--foreground-tertiary)] hover:text-[var(--error)]"
title="Remove"
>
<X className="w-3 h-3" />
</button>
</div>
</td>
</tr>
{/* Expanded details row */}
{isItemExpanded && (
<tr key={`${item.path}-details`} className="bg-[var(--surface)]/30">
<td colSpan={6} className="py-3 px-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-xs">
<div>
<span className="text-[var(--foreground-tertiary)]">Full Path:</span>
<div className="font-mono text-[var(--foreground)] mt-1 break-all">{item.path}</div>
</div>
<div>
<span className="text-[var(--foreground-tertiary)]">Accessed:</span>
<div className="text-[var(--foreground)] mt-1">{formatFullTime(item.timestamp)}</div>
</div>
<div>
<span className="text-[var(--foreground-tertiary)]">Type:</span>
<div className="text-[var(--foreground)] mt-1 capitalize">{item.type}</div>
</div>
{item.prdData && (
<div>
<span className="text-[var(--foreground-tertiary)]">PRD Data:</span>
<div className="text-[var(--foreground)] mt-1">
{item.prdData.featureTitle && <div>Feature: {item.prdData.featureTitle}</div>}
{item.prdData.scope && <div>Scope: {item.prdData.scope}</div>}
{item.prdData.priority && <div>Priority: {item.prdData.priority}</div>}
</div>
</div>
)}
</div>
</td>
</tr>
)}
</>
)
})}
</tbody>
</table>
</div>
)}
</footer>
)
}