import React, { useState, useEffect } from 'react'; import axios from 'axios'; const FIELD_LABELS = { incident_time: 'Incident Time', location: 'Location / Context', details: 'Incident Notes', submitted_by: 'Submitted By', witness_name: 'Witness / Documenting Officer', }; const s = { overlay: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.8)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center', }, modal: { background: '#111217', color: '#f8f9fa', width: '520px', maxWidth: '95vw', maxHeight: '90vh', overflowY: 'auto', borderRadius: '10px', boxShadow: '0 8px 40px rgba(0,0,0,0.8)', border: '1px solid #222', }, header: { background: 'linear-gradient(135deg, #000000, #151622)', color: 'white', padding: '18px 22px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid #222', position: 'sticky', top: 0, zIndex: 10, }, headerLeft: {}, title: { fontSize: '15px', fontWeight: 700 }, subtitle: { fontSize: '11px', color: '#9ca0b8', marginTop: '2px' }, closeBtn: { background: 'none', border: 'none', color: 'white', fontSize: '20px', cursor: 'pointer', lineHeight: 1, }, body: { padding: '22px' }, notice: { background: '#0e1a30', border: '1px solid #1e3a5f', borderRadius: '6px', padding: '10px 14px', fontSize: '12px', color: '#7eb8f7', marginBottom: '18px', }, label: { fontSize: '11px', color: '#9ca0b8', textTransform: 'uppercase', letterSpacing: '0.5px', marginBottom: '5px' }, input: { width: '100%', background: '#0d0e14', border: '1px solid #2a2b3a', borderRadius: '6px', color: '#f8f9fa', padding: '9px 12px', fontSize: '13px', marginBottom: '14px', outline: 'none', boxSizing: 'border-box', }, textarea: { width: '100%', background: '#0d0e14', border: '1px solid #2a2b3a', borderRadius: '6px', color: '#f8f9fa', padding: '9px 12px', fontSize: '13px', marginBottom: '14px', outline: 'none', boxSizing: 'border-box', minHeight: '80px', resize: 'vertical', }, divider: { borderTop: '1px solid #1c1d29', margin: '16px 0' }, sectionTitle: { fontSize: '11px', fontWeight: 700, color: '#9ca0b8', textTransform: 'uppercase', letterSpacing: '0.5px', marginBottom: '12px', }, amendRow: { background: '#0d0e14', border: '1px solid #1c1d29', borderRadius: '6px', padding: '10px 12px', marginBottom: '8px', fontSize: '12px', }, amendField: { fontWeight: 700, color: '#c0c2d6', marginBottom: '4px' }, amendOld: { color: '#ff7070', textDecoration: 'line-through', marginRight: '6px' }, amendNew: { color: '#9ef7c1' }, amendMeta: { fontSize: '10px', color: '#555a7a', marginTop: '4px' }, row: { display: 'flex', gap: '10px', justifyContent: 'flex-end', marginTop: '6px' }, btn: (color, bg) => ({ padding: '8px 18px', borderRadius: '6px', fontWeight: 700, fontSize: '13px', cursor: 'pointer', border: `1px solid ${color}`, color, background: bg || 'none', }), error: { background: '#3c1114', border: '1px solid #f5c6cb', borderRadius: '6px', padding: '10px 12px', fontSize: '12px', color: '#ffb3b8', marginBottom: '14px', }, }; function fmtDt(iso) { if (!iso) return '—'; return new Date(iso).toLocaleString('en-US', { timeZone: 'America/Chicago', dateStyle: 'medium', timeStyle: 'short' }); } export default function AmendViolationModal({ violation, onClose, onSaved }) { const [fields, setFields] = useState({ incident_time: violation.incident_time || '', location: violation.location || '', details: violation.details || '', submitted_by: violation.submitted_by || '', witness_name: violation.witness_name || '', }); const [changedBy, setChangedBy] = useState(''); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const [amendments, setAmendments] = useState([]); useEffect(() => { axios.get(`/api/violations/${violation.id}/amendments`) .then(r => setAmendments(r.data)) .catch(() => {}); }, [violation.id]); const hasChanges = Object.entries(fields).some( ([k, v]) => v !== (violation[k] || '') ); const handleSave = async () => { setError(''); setSaving(true); try { // Only send fields that actually changed const patch = Object.fromEntries( Object.entries(fields).filter(([k, v]) => v !== (violation[k] || '')) ); await axios.patch(`/api/violations/${violation.id}/amend`, { ...patch, changed_by: changedBy || null }); onSaved(); onClose(); } catch (e) { setError(e.response?.data?.error || 'Failed to save amendment'); } finally { setSaving(false); } }; const set = (field, value) => setFields(prev => ({ ...prev, [field]: value })); return (
e.target === e.currentTarget && onClose()}>
Amend Violation
CPAS-{String(violation.id).padStart(5, '0')} · {violation.violation_name} · {violation.incident_date}
Only non-scoring fields can be amended. Point values, violation type, and incident date are immutable — delete and re-submit if those need to change.
{error &&
{error}
} {Object.entries(FIELD_LABELS).map(([field, label]) => (
{label}
{field === 'details' ? (