diff --git a/client/src/components/EditEmployeeModal.jsx b/client/src/components/EditEmployeeModal.jsx new file mode 100644 index 0000000..a438e1b --- /dev/null +++ b/client/src/components/EditEmployeeModal.jsx @@ -0,0 +1,189 @@ +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 ( +