// ArchiveSummaryBar.js // Displays four stat cards for archive lifecycle states: ACTIVE, ARCHIVED, RETURNED, CLOSED. // Fetches counts from /api/ivanti/archive/stats on mount. import React, { useState, useEffect } from 'react'; import { Activity, Archive, RotateCcw, XCircle, Loader } from 'lucide-react'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const STATE_CONFIG = [ { key: 'ACTIVE', label: 'Active', color: '#0EA5E9', Icon: Activity, }, { key: 'ARCHIVED', label: 'Archived', color: '#F59E0B', Icon: Archive, }, { key: 'RETURNED', label: 'Returned', color: '#10B981', Icon: RotateCcw, }, { key: 'CLOSED', label: 'Closed', color: '#EF4444', Icon: XCircle, }, ]; function StatCard({ stateKey, label, color, Icon, count, active, onClick }) { const [hovered, setHovered] = useState(false); const isHighlighted = active || hovered; const cardStyle = { flex: '1 1 0', minWidth: '140px', background: 'linear-gradient(135deg, rgba(30, 41, 59, 0.95), rgba(51, 65, 85, 0.9))', border: `2px solid ${isHighlighted ? color : `rgba(${hexToRgb(color)}, 0.3)`}`, borderRadius: '0.5rem', padding: '1rem', cursor: 'pointer', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', transform: isHighlighted ? 'translateY(-2px)' : 'translateY(0)', boxShadow: isHighlighted ? `0 4px 16px rgba(0, 0, 0, 0.5), 0 0 20px rgba(${hexToRgb(color)}, 0.25)` : '0 4px 16px rgba(0, 0, 0, 0.5)', position: 'relative', overflow: 'hidden', }; const accentLineStyle = { position: 'absolute', top: 0, left: 0, right: 0, height: '2px', background: `linear-gradient(90deg, transparent, ${color}, transparent)`, boxShadow: `0 0 8px ${color}`, }; return (
onClick(stateKey)} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(stateKey); } }} aria-label={`${label}: ${count} findings. ${active ? 'Currently filtered.' : 'Click to filter.'}`} >
{label}
{count != null ? count : '—'}
); } // Convert hex color to r, g, b string for use in rgba() function hexToRgb(hex) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `${r}, ${g}, ${b}`; } export default function ArchiveSummaryBar({ onStateClick, activeFilter, refreshKey }) { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); useEffect(() => { let cancelled = false; const load = async () => { setLoading(true); setError(false); try { const res = await fetch(`${API_BASE}/ivanti/archive/stats`, { credentials: 'include' }); if (res.ok && !cancelled) { const data = await res.json(); setStats(data); } else if (!cancelled) { setError(true); } } catch { if (!cancelled) setError(true); } finally { if (!cancelled) setLoading(false); } }; load(); // Re-fetch every 60s so stats stay reasonably fresh after syncs const interval = setInterval(load, 60000); return () => { cancelled = true; clearInterval(interval); }; }, [refreshKey]); if (loading) { return (
Loading archive stats…
); } if (error) { return (
Unable to load archive statistics
); } const handleClick = (state) => { if (onStateClick) onStateClick(state); }; return (
{STATE_CONFIG.map(({ key, label, color, Icon }) => ( ))}
); }