Phase 4
This commit is contained in:
68
client/src/components/NegateModal.jsx
Executable file
68
client/src/components/NegateModal.jsx
Executable file
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const RESOLUTION_TYPES = [
|
||||
'Corrective Training Completed',
|
||||
'Management Discretion',
|
||||
'Data Entry Error',
|
||||
'Successfully Appealed',
|
||||
];
|
||||
|
||||
const s = {
|
||||
overlay: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.65)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center' },
|
||||
box: { background: 'white', borderRadius: '10px', padding: '28px', width: '440px', maxWidth: '95vw', boxShadow: '0 8px 32px rgba(0,0,0,0.22)' },
|
||||
title: { fontSize: '17px', fontWeight: 700, color: '#2c3e50', marginBottom: '6px' },
|
||||
sub: { fontSize: '12px', color: '#888', marginBottom: '20px' },
|
||||
label: { fontWeight: 600, color: '#555', fontSize: '12px', marginBottom: '5px', display: 'block' },
|
||||
input: { width: '100%', padding: '9px 12px', border: '1px solid #ddd', borderRadius: '5px', fontSize: '13px', fontFamily: 'inherit', marginBottom: '14px' },
|
||||
btnRow: { display: 'flex', gap: '10px', justifyContent: 'flex-end', marginTop: '8px' },
|
||||
btnOk: { padding: '10px 22px', background: '#856404', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 700, fontSize: '13px' },
|
||||
btnCancel:{ padding: '10px 22px', background: '#f1f3f5', color: '#555', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 600, fontSize: '13px' },
|
||||
violBox: { background: '#fff3cd', border: '1px solid #ffc107', borderRadius: '6px', padding: '10px 14px', marginBottom: '18px', fontSize: '13px' },
|
||||
};
|
||||
|
||||
export default function NegateModal({ violation, onConfirm, onCancel }) {
|
||||
const [resType, setResType] = useState('');
|
||||
const [details, setDetails] = useState('');
|
||||
const [resolvedBy, setResolvedBy] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!resType) { setError('Please select a resolution type.'); return; }
|
||||
onConfirm({ resolution_type: resType, details, resolved_by: resolvedBy });
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={s.overlay}>
|
||||
<div style={s.box}>
|
||||
<div style={s.title}>⊘ Negate Violation Points</div>
|
||||
<div style={s.sub}>This will zero out the points from this incident. The record remains in the audit log.</div>
|
||||
|
||||
<div style={s.violBox}>
|
||||
<strong>{violation.violation_name}</strong> · {violation.points} pts · {violation.incident_date}
|
||||
</div>
|
||||
|
||||
<label style={s.label}>Resolution Type *</label>
|
||||
<select style={s.input} value={resType} onChange={e => { setResType(e.target.value); setError(''); }}>
|
||||
<option value="">-- Select Resolution --</option>
|
||||
{RESOLUTION_TYPES.map(r => <option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
|
||||
<label style={s.label}>Additional Details</label>
|
||||
<textarea style={{ ...s.input, resize: 'vertical', minHeight: '70px' }}
|
||||
placeholder="Training course completed, specific context, approving manager notes…"
|
||||
value={details} onChange={e => setDetails(e.target.value)} />
|
||||
|
||||
<label style={s.label}>Resolved By</label>
|
||||
<input style={s.input} type="text" placeholder="Officer / Manager name"
|
||||
value={resolvedBy} onChange={e => setResolvedBy(e.target.value)} />
|
||||
|
||||
{error && <div style={{ color: '#c0392b', fontSize: '12px', marginBottom: '10px' }}>{error}</div>}
|
||||
|
||||
<div style={s.btnRow}>
|
||||
<button style={s.btnCancel} onClick={onCancel}>Cancel</button>
|
||||
<button style={s.btnOk} onClick={handleSubmit}>Confirm Negation</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user