Files
resolutionflow/frontend/src/components/flowpilot/EscalationQueue.tsx
chihlasm 1f4a8a6389 fix(responsive): audit and fix FlowPilot + analytics components for mobile/tablet
Add responsive Tailwind classes across 11 FlowPilot-related components:
- FlowPilotSession: collapsible mobile sidebar summary, responsive padding
- FlowPilotActionBar: stacked buttons on mobile, responsive resolve modal
- FlowPilotIntake: responsive submit area, padding, heading sizes
- FlowPilotStepCard: responsive padding (p-3/p-4/p-5), stacked action buttons
- FlowPilotOptions: responsive padding, 44px touch targets
- SessionDocView: responsive card padding, touch-friendly star ratings
- EscalateModal: stacked buttons on mobile with min touch targets
- EscalationQueue: responsive card padding, touch targets
- InSessionScriptGenerator: responsive padding, stacked continue buttons
- ReviewQueuePage: responsive two-panel layout (stacked on mobile)
- FlowPilotAnalyticsPage: responsive header, chart card padding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 20:19:52 +00:00

141 lines
4.5 KiB
TypeScript

import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { AlertTriangle, Clock, Hash, Ticket, Loader2, RefreshCw } from 'lucide-react'
import { aiSessionsApi } from '@/api'
import type { AISessionSummary } from '@/types/ai-session'
interface EscalationQueueProps {
onPickup?: (sessionId: string) => void
}
export function EscalationQueue({ onPickup }: EscalationQueueProps) {
const navigate = useNavigate()
const [sessions, setSessions] = useState<AISessionSummary[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const loadQueue = async () => {
setIsLoading(true)
setError(null)
try {
const data = await aiSessionsApi.getEscalationQueue()
setSessions(data)
} catch {
setError('Failed to load escalation queue')
} finally {
setIsLoading(false)
}
}
useEffect(() => {
loadQueue()
}, [])
const handlePickup = (sessionId: string) => {
if (onPickup) {
onPickup(sessionId)
} else {
navigate(`/pilot/${sessionId}?pickup=true`)
}
}
if (isLoading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 size={20} className="animate-spin text-muted-foreground" />
</div>
)
}
if (error) {
return (
<div className="py-12 text-center">
<p className="text-sm text-rose-400">{error}</p>
<button
onClick={loadQueue}
className="mt-2 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
Try again
</button>
</div>
)
}
if (sessions.length === 0) {
return (
<div className="py-12 text-center">
<AlertTriangle size={24} className="mx-auto mb-2 text-muted-foreground/40" />
<p className="text-sm text-muted-foreground">No sessions awaiting escalation</p>
<button
onClick={loadQueue}
className="mt-3 flex items-center gap-1.5 mx-auto text-xs text-muted-foreground hover:text-foreground transition-colors"
>
<RefreshCw size={12} />
Refresh
</button>
</div>
)
}
return (
<div className="space-y-3">
<div className="flex items-center justify-between px-1">
<h3 className="font-label text-[0.625rem] uppercase tracking-wider text-[#5a6170]">
Awaiting pickup ({sessions.length})
</h3>
<button
onClick={loadQueue}
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
<RefreshCw size={10} />
Refresh
</button>
</div>
{sessions.map((session) => (
<div key={session.id} className="glass-card p-3 sm:p-4 space-y-3">
<div>
<p className="text-sm font-semibold text-foreground">
{session.problem_summary || 'Untitled session'}
</p>
{session.escalation_reason && (
<p className="mt-1 text-xs text-amber-400 line-clamp-2">
Reason: {session.escalation_reason}
</p>
)}
</div>
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{session.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
{session.problem_domain}
</span>
)}
<span className="flex items-center gap-1">
<Hash size={10} />
{session.step_count} steps
</span>
<span className="flex items-center gap-1">
<Clock size={10} />
{new Date(session.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
{session.psa_ticket_id && (
<span className="flex items-center gap-1 text-primary">
<Ticket size={10} />
#{session.psa_ticket_id}
</span>
)}
</div>
<button
onClick={() => handlePickup(session.id)}
className="w-full min-h-[44px] rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
>
Pick Up Session
</button>
</div>
))}
</div>
)
}