import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { violationData, violationGroups } from '../data/violations'; import useEmployeeIntelligence from '../hooks/useEmployeeIntelligence'; import CpasBadge from './CpasBadge'; import TierWarning from './TierWarning'; import ViolationHistory from './ViolationHistory'; const s = { content: { padding: '40px' }, section: { background: '#f8f9fa', borderLeft: '4px solid #667eea', padding: '20px', marginBottom: '30px', borderRadius: '4px' }, sectionTitle: { color: '#2c3e50', fontSize: '20px', marginBottom: '15px' }, grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '15px', marginTop: '15px' }, item: { display: 'flex', flexDirection: 'column' }, label: { fontWeight: 600, color: '#555', marginBottom: '5px', fontSize: '13px' }, input: { padding: '10px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '14px', fontFamily: 'inherit' }, fullCol: { gridColumn: '1 / -1' }, contextBox: { background: '#f1f3f5', border: '1px solid #ced4da', borderRadius: '4px', padding: '10px', fontSize: '12px', color: '#444', marginTop: '4px' }, repeatBadge: { display: 'inline-block', marginLeft: '8px', padding: '1px 7px', borderRadius: '10px', fontSize: '11px', fontWeight: 700, background: '#fff3cd', color: '#856404', border: '1px solid #ffc107' }, repeatWarn: { background: '#fff3cd', border: '1px solid #ffc107', borderRadius: '4px', padding: '8px 12px', marginTop: '6px', fontSize: '12px', color: '#856404' }, pointBox: { background: '#fff3cd', border: '2px solid #ffc107', padding: '15px', borderRadius: '6px', marginTop: '15px', textAlign: 'center' }, pointValue: { fontSize: '24px', fontWeight: 'bold', color: '#667eea', margin: '10px 0' }, scoreRow: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '14px', flexWrap: 'wrap' }, btnRow: { display: 'flex', gap: '15px', justifyContent: 'center', marginTop: '30px', flexWrap: 'wrap' }, btnPrimary: { padding: '15px 40px', fontSize: '16px', fontWeight: 600, border: 'none', borderRadius: '6px', cursor: 'pointer', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white', textTransform: 'uppercase' }, btnSecondary: { padding: '15px 40px', fontSize: '16px', fontWeight: 600, border: 'none', borderRadius: '6px', cursor: 'pointer', background: '#6c757d', color: 'white', textTransform: 'uppercase' }, note: { background: '#e7f3ff', borderLeft: '4px solid #2196F3', padding: '15px', margin: '20px 0', borderRadius: '4px' }, statusOk: { marginTop: '15px', padding: '15px', borderRadius: '6px', textAlign: 'center', fontWeight: 600, background: '#d4edda', color: '#155724', border: '1px solid #c3e6cb' }, statusErr: { marginTop: '15px', padding: '15px', borderRadius: '6px', textAlign: 'center', fontWeight: 600, background: '#f8d7da', color: '#721c24', border: '1px solid #f5c6cb' }, }; const EMPTY_FORM = { employeeId: '', employeeName: '', department: '', supervisor: '', witnessName: '', violationType: '', incidentDate: '', incidentTime: '', amount: '', minutesLate: '', location: '', additionalDetails: '', points: 1, }; export default function ViolationForm() { const [employees, setEmployees] = useState([]); const [form, setForm] = useState(EMPTY_FORM); const [violation, setViolation] = useState(null); const [status, setStatus] = useState(null); // Phase 2: pull score + history whenever employee changes const intel = useEmployeeIntelligence(form.employeeId || null); useEffect(() => { axios.get('/api/employees').then(r => setEmployees(r.data)).catch(() => {}); }, []); // When violation type changes, check all-time counts and auto-suggest higher pts for recidivists useEffect(() => { if (!violation || !form.violationType) return; const allTime = intel.countsAllTime[form.violationType]; if (allTime && allTime.count >= 1 && violation.minPoints !== violation.maxPoints) { // Suggest max points for repeat offenders setForm(prev => ({ ...prev, points: violation.maxPoints })); } else { setForm(prev => ({ ...prev, points: violation.minPoints })); } }, [form.violationType, violation, intel.countsAllTime]); const handleEmployeeSelect = e => { const emp = employees.find(x => x.id === parseInt(e.target.value)); if (!emp) return; setForm(prev => ({ ...prev, employeeId: emp.id, employeeName: emp.name, department: emp.department || '', supervisor: emp.supervisor || '' })); }; const handleViolationChange = e => { const key = e.target.value; const v = violationData[key] || null; setViolation(v); setForm(prev => ({ ...prev, violationType: key, points: v ? v.minPoints : 1 })); }; const handleChange = e => setForm(prev => ({ ...prev, [e.target.name]: e.target.value })); const handleSubmit = async e => { e.preventDefault(); if (!form.violationType) return setStatus({ ok: false, msg: 'Please select a violation type.' }); if (!form.employeeName) return setStatus({ ok: false, msg: 'Please enter an employee name.' }); try { const empRes = await axios.post('/api/employees', { name: form.employeeName, department: form.department, supervisor: form.supervisor }); const employeeId = empRes.data.id; await axios.post('/api/violations', { employee_id: employeeId, violation_type: form.violationType, violation_name: violation?.name || form.violationType, category: violation?.category || 'General', points: parseInt(form.points), incident_date: form.incidentDate, incident_time: form.incidentTime || null, location: form.location || null, details: form.additionalDetails || null, witness_name: form.witnessName || null, }); // Refresh employee list and re-run intel for updated score const empList = await axios.get('/api/employees'); setEmployees(empList.data); setStatus({ ok: true, msg: '✓ Violation recorded successfully' }); setForm(EMPTY_FORM); setViolation(null); } catch (err) { setStatus({ ok: false, msg: '✗ Error: ' + (err.response?.data?.error || err.message) }); } }; const showField = f => violation?.fields?.includes(f); const priorCount90 = key => intel.counts90[key] || 0; const isRepeat = key => (intel.countsAllTime[key]?.count || 0) >= 1; return (
{/* ── Employee Information ────────────────────────────────── */}

Employee Information

{/* CPAS score banner — shown once employee is selected */} {intel.score && form.employeeId && (
Current Standing: {intel.score.violation_count} violation{intel.score.violation_count !== 1 ? 's' : ''} in last 90 days
)} {employees.length > 0 && (
)}
{[['employeeName','Employee Name','text','John Doe'],['department','Department','text','Engineering'],['supervisor','Supervisor Name','text','Jane Smith'],['witnessName','Witness Name (Officer)','text','Officer Name']].map(([name,label,type,ph]) => (
))}
{/* ── Violation Details ────────────────────────────────────── */}

Violation Details

{/* Violation type dropdown with prior-use badges */}
{/* Handbook definition */} {violation && (
{violation.name} {isRepeat(form.violationType) && form.employeeId && ( ★ Repeat — {intel.countsAllTime[form.violationType]?.count}x prior )}
{violation.description}
{violation.chapter}
)} {/* Recidivist auto-suggest notice */} {violation && isRepeat(form.violationType) && form.employeeId && violation.minPoints !== violation.maxPoints && (
Repeat offense detected. Point slider set to maximum ({violation.maxPoints} pts) per recidivist policy. Adjust if needed.
)}
{/* Incident date */}
{showField('time') && (
)} {showField('minutes') && (
)} {showField('amount') && (
)} {showField('location') && (
)} {showField('description') && (