/** * ResolutionNotePreview — Phase 3/4 popover for the Resolve AND Escalate flows. * * Persistent (not modal) popover showing the draft markdown that would be * posted to the customer ticket on Confirm. Phase 3 was read-only; Phase 4 * adds inline editing + a "Confirm & post" action that writes to PSA and * transitions the session state. * * Kind switches the labels, button colors, and confirm-CTA text — the * underlying mechanics (preview fetch + edit + post) are identical. */ import { useState, useEffect } from 'react' import { Loader2, RefreshCw, X, FileText, Pencil, Check, ArrowUpRight } from 'lucide-react' import { MarkdownContent } from '@/components/ui/MarkdownContent' import { cn } from '@/lib/utils' import type { ResolutionNotePreview as PreviewData } from '@/api/sessionSuggestedFixes' export type PreviewKind = 'resolve' | 'escalate' interface ResolutionNotePreviewProps { kind: PreviewKind open: boolean loading: boolean preview: PreviewData | null error: string | null onClose: () => void onRefresh: () => Promise | void onConfirm: (markdown: string) => Promise posting: boolean } export function ResolutionNotePreview({ kind, open, loading, preview, error, onClose, onRefresh, onConfirm, posting, }: ResolutionNotePreviewProps) { const [refreshing, setRefreshing] = useState(false) const [editing, setEditing] = useState(false) const [draft, setDraft] = useState('') // Keep the draft textarea in sync whenever fresh markdown arrives and we // aren't in the middle of editing. Once the engineer edits, their changes // win — we don't blow them away on a refetch. useEffect(() => { if (!editing && preview?.markdown) { setDraft(preview.markdown) } }, [preview?.markdown, editing]) if (!open) return null const label = kind === 'resolve' ? 'Resolution note' : 'Escalation handoff package' const confirmLabel = kind === 'resolve' ? 'Confirm & post to PSA' : 'Confirm & escalate' const confirmButtonTone = kind === 'resolve' ? 'bg-success text-bg-page hover:bg-success/90' : 'bg-warning text-bg-page hover:bg-warning/90' const KindIcon = kind === 'resolve' ? FileText : ArrowUpRight const handleRefresh = async () => { setRefreshing(true) try { await onRefresh() } finally { setRefreshing(false) } } const handleConfirm = async () => { if (!draft.trim()) return await onConfirm(draft) } return (
{label} preview {preview?.target_ticket_ref && ( → {preview.target_ticket_ref} )} {!preview?.target_ticket_ref && ( local only · no PSA ticket linked )} {preview?.from_cache && ( cached )}
{!editing && preview && ( )}
{loading && !preview && (
Drafting {label.toLowerCase()} from session state...
)} {error &&
{error}
} {preview && editing && (