Files
resolutionflow/frontend/src/components/dashboard/ActiveFlowPilotSessions.tsx
Michael Chihlas 912075cd43 refactor: dashboard design critique — eliminate redundancy, differentiate sections
- Remove GreetingStatStrip (duplicated PerformanceCards data)
- Strip left-border accent from stat cards (AI slop pattern)
- Redesign KnowledgeBaseCards: icon grid → compact row list with icon badges
- Redesign TeamSummary: distinct inline-row layout, no longer identical twin
- Differentiate hover: stat cards use subtle border-hover, sessions keep springy lift
- Add loading skeletons to PerformanceCards, KnowledgeBaseCards, TeamSummary
- Add error state to PerformanceCards
- Extract timeAgo() to shared lib/timeAgo.ts (replaced 4 duplicates)
- Fix Skeleton bg-brand-border (undefined CSS var) → border-default
- Fix double text-xs text-[0.5625rem] class conflicts across dashboard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:06:30 -04:00

91 lines
3.8 KiB
TypeScript

import { useState, useEffect } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Clock, ArrowRight, Route, MessageCircle } from 'lucide-react'
import { aiSessionsApi } from '@/api/aiSessions'
import type { AISessionSummary } from '@/types/ai-session'
import { cn } from '@/lib/utils'
import { timeAgo } from '@/lib/timeAgo'
export function ActiveFlowPilotSessions({ hideHeader = false }: { hideHeader?: boolean }) {
const [sessions, setSessions] = useState<AISessionSummary[]>([])
const [loading, setLoading] = useState(true)
const navigate = useNavigate()
useEffect(() => {
aiSessionsApi.listSessions({ status: 'active', limit: 6 })
.then(setSessions)
.catch(() => {})
.finally(() => setLoading(false))
}, [])
if (loading || sessions.length === 0) return null
return (
<div className="card-flat">
{!hideHeader && (
<div
className="flex items-center justify-between px-5 py-3"
style={{ borderBottom: '1px solid var(--color-border-default)' }}
>
<div className="flex items-center gap-2">
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
{sessions.length > 0 && (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-accent-dim px-1.5 text-[0.625rem] font-bold text-primary">
{sessions.length}
</span>
)}
</div>
<Link
to="/sessions?filter=active"
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
>
View all <ArrowRight size={10} />
</Link>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
{sessions.map((session) => (
<button
key={session.id}
onClick={() => navigate(session.session_type === 'chat' ? `/assistant/${session.id}` : `/pilot/${session.id}`)}
className="card-interactive p-4 text-left"
>
<div className="flex items-start justify-between gap-2 mb-2">
{session.session_type === 'chat' ? (
<MessageCircle size={14} className="shrink-0 text-violet-400 mt-0.5" />
) : (
<Route size={14} className="shrink-0 text-primary mt-0.5" />
)}
<span
className={cn(
'font-sans text-[0.5625rem] uppercase px-1.5 py-0.5 rounded',
session.confidence_tier === 'guided' && 'bg-emerald-400/10 text-emerald-400',
session.confidence_tier === 'exploring' && 'bg-amber-400/10 text-amber-400',
session.confidence_tier === 'discovery' && 'bg-blue-400/10 text-blue-400',
!session.confidence_tier && 'bg-card text-muted-foreground',
)}
>
{session.confidence_tier || 'starting'}
</span>
</div>
<p className="text-sm font-medium text-foreground line-clamp-2">
{session.session_type === 'chat'
? (session.title || session.problem_summary || 'Chat in progress')
: (session.problem_summary || 'Session in progress')}
</p>
<div className="mt-2 flex items-center gap-2 text-[0.625rem] text-muted-foreground">
<span className="flex items-center gap-1">
<Clock size={10} />
{timeAgo(session.created_at)}
</span>
<span>&middot;</span>
<span>{session.step_count} {session.session_type === 'chat' ? 'messages' : 'steps'}</span>
</div>
</button>
))}
</div>
</div>
)
}