Files
sam-react-prod/src/components/hr/EmployeeManagement/EmployeeForm.tsx
kent 7b917fcbcd fix(WEB): EmployeeForm toast 중복 import 제거
- sonner의 toast가 6번, 21번 줄에서 중복 import되어 빌드 실패
- 21번 줄의 중복 import 제거하여 빌드 오류 해결
2025-12-30 18:06:54 +09:00

893 lines
34 KiB
TypeScript

'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<EmployeeFormData>(initialFormData);
const [errors, setErrors] = useState<ValidationErrors>({});
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<FieldSettings>(initialFieldSettings);
const title = mode === 'create' ? '사원 등록' : mode === 'edit' ? '사원 수정' : '사원 상세';
const description = mode === 'create'
? '새로운 사원 정보를 입력합니다'
: mode === 'edit'
? '사원 정보를 수정합니다'
: '사원 정보를 확인합니다';
// 직급/직책/부서 목록
const [ranks, setRanks] = useState<PositionItem[]>([]);
const [titles, setTitles] = useState<PositionItem[]>([]);
const [departments, setDepartments] = useState<DepartmentItem[]>([]);
// 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 (
<PageLayout>
{/* 헤더 + 버튼 영역 */}
<div className="flex items-start justify-between mb-6">
<PageHeader
title={title}
description={description}
icon={Users}
/>
{!isViewMode && (
<Button
type="button"
variant="outline"
onClick={() => setShowFieldSettings(true)}
>
<Settings className="w-4 h-4 mr-2" />
</Button>
)}
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* 사원 정보 - 프로필 사진 + 기본 정보 */}
<Card>
<CardHeader className="bg-black text-white rounded-t-lg">
<CardTitle className="text-base font-medium"> </CardTitle>
</CardHeader>
<CardContent className="pt-6">
{/* 기본 정보 필드들 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="name"> *</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => handleChange('name', e.target.value)}
placeholder="이름을 입력하세요"
disabled={isViewMode}
className={errors.name ? 'border-red-500' : ''}
/>
{errors.name && <p className="text-sm text-red-500">{errors.name}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="residentNumber"></Label>
<Input
id="residentNumber"
value={formData.residentNumber}
onChange={(e) => handleChange('residentNumber', e.target.value)}
placeholder="000000-0000000"
disabled={isViewMode}
/>
</div>
<div className="space-y-2">
<Label htmlFor="phone"></Label>
<Input
id="phone"
value={formData.phone}
onChange={(e) => handleChange('phone', e.target.value)}
placeholder="010-0000-0000"
disabled={isViewMode}
/>
</div>
<div className="space-y-2">
<Label htmlFor="email"> *</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
placeholder="email@company.com"
disabled={isViewMode}
className={errors.email ? 'border-red-500' : ''}
/>
{errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="salary"></Label>
<Input
id="salary"
type="number"
value={formData.salary}
onChange={(e) => handleChange('salary', e.target.value)}
placeholder="연봉 (원)"
disabled={isViewMode}
/>
</div>
</div>
{/* 급여 계좌 */}
<div className="space-y-2 mt-6">
<Label></Label>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
<Input
value={formData.bankAccount.bankName}
onChange={(e) => handleChange('bankAccount', { ...formData.bankAccount, bankName: e.target.value })}
placeholder="은행명"
disabled={isViewMode}
/>
<Input
value={formData.bankAccount.accountNumber}
onChange={(e) => handleChange('bankAccount', { ...formData.bankAccount, accountNumber: e.target.value })}
placeholder="계좌번호"
disabled={isViewMode}
/>
<Input
value={formData.bankAccount.accountHolder}
onChange={(e) => handleChange('bankAccount', { ...formData.bankAccount, accountHolder: e.target.value })}
placeholder="예금주"
disabled={isViewMode}
/>
</div>
</div>
</CardContent>
</Card>
{/* 사원 상세 */}
{(fieldSettings.showProfileImage || fieldSettings.showEmployeeCode || fieldSettings.showGender || fieldSettings.showAddress) && (
<Card>
<CardHeader className="bg-black text-white rounded-t-lg">
<CardTitle className="text-base font-medium"> </CardTitle>
</CardHeader>
<CardContent className="pt-6 space-y-4">
{/* 프로필 사진 + 사원코드/성별 */}
<div className="flex gap-6">
{/* 프로필 사진 영역 */}
{fieldSettings.showProfileImage && (
<div className="space-y-2 flex-shrink-0">
<Label> </Label>
<div className={`w-32 h-32 border border-dashed border-gray-300 rounded-md flex flex-col items-center justify-center bg-gray-50 relative ${isViewMode ? '' : 'cursor-pointer hover:bg-gray-100'}`}>
{formData.profileImage ? (
<img
src={formData.profileImage}
alt="프로필"
className="w-full h-full object-cover rounded-md"
/>
) : (
<>
<span className="text-sm text-gray-400 mb-1">IMG</span>
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
<Camera className="w-4 h-4 text-gray-500" />
</div>
</>
)}
{!isViewMode && (
<input
type="file"
accept="image/png,image/jpeg,image/gif"
className="absolute inset-0 opacity-0 cursor-pointer"
onChange={async (e) => {
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);
}
}
}}
/>
)}
</div>
{!isViewMode && (
<p className="text-xs text-gray-500">
1 250 X 250px, 10MB <br />PNG, JPEG, GIF
</p>
)}
</div>
)}
{/* 사원코드, 성별 */}
<div className="flex-1 space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{fieldSettings.showEmployeeCode && (
<div className="space-y-2">
<Label htmlFor="employeeCode"></Label>
<Input
id="employeeCode"
value={formData.employeeCode}
onChange={(e) => handleChange('employeeCode', e.target.value)}
placeholder="사원코드를 입력해주세요"
disabled={isViewMode}
/>
</div>
)}
{fieldSettings.showGender && (
<div className="space-y-2">
<Label></Label>
<RadioGroup
value={formData.gender}
onValueChange={(value) => handleChange('gender', value)}
className="flex items-center gap-4 h-10"
disabled={isViewMode}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="male" id="gender-male" disabled={isViewMode} />
<Label htmlFor="gender-male" className="font-normal cursor-pointer"></Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="female" id="gender-female" disabled={isViewMode} />
<Label htmlFor="gender-female" className="font-normal cursor-pointer"></Label>
</div>
</RadioGroup>
</div>
)}
</div>
{/* 주소 (사원코드/성별 아래) */}
{fieldSettings.showAddress && (
<div className="space-y-2">
<Label></Label>
<div className="flex gap-2">
{!isViewMode && (
<Button type="button" variant="default" size="sm" onClick={openPostcode} className="bg-blue-500 hover:bg-blue-600">
</Button>
)}
<Input
value={formData.address.zipCode}
onChange={(e) => handleChange('address', { ...formData.address, zipCode: e.target.value })}
placeholder=""
className="w-24"
readOnly
disabled={isViewMode}
/>
<Input
value={formData.address.address2}
onChange={(e) => handleChange('address', { ...formData.address, address2: e.target.value })}
placeholder="상세주소를 입력해주세요"
className="flex-1"
disabled={isViewMode}
/>
</div>
</div>
)}
</div>
</div>
</CardContent>
</Card>
)}
{/* 인사 정보 */}
{(fieldSettings.showHireDate || fieldSettings.showEmploymentType || fieldSettings.showRank || fieldSettings.showStatus || fieldSettings.showDepartment || fieldSettings.showPosition || fieldSettings.showClockInLocation || fieldSettings.showClockOutLocation || fieldSettings.showResignationDate || fieldSettings.showResignationReason) && (
<Card>
<CardHeader className="bg-black text-white rounded-t-lg">
<CardTitle className="text-base font-medium"> </CardTitle>
</CardHeader>
<CardContent className="pt-6 space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{fieldSettings.showHireDate && (
<div className="space-y-2">
<Label htmlFor="hireDate"></Label>
<Input
id="hireDate"
type="date"
value={formData.hireDate}
onChange={(e) => handleChange('hireDate', e.target.value)}
disabled={isViewMode}
/>
</div>
)}
{fieldSettings.showEmploymentType && (
<div className="space-y-2">
<Label htmlFor="employmentType"></Label>
<Select
value={formData.employmentType}
onValueChange={(value) => handleChange('employmentType', value)}
disabled={isViewMode}
>
<SelectTrigger disabled={isViewMode}>
<SelectValue placeholder="고용형태 선택" />
</SelectTrigger>
<SelectContent>
{Object.entries(EMPLOYMENT_TYPE_LABELS).map(([value, label]) => (
<SelectItem key={value} value={value}>{label}</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{fieldSettings.showRank && (
<div className="space-y-2">
<Label htmlFor="rank"></Label>
<Input
id="rank"
value={formData.rank}
onChange={(e) => handleChange('rank', e.target.value)}
placeholder="직급 입력"
disabled={isViewMode}
/>
</div>
)}
{fieldSettings.showStatus && (
<div className="space-y-2">
<Label htmlFor="status"></Label>
<Select
value={formData.status}
onValueChange={(value) => handleChange('status', value)}
disabled={isViewMode}
>
<SelectTrigger disabled={isViewMode}>
<SelectValue placeholder="상태 선택" />
</SelectTrigger>
<SelectContent>
{Object.entries(EMPLOYEE_STATUS_LABELS).map(([value, label]) => (
<SelectItem key={value} value={value}>{label}</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div>
{/* 부서/직책 */}
{(fieldSettings.showDepartment || fieldSettings.showPosition) && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label>/</Label>
{!isViewMode && (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAddDepartmentPosition}
>
<Plus className="w-4 h-4 mr-1" />
</Button>
)}
</div>
{formData.departmentPositions.length === 0 ? (
<p className="text-sm text-muted-foreground py-4 text-center border rounded-md">
{isViewMode ? '등록된 부서/직책이 없습니다' : '부서/직책을 추가해주세요'}
</p>
) : (
<div className="space-y-2">
{formData.departmentPositions.map((dp) => (
<div key={dp.id} className="flex items-center gap-2">
<Input
value={dp.departmentName}
onChange={(e) => handleDepartmentPositionChange(dp.id, 'departmentName', e.target.value)}
placeholder="부서명"
className="flex-1"
disabled={isViewMode}
/>
<Input
value={dp.positionName}
onChange={(e) => handleDepartmentPositionChange(dp.id, 'positionName', e.target.value)}
placeholder="직책"
className="flex-1"
disabled={isViewMode}
/>
{!isViewMode && (
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => handleRemoveDepartmentPosition(dp.id)}
>
<Trash2 className="w-4 h-4 text-destructive" />
</Button>
)}
</div>
))}
</div>
)}
</div>
)}
{/* 출근/퇴근 위치 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{fieldSettings.showClockInLocation && (
<div className="space-y-2">
<Label htmlFor="clockInLocation"> </Label>
<Select
value={formData.clockInLocation}
onValueChange={(value) => handleChange('clockInLocation', value)}
disabled={isViewMode}
>
<SelectTrigger disabled={isViewMode}>
<SelectValue placeholder="출근 위치 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="headquarters"></SelectItem>
<SelectItem value="branch1">1</SelectItem>
<SelectItem value="branch2">2</SelectItem>
<SelectItem value="remote"></SelectItem>
</SelectContent>
</Select>
</div>
)}
{fieldSettings.showClockOutLocation && (
<div className="space-y-2">
<Label htmlFor="clockOutLocation"> </Label>
<Select
value={formData.clockOutLocation}
onValueChange={(value) => handleChange('clockOutLocation', value)}
disabled={isViewMode}
>
<SelectTrigger disabled={isViewMode}>
<SelectValue placeholder="퇴근 위치 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="headquarters"></SelectItem>
<SelectItem value="branch1">1</SelectItem>
<SelectItem value="branch2">2</SelectItem>
<SelectItem value="remote"></SelectItem>
</SelectContent>
</Select>
</div>
)}
</div>
{/* 퇴사일/퇴직사유 */}
{(fieldSettings.showResignationDate || fieldSettings.showResignationReason) && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{fieldSettings.showResignationDate && (
<div className="space-y-2">
<Label htmlFor="resignationDate"></Label>
<Input
id="resignationDate"
type="date"
value={formData.resignationDate}
onChange={(e) => handleChange('resignationDate', e.target.value)}
disabled={isViewMode}
/>
</div>
)}
{fieldSettings.showResignationReason && (
<div className="space-y-2">
<Label htmlFor="resignationReason"></Label>
<Input
id="resignationReason"
value={formData.resignationReason}
onChange={(e) => handleChange('resignationReason', e.target.value)}
placeholder="퇴직 사유를 입력하세요"
disabled={isViewMode}
/>
</div>
)}
</div>
)}
</CardContent>
</Card>
)}
{/* 사용자 정보 */}
<Card>
<CardHeader className="bg-black text-white rounded-t-lg">
<CardTitle className="text-base font-medium"> </CardTitle>
</CardHeader>
<CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="userId"> *</Label>
<Input
id="userId"
value={formData.userId}
onChange={(e) => handleChange('userId', e.target.value)}
placeholder="사용자 아이디"
disabled={isViewMode}
className={errors.userId ? 'border-red-500' : ''}
/>
{errors.userId && <p className="text-sm text-red-500">{errors.userId}</p>}
</div>
{mode === 'create' && (
<>
<div className="space-y-2">
<Label htmlFor="password"> *</Label>
<Input
id="password"
type="password"
value={formData.password}
onChange={(e) => handleChange('password', e.target.value)}
placeholder="비밀번호"
className={errors.password ? 'border-red-500' : ''}
/>
{errors.password && <p className="text-sm text-red-500">{errors.password}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword"> *</Label>
<Input
id="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={(e) => handleChange('confirmPassword', e.target.value)}
placeholder="비밀번호 확인"
className={errors.confirmPassword ? 'border-red-500' : ''}
/>
{errors.confirmPassword && <p className="text-sm text-red-500">{errors.confirmPassword}</p>}
</div>
</>
)}
<div className="space-y-2">
<Label htmlFor="role"></Label>
<Select
value={formData.role}
onValueChange={(value) => handleChange('role', value)}
disabled={isViewMode}
>
<SelectTrigger disabled={isViewMode}>
<SelectValue placeholder="권한 선택" />
</SelectTrigger>
<SelectContent>
{Object.entries(USER_ROLE_LABELS).map(([value, label]) => (
<SelectItem key={value} value={value}>{label}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="accountStatus"></Label>
<Select
value={formData.accountStatus}
onValueChange={(value) => handleChange('accountStatus', value)}
disabled={isViewMode}
>
<SelectTrigger disabled={isViewMode}>
<SelectValue placeholder="상태 선택" />
</SelectTrigger>
<SelectContent>
{Object.entries(USER_ACCOUNT_STATUS_LABELS).map(([value, label]) => (
<SelectItem key={value} value={value}>{label}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
{/* 버튼 영역 */}
<div className="flex items-center justify-between">
<Button type="button" variant="outline" onClick={handleCancel}>
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
{isViewMode ? (
<div className="flex gap-2">
<Button type="button" onClick={onEdit}>
<Edit className="w-4 h-4 mr-2" />
</Button>
<Button type="button" variant="destructive" onClick={onDelete}>
<Trash2 className="w-4 h-4 mr-2" />
</Button>
</div>
) : (
<Button type="submit">
<Save className="w-4 h-4 mr-2" />
{mode === 'create' ? '등록' : '저장'}
</Button>
)}
</div>
</form>
{/* 항목 설정 모달 */}
<FieldSettingsDialog
open={showFieldSettings}
onOpenChange={setShowFieldSettings}
settings={fieldSettings}
onSave={handleSaveFieldSettings}
/>
</PageLayout>
);
}