Upload files to "client/src/components"
This commit is contained in:
@@ -2,115 +2,70 @@ import React, { useState } from 'react';
|
||||
|
||||
const s = {
|
||||
overlay: {
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
background: 'rgba(0,0,0,0.75)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.75)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
zIndex: 2000,
|
||||
},
|
||||
modal: {
|
||||
width: '480px',
|
||||
maxWidth: '95vw',
|
||||
background: '#111217',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 16px 40px rgba(0,0,0,0.8)',
|
||||
color: '#f8f9fa',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid #2a2b3a',
|
||||
width: '480px', maxWidth: '95vw', background: '#111217', borderRadius: '12px',
|
||||
boxShadow: '0 16px 40px rgba(0,0,0,0.8)', color: '#f8f9fa',
|
||||
overflow: 'hidden', border: '1px solid #2a2b3a',
|
||||
},
|
||||
header: {
|
||||
padding: '18px 24px',
|
||||
borderBottom: '1px solid #222',
|
||||
padding: '18px 24px', borderBottom: '1px solid #222',
|
||||
background: 'linear-gradient(135deg, #000000, #151622)',
|
||||
},
|
||||
title: {
|
||||
fontSize: '18px',
|
||||
fontWeight: 700,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: '12px',
|
||||
color: '#c0c2d6',
|
||||
marginTop: '4px',
|
||||
},
|
||||
body: {
|
||||
padding: '18px 24px 8px 24px',
|
||||
},
|
||||
title: { fontSize: '18px', fontWeight: 700 },
|
||||
subtitle: { fontSize: '12px', color: '#c0c2d6', marginTop: '4px' },
|
||||
body: { padding: '18px 24px 8px 24px' },
|
||||
pill: {
|
||||
background: '#3b2e00',
|
||||
borderRadius: '6px',
|
||||
padding: '8px 10px',
|
||||
fontSize: '12px',
|
||||
color: '#ffd666',
|
||||
border: '1px solid #d4af37',
|
||||
marginBottom: '14px',
|
||||
},
|
||||
label: {
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
marginBottom: '4px',
|
||||
color: '#e5e7f1',
|
||||
background: '#3b2e00', borderRadius: '6px', padding: '8px 10px',
|
||||
fontSize: '12px', color: '#ffd666', border: '1px solid #d4af37', marginBottom: '14px',
|
||||
},
|
||||
label: { fontSize: '13px', fontWeight: 600, marginBottom: '4px', color: '#e5e7f1' },
|
||||
input: {
|
||||
width: '100%',
|
||||
padding: '9px 10px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #333544',
|
||||
background: '#050608',
|
||||
color: '#f8f9fa',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'inherit',
|
||||
marginBottom: '14px',
|
||||
width: '100%', padding: '9px 10px', borderRadius: '6px',
|
||||
border: '1px solid #333544', background: '#050608', color: '#f8f9fa',
|
||||
fontSize: '13px', fontFamily: 'inherit', marginBottom: '14px',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
textarea: {
|
||||
width: '100%',
|
||||
minHeight: '80px',
|
||||
resize: 'vertical',
|
||||
padding: '9px 10px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #333544',
|
||||
background: '#050608',
|
||||
color: '#f8f9fa',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'inherit',
|
||||
marginBottom: '14px',
|
||||
width: '100%', minHeight: '80px', resize: 'vertical',
|
||||
padding: '9px 10px', borderRadius: '6px', border: '1px solid #333544',
|
||||
background: '#050608', color: '#f8f9fa', fontSize: '13px',
|
||||
fontFamily: 'inherit', marginBottom: '14px', boxSizing: 'border-box',
|
||||
},
|
||||
footer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
gap: '10px',
|
||||
padding: '16px 24px 20px 24px',
|
||||
background: '#0c0d14',
|
||||
borderTop: '1px solid #222',
|
||||
display: 'flex', justifyContent: 'flex-end', gap: '10px',
|
||||
padding: '16px 24px 20px 24px', background: '#0c0d14', borderTop: '1px solid #222',
|
||||
},
|
||||
btnCancel: {
|
||||
padding: '10px 20px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #333544',
|
||||
background: '#050608',
|
||||
color: '#f8f9fa',
|
||||
fontWeight: 600,
|
||||
fontSize: '13px',
|
||||
cursor: 'pointer',
|
||||
padding: '10px 20px', borderRadius: '6px', border: '1px solid #333544',
|
||||
background: '#050608', color: '#f8f9fa', fontWeight: 600,
|
||||
fontSize: '13px', cursor: 'pointer',
|
||||
},
|
||||
btnConfirm: {
|
||||
padding: '10px 22px',
|
||||
borderRadius: '6px',
|
||||
border: 'none',
|
||||
padding: '10px 22px', borderRadius: '6px', border: 'none',
|
||||
background: 'linear-gradient(135deg, #d4af37 0%, #ffdf8a 100%)',
|
||||
color: '#000',
|
||||
fontWeight: 700,
|
||||
fontSize: '13px',
|
||||
cursor: 'pointer',
|
||||
textTransform: 'uppercase',
|
||||
color: '#000', fontWeight: 700, fontSize: '13px',
|
||||
cursor: 'pointer', textTransform: 'uppercase',
|
||||
},
|
||||
};
|
||||
|
||||
const RESOLUTION_OPTIONS = [
|
||||
'Corrective Training Completed',
|
||||
'Verbal Warning Issued',
|
||||
'Written Warning Issued',
|
||||
'Management Review',
|
||||
'Policy Exception Approved',
|
||||
'Data Entry Error',
|
||||
'Other',
|
||||
];
|
||||
|
||||
export default function NegateModal({ violation, onConfirm, onCancel }) {
|
||||
const [resolutionType, setResolutionType] = useState('Corrective Training Completed');
|
||||
const [details, setDetails] = useState('');
|
||||
const [resolvedBy, setResolvedBy] = useState('');
|
||||
const [details, setDetails] = useState('');
|
||||
const [resolvedBy, setResolvedBy] = useState('');
|
||||
|
||||
if (!violation) return null;
|
||||
|
||||
@@ -123,75 +78,59 @@ export default function NegateModal({ violation, onConfirm, onCancel }) {
|
||||
});
|
||||
};
|
||||
|
||||
// FIX: overlay click only closes on backdrop, NOT modal children
|
||||
const handleOverlayClick = (e) => {
|
||||
if (e.target === e.currentTarget && onCancel) onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={s.overlay} onClick={handleOverlayClick}>
|
||||
<div style={s.modal}>
|
||||
{/* FIX: stopPropagation prevents modal clicks from bubbling to overlay */}
|
||||
<div style={s.modal} onClick={(e) => e.stopPropagation()}>
|
||||
|
||||
<div style={s.header}>
|
||||
<div style={s.title}>⊘ Negate Violation Points</div>
|
||||
<div style={s.title}>Negate Violation</div>
|
||||
<div style={s.subtitle}>
|
||||
This will zero out the points from this incident. The record remains in the audit log.
|
||||
Record resolution for: <strong>{violation.violation_name}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={s.body}>
|
||||
<div style={s.pill}>
|
||||
{violation.violation_name} · {violation.points} pts · {violation.incident_date}
|
||||
⚠ {violation.points} pt{violation.points !== 1 ? 's' : ''} · {violation.incident_date} · {violation.category}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={s.label}>Resolution Type *</div>
|
||||
<select
|
||||
style={s.input}
|
||||
value={resolutionType}
|
||||
onChange={e => setResolutionType(e.target.value)}
|
||||
>
|
||||
<option>Corrective Training Completed</option>
|
||||
<option>Documentation Error</option>
|
||||
<option>Policy Clarification / Exception</option>
|
||||
<option>Management Discretion</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={s.label}>Resolution Type</div>
|
||||
<select
|
||||
style={s.input}
|
||||
value={resolutionType}
|
||||
onChange={(e) => setResolutionType(e.target.value)}
|
||||
>
|
||||
{RESOLUTION_OPTIONS.map((opt) => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<div>
|
||||
<div style={s.label}>Additional Details</div>
|
||||
<textarea
|
||||
style={s.textarea}
|
||||
value={details}
|
||||
onChange={e => setDetails(e.target.value)}
|
||||
placeholder="Briefly describe why points are being negated..."
|
||||
/>
|
||||
</div>
|
||||
<div style={s.label}>Details / Notes</div>
|
||||
<textarea
|
||||
style={s.textarea}
|
||||
placeholder="Describe the resolution or context…"
|
||||
value={details}
|
||||
onChange={(e) => setDetails(e.target.value)}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div style={s.label}>Resolved By</div>
|
||||
<input
|
||||
style={s.input}
|
||||
value={resolvedBy}
|
||||
onChange={e => setResolvedBy(e.target.value)}
|
||||
placeholder="Supervisor or HR"
|
||||
/>
|
||||
</div>
|
||||
<div style={s.label}>Resolved By</div>
|
||||
<input
|
||||
style={s.input}
|
||||
placeholder="Manager or HR name…"
|
||||
value={resolvedBy}
|
||||
onChange={(e) => setResolvedBy(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={s.footer}>
|
||||
<button
|
||||
type="button"
|
||||
style={s.btnCancel}
|
||||
onClick={() => onCancel && onCancel()}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
style={s.btnConfirm}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
Confirm Negation
|
||||
</button>
|
||||
<button style={s.btnCancel} onClick={onCancel}>Cancel</button>
|
||||
<button style={s.btnConfirm} onClick={handleConfirm}>Confirm Negation</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user