import React, { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; import CpasBadge, { getTier } from './CpasBadge'; import NegateModal from './NegateModal'; const s = { overlay: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.55)', zIndex: 1000, display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-end' }, panel: { background: 'white', width: '680px', maxWidth: '95vw', height: '100vh', overflowY: 'auto', boxShadow: '-4px 0 24px rgba(0,0,0,0.18)', display: 'flex', flexDirection: 'column' }, header: { background: 'linear-gradient(135deg, #2c3e50, #34495e)', color: 'white', padding: '24px 28px', position: 'sticky', top: 0, zIndex: 10 }, closeBtn: { float: 'right', background: 'none', border: 'none', color: 'white', fontSize: '22px', cursor: 'pointer', lineHeight: 1, marginTop: '-2px' }, body: { padding: '24px 28px', flex: 1 }, scoreRow: { display: 'flex', gap: '12px', flexWrap: 'wrap', marginBottom: '24px' }, scoreCard: { flex: '1', minWidth: '100px', background: '#f8f9fa', borderRadius: '8px', padding: '14px', textAlign: 'center', border: '1px solid #dee2e6' }, scoreNum: { fontSize: '26px', fontWeight: 800 }, scoreLbl: { fontSize: '11px', color: '#888', marginTop: '3px' }, sectionHd: { fontSize: '13px', fontWeight: 700, color: '#34495e', textTransform: 'uppercase', letterSpacing: '0.5px', marginBottom: '10px', marginTop: '24px' }, table: { width: '100%', borderCollapse: 'collapse', fontSize: '12px' }, th: { background: '#f1f3f5', padding: '8px 10px', textAlign: 'left', color: '#555', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase' }, td: { padding: '9px 10px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'top' }, negatedRow: { background: '#f8f8f8', color: '#aaa' }, actionBtn: (color) => ({ background: 'none', border: `1px solid ${color}`, color, borderRadius: '4px', padding: '3px 8px', fontSize: '11px', cursor: 'pointer', marginRight: '4px', fontWeight: 600 }), resTag: { display: 'inline-block', padding: '2px 8px', borderRadius: '10px', fontSize: '10px', fontWeight: 700, background: '#d4edda', color: '#155724', border: '1px solid #c3e6cb' }, pdfBtn: { background: 'none', border: '1px solid #667eea', color: '#667eea', borderRadius: '4px', padding: '3px 8px', fontSize: '11px', cursor: 'pointer', fontWeight: 600 }, deleteConfirm: { background: '#f8d7da', border: '1px solid #f5c6cb', borderRadius: '6px', padding: '12px', marginTop: '8px', fontSize: '12px' }, }; export default function EmployeeModal({ employeeId, onClose }) { const [employee, setEmployee] = useState(null); const [score, setScore] = useState(null); const [violations, setViolations] = useState([]); const [loading, setLoading] = useState(true); const [negating, setNegating] = useState(null); const [confirmDel, setConfirmDel] = useState(null); const load = useCallback(() => { setLoading(true); Promise.all([ axios.get('/api/employees'), axios.get(`/api/employees/${employeeId}/score`), axios.get(`/api/violations/employee/${employeeId}?limit=100`), ]).then(([empRes, scoreRes, violRes]) => { const emp = empRes.data.find(e => e.id === employeeId); setEmployee(emp || null); setScore(scoreRes.data); setViolations(violRes.data); }).finally(() => setLoading(false)); }, [employeeId]); useEffect(() => { load(); }, [load]); const handleDownloadPdf = async (violId, empName, date) => { const response = await axios.get(`/api/violations/${violId}/pdf`, { responseType: 'blob' }); const url = window.URL.createObjectURL(new Blob([response.data], { type: 'application/pdf' })); const link = document.createElement('a'); link.href = url; link.download = `CPAS_${(empName||'').replace(/[^a-z0-9]/gi,'_')}_${date}.pdf`; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); }; const handleHardDelete = async (id) => { await axios.delete(`/api/violations/${id}`); setConfirmDel(null); load(); // ← refetch employee list, score, and violations }; const handleRestore = async (id) => { await axios.patch(`/api/violations/${id}/restore`); load(); // ← refetch employee list, score, and violations }; const handleNegate = async ({ resolution_type, details, resolved_by }) => { await axios.patch(`/api/violations/${negating.id}/negate`, { resolution_type, details, resolved_by }); setNegating(null); load(); // ← CRITICAL FIX: refetch score immediately after negation }; const tier = score ? getTier(score.active_points) : null; const active = violations.filter(v => !v.negated); const negated = violations.filter(v => v.negated); return (
Loading…
) : (<> {/* ── Score cards ───────────────────────── */}No active violations on record.
) : (| Date | Violation | Pts | Actions |
|---|---|---|---|
| {v.incident_date} |
{v.violation_name}
{v.category}
{v.details && {v.details} }
|
{v.points} |
{confirmDel === v.id ? (
Permanently delete? This cannot be undone.
) : (
)}
|
| Date | Violation | Pts | Resolution | Actions |
|---|---|---|---|---|
| {v.incident_date} |
{v.violation_name}
{v.category}
|
{v.points} |
{v.resolution_type}
{v.resolution_details && {v.resolution_details} }
{v.resolved_by && by {v.resolved_by} }
|
{confirmDel === v.id ? (
Permanently delete?
) : (
)}
|