3,200+ hardcoded color values replaced with CSS variable-backed Tailwind classes (bg-card, text-foreground, border-border, etc.). Enables light mode via CSS variable swap. Only syntax highlighting colors and intentional one-offs remain hardcoded (~15 values). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import { AlertTriangle } from 'lucide-react'
|
|
import { aiSessionsApi } from '@/api/aiSessions'
|
|
import type { AISessionSummary } from '@/types/ai-session'
|
|
|
|
function timeAgo(dateStr: string): string {
|
|
const diffMs = Date.now() - new Date(dateStr).getTime()
|
|
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`
|
|
return `${Math.floor(hours / 24)}d ago`
|
|
}
|
|
|
|
export function PendingEscalations() {
|
|
const [escalations, setEscalations] = useState<AISessionSummary[]>([])
|
|
const navigate = useNavigate()
|
|
|
|
useEffect(() => {
|
|
aiSessionsApi.getEscalationQueue()
|
|
.then(setEscalations)
|
|
.catch(() => {})
|
|
}, [])
|
|
|
|
if (escalations.length === 0) return null
|
|
|
|
return (
|
|
<div
|
|
className="card-flat overflow-hidden"
|
|
style={{ borderColor: 'rgba(251, 191, 36, 0.2)' }}
|
|
>
|
|
<div
|
|
className="flex items-center justify-between px-5 py-3"
|
|
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<AlertTriangle size={14} className="text-amber-400" />
|
|
<h3 className="font-heading text-sm font-bold text-foreground">
|
|
Pending Escalations
|
|
<span className="ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-amber-400/10 px-1.5 text-[0.625rem] font-bold text-amber-400">
|
|
{escalations.length}
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<Link
|
|
to="/escalations"
|
|
className="text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
View all
|
|
</Link>
|
|
</div>
|
|
<div>
|
|
{escalations.slice(0, 3).map((esc, i) => (
|
|
<div
|
|
key={esc.id}
|
|
className="flex items-center gap-3 px-5 py-3"
|
|
style={{
|
|
borderBottom: i < Math.min(escalations.length, 3) - 1
|
|
? '1px solid var(--glass-border)'
|
|
: undefined,
|
|
}}
|
|
>
|
|
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-400 animate-pulse" />
|
|
<div className="flex-1 min-w-0">
|
|
<div className="text-sm text-foreground truncate">
|
|
{esc.problem_summary || 'Escalated session'}
|
|
</div>
|
|
<div className="text-[0.6875rem] text-muted-foreground">
|
|
{esc.problem_domain || 'General'}
|
|
<span className="mx-1.5 text-[var(--text-dimmed)]">·</span>
|
|
<span className="font-sans text-xs">{timeAgo(esc.created_at)}</span>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => navigate(`/pilot/${esc.id}?pickup=true`)}
|
|
className="shrink-0 rounded-lg border border-amber-400/30 bg-amber-400/10 px-3 py-1 text-[0.6875rem] font-medium text-amber-400 hover:bg-amber-400/20 transition-colors"
|
|
>
|
|
Pick up
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|