'use client'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible'; import { Separator } from '@/components/ui/separator'; import { ChevronRight, ChevronDown, ExternalLink, AlertTriangle } from 'lucide-react'; import { ShareModal } from './share-modal'; import { AnalyzeButton } from './analyze-button'; import type { PersistedAnalysis, Visibility } from '@/lib/types/analyzer'; interface AnalysisViewProps { analysis: PersistedAnalysis; } const VISIBILITY_MARKER: Record = { customer_facing: '🟢', internal_only: '🔒', mixed: '🔄', }; const VISIBILITY_LABEL: Record = { customer_facing: 'Customer-facing', internal_only: 'Internal only', mixed: 'Mixed (both)', }; const SEVERITY_TONE: Record = { high: 'border-red-500 bg-red-500/5', medium: 'border-amber-500 bg-amber-500/5', low: 'border-blue-500 bg-blue-500/5', }; function ConfidenceBadge({ score }: { score: number | null }) { if (score === null) return null; const pct = Math.round(score * 100); const tone = score >= 0.8 ? 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400' : score >= 0.6 ? 'bg-amber-500/10 text-amber-700 dark:text-amber-400' : 'bg-red-500/10 text-red-700 dark:text-red-400'; return ( Confidence {pct}% ); } function ModelBadges({ a }: { a: PersistedAnalysis }) { return (
{a.haikuUsed && Haiku} {a.sonnetUsed && Sonnet} {a.opusUsed && Opus}
); } export function AnalysisView({ analysis: a }: AnalysisViewProps) { const [expandedEvent, setExpandedEvent] = useState(null); const [nextStepOpen, setNextStepOpen] = useState(false); const timelineByTimestamp = useMemo(() => { const map = new Map(); (a.timeline ?? []).forEach((event, idx) => { if (!map.has(event.timestamp)) map.set(event.timestamp, idx); }); return map; }, [a.timeline]); function jumpToEvent(timestamp: string) { const idx = timelineByTimestamp.get(timestamp); if (idx === undefined) return; setExpandedEvent(idx); document .getElementById(`timeline-event-${idx}`) ?.scrollIntoView({ behavior: 'smooth', block: 'center' }); } return (
{/* 1. Header */}
{a.ticketNumber} v{a.analysisVersion} {a.needsHumanReview && ( Needs review )}
AI Analysis · {new Date(a.triggeredAt).toLocaleString()}

{a.totalInputTokens.toLocaleString()} in /{' '} {a.totalOutputTokens.toLocaleString()} out tokens · cost{' '} {`$${a.estimatedCostUsd.toFixed(4)}`}

{/* 2. Summary */} {a.summary && ( Summary

{a.summary}

)} {/* 3. Next step */} {a.nextStep && ( Next step

{a.nextStep}

{a.nextStepRationale && (

{a.nextStepRationale}

)}
)} {/* 4. Timeline */} {a.timeline && a.timeline.length > 0 && ( Timeline
    {a.timeline.map((event, idx) => (
  1. ))}
)} {/* 5+6. What was done / should have been done — side by side on wide */} {((a.whatWasDone?.length ?? 0) > 0 || (a.whatShouldHaveBeenDone?.length ?? 0) > 0) && (
What was done
    {(a.whatWasDone ?? []).map((item, i) => (
  • {item}
  • ))}
What should have been done
    {(a.whatShouldHaveBeenDone ?? []).map((item, i) => (
  • {item}
  • ))}
)} {/* 7. Gaps */} {a.gaps && a.gaps.length > 0 && ( Gaps {a.gaps.map((gap, i) => (
{gap.severity}

{gap.description}

{gap.evidence_timestamps.length > 0 && (
Evidence:{' '} {gap.evidence_timestamps.map((ts, j) => ( ))}
)}
))}
)} {/* 8. Post-resolution analysis */} {a.postResolutionAnalysis && ( Post-resolution analysis

{a.postResolutionAnalysis}

)} {/* 9. Human review flags */} {a.needsHumanReview && ( Human review flags
    {(a.humanReviewReasons ?? []).map((reason, i) => (
  • {reason}
  • ))}
)} {/* 10. IT Glue references */} {a.itglueDocsReferenced.length > 0 && ( IT Glue references
    {a.itglueDocsReferenced.map((doc) => (
  • {doc.doc_type}
    {doc.url ? ( {doc.name} ) : ( {doc.name} )}

    {doc.relevance_reason}

  • ))}
)} {/* Expanded event detail (rendered separately so it floats independently) */} {expandedEvent !== null && a.timeline?.[expandedEvent] && ( Event detail

{a.timeline[expandedEvent].actor} ·{' '} {new Date(a.timeline[expandedEvent].timestamp).toLocaleString()}

{a.timeline[expandedEvent].action}

Visibility: {VISIBILITY_LABEL[a.timeline[expandedEvent].visibility]} {' · '} Source: {a.timeline[expandedEvent].source} {' · '} Actor type: {a.timeline[expandedEvent].actor_type}

)}
); }