'use client'; import { useState, useEffect, useRef } from 'react'; import { useDaumPostcode } from '@/hooks/useDaumPostcode'; import { useRouter, useParams } from 'next/navigation'; import { toast } from 'sonner'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Users, Plus, Trash2, ArrowLeft, Save, Settings, Camera, Edit } from 'lucide-react'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { FieldSettingsDialog } from './FieldSettingsDialog'; import type { Employee, EmployeeFormData, DepartmentPosition, FieldSettings, } from './types'; import { EMPLOYMENT_TYPE_LABELS, GENDER_LABELS, USER_ROLE_LABELS, USER_ACCOUNT_STATUS_LABELS, EMPLOYEE_STATUS_LABELS, DEFAULT_FIELD_SETTINGS, } from './types'; import { getPositions, getDepartments, type PositionItem, type DepartmentItem } from './actions'; import { getProfileImageUrl } from './utils'; interface EmployeeFormProps { mode: 'create' | 'edit' | 'view'; employee?: Employee; onSave?: (data: EmployeeFormData) => void; onEdit?: () => void; onDelete?: () => void; fieldSettings?: FieldSettings; } // 유효성 검사 에러 타입 interface ValidationErrors { name?: string; email?: string; userId?: string; password?: string; confirmPassword?: string; } const initialFormData: EmployeeFormData = { name: '', residentNumber: '', phone: '', email: '', salary: '', bankAccount: { bankName: '', accountNumber: '', accountHolder: '' }, profileImage: '', employeeCode: '', gender: '', address: { zipCode: '', address1: '', address2: '' }, hireDate: '', employmentType: '', rank: '', status: 'active', departmentPositions: [], clockInLocation: '', clockOutLocation: '', resignationDate: '', resignationReason: '', hasUserAccount: false, userId: '', password: '', confirmPassword: '', role: 'user', accountStatus: 'active', }; export function EmployeeForm({ mode, employee, onSave, onEdit, onDelete, fieldSettings: initialFieldSettings = DEFAULT_FIELD_SETTINGS, }: EmployeeFormProps) { const router = useRouter(); const params = useParams(); const locale = params.locale as string || 'ko'; const [formData, setFormData] = useState(initialFormData); const [errors, setErrors] = useState({}); const isViewMode = mode === 'view'; // Daum 우편번호 서비스 const { openPostcode } = useDaumPostcode({ onComplete: (result) => { setFormData(prev => ({ ...prev, address: { ...prev.address, zipCode: result.zonecode, address1: result.address, }, })); }, }); // 항목 설정 상태 const [showFieldSettings, setShowFieldSettings] = useState(false); const [fieldSettings, setFieldSettings] = useState(initialFieldSettings); const title = mode === 'create' ? '사원 등록' : mode === 'edit' ? '사원 수정' : '사원 상세'; const description = mode === 'create' ? '새로운 사원 정보를 입력합니다' : mode === 'edit' ? '사원 정보를 수정합니다' : '사원 정보를 확인합니다'; // 직급/직책/부서 목록 const [ranks, setRanks] = useState([]); const [titles, setTitles] = useState([]); const [departments, setDepartments] = useState([]); // localStorage에서 항목 설정 로드 useEffect(() => { const saved = localStorage.getItem('employeeFieldSettings'); if (saved) { try { setFieldSettings(JSON.parse(saved)); } catch { // ignore parse error } } }, []); // 직급/직책/부서 목록 로드 useEffect(() => { const loadData = async () => { const [rankList, titleList, deptList] = await Promise.all([ getPositions('rank'), getPositions('title'), getDepartments(), ]); setRanks(rankList); setTitles(titleList); setDepartments(deptList); }; loadData(); }, []); // 항목 설정 저장 const handleSaveFieldSettings = (newSettings: FieldSettings) => { setFieldSettings(newSettings); localStorage.setItem('employeeFieldSettings', JSON.stringify(newSettings)); }; // 데이터 초기화 (edit, view 모드) useEffect(() => { if (employee && (mode === 'edit' || mode === 'view')) { setFormData({ name: employee.name, residentNumber: employee.residentNumber || '', phone: employee.phone || '', email: employee.email || '', salary: employee.salary?.toString() || '', bankAccount: employee.bankAccount || { bankName: '', accountNumber: '', accountHolder: '' }, profileImage: employee.profileImage || '', employeeCode: employee.employeeCode || '', gender: employee.gender || '', address: employee.address || { zipCode: '', address1: '', address2: '' }, hireDate: employee.hireDate || '', employmentType: employee.employmentType || '', rank: employee.rank || '', status: employee.status, departmentPositions: employee.departmentPositions || [], clockInLocation: employee.clockInLocation || '', clockOutLocation: employee.clockOutLocation || '', resignationDate: employee.resignationDate || '', resignationReason: employee.resignationReason || '', hasUserAccount: !!employee.userInfo, userId: employee.userInfo?.userId || '', password: '', confirmPassword: '', role: employee.userInfo?.role || 'user', accountStatus: employee.userInfo?.accountStatus || 'active', }); } }, [employee, mode]); // 입력 변경 핸들러 const handleChange = (field: keyof EmployeeFormData, value: unknown) => { setFormData(prev => ({ ...prev, [field]: value })); // 에러 초기화 if (errors[field as keyof ValidationErrors]) { setErrors(prev => ({ ...prev, [field]: undefined })); } }; // 이메일 형식 검사 const isValidEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; // 유효성 검사 const validateForm = (): boolean => { const newErrors: ValidationErrors = {}; // 이름 필수 if (!formData.name.trim()) { newErrors.name = '이름을 입력해주세요.'; } // 이메일 필수 + 형식 검사 if (!formData.email.trim()) { newErrors.email = '이메일을 입력해주세요.'; } else if (!isValidEmail(formData.email)) { newErrors.email = '올바른 이메일 형식이 아닙니다.'; } // 아이디 필수 if (!formData.userId.trim()) { newErrors.userId = '아이디를 입력해주세요.'; } // 등록 모드일 때 비밀번호 검사 if (mode === 'create') { if (!formData.password) { newErrors.password = '비밀번호를 입력해주세요.'; } else if (formData.password.length < 6) { newErrors.password = '비밀번호는 6자 이상이어야 합니다.'; } if (formData.password !== formData.confirmPassword) { newErrors.confirmPassword = '비밀번호가 일치하지 않습니다.'; } } setErrors(newErrors); // 에러가 있으면 첫 번째 에러 메시지 표시 const firstError = Object.values(newErrors)[0]; if (firstError) { toast.error(firstError); return false; } return true; }; // 부서/직책 추가 const handleAddDepartmentPosition = () => { const newDP: DepartmentPosition = { id: String(Date.now()), departmentId: '', departmentName: '', positionId: '', positionName: '', }; setFormData(prev => ({ ...prev, departmentPositions: [...prev.departmentPositions, newDP], })); }; // 부서/직책 삭제 const handleRemoveDepartmentPosition = (id: string) => { setFormData(prev => ({ ...prev, departmentPositions: prev.departmentPositions.filter(dp => dp.id !== id), })); }; // 부서/직책 변경 const handleDepartmentPositionChange = (id: string, field: keyof DepartmentPosition, value: string) => { setFormData(prev => ({ ...prev, departmentPositions: prev.departmentPositions.map(dp => dp.id === id ? { ...dp, [field]: value } : dp ), })); }; // 저장 const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // view 모드에서는 저장 불가 if (isViewMode) return; // 유효성 검사 if (!validateForm()) { return; } onSave?.(formData); }; // 취소 (목록으로 이동) const handleCancel = () => { router.push(`/${locale}/hr/employee-management`); }; return ( {/* 헤더 + 버튼 영역 */}
{!isViewMode && ( )}
{/* 사원 정보 - 프로필 사진 + 기본 정보 */} 사원 정보 {/* 기본 정보 필드들 */}
handleChange('name', e.target.value)} placeholder="이름을 입력하세요" disabled={isViewMode} className={errors.name ? 'border-red-500' : ''} /> {errors.name &&

{errors.name}

}
handleChange('residentNumber', e.target.value)} placeholder="000000-0000000" disabled={isViewMode} />
handleChange('phone', e.target.value)} placeholder="010-0000-0000" disabled={isViewMode} />
handleChange('email', e.target.value)} placeholder="email@company.com" disabled={isViewMode} className={errors.email ? 'border-red-500' : ''} /> {errors.email &&

{errors.email}

}
handleChange('salary', e.target.value)} placeholder="연봉 (원)" disabled={isViewMode} />
{/* 급여 계좌 */}
handleChange('bankAccount', { ...formData.bankAccount, bankName: e.target.value })} placeholder="은행명" disabled={isViewMode} /> handleChange('bankAccount', { ...formData.bankAccount, accountNumber: e.target.value })} placeholder="계좌번호" disabled={isViewMode} /> handleChange('bankAccount', { ...formData.bankAccount, accountHolder: e.target.value })} placeholder="예금주" disabled={isViewMode} />
{/* 사원 상세 */} {(fieldSettings.showProfileImage || fieldSettings.showEmployeeCode || fieldSettings.showGender || fieldSettings.showAddress) && ( 사원 상세 {/* 프로필 사진 + 사원코드/성별 */}
{/* 프로필 사진 영역 */} {fieldSettings.showProfileImage && (
{formData.profileImage ? ( 프로필 ) : ( <> IMG
)} {!isViewMode && ( { const file = e.target.files?.[0]; if (file) { // 미리보기 즉시 표시 handleChange('profileImage', URL.createObjectURL(file)); // 서버에 업로드 const result = await uploadProfileImage(file); if (result.success && result.data?.url) { handleChange('profileImage', result.data.url); } } }} /> )}
{!isViewMode && (

1 250 X 250px, 10MB 이하의
PNG, JPEG, GIF

)}
)} {/* 사원코드, 성별 */}
{fieldSettings.showEmployeeCode && (
handleChange('employeeCode', e.target.value)} placeholder="사원코드를 입력해주세요" disabled={isViewMode} />
)} {fieldSettings.showGender && (
handleChange('gender', value)} className="flex items-center gap-4 h-10" disabled={isViewMode} >
)}
{/* 주소 (사원코드/성별 아래) */} {fieldSettings.showAddress && (
{!isViewMode && ( )} handleChange('address', { ...formData.address, zipCode: e.target.value })} placeholder="" className="w-24" readOnly disabled={isViewMode} /> handleChange('address', { ...formData.address, address2: e.target.value })} placeholder="상세주소를 입력해주세요" className="flex-1" disabled={isViewMode} />
)}
)} {/* 인사 정보 */} {(fieldSettings.showHireDate || fieldSettings.showEmploymentType || fieldSettings.showRank || fieldSettings.showStatus || fieldSettings.showDepartment || fieldSettings.showPosition || fieldSettings.showClockInLocation || fieldSettings.showClockOutLocation || fieldSettings.showResignationDate || fieldSettings.showResignationReason) && ( 인사 정보
{fieldSettings.showHireDate && (
handleChange('hireDate', e.target.value)} disabled={isViewMode} />
)} {fieldSettings.showEmploymentType && (
)} {fieldSettings.showRank && (
handleChange('rank', e.target.value)} placeholder="직급 입력" disabled={isViewMode} />
)} {fieldSettings.showStatus && (
)}
{/* 부서/직책 */} {(fieldSettings.showDepartment || fieldSettings.showPosition) && (
{!isViewMode && ( )}
{formData.departmentPositions.length === 0 ? (

{isViewMode ? '등록된 부서/직책이 없습니다' : '부서/직책을 추가해주세요'}

) : (
{formData.departmentPositions.map((dp) => (
handleDepartmentPositionChange(dp.id, 'departmentName', e.target.value)} placeholder="부서명" className="flex-1" disabled={isViewMode} /> handleDepartmentPositionChange(dp.id, 'positionName', e.target.value)} placeholder="직책" className="flex-1" disabled={isViewMode} /> {!isViewMode && ( )}
))}
)}
)} {/* 출근/퇴근 위치 */}
{fieldSettings.showClockInLocation && (
)} {fieldSettings.showClockOutLocation && (
)}
{/* 퇴사일/퇴직사유 */} {(fieldSettings.showResignationDate || fieldSettings.showResignationReason) && (
{fieldSettings.showResignationDate && (
handleChange('resignationDate', e.target.value)} disabled={isViewMode} />
)} {fieldSettings.showResignationReason && (
handleChange('resignationReason', e.target.value)} placeholder="퇴직 사유를 입력하세요" disabled={isViewMode} />
)}
)}
)} {/* 사용자 정보 */} 사용자 정보
handleChange('userId', e.target.value)} placeholder="사용자 아이디" disabled={isViewMode} className={errors.userId ? 'border-red-500' : ''} /> {errors.userId &&

{errors.userId}

}
{mode === 'create' && ( <>
handleChange('password', e.target.value)} placeholder="비밀번호" className={errors.password ? 'border-red-500' : ''} /> {errors.password &&

{errors.password}

}
handleChange('confirmPassword', e.target.value)} placeholder="비밀번호 확인" className={errors.confirmPassword ? 'border-red-500' : ''} /> {errors.confirmPassword &&

{errors.confirmPassword}

}
)}
{/* 버튼 영역 */}
{isViewMode ? (
) : ( )}
{/* 항목 설정 모달 */}
); }