/** * SLA Dashboard Widget — Real-time SLA monitoring panel * * Displays: * - Active vs breached vs warning counts * - Breach rate percentage * - Per-priority breakdown * - Recent escalations * - Trend indicators */ import React, { useState, useEffect, useMemo } from 'react'; import { Clock, AlertTriangle, ShieldCheck, TrendingUp, TrendingDown, AlertCircle, Activity, RefreshCw, ArrowUpRight, ChevronRight, Users, Gauge, } from 'lucide-react'; const PRIORITY_COLORS = { critical: { bg: '#FEF2F2', text: '#DC2626', border: '#FECACA', dot: '#DC2626' }, high: { bg: '#FFF7ED', text: '#EA580C', border: '#FED7AA', dot: '#EA580C' }, medium: { bg: '#FEFCE8', text: '#CA8A04', border: '#FDE68A', dot: '#CA8A04' }, low: { bg: '#F0FDF4', text: '#16A34A', border: '#BBF7D0', dot: '#16A34A' }, }; import { API_CONFIG } from '@/config'; const API_BASE = API_CONFIG.BACKEND_URL; /** * Fetch SLA dashboard stats from the backend. */ async function fetchSLAStats() { const res = await fetch(`${API_BASE}/sla/stats`, { headers: { 'Content-Type': 'application/json' }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } /** * Fetch recent escalations. */ async function fetchEscalations(limit = 10) { const res = await fetch(`${API_BASE}/sla/escalations?limit=${limit}`, { headers: { 'Content-Type': 'application/json' }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } /** * Format seconds to human-readable duration. */ function formatDuration(seconds) { if (!seconds && seconds !== 0) return '—'; if (seconds <= 0) return 'Overdue'; const hrs = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); if (hrs > 0) return `${hrs}h ${mins}m`; return `${mins}m`; } /** * Main SLA Dashboard Component */ export default function SLADashboard({ compact = false, onViewAll }) { const [stats, setStats] = useState(null); const [escalations, setEscalations] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [refreshing, setRefreshing] = useState(false); const fetchData = async () => { try { setRefreshing(true); const [statsData, escData] = await Promise.all([ fetchSLAStats(), fetchEscalations(compact ? 5 : 10), ]); setStats(statsData); setEscalations(escData?.escalations || escData || []); setError(null); } catch (err) { console.error('[SLA-Dashboard] Fetch error:', err); setError(err.message); } finally { setLoading(false); setRefreshing(false); } }; useEffect(() => { fetchData(); }, []); // Auto-refresh every 60 seconds useEffect(() => { const interval = setInterval(fetchData, 60_000); return () => clearInterval(interval); }, []); // ── KPIs ────────────────────────────────────────────────────────────────── const kpis = useMemo(() => { if (!stats) return []; return [ { label: 'Active Tickets', value: stats.active ?? 0, icon: Activity, color: '#3b82f6', bg: '#EFF6FF', border: '#BFDBFE', subtitle: 'Being monitored', }, { label: 'SLA Warnings', value: stats.warning ?? 0, icon: AlertTriangle, color: '#F59E0B', bg: '#FEFCE8', border: '#FDE68A', subtitle: 'Nearing breach', pulse: (stats.warning ?? 0) > 0, }, { label: 'SLA Breached', value: stats.breached ?? 0, icon: AlertCircle, color: '#DC2626', bg: '#FEF2F2', border: '#FECACA', subtitle: 'Requires immediate action', pulse: (stats.breached ?? 0) > 0, }, { label: 'Breach Rate', value: stats.breach_rate != null ? `${stats.breach_rate}%` : '0%', icon: Gauge, color: (stats.breach_rate ?? 0) > 10 ? '#DC2626' : '#16A34A', bg: (stats.breach_rate ?? 0) > 10 ? '#FEF2F2' : '#F0FDF4', border: (stats.breach_rate ?? 0) > 10 ? '#FECACA' : '#BBF7D0', subtitle: 'Of total tickets', }, ]; }, [stats]); // ── Loading state ───────────────────────────────────────────────────────── if (loading) { return (

Loading SLA Dashboard...

); } if (error) { return (

Connection Error

{error}

); } // ── Empty state ─────────────────────────────────────────────────────────── if (!stats || (stats.active === 0 && stats.breached === 0)) { return (

All SLA Targets Met

No active SLA violations.

); } // ── Render ──────────────────────────────────────────────────────────────── return (
{/* Header with refresh */}

SLA Monitoring

{/* KPI Cards */}
{kpis.map((kpi, idx) => (

{kpi.value}

{kpi.label}

{kpi.subtitle}

))}
{/* Per-Priority Breakdown */} {stats?.by_priority && !compact && (

SLA Breakdown by Priority

{Object.entries(stats.by_priority).map(([priority, data]) => { const colors = PRIORITY_COLORS[priority] || PRIORITY_COLORS.medium; const total = data.total || 0; const breached = data.breached || 0; const warning = data.warning || 0; const healthy = total - breached - warning; const breachPct = total > 0 ? Math.round((breached / total) * 100) : 0; return (
{priority}
{healthy > 0 && (
0 ? (healthy / total) * 100 : 0}%`, background: '#16A34A', }} /> )} {warning > 0 && (
0 ? (warning / total) * 100 : 0}%`, background: '#F59E0B', }} /> )} {breached > 0 && (
0 ? (breached / total) * 100 : 0}%`, background: '#DC2626', }} /> )}
{total} {breachPct > 0 && ( {breachPct}% )}
); })}
)} {/* Recent Escalations */} {escalations.length > 0 && !compact && (

Recent Escalations

{onViewAll && ( )}
{escalations.slice(0, compact ? 3 : 5).map((esc, idx) => { const status = esc.sla_status || 'breached'; const level = esc.escalation_level || 0; const isBreached = status === 'breached'; return (
{isBreached ? : }

{esc.ticket_subject || `Ticket #${(esc.ticket_id || '').toString().slice(0, 8)}`}

L{level} · {esc.assigned_team || 'Unassigned'} {esc.remaining_seconds != null && ` · ${formatDuration(esc.remaining_seconds)}`}

{status}
); })}
)}
); }