feat: cockpit UX polish — conversation log, interactive steps, onboarding
- Redesign conversation log: proper role labels, left-border accents, larger text, CSS variable background, min-height guarantee - Interactive steps panel: click-to-complete, click-to-select, progress bar with counter, hover-reveal descriptions, smooth transitions - Replace noop overflow button with real dropdown menu (Pause, Copy Link, Close Case) with keyboard/click-outside dismiss - Evidence cycling: right-click to reverse-cycle status, tooltips on icons - First-run onboarding overlay labeling the three cockpit zones, auto- dismisses on first message or manual dismiss, persisted via localStorage - Drag handle: taller, visible hover state, title tooltip - Simplify input placeholder (remove redundant paste-log hint) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { Pencil, X, Check, ExternalLink } from 'lucide-react'
|
||||
import { Pencil, X, Check, ExternalLink, Pause, XCircle, Link2 } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { toast } from '@/lib/toast'
|
||||
import type { TriageMeta } from '@/types/ai-session'
|
||||
|
||||
interface IncidentHeaderProps {
|
||||
@@ -9,7 +10,8 @@ interface IncidentHeaderProps {
|
||||
sessionId: string
|
||||
onFieldSave: (field: keyof TriageMeta, value: string) => void
|
||||
onResolve: () => void
|
||||
onOverflow: () => void
|
||||
onPause?: () => void
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
interface EditPopoverProps {
|
||||
@@ -95,12 +97,81 @@ function HeaderField({ label, value, placeholder, onSave, isHypothesis }: Header
|
||||
)
|
||||
}
|
||||
|
||||
function OverflowMenu({ onPause, onClose, sessionId }: { onPause?: () => void; onClose?: () => void; sessionId: string }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) setOpen(false)
|
||||
}
|
||||
const handleEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setOpen(false)
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
document.addEventListener('keydown', handleEsc)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
document.removeEventListener('keydown', handleEsc)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const handleCopyLink = () => {
|
||||
navigator.clipboard.writeText(`${window.location.origin}/assistant/${sessionId}`)
|
||||
toast.success('Session link copied')
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={menuRef} className="relative">
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="bg-elevated border border-default rounded px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
⋯
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute right-0 top-full mt-1 z-50 w-44 bg-elevated border border-hover rounded-md shadow-lg py-1">
|
||||
{onPause && (
|
||||
<button
|
||||
onClick={() => { onPause(); setOpen(false) }}
|
||||
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-card transition-colors text-left"
|
||||
>
|
||||
<Pause size={12} />
|
||||
Pause Case
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleCopyLink}
|
||||
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-card transition-colors text-left"
|
||||
>
|
||||
<Link2 size={12} />
|
||||
Copy Link
|
||||
</button>
|
||||
{onClose && (
|
||||
<button
|
||||
onClick={() => { onClose(); setOpen(false) }}
|
||||
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-danger hover:text-danger hover:bg-danger-dim transition-colors text-left"
|
||||
>
|
||||
<XCircle size={12} />
|
||||
Close Case
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function IncidentHeader({
|
||||
triageMeta,
|
||||
psaTicketId,
|
||||
sessionId,
|
||||
onFieldSave,
|
||||
onResolve,
|
||||
onOverflow,
|
||||
onPause,
|
||||
onClose,
|
||||
}: IncidentHeaderProps) {
|
||||
return (
|
||||
<div className="bg-card border-b border-default px-4 py-2 flex items-center gap-4 flex-wrap">
|
||||
@@ -146,12 +217,7 @@ export function IncidentHeader({
|
||||
>
|
||||
Resolve
|
||||
</button>
|
||||
<button
|
||||
onClick={onOverflow}
|
||||
className="bg-elevated border border-default rounded px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
⋯
|
||||
</button>
|
||||
<OverflowMenu onPause={onPause} onClose={onClose} sessionId={sessionId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user