Mechanical find-and-replace: rgba(249,115,22,...) → rgba(96,165,250,...) across ~40 component and page files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
228 lines
9.4 KiB
TypeScript
228 lines
9.4 KiB
TypeScript
import { useState } from 'react'
|
|
import { CheckCircle2, ArrowUpRight, Pause, X, FileText } from 'lucide-react'
|
|
import { EscalateModal } from './EscalateModal'
|
|
import { StatusUpdateModal } from './StatusUpdateModal'
|
|
import type {
|
|
ResolveSessionRequest,
|
|
EscalateSessionRequest,
|
|
SessionDocumentation,
|
|
StatusUpdateAudience,
|
|
StatusUpdateLength,
|
|
StatusUpdateContext,
|
|
StatusUpdateResponse,
|
|
} from '@/types/ai-session'
|
|
|
|
interface FlowPilotActionBarProps {
|
|
canResolve: boolean
|
|
canEscalate: boolean
|
|
isProcessing: boolean
|
|
hasPsaTicket?: boolean
|
|
sessionId?: string
|
|
canShareUpdate?: boolean
|
|
onResolve: (data: ResolveSessionRequest) => Promise<SessionDocumentation>
|
|
onEscalate: (data: EscalateSessionRequest) => Promise<SessionDocumentation>
|
|
onPause?: () => Promise<void>
|
|
onAbandon?: () => Promise<void>
|
|
onGenerateStatusUpdate?: (audience: StatusUpdateAudience, length: StatusUpdateLength, context: StatusUpdateContext) => Promise<StatusUpdateResponse>
|
|
}
|
|
|
|
export function FlowPilotActionBar({
|
|
canResolve,
|
|
canEscalate,
|
|
isProcessing,
|
|
hasPsaTicket = false,
|
|
sessionId,
|
|
canShareUpdate = false,
|
|
onResolve,
|
|
onEscalate,
|
|
onPause,
|
|
onAbandon,
|
|
onGenerateStatusUpdate,
|
|
}: FlowPilotActionBarProps) {
|
|
const [showResolve, setShowResolve] = useState(false)
|
|
const [showEscalate, setShowEscalate] = useState(false)
|
|
const [showAbandon, setShowAbandon] = useState(false)
|
|
const [showStatusUpdate, setShowStatusUpdate] = useState(false)
|
|
const [resolutionSummary, setResolutionSummary] = useState('')
|
|
const [submitting, setSubmitting] = useState(false)
|
|
|
|
const handleResolve = async () => {
|
|
if (!resolutionSummary.trim() || resolutionSummary.length < 5) return
|
|
setSubmitting(true)
|
|
try {
|
|
await onResolve({ resolution_summary: resolutionSummary })
|
|
setShowResolve(false)
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
const handlePause = async () => {
|
|
if (onPause) {
|
|
setSubmitting(true)
|
|
try {
|
|
await onPause()
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleAbandon = async () => {
|
|
if (onAbandon) {
|
|
setSubmitting(true)
|
|
try {
|
|
await onAbandon()
|
|
setShowAbandon(false)
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* Bottom bar — fixed to viewport bottom, single row on all screen sizes */}
|
|
<div
|
|
className="fixed bottom-0 right-0 z-40 flex items-center gap-1.5 sm:gap-3 border-t border-border bg-card px-2 sm:px-5 py-2 sm:py-3"
|
|
style={{ left: 'var(--sidebar-w, 0px)' }}
|
|
>
|
|
{/* Primary actions */}
|
|
<button
|
|
onClick={() => { setShowResolve(true); setShowEscalate(false) }}
|
|
disabled={!canResolve || isProcessing}
|
|
className="flex items-center justify-center gap-1.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20 px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-emerald-400 hover:bg-emerald-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
|
>
|
|
<CheckCircle2 size={15} />
|
|
Resolve
|
|
</button>
|
|
<button
|
|
onClick={() => setShowEscalate(true)}
|
|
disabled={!canEscalate || isProcessing}
|
|
className="flex items-center justify-center gap-1.5 rounded-lg bg-amber-500/10 border border-amber-500/20 px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-amber-400 hover:bg-amber-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
|
>
|
|
<ArrowUpRight size={15} />
|
|
Escalate
|
|
</button>
|
|
{canShareUpdate && onGenerateStatusUpdate && (
|
|
<button
|
|
onClick={() => setShowStatusUpdate(true)}
|
|
disabled={isProcessing}
|
|
className="flex items-center justify-center gap-1.5 rounded-lg bg-orange-500/10 border border-orange-500/20 px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-orange-400 hover:bg-orange-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
|
title="Share Update"
|
|
>
|
|
<FileText size={15} />
|
|
<span className="hidden sm:inline">Share Update</span>
|
|
</button>
|
|
)}
|
|
|
|
{/* Spacer */}
|
|
<div className="flex-1" />
|
|
|
|
{/* Secondary actions — right side */}
|
|
{onPause && (
|
|
<button
|
|
onClick={handlePause}
|
|
disabled={isProcessing || submitting}
|
|
className="flex items-center justify-center gap-1.5 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
|
>
|
|
<Pause size={15} />
|
|
<span className="hidden sm:inline">Pause</span>
|
|
</button>
|
|
)}
|
|
{onAbandon && (
|
|
<button
|
|
onClick={() => setShowAbandon(true)}
|
|
disabled={isProcessing || submitting}
|
|
className="flex items-center justify-center gap-1.5 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
|
>
|
|
<X size={15} />
|
|
<span className="hidden sm:inline">Close</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Resolve modal */}
|
|
{showResolve && (
|
|
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
|
|
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
|
|
<h3 className="font-heading text-lg font-semibold text-foreground mb-1">Resolve Session</h3>
|
|
<p className="text-sm text-muted-foreground mb-4">Summarize what fixed the issue. This will be included in the auto-generated documentation.</p>
|
|
<textarea
|
|
value={resolutionSummary}
|
|
onChange={(e) => setResolutionSummary(e.target.value)}
|
|
placeholder="What resolved the issue?"
|
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(96,165,250,0.3)] focus:outline-none resize-none"
|
|
rows={4}
|
|
autoFocus
|
|
/>
|
|
<div className="mt-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
<button
|
|
onClick={() => setShowResolve(false)}
|
|
className="rounded-lg px-4 py-2 min-h-[44px] text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleResolve}
|
|
disabled={resolutionSummary.length < 5 || submitting}
|
|
className="rounded-lg bg-emerald-500/20 border border-emerald-500/30 px-4 py-2 min-h-[44px] text-sm font-medium text-emerald-400 hover:bg-emerald-500/30 disabled:opacity-50 transition-colors"
|
|
>
|
|
{submitting ? 'Resolving...' : 'Resolve Session'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Close/Abandon confirmation */}
|
|
{showAbandon && (
|
|
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
|
|
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
|
|
<h3 className="font-heading text-lg font-semibold text-foreground mb-1">Close Session</h3>
|
|
<p className="text-sm text-muted-foreground mb-4">
|
|
Are you sure you want to close this session? The session history will be kept but it won't count as resolved.
|
|
</p>
|
|
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
<button
|
|
onClick={() => setShowAbandon(false)}
|
|
className="rounded-lg px-4 py-2 min-h-[44px] text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleAbandon}
|
|
disabled={submitting}
|
|
className="rounded-lg bg-rose-500/20 border border-rose-500/30 px-4 py-2 min-h-[44px] text-sm font-medium text-rose-400 hover:bg-rose-500/30 disabled:opacity-50 transition-colors"
|
|
>
|
|
{submitting ? 'Closing...' : 'Close Session'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Escalate modal */}
|
|
<EscalateModal
|
|
open={showEscalate}
|
|
onClose={() => setShowEscalate(false)}
|
|
onEscalate={onEscalate}
|
|
isProcessing={isProcessing || submitting}
|
|
hasPsaTicket={hasPsaTicket}
|
|
sessionId={sessionId}
|
|
/>
|
|
|
|
{/* Status Update modal */}
|
|
{onGenerateStatusUpdate && (
|
|
<StatusUpdateModal
|
|
open={showStatusUpdate}
|
|
onClose={() => setShowStatusUpdate(false)}
|
|
onGenerate={onGenerateStatusUpdate}
|
|
context="status"
|
|
hasPsaTicket={hasPsaTicket}
|
|
/>
|
|
)}
|
|
</>
|
|
)
|
|
}
|