fix: opaque overflow menu, add Update button to cockpit and assistant headers
The IncidentHeader overflow menu was see-through due to bg-elevated on bg-card. Switched to bg-card with shadow-xl and fixed-scrim dismiss pattern matching production FlowPilotSessionPage. Replaced unicode ellipsis with MoreHorizontal icon + aria-label. Added Update button (StatusUpdateModal) to IncidentHeader and FlowPilotPage header bar for feature parity with production. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useRef, useEffect } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import { Pencil, X, Check, ExternalLink, Pause, XCircle, Link2 } from 'lucide-react'
|
import { Pencil, X, Check, ExternalLink, Pause, XCircle, Link2, MoreHorizontal, FileText } from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
import type { TriageMeta } from '@/types/ai-session'
|
import type { TriageMeta } from '@/types/ai-session'
|
||||||
@@ -9,6 +9,7 @@ interface IncidentHeaderProps {
|
|||||||
psaTicketId: string | null
|
psaTicketId: string | null
|
||||||
onFieldSave: (field: keyof TriageMeta, value: string) => void
|
onFieldSave: (field: keyof TriageMeta, value: string) => void
|
||||||
onResolve: () => void
|
onResolve: () => void
|
||||||
|
onStatusUpdate?: () => void
|
||||||
onPause?: () => void
|
onPause?: () => void
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
@@ -98,22 +99,14 @@ function HeaderField({ label, value, placeholder, onSave, isHypothesis }: Header
|
|||||||
|
|
||||||
function OverflowMenu({ onPause, onClose }: { onPause?: () => void; onClose?: () => void }) {
|
function OverflowMenu({ onPause, onClose }: { onPause?: () => void; onClose?: () => void }) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const menuRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
const handleClickOutside = (e: MouseEvent) => {
|
|
||||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) setOpen(false)
|
|
||||||
}
|
|
||||||
const handleEsc = (e: KeyboardEvent) => {
|
const handleEsc = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') setOpen(false)
|
if (e.key === 'Escape') setOpen(false)
|
||||||
}
|
}
|
||||||
document.addEventListener('mousedown', handleClickOutside)
|
|
||||||
document.addEventListener('keydown', handleEsc)
|
document.addEventListener('keydown', handleEsc)
|
||||||
return () => {
|
return () => { document.removeEventListener('keydown', handleEsc) }
|
||||||
document.removeEventListener('mousedown', handleClickOutside)
|
|
||||||
document.removeEventListener('keydown', handleEsc)
|
|
||||||
}
|
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
const handleCopyLink = () => {
|
const handleCopyLink = () => {
|
||||||
@@ -123,41 +116,45 @@ function OverflowMenu({ onPause, onClose }: { onPause?: () => void; onClose?: ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={menuRef} className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
className="bg-elevated border border-default rounded px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
aria-label="More actions"
|
||||||
|
className="flex items-center justify-center rounded-lg px-2 py-1.5 text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors"
|
||||||
>
|
>
|
||||||
⋯
|
<MoreHorizontal size={16} />
|
||||||
</button>
|
</button>
|
||||||
{open && (
|
{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 && (
|
<div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
|
||||||
|
<div className="absolute right-0 top-full mt-1 z-50 w-44 rounded-lg border border-border bg-card py-1 shadow-xl">
|
||||||
|
{onPause && (
|
||||||
|
<button
|
||||||
|
onClick={() => { onPause(); setOpen(false) }}
|
||||||
|
className="w-full flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors text-left"
|
||||||
|
>
|
||||||
|
<Pause size={13} />
|
||||||
|
Pause Case
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => { onPause(); setOpen(false) }}
|
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"
|
className="w-full flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors text-left"
|
||||||
>
|
>
|
||||||
<Pause size={12} />
|
<Link2 size={13} />
|
||||||
Pause Case
|
Copy Link
|
||||||
</button>
|
</button>
|
||||||
)}
|
{onClose && (
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyLink}
|
onClick={() => { onClose(); 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"
|
className="w-full flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-rose-400 hover:bg-rose-500/10 transition-colors text-left"
|
||||||
>
|
>
|
||||||
<Link2 size={12} />
|
<XCircle size={13} />
|
||||||
Copy Link
|
Close Case
|
||||||
</button>
|
</button>
|
||||||
{onClose && (
|
)}
|
||||||
<button
|
</div>
|
||||||
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>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -168,6 +165,7 @@ export function IncidentHeader({
|
|||||||
psaTicketId,
|
psaTicketId,
|
||||||
onFieldSave,
|
onFieldSave,
|
||||||
onResolve,
|
onResolve,
|
||||||
|
onStatusUpdate,
|
||||||
onPause,
|
onPause,
|
||||||
onClose,
|
onClose,
|
||||||
}: IncidentHeaderProps) {
|
}: IncidentHeaderProps) {
|
||||||
@@ -215,6 +213,15 @@ export function IncidentHeader({
|
|||||||
>
|
>
|
||||||
Resolve
|
Resolve
|
||||||
</button>
|
</button>
|
||||||
|
{onStatusUpdate && (
|
||||||
|
<button
|
||||||
|
onClick={onStatusUpdate}
|
||||||
|
className="flex items-center gap-1.5 bg-blue-500/10 border border-blue-500/20 rounded px-3 py-1 text-xs font-medium text-blue-400 hover:bg-blue-500/20 transition-colors"
|
||||||
|
>
|
||||||
|
<FileText size={13} />
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<OverflowMenu onPause={onPause} onClose={onClose} />
|
<OverflowMenu onPause={onPause} onClose={onClose} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ export default function CockpitPage() {
|
|||||||
psaTicketId={psaTicketId}
|
psaTicketId={psaTicketId}
|
||||||
onFieldSave={handleTriageFieldSave}
|
onFieldSave={handleTriageFieldSave}
|
||||||
onResolve={() => session.setShowConclude(true)}
|
onResolve={() => session.setShowConclude(true)}
|
||||||
|
onStatusUpdate={session.messages.length >= 2 ? () => session.setShowStatusUpdate(true) : undefined}
|
||||||
onClose={() => session.setShowConclude(true)}
|
onClose={() => session.setShowConclude(true)}
|
||||||
/>
|
/>
|
||||||
{/* View toggle bar — desktop only (mobile has it in the header) */}
|
{/* View toggle bar — desktop only (mobile has it in the header) */}
|
||||||
|
|||||||
@@ -128,8 +128,32 @@ export default function FlowPilotPage() {
|
|||||||
|
|
||||||
{session.activeChatId ? (
|
{session.activeChatId ? (
|
||||||
<>
|
<>
|
||||||
{/* Desktop view toggle bar */}
|
{/* Desktop view toggle + action bar */}
|
||||||
<div className="hidden sm:flex items-center justify-end px-4 py-1.5 border-b border-border/50">
|
<div className="hidden sm:flex items-center justify-between px-4 py-1.5 border-b border-border/50">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
{session.messages.length >= 2 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => session.setShowStatusUpdate(true)}
|
||||||
|
disabled={session.loading}
|
||||||
|
className="flex items-center gap-1.5 bg-blue-500/10 border border-blue-500/20 rounded-lg px-3 py-1 text-xs font-medium text-blue-400 hover:bg-blue-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
||||||
|
>
|
||||||
|
<FileText size={13} />
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => session.setShowConclude(true)}
|
||||||
|
disabled={session.loading}
|
||||||
|
className="flex items-center gap-1.5 bg-amber-500/10 border border-amber-500/20 rounded-lg px-3 py-1 text-xs font-medium text-amber-400 hover:bg-amber-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
||||||
|
>
|
||||||
|
<Flag size={13} />
|
||||||
|
Conclude
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<ViewToggle currentView="flowpilot" sessionId={session.activeChatId} />
|
<ViewToggle currentView="flowpilot" sessionId={session.activeChatId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user