feat: add workspace system and sidebar layout (UI design system Phase A+B)

Backend: Workspace model, migration (036), schemas, CRUD API endpoints.
Adds workspace_id to trees and categories, seeds 4 default workspaces
per account, auto-assigns existing trees by tree_type.

Frontend: Complete AppLayout rewrite from top-nav to CSS Grid shell
with persistent sidebar + topbar. New components: WorkspaceSwitcher,
NavItem, CategoryList, TagCloud, TopBar, Sidebar. Dashboard components:
QuickStats, FiltersBar, SectionGroup, TreeListItem, SessionsPanel.
WorkspaceStore with localStorage persistence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-15 01:16:33 -05:00
parent ef829f06a4
commit d6f4286570
31 changed files with 1431 additions and 250 deletions

View File

@@ -0,0 +1,51 @@
import { Link, useLocation } from 'react-router-dom'
import type { LucideIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
interface NavItemProps {
href: string
icon: LucideIcon
label: string
badge?: number | 'dot'
matchPaths?: string[]
}
export function NavItem({ href, icon: Icon, label, badge, matchPaths }: NavItemProps) {
const location = useLocation()
const isActive = matchPaths
? matchPaths.some(p => location.pathname.startsWith(p))
: href === '/'
? location.pathname === '/'
: location.pathname.startsWith(href)
return (
<Link
to={href}
className={cn(
'group relative flex items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium transition-all duration-120',
isActive
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
)}
>
{/* Active indicator bar */}
{isActive && (
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-gradient-brand" />
)}
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} />
<span className="truncate">{label}</span>
{/* Badge */}
{badge !== undefined && badge !== 0 && (
badge === 'dot' ? (
<span className="ml-auto h-1.5 w-1.5 shrink-0 rounded-full bg-brand-gradient-from" />
) : (
<span className="ml-auto shrink-0 rounded-full bg-card border border-border px-2 text-[0.6875rem] font-label text-muted-foreground">
{badge}
</span>
)
)}
</Link>
)
}