/** * 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...
{error}
No active SLA violations.
{kpi.value}
{kpi.label}
{kpi.subtitle}
{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)}`}