import React, { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; const ACTION_COLORS = { employee_created: '#667eea', employee_edited: '#9b8af8', employee_merged: '#f0a500', violation_created: '#28a745', violation_amended: '#4db6ac', violation_negated: '#ffc107', violation_restored:'#17a2b8', violation_deleted: '#dc3545', }; const ACTION_LABELS = { employee_created: 'Employee Created', employee_edited: 'Employee Edited', employee_merged: 'Employee Merged', violation_created: 'Violation Logged', violation_amended: 'Violation Amended', violation_negated: 'Violation Negated', violation_restored:'Violation Restored', violation_deleted: 'Violation Deleted', }; const ENTITY_LABELS = { employee: 'Employee', violation: 'Violation', }; const s = { overlay: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.75)', zIndex: 1000, display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-end', }, panel: { background: '#111217', color: '#f8f9fa', width: '680px', maxWidth: '95vw', height: '100vh', overflowY: 'auto', boxShadow: '-4px 0 24px rgba(0,0,0,0.7)', display: 'flex', flexDirection: 'column', }, header: { background: 'linear-gradient(135deg, #000000, #151622)', color: 'white', padding: '22px 26px', position: 'sticky', top: 0, zIndex: 10, borderBottom: '1px solid #222', }, headerRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, title: { fontSize: '17px', fontWeight: 700 }, subtitle: { fontSize: '12px', color: '#9ca0b8', marginTop: '3px' }, closeBtn: { background: 'none', border: 'none', color: 'white', fontSize: '22px', cursor: 'pointer', lineHeight: 1, }, filters: { padding: '14px 26px', borderBottom: '1px solid #1c1d29', display: 'flex', gap: '10px', flexWrap: 'wrap', }, select: { background: '#0d0e14', border: '1px solid #2a2b3a', borderRadius: '6px', color: '#f8f9fa', padding: '7px 12px', fontSize: '12px', outline: 'none', }, body: { padding: '16px 26px', flex: 1 }, entry: { borderBottom: '1px solid #1c1d29', padding: '12px 0', display: 'flex', gap: '12px', alignItems: 'flex-start', }, dot: (action) => ({ width: '8px', height: '8px', borderRadius: '50%', marginTop: '5px', flexShrink: 0, background: ACTION_COLORS[action] || '#555', }), entryMain: { flex: 1, minWidth: 0 }, actionBadge: (action) => ({ display: 'inline-block', padding: '2px 8px', borderRadius: '10px', fontSize: '10px', fontWeight: 700, letterSpacing: '0.3px', marginRight: '6px', background: (ACTION_COLORS[action] || '#555') + '22', color: ACTION_COLORS[action] || '#aaa', border: `1px solid ${(ACTION_COLORS[action] || '#555')}44`, }), entityRef: { fontSize: '11px', color: '#9ca0b8' }, details: { fontSize: '11px', color: '#667', marginTop: '4px', fontFamily: 'monospace', wordBreak: 'break-all' }, meta: { fontSize: '10px', color: '#555a7a', marginTop: '4px' }, empty: { textAlign: 'center', color: '#555a7a', padding: '60px 0', fontSize: '13px' }, loadMore: { width: '100%', background: 'none', border: '1px solid #2a2b3a', borderRadius: '6px', color: '#9ca0b8', padding: '10px', cursor: 'pointer', fontSize: '12px', marginTop: '16px', }, }; function fmtDt(iso) { if (!iso) return '—'; return new Date(iso).toLocaleString('en-US', { timeZone: 'America/Chicago', dateStyle: 'medium', timeStyle: 'short', }); } function renderDetails(detailsStr) { if (!detailsStr) return null; try { const obj = JSON.parse(detailsStr); return JSON.stringify(obj, null, 0) .replace(/^\{/, '').replace(/\}$/, '').replace(/","/g, ' '); } catch { return detailsStr; } } export default function AuditLog({ onClose }) { const [entries, setEntries] = useState([]); const [loading, setLoading] = useState(true); const [offset, setOffset] = useState(0); const [hasMore, setHasMore] = useState(false); const [filterType, setFilterType] = useState(''); const [filterAction, setFilterAction] = useState(''); const LIMIT = 50; const load = useCallback((reset = false) => { setLoading(true); const o = reset ? 0 : offset; const params = { limit: LIMIT, offset: o }; if (filterType) params.entity_type = filterType; if (filterAction) params.action = filterAction; // future: server-side action filter axios.get('/api/audit', { params }) .then(r => { const data = r.data; // Client-side action filter (cheap enough at this scale) const filtered = filterAction ? data.filter(e => e.action === filterAction) : data; setEntries(prev => reset ? filtered : [...prev, ...filtered]); setHasMore(data.length === LIMIT); setOffset(o + LIMIT); }) .finally(() => setLoading(false)); }, [offset, filterType, filterAction]); useEffect(() => { load(true); }, [filterType, filterAction]); // eslint-disable-line const handleOverlay = e => { if (e.target === e.currentTarget) onClose(); }; return (