import React from 'react'; import { CheckCircle2, Clock, Hash, Flag, FolderOpen, Users, BrainCircuit, ScanSearch, Layers, Network, Zap } from 'lucide-react'; import { formatFullTimestamp } from '../../utils/dateUtils'; import { motion, AnimatePresence } from 'framer-motion'; import useTicketStore from '../../store/ticketStore'; // ─── Step definitions ───────────────────────────────────────────────────────── const STEPS = [ { Icon: ScanSearch, title: '1. Ingestion', description: 'Ticket received and OCR text extracted.', timelineKey: 'created' }, { Icon: BrainCircuit, title: '2. AI Analysis', description: 'Context understood by LLM Engine.', timelineKey: 'ai_analyzed' }, { Icon: Layers, title: '3. Neural Triage', description: 'Category & Priority identified.', timelineKey: 'triaged' }, { Icon: ScanSearch, title: '4. Metadata Harvesting', description: 'IPs, Hostnames & Errors extracted.', timelineKey: 'metadata_harvested' }, { Icon: Network, title: '5. Intelligent Routing', description: 'Routed to optimal support team.', timelineKey: 'routed' }, { Icon: Zap, title: '6. Resolution Phase', description: 'Solving / Auto-resolution in progress.', timelineKey: 'resolution_started', finalKey: 'resolved_at' }, ]; // ─── Status → step index ────────────────────────────────────────────────────── const STATUS_STEP_MAP = { submitted: 0, open: 0, new: 0, analyzing: 1, processing: 1, duplicate_check: 2, checking_duplicates: 2, auto_resolve: 3, troubleshooting: 3, pending_human: 4, escalated: 4, assigned: 4, in_progress: 4, resolved: 5, closed: 5, done: 5, }; const getActiveStep = (status = '') => { const key = (status || '').toLowerCase().replace(/\s+/g, '_').trim(); if (key in STATUS_STEP_MAP) return STATUS_STEP_MAP[key]; // Fuzzy matching if (key.includes('resolv') || key.includes('closed') || key.includes('done')) return 5; if (key.includes('human') || key.includes('escalat') || key.includes('assign') || key.includes('progress')) return 4; if (key.includes('auto') || key.includes('plan')) return 3; if (key.includes('duplicate')) return 2; if (key.includes('analys') || key.includes('process') || key.includes('understanding')) return 1; if (key.includes('submit') || key.includes('open') || key.includes('new')) return 0; return 0; }; const getStepState = (idx, activeStep) => { if (idx < activeStep) return 'completed'; if (idx === activeStep) return 'active'; return 'pending'; }; // ─── Priority color ─────────────────────────────────────────────────────────── const priorityStyle = (p = '') => { const l = String(p || '').toLowerCase(); if (l === 'critical' || l === 'high') return 'text-red-600 bg-red-50 border-red-100'; if (l === 'medium') return 'text-amber-600 bg-amber-50 border-amber-100'; return 'text-emerald-700 bg-emerald-50 border-emerald-100'; }; // ─── Sub-components ─────────────────────────────────────────────────────────── const StepNode = ({ state, Icon }) => { const ring = 'w-10 h-10 rounded-full flex items-center justify-center'; if (state === 'completed') { return (
); } if (state === 'active') { return (
); } return (
); }; // ─── Main Component ─────────────────────────────────────────────────────────── // Subscribes directly to the Zustand store so it re-renders on any status change. // Optional `ticketId` prop: looks up the specific ticket in `tickets[]`. // Falls back to `activeTicket` if no ID is provided. const TicketTimeline = ({ ticketId, ticket: passedTicket, className = '', forceStep }) => { // Reactive store subscription const activeTicket = useTicketStore(s => s.activeTicket); const tickets = useTicketStore(s => s.tickets); const aiTicket = useTicketStore(s => s.aiTicket); // Resolve which ticket to display const ticket = passedTicket || (ticketId ? (tickets.find(t => t.ticket_id === ticketId) || activeTicket) : (activeTicket || aiTicket)); if (!ticket) return null; const activeStep = forceStep !== undefined ? forceStep : getActiveStep(ticket.status); const completedCount = activeStep; const progressPct = Math.round((completedCount / (STEPS.length - 1)) * 100); const getTimestamp = (idx, state) => { if (state === 'pending') return null; const step = STEPS[idx]; const timeline = ticket.timeline || {}; // Use specific timeline key if it exists if (timeline[step.timelineKey]) { return formatFullTimestamp(timeline[step.timelineKey]); } // Fallback or Final Resolution Handle if (idx === STEPS.length - 1 && (ticket.resolved_at || ticket.status === 'resolved')) { return formatFullTimestamp(ticket.resolved_at || ticket.updated_at); } // Final Fallback for ingestion if (idx === 0) return formatFullTimestamp(ticket.created_at || ticket.timestamp); return state === 'active' ? 'In progress' : 'Completed'; }; return (
{/* ════════════════════════════════════ TICKET SUMMARY CARD ════════════════════════════════════ */}

Ticket Summary

{/* ════════════════════════════════════ PROGRESS HEADER ════════════════════════════════════ */}

Issue Progress

{completedCount} of {STEPS.length} steps complete

{progressPct}%
{/* Progress bar */}
{/* ════════════════════════════════════ TIMELINE STEPS ════════════════════════════════════ */}
{STEPS.map((step, idx) => { const state = getStepState(idx, activeStep); const ts = getTimestamp(idx, state); const isLast = idx === STEPS.length - 1; return ( {/* Left: icon + connector */}
{!isLast && (
)}
{/* Right: text */}

{step.title}

{step.description}

{ts && (
{ts}
)}
); })}
); }; // ─── Small helper: one summary field ───────────────────────────────────────── const SummaryField = ({ Icon, label, value, valueClass }) => (

{label}

{valueClass ? {value} :

{value}

}
); export default TicketTimeline;