Files
resolutionflow/frontend/src/components/flowpilot/SessionDocView.tsx
Michael Chihlas f45b045943 refactor: resolve merge conflicts — combine main improvements with token normalization
- .gitignore: keep both graphify-out/ entries and main's .gitnexus entry
- ScriptCodeBlock/ScriptPreviewModal: take main's border-border and text-accent-text
  for filename labels; use neutral ghost style for Save button in ScriptCodeBlock;
  use bg-accent (normalized from bg-primary) for Save button in ScriptPreviewModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 20:23:36 -04:00

234 lines
9.0 KiB
TypeScript

import { useState } from 'react'
import { FileText, Clock, CheckCircle2, ArrowUpRight, Star, AlertTriangle, Loader2, RefreshCw, Info } from 'lucide-react'
import { aiSessionsApi } from '@/api'
import { toast } from '@/lib/toast'
import type { SessionDocumentation } from '@/types/ai-session'
interface SessionDocViewProps {
documentation: SessionDocumentation
onRate?: (rating: number) => void
currentRating?: number | null
psaPushStatus?: string | null
psaPushError?: string | null
memberMappingWarning?: string | null
sessionId?: string
ticketId?: string | null
}
export function SessionDocView({
documentation,
onRate,
currentRating,
psaPushStatus,
psaPushError,
memberMappingWarning,
sessionId,
ticketId,
}: SessionDocViewProps) {
const [retrying, setRetrying] = useState(false)
const [currentPushStatus, setCurrentPushStatus] = useState(psaPushStatus)
const [currentPushError, setCurrentPushError] = useState(psaPushError)
const handleRetry = async () => {
if (!sessionId) return
setRetrying(true)
try {
const result = await aiSessionsApi.retryPsaPush(sessionId)
setCurrentPushStatus(result.psa_push_status)
setCurrentPushError(result.psa_push_error)
if (result.psa_push_status === 'sent') {
toast.success('Documentation pushed to ticket successfully')
}
} catch {
toast.error('Retry failed')
} finally {
setRetrying(false)
}
}
return (
<div className="space-y-4 sm:space-y-5">
{/* PSA Push Status */}
{currentPushStatus && currentPushStatus !== 'no_psa' && (
<div
className={`rounded-xl border px-3 py-3 sm:px-4 flex flex-wrap items-center gap-2 sm:gap-3 ${
currentPushStatus === 'sent'
? 'border-emerald-400/20 bg-emerald-400/5'
: currentPushStatus === 'pending_retry'
? 'border-warning/20 bg-warning/5'
: 'border-danger/20 bg-rose-500/5'
}`}
>
{currentPushStatus === 'sent' && (
<>
<CheckCircle2 size={16} className="text-success shrink-0" />
<span className="text-sm text-success">
Documentation pushed to ticket {ticketId ? `#${ticketId}` : ''}
</span>
</>
)}
{currentPushStatus === 'pending_retry' && (
<>
<Loader2 size={16} className="text-warning shrink-0 animate-spin" />
<span className="text-sm text-warning">
Documentation queued for push will sync shortly
</span>
</>
)}
{currentPushStatus === 'failed' && (
<>
<AlertTriangle size={16} className="text-danger shrink-0" />
<div className="flex-1">
<span className="text-sm text-danger">
Failed to push to ticket{currentPushError ? `${currentPushError}` : ''}
</span>
</div>
<button
onClick={handleRetry}
disabled={retrying}
className="flex items-center gap-1.5 rounded-lg bg-danger-dim px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-50"
>
{retrying ? <Loader2 size={12} className="animate-spin" /> : <RefreshCw size={12} />}
Retry
</button>
</>
)}
</div>
)}
{/* Member mapping warning */}
{memberMappingWarning && (
<div className="rounded-xl border border-accent/20 bg-accent/5 px-4 py-3 flex items-start gap-3">
<Info size={16} className="text-accent shrink-0 mt-0.5" />
<span className="text-sm text-accent">
Time entry was not created {memberMappingWarning} Session timing is included in the ticket note.
</span>
</div>
)}
{/* Header */}
<div className="card-flat p-3 sm:p-4 lg:p-5">
<div className="flex items-start gap-3">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-accent-dim text-primary">
<FileText size={16} />
</span>
<div className="flex-1">
<h3 className="font-heading text-lg font-semibold text-foreground">Session Documentation</h3>
<p className="mt-1 text-sm text-muted-foreground">{documentation.problem_summary}</p>
<div className="mt-2 flex items-center gap-3 flex-wrap">
{documentation.problem_domain && (
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
{documentation.problem_domain}
</span>
)}
{documentation.duration_display && (
<span className="flex items-center gap-1 text-xs text-muted-foreground">
<Clock size={12} />
{documentation.duration_display}
</span>
)}
<span className="text-xs text-muted-foreground">
{documentation.total_steps} steps
</span>
</div>
</div>
</div>
</div>
{/* Outcome */}
{(documentation.resolution_summary || documentation.escalation_reason) && (
<div className={`card-flat p-4 border-l-2 ${documentation.resolution_summary ? 'border-l-success' : 'border-l-warning'}`}>
<div className="flex items-center gap-2 mb-1">
{documentation.resolution_summary ? (
<CheckCircle2 size={14} className="text-success" />
) : (
<ArrowUpRight size={14} className="text-warning" />
)}
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground">
{documentation.resolution_summary ? 'Resolved' : 'Escalated'}
</span>
</div>
<p className="text-sm text-foreground">
{documentation.resolution_summary || documentation.escalation_reason}
</p>
</div>
)}
{/* Intake summary */}
<div className="card-flat p-3 sm:p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground mb-2">
Original intake
</h4>
<p className="text-sm text-foreground whitespace-pre-wrap">{documentation.intake_summary}</p>
</div>
{/* Diagnostic steps */}
<div className="space-y-2">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground px-1">
Diagnostic trail
</h4>
{documentation.diagnostic_steps.map((step) => (
<div key={step.step_number} className="card-flat p-3 sm:p-4">
<div className="flex items-start gap-3">
<span className="font-sans text-xs flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-card text-[0.625rem] text-muted-foreground border border-border">
{step.step_number}
</span>
<div className="flex-1">
<p className="text-sm text-foreground">{step.description}</p>
{step.engineer_response && (
<p className="mt-1 text-xs text-primary"> {step.engineer_response}</p>
)}
{step.outcome && (
<p className="mt-1 text-xs text-muted-foreground">Outcome: {step.outcome}</p>
)}
</div>
</div>
</div>
))}
</div>
{/* Follow-up recommendations */}
{documentation.follow_up_recommendations.length > 0 && (
<div className="card-flat p-3 sm:p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground mb-2">
Follow-up
</h4>
<ul className="space-y-1">
{documentation.follow_up_recommendations.map((rec, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-foreground">
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-primary" />
{rec}
</li>
))}
</ul>
</div>
)}
{/* Rating */}
{onRate && (
<div className="card-flat p-3 sm:p-4 text-center">
<p className="text-sm text-muted-foreground mb-2">How helpful was this session?</p>
<div className="flex items-center justify-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
onClick={() => onRate(star)}
className="p-2 sm:p-1 transition-colors"
>
<Star
size={20}
className={
(currentRating ?? 0) >= star
? 'fill-warning text-warning'
: 'text-muted-foreground hover:text-warning'
}
/>
</button>
))}
</div>
</div>
)}
</div>
)
}