Files
resolutionflow/frontend/src/components/layout/NotificationsPanel.tsx
chihlasm fa709faa60 feat: UI design system - sidebar layout, workspace system, and shell redesign (#77)
* 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>

* feat: add command palette search, dashboard rewrite, and shell height fixes (Phase C)

- Add ⌘K command palette with debounced search across flows and sessions
- Rewrite QuickStartPage as dashboard with stats, filters, sessions panel
- Fix h-[calc(100vh-4rem)] → h-full across all pages for CSS Grid shell
- Add active session count badge to sidebar Sessions nav item

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add sidebar collapse, category/tag filtering, and workspace CRUD (Phase D)

- Sidebar collapse/expand toggle with icon-only rail mode (persisted)
- Sidebar category/tag clicks navigate to /trees with URL params
- TreeLibraryPage syncs filters from URL search params bidirectionally
- Workspace create modal with icon picker and auto-slug generation
- TopBar logo adapts to collapsed sidebar state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Quick Launch modal with actions and recent flows

- Zap button opens Quick Launch with create/navigate shortcuts
- Shows recent flows for quick session start
- Keyboard navigation support (arrows + enter)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add activity notifications panel with session feed

- Bell icon shows dot indicator for recent activity
- Dropdown panel shows recent sessions with status icons
- Links to session detail and sessions list page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: remove workspace system, add pinned flows and label renames

Replace workspace system with pinned flows API (pin/unpin/list/reorder).
Rename user-facing labels: Tree→Flow, Procedure→Project. Add sidebar
nav sub-items for flow type filtering. Remove 11 workspace files,
add migrations 037-038, clean all workspace references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: collapsed sidebar layout scaling and toggle button size

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate auth pages to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate TreeLibraryPage to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate session pages to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate TreeEditorPage to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate TreeNavigationPage to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate session sharing components to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove workspace dropdown animation (dead code)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate common components to new design system

Migrate 15 components from monochrome glass-card design to purple gradient
accent design system tokens (bg-card, border-border, text-foreground,
bg-gradient-brand, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate procedural and step library components to new design system

Migrate 10 components from monochrome glass-card design to purple gradient
accent design system tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate admin pages and components to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate remaining pages to new design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: migrate remaining components to new design system

Migrates 38 files: tree-editor forms, session modals, step library,
common components, library views, tree preview, and misc UI to use
design tokens (bg-card, border-border, text-foreground, bg-accent,
bg-gradient-brand) replacing old monochrome patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: keep brand text visible on sidebar collapse, hide sub-items until hover

- TopBar: always show "ResolutionFlow" text regardless of sidebar state
- NavItem: sub-items (Troubleshooting, Projects) hidden by default,
  revealed on hover or when a child route is active

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 22:45:19 -05:00

106 lines
4.0 KiB
TypeScript

import { useState, useEffect, useRef } from 'react'
import { Link } from 'react-router-dom'
import { Bell, CheckCircle, Clock } from 'lucide-react'
import { sessionsApi } from '@/api/sessions'
import type { Session } from '@/types/session'
function timeAgo(dateStr: string): string {
const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000)
if (diff < 60) return 'just now'
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`
return `${Math.floor(diff / 86400)}d ago`
}
export function NotificationsPanel() {
const [open, setOpen] = useState(false)
const [sessions, setSessions] = useState<Session[]>([])
const [hasNew, setHasNew] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
sessionsApi.list({ size: 8 })
.then(data => {
setSessions(data)
// Mark as "new" if any session was updated in the last hour
const oneHourAgo = Date.now() - 3600000
setHasNew(data.some(s => new Date(s.started_at).getTime() > oneHourAgo))
})
.catch(() => {})
}, [])
useEffect(() => {
const handler = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
}
if (open) document.addEventListener('mousedown', handler)
return () => document.removeEventListener('mousedown', handler)
}, [open])
return (
<div className="relative" ref={ref}>
<button
onClick={() => { setOpen(!open); setHasNew(false) }}
className="relative rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors"
title="Notifications"
>
<Bell size={18} />
{hasNew && (
<span className="absolute right-1.5 top-1.5 h-2 w-2 rounded-full bg-primary" />
)}
</button>
{open && (
<div className="absolute right-0 z-50 mt-2 w-80 rounded-xl border border-border bg-card shadow-xl animate-scale-in">
<div className="flex items-center justify-between border-b border-border px-4 py-3">
<h3 className="text-sm font-heading font-semibold text-foreground">Activity</h3>
<Link
to="/sessions"
onClick={() => setOpen(false)}
className="text-[0.6875rem] text-muted-foreground hover:text-foreground"
>
View All
</Link>
</div>
{sessions.length === 0 ? (
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
No recent activity
</div>
) : (
<div className="max-h-72 overflow-y-auto divide-y divide-border">
{sessions.map(session => (
<Link
key={session.id}
to={`/sessions/${session.id}`}
onClick={() => setOpen(false)}
className="flex items-start gap-3 px-4 py-3 hover:bg-accent/50 transition-colors"
>
<div className="mt-0.5">
{session.completed_at ? (
<CheckCircle size={16} className="text-emerald-400" />
) : (
<Clock size={16} className="text-amber-400" />
)}
</div>
<div className="min-w-0 flex-1">
<p className="text-sm text-foreground truncate">
{session.tree_snapshot?.name || 'Session'}
</p>
<p className="text-[0.6875rem] text-muted-foreground">
{session.completed_at
? `Completed ${timeAgo(session.completed_at)}`
: `Started ${timeAgo(session.started_at)}`}
{session.client_name && ` · ${session.client_name}`}
</p>
</div>
</Link>
))}
</div>
)}
</div>
)}
</div>
)
}