Files
cpas/client/src/components/EditEmployeeModal.jsx

190 lines
7.9 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import axios from 'axios';
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: '480px', maxWidth: '95vw',
borderRadius: '10px', boxShadow: '0 8px 40px rgba(0,0,0,0.8)',
border: '1px solid #222', overflow: 'hidden',
},
header: {
background: 'linear-gradient(135deg, #000000, #151622)', color: 'white',
padding: '18px 22px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
borderBottom: '1px solid #222',
},
title: { fontSize: '15px', fontWeight: 700 },
closeBtn: {
background: 'none', border: 'none', color: 'white', fontSize: '20px',
cursor: 'pointer', lineHeight: 1,
},
body: { padding: '22px' },
tabs: { display: 'flex', gap: '4px', marginBottom: '20px' },
tab: (active) => ({
flex: 1, padding: '8px', borderRadius: '6px', cursor: 'pointer', fontSize: '12px',
fontWeight: 700, textAlign: 'center', border: '1px solid',
background: active ? '#1a1c2e' : 'none',
borderColor: active ? '#667eea' : '#2a2b3a',
color: active ? '#667eea' : '#777',
}),
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',
},
select: {
width: '100%', background: '#0d0e14', border: '1px solid #2a2b3a', borderRadius: '6px',
color: '#f8f9fa', padding: '9px 12px', fontSize: '13px', marginBottom: '14px',
outline: 'none', boxSizing: 'border-box',
},
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',
},
success: {
background: '#0a2e1f', border: '1px solid #0f5132', borderRadius: '6px',
padding: '10px 12px', fontSize: '12px', color: '#9ef7c1', marginBottom: '14px',
},
mergeWarning: {
background: '#2a1f00', border: '1px solid #7a5000', borderRadius: '6px',
padding: '12px', fontSize: '12px', color: '#ffc107', marginBottom: '14px', lineHeight: 1.5,
},
};
export default function EditEmployeeModal({ employee, onClose, onSaved }) {
const [tab, setTab] = useState('edit');
// Edit state
const [name, setName] = useState(employee.name);
const [department, setDepartment] = useState(employee.department || '');
const [supervisor, setSupervisor] = useState(employee.supervisor || '');
const [editError, setEditError] = useState('');
const [editSaving, setEditSaving] = useState(false);
// Merge state
const [allEmployees, setAllEmployees] = useState([]);
const [sourceId, setSourceId] = useState('');
const [mergeError, setMergeError] = useState('');
const [mergeResult, setMergeResult] = useState(null);
const [merging, setMerging] = useState(false);
useEffect(() => {
if (tab === 'merge') {
axios.get('/api/employees').then(r => setAllEmployees(r.data));
}
}, [tab]);
const handleEdit = async () => {
setEditError('');
setEditSaving(true);
try {
await axios.patch(`/api/employees/${employee.id}`, { name, department, supervisor });
onSaved();
onClose();
} catch (e) {
setEditError(e.response?.data?.error || 'Failed to save changes');
} finally {
setEditSaving(false);
}
};
const handleMerge = async () => {
if (!sourceId) return setMergeError('Select an employee to merge in');
setMergeError('');
setMerging(true);
try {
const r = await axios.post(`/api/employees/${employee.id}/merge`, { source_id: parseInt(sourceId) });
setMergeResult(r.data);
onSaved(); // refresh dashboard / parent list
} catch (e) {
setMergeError(e.response?.data?.error || 'Merge failed');
} finally {
setMerging(false);
}
};
const otherEmployees = allEmployees.filter(e => e.id !== employee.id);
return (
<div style={s.overlay} onClick={e => e.target === e.currentTarget && onClose()}>
<div style={s.modal}>
<div style={s.header}>
<div style={s.title}>Edit Employee</div>
<button style={s.closeBtn} onClick={onClose}></button>
</div>
<div style={s.body}>
<div style={s.tabs}>
<button style={s.tab(tab === 'edit')} onClick={() => setTab('edit')}>Edit Details</button>
<button style={s.tab(tab === 'merge')} onClick={() => setTab('merge')}>Merge Duplicate</button>
</div>
{tab === 'edit' && (
<>
{editError && <div style={s.error}>{editError}</div>}
<div style={s.label}>Full Name</div>
<input style={s.input} value={name} onChange={e => setName(e.target.value)} />
<div style={s.label}>Department</div>
<input style={s.input} value={department} onChange={e => setDepartment(e.target.value)} placeholder="Optional" />
<div style={s.label}>Supervisor</div>
<input style={s.input} value={supervisor} onChange={e => setSupervisor(e.target.value)} placeholder="Optional" />
<div style={s.row}>
<button style={s.btn('#888')} onClick={onClose}>Cancel</button>
<button style={s.btn('#fff', '#667eea')} onClick={handleEdit} disabled={editSaving}>
{editSaving ? 'Saving…' : 'Save Changes'}
</button>
</div>
</>
)}
{tab === 'merge' && (
<>
{mergeResult ? (
<div style={s.success}>
Merge complete {mergeResult.violations_reassigned} violation{mergeResult.violations_reassigned !== 1 ? 's' : ''} reassigned
to <strong>{employee.name}</strong>. The duplicate record has been removed.
</div>
) : (
<>
<div style={s.mergeWarning}>
This will reassign <strong>all violations</strong> from the selected employee into{' '}
<strong>{employee.name}</strong>, then permanently delete the duplicate record.
This cannot be undone.
</div>
{mergeError && <div style={s.error}>{mergeError}</div>}
<div style={s.label}>Duplicate to merge into {employee.name}</div>
<select style={s.select} value={sourceId} onChange={e => setSourceId(e.target.value)}>
<option value=""> select employee </option>
{otherEmployees.map(e => (
<option key={e.id} value={e.id}>{e.name}{e.department ? ` (${e.department})` : ''}</option>
))}
</select>
<div style={s.row}>
<button style={s.btn('#888')} onClick={onClose}>Cancel</button>
<button style={s.btn('#fff', '#c0392b')} onClick={handleMerge} disabled={merging || !sourceId}>
{merging ? 'Merging…' : 'Merge & Delete Duplicate'}
</button>
</div>
</>
)}
{mergeResult && (
<div style={s.row}>
<button style={s.btn('#fff', '#667eea')} onClick={onClose}>Done</button>
</div>
)}
</>
)}
</div>
</div>
</div>
);
}