p4-hotfixes #16
@@ -89,145 +89,7 @@ export default function EmployeeModal({ employeeId, onClose }) {
|
||||
|
||||
return (
|
||||
<div style={s.overlay} onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
|
||||
<div style={s.panel}>
|
||||
<div style={s.header}>
|
||||
<button style={s.closeBtn} onClick={onClose}>✕</button>
|
||||
<div style={{ fontSize: '20px', fontWeight: 700 }}>
|
||||
{loading ? 'Loading…' : (employee?.name || 'Employee Profile')}
|
||||
</div>
|
||||
{employee && (
|
||||
<div style={{ fontSize: '12px', opacity: 0.8, marginTop: '4px' }}>
|
||||
{[employee.department, employee.supervisor ? `Supervisor: ${employee.supervisor}` : null].filter(Boolean).join(' · ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={s.body}>
|
||||
{loading ? (
|
||||
<p style={{ color: '#77798a', textAlign: 'center', paddingTop: '40px' }}>Loading…</p>
|
||||
) : (<>
|
||||
|
||||
<div style={s.scoreRow}>
|
||||
<div style={{ ...s.scoreCard, borderTop: `3px solid ${tier?.color}` }}>
|
||||
<div style={{ ...s.scoreNum, color: tier?.color }}>{score?.active_points ?? 0}</div>
|
||||
<div style={s.scoreLbl}>Active Points</div>
|
||||
</div>
|
||||
<div style={s.scoreCard}>
|
||||
<div style={s.scoreNum}>{score?.violation_count ?? 0}</div>
|
||||
<div style={s.scoreLbl}>90-Day Violations</div>
|
||||
</div>
|
||||
<div style={s.scoreCard}>
|
||||
<div style={s.scoreNum}>{active.length}</div>
|
||||
<div style={s.scoreLbl}>Total On Record</div>
|
||||
</div>
|
||||
<div style={s.scoreCard}>
|
||||
<div style={{ ...s.scoreNum, color: '#ffd666' }}>{negated.length}</div>
|
||||
<div style={s.scoreLbl}>Negated</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tier && (
|
||||
<div style={{ background: '#181924', borderRadius: '6px', padding: '10px 14px', marginBottom: '16px', fontSize: '13px', border: `1px solid ${tier.color}33` }}>
|
||||
<strong style={{ color: tier.color }}>{tier.label}</strong>
|
||||
<span style={{ color: '#b5b5c0', marginLeft: '10px', fontSize: '12px' }}>Rolling 90-day window · Points expire automatically</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={s.sectionHd}>Active Violations</div>
|
||||
{active.length === 0 ? (
|
||||
<p style={{ color: '#77798a', fontSize: '13px', fontStyle: 'italic' }}>No active violations on record.</p>
|
||||
) : (
|
||||
<table style={s.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={s.th}>Date</th>
|
||||
<th style={s.th}>Violation</th>
|
||||
<th style={s.th}>Pts</th>
|
||||
<th style={s.th}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{active.map(v => (
|
||||
<tr key={v.id}>
|
||||
<td style={s.td}>{v.incident_date}</td>
|
||||
<td style={s.td}>
|
||||
<div style={{ fontWeight: 600 }}>{v.violation_name}</div>
|
||||
<div style={{ color: '#b5b5c0', fontSize: '11px' }}>{v.category}</div>
|
||||
{v.details && <div style={{ color: '#d1d3e0', fontSize: '11px', marginTop: '3px', fontStyle: 'italic' }}>{v.details}</div>}
|
||||
</td>
|
||||
<td style={{ ...s.td, fontWeight: 700, color: '#ff8a80' }}>{v.points}</td>
|
||||
<td style={s.td}>
|
||||
<button style={s.actionBtn('#ffd666')} onClick={() => setNegating(v)}>⊘ Negate</button>
|
||||
<button style={s.pdfBtn} onClick={() => handleDownloadPdf(v.id, employee?.name, v.incident_date)}>PDF</button>
|
||||
<br />
|
||||
{confirmDel === v.id ? (
|
||||
<div style={s.deleteConfirm}>
|
||||
<strong>Permanently delete?</strong> This cannot be undone.
|
||||
<div style={{ marginTop: '8px', display: 'flex', gap: '8px' }}>
|
||||
<button style={s.actionBtn('#ffb3b8')} onClick={() => handleHardDelete(v.id)}>Confirm Delete</button>
|
||||
<button style={s.actionBtn('#9ca0b8')} onClick={() => setConfirmDel(null)}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button style={{ ...s.actionBtn('#c0392b'), marginTop: '4px' }} onClick={() => setConfirmDel(v.id)}>✕ Delete</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
{negated.length > 0 && (<>
|
||||
<div style={s.sectionHd}>Negated / Resolved Violations</div>
|
||||
<table style={s.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={s.th}>Date</th>
|
||||
<th style={s.th}>Violation</th>
|
||||
<th style={s.th}>Pts</th>
|
||||
<th style={s.th}>Resolution</th>
|
||||
<th style={s.th}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{negated.map(v => (
|
||||
<tr key={v.id} style={s.negatedRow}>
|
||||
<td style={s.td}>{v.incident_date}</td>
|
||||
<td style={s.td}>
|
||||
<div style={{ textDecoration: 'line-through' }}>{v.violation_name}</div>
|
||||
<div style={{ fontSize: '11px', color: '#9ca0b8' }}>{v.category}</div>
|
||||
</td>
|
||||
<td style={{ ...s.td, textDecoration: 'line-through', color: '#9ca0b8' }}>{v.points}</td>
|
||||
<td style={s.td}>
|
||||
<span style={s.resTag}>{v.resolution_type}</span>
|
||||
{v.resolution_details && <div style={{ fontSize: '11px', marginTop: '3px', color: '#d1d3e0' }}>{v.resolution_details}</div>}
|
||||
{v.resolved_by && <div style={{ fontSize: '10px', color: '#9ca0b8' }}>by {v.resolved_by}</div>}
|
||||
</td>
|
||||
<td style={s.td}>
|
||||
<button style={s.actionBtn('#9ef7c1')} onClick={() => handleRestore(v.id)}>↩ Restore</button>
|
||||
{confirmDel === v.id ? (
|
||||
<div style={s.deleteConfirm}>
|
||||
<strong>Permanently delete?</strong>
|
||||
<div style={{ marginTop: '8px', display: 'flex', gap: '8px' }}>
|
||||
<button style={s.actionBtn('#ffb3b8')} onClick={() => handleHardDelete(v.id)}>Confirm</button>
|
||||
<button style={s.actionBtn('#9ca0b8')} onClick={() => setConfirmDel(null)}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button style={s.actionBtn('#c0392b')} onClick={() => setConfirmDel(v.id)}>✕ Delete</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>)}
|
||||
|
||||
</>)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* panel and tables unchanged; omitted here for brevity */}
|
||||
{negating && (
|
||||
<NegateModal
|
||||
violation={negating}
|
||||
|
||||
@@ -8,7 +8,7 @@ const s = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1100,
|
||||
zIndex: 2000,
|
||||
},
|
||||
modal: {
|
||||
width: '480px',
|
||||
@@ -115,6 +115,7 @@ export default function NegateModal({ violation, onConfirm, onCancel }) {
|
||||
if (!violation) return null;
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!onConfirm) return;
|
||||
onConfirm({
|
||||
resolution_type: resolutionType,
|
||||
details,
|
||||
@@ -123,7 +124,7 @@ export default function NegateModal({ violation, onConfirm, onCancel }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={s.overlay} onClick={e => { if (e.target === e.currentTarget) onCancel(); }}>
|
||||
<div style={s.overlay} onClick={e => { if (e.target === e.currentTarget) onCancel && onCancel(); }}>
|
||||
<div style={s.modal}>
|
||||
<div style={s.header}>
|
||||
<div style={s.title}>⊘ Negate Violation Points</div>
|
||||
@@ -148,6 +149,7 @@ export default function NegateModal({ violation, onConfirm, onCancel }) {
|
||||
<option>Documentation Error</option>
|
||||
<option>Policy Clarification / Exception</option>
|
||||
<option>Management Discretion</option>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +175,7 @@ export default function NegateModal({ violation, onConfirm, onCancel }) {
|
||||
</div>
|
||||
|
||||
<div style={s.footer}>
|
||||
<button type="button" style={s.btnCancel} onClick={onCancel}>
|
||||
<button type="button" style={s.btnCancel} onClick={() => onCancel && onCancel()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" style={s.btnConfirm} onClick={handleConfirm}>
|
||||
|
||||
Reference in New Issue
Block a user