feat: 관리자 전용 직원 수정/삭제 모달 및 신규 파견 등록/종료 기능 추가
This commit is contained in:
@@ -10,6 +10,7 @@ import { useAuthStore } from '@/stores';
|
||||
import employeeService, { type PortfolioEntry, type CreateDispatchRequest } from '@/services/employeeService';
|
||||
import projectService from '@/services/projectService';
|
||||
import type { EmployeeSearchResult, Project } from '@/types';
|
||||
import CreateDispatchModal from '@/components/dispatch/CreateDispatchModal';
|
||||
|
||||
function initialColor(name: string) {
|
||||
const colors = [
|
||||
@@ -275,7 +276,27 @@ export default function DispatchPage() {
|
||||
<span>공수 {entry.contributionRate}%</span>
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs bg-primary/10 text-primary px-2.5 py-1 rounded-full font-medium">{entry.contributionRate}%</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs bg-primary/10 text-primary px-2.5 py-1 rounded-full font-medium">{entry.contributionRate}%</span>
|
||||
{(user?.roleCode === 'MANAGER' || user?.roleCode === 'ADMIN') && !entry.endDate && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (!confirm(`'${entry.projectName}' 파견을 종료하시겠습니까?`)) return;
|
||||
try {
|
||||
await employeeService.endDispatch(entry.dispatchId);
|
||||
alert('파견이 종료되었습니다.');
|
||||
fetchPortfolio();
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.message || '파견 종료 실패');
|
||||
}
|
||||
}}
|
||||
className="px-2.5 py-1 bg-red-100 text-red-700 hover:bg-red-200 rounded-full text-xs font-semibold transition-colors"
|
||||
>
|
||||
파견 종료
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-4 sm:px-6 py-4 space-y-3">
|
||||
|
||||
@@ -8,6 +8,7 @@ import apiClient from '@/services/apiClient';
|
||||
import { useCommonCodes } from '@/hooks/useCommonCodes';
|
||||
import { useAuthStore } from '@/stores';
|
||||
import type { Employee, RoleCode, EmpStatus, DispatchStatus } from '@/types';
|
||||
import EmployeeEditModal from '@/components/employees/EmployeeEditModal';
|
||||
|
||||
const statusColor: Record<string, string> = {
|
||||
ACTIVE: 'bg-status-resolved-bg text-status-resolved-text',
|
||||
@@ -36,6 +37,7 @@ export default function EmployeesPage() {
|
||||
|
||||
// Detail Modal State
|
||||
const [selectedEmployee, setSelectedEmployee] = useState<Employee | null>(null);
|
||||
const [editingEmployee, setEditingEmployee] = useState<Employee | null>(null);
|
||||
const [detailDispatch, setDetailDispatch] = useState<any[]>([]);
|
||||
const [detailEquipment, setDetailEquipment] = useState<any[]>([]);
|
||||
const [detailLoading, setDetailLoading] = useState(false);
|
||||
@@ -580,7 +582,21 @@ export default function EmployeesPage() {
|
||||
</td>
|
||||
<td className="py-4 px-4 sm:px-6 text-text-muted truncate max-w-[150px] sm:max-w-none">{emp.email}</td>
|
||||
<td className="py-4 px-4 sm:px-6">
|
||||
<span className="text-xs text-text-muted">View details →</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-text-muted hover:text-primary transition-colors">상세 →</span>
|
||||
{(user?.roleCode === 'ADMIN' || user?.roleCode === 'MANAGER') && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setEditingEmployee(emp);
|
||||
}}
|
||||
className="px-2 py-1 bg-primary/10 text-primary hover:bg-primary/20 rounded text-xs font-semibold transition-colors"
|
||||
>
|
||||
수정
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -606,9 +622,23 @@ export default function EmployeesPage() {
|
||||
<p className="text-xs text-text-muted truncate">{roleLabel[emp.roleCode] || emp.roleCode}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`text-[10px] font-medium px-2.5 py-1 rounded-full ${statusColor[emp.empStatus] || 'bg-gray-100 text-gray-600'}`}>
|
||||
{emp.empStatus === 'ACTIVE' ? '재직' : emp.empStatus === 'ON_LEAVE' ? '휴직' : '퇴사'}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-[10px] font-medium px-2.5 py-1 rounded-full ${statusColor[emp.empStatus] || 'bg-gray-100 text-gray-600'}`}>
|
||||
{emp.empStatus === 'ACTIVE' ? '재직' : emp.empStatus === 'ON_LEAVE' ? '휴직' : '퇴사'}
|
||||
</span>
|
||||
{(user?.roleCode === 'ADMIN' || user?.roleCode === 'MANAGER') && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setEditingEmployee(emp);
|
||||
}}
|
||||
className="px-2 py-1 bg-primary/10 text-primary hover:bg-primary/20 rounded text-xs font-semibold"
|
||||
>
|
||||
수정
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs text-text-muted">
|
||||
<div className="truncate">Email: {emp.email}</div>
|
||||
@@ -620,6 +650,16 @@ export default function EmployeesPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Employee Edit Modal */}
|
||||
<EmployeeEditModal
|
||||
isOpen={!!editingEmployee}
|
||||
onClose={() => setEditingEmployee(null)}
|
||||
employee={editingEmployee}
|
||||
onSuccess={() => {
|
||||
fetchEmployees();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user