628 lines
23 KiB
TypeScript
628 lines
23 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState, useEffect, useRef } from 'react';
|
||
|
|
import { useRouter } from 'next/navigation';
|
||
|
|
import Image from 'next/image';
|
||
|
|
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 { Switch } from '@/components/ui/switch';
|
||
|
|
import { Users, Plus, Trash2, ArrowLeft, Save, Camera, User } from 'lucide-react';
|
||
|
|
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';
|
||
|
|
|
||
|
|
interface EmployeeFormProps {
|
||
|
|
mode: 'create' | 'edit';
|
||
|
|
employee?: Employee;
|
||
|
|
onSave: (data: EmployeeFormData) => void;
|
||
|
|
fieldSettings?: FieldSettings;
|
||
|
|
}
|
||
|
|
|
||
|
|
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: [],
|
||
|
|
hasUserAccount: false,
|
||
|
|
userId: '',
|
||
|
|
password: '',
|
||
|
|
confirmPassword: '',
|
||
|
|
role: 'user',
|
||
|
|
accountStatus: 'active',
|
||
|
|
};
|
||
|
|
|
||
|
|
export function EmployeeForm({
|
||
|
|
mode,
|
||
|
|
employee,
|
||
|
|
onSave,
|
||
|
|
fieldSettings = DEFAULT_FIELD_SETTINGS,
|
||
|
|
}: EmployeeFormProps) {
|
||
|
|
const router = useRouter();
|
||
|
|
const [formData, setFormData] = useState<EmployeeFormData>(initialFormData);
|
||
|
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||
|
|
|
||
|
|
const title = mode === 'create' ? '사원 등록' : '사원 수정';
|
||
|
|
|
||
|
|
// 데이터 초기화
|
||
|
|
useEffect(() => {
|
||
|
|
if (employee && mode === 'edit') {
|
||
|
|
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 || [],
|
||
|
|
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 }));
|
||
|
|
};
|
||
|
|
|
||
|
|
// 부서/직책 추가
|
||
|
|
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();
|
||
|
|
onSave(formData);
|
||
|
|
};
|
||
|
|
|
||
|
|
// 취소
|
||
|
|
const handleCancel = () => {
|
||
|
|
router.back();
|
||
|
|
};
|
||
|
|
|
||
|
|
// 프로필 이미지 업로드 핸들러
|
||
|
|
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
|
|
const file = e.target.files?.[0];
|
||
|
|
if (file) {
|
||
|
|
const reader = new FileReader();
|
||
|
|
reader.onloadend = () => {
|
||
|
|
setPreviewImage(reader.result as string);
|
||
|
|
handleChange('profileImage', reader.result as string);
|
||
|
|
};
|
||
|
|
reader.readAsDataURL(file);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// 프로필 이미지 삭제 핸들러
|
||
|
|
const handleRemoveImage = () => {
|
||
|
|
setPreviewImage(null);
|
||
|
|
handleChange('profileImage', '');
|
||
|
|
if (fileInputRef.current) {
|
||
|
|
fileInputRef.current.value = '';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<PageLayout>
|
||
|
|
<PageHeader
|
||
|
|
title={title}
|
||
|
|
description={mode === 'create' ? '새로운 사원 정보를 입력합니다' : '사원 정보를 수정합니다'}
|
||
|
|
icon={Users}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<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="flex flex-col md:flex-row gap-6">
|
||
|
|
{/* 프로필 사진 영역 */}
|
||
|
|
{fieldSettings.showProfileImage && (
|
||
|
|
<div className="flex flex-col items-center gap-3">
|
||
|
|
<div className="relative w-32 h-32 rounded-full border-2 border-dashed border-gray-300 bg-gray-50 flex items-center justify-center overflow-hidden">
|
||
|
|
{previewImage || formData.profileImage ? (
|
||
|
|
<Image
|
||
|
|
src={previewImage || formData.profileImage}
|
||
|
|
alt="프로필 사진"
|
||
|
|
fill
|
||
|
|
className="object-cover"
|
||
|
|
/>
|
||
|
|
) : (
|
||
|
|
<User className="w-12 h-12 text-gray-400" />
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<div className="flex gap-2">
|
||
|
|
<input
|
||
|
|
ref={fileInputRef}
|
||
|
|
type="file"
|
||
|
|
accept="image/*"
|
||
|
|
onChange={handleImageUpload}
|
||
|
|
className="hidden"
|
||
|
|
id="profile-image-input"
|
||
|
|
/>
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="outline"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => fileInputRef.current?.click()}
|
||
|
|
>
|
||
|
|
<Camera className="w-4 h-4 mr-1" />
|
||
|
|
사진 등록
|
||
|
|
</Button>
|
||
|
|
{(previewImage || formData.profileImage) && (
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={handleRemoveImage}
|
||
|
|
>
|
||
|
|
<Trash2 className="w-4 h-4" />
|
||
|
|
</Button>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 기본 정보 필드들 */}
|
||
|
|
<div className="flex-1 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="이름을 입력하세요"
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
</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"
|
||
|
|
/>
|
||
|
|
</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"
|
||
|
|
/>
|
||
|
|
</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"
|
||
|
|
/>
|
||
|
|
</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="연봉 (원)"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</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="은행명"
|
||
|
|
/>
|
||
|
|
<Input
|
||
|
|
value={formData.bankAccount.accountNumber}
|
||
|
|
onChange={(e) => handleChange('bankAccount', { ...formData.bankAccount, accountNumber: e.target.value })}
|
||
|
|
placeholder="계좌번호"
|
||
|
|
/>
|
||
|
|
<Input
|
||
|
|
value={formData.bankAccount.accountHolder}
|
||
|
|
onChange={(e) => handleChange('bankAccount', { ...formData.bankAccount, accountHolder: e.target.value })}
|
||
|
|
placeholder="예금주"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* 선택 정보 (사원 상세) */}
|
||
|
|
{(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="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="자동생성 또는 직접입력"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{fieldSettings.showGender && (
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="gender">성별</Label>
|
||
|
|
<Select
|
||
|
|
value={formData.gender}
|
||
|
|
onValueChange={(value) => handleChange('gender', value)}
|
||
|
|
>
|
||
|
|
<SelectTrigger>
|
||
|
|
<SelectValue placeholder="성별 선택" />
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
{Object.entries(GENDER_LABELS).map(([value, label]) => (
|
||
|
|
<SelectItem key={value} value={value}>{label}</SelectItem>
|
||
|
|
))}
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{fieldSettings.showAddress && (
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label>주소</Label>
|
||
|
|
<div className="flex gap-2">
|
||
|
|
<Input
|
||
|
|
value={formData.address.zipCode}
|
||
|
|
onChange={(e) => handleChange('address', { ...formData.address, zipCode: e.target.value })}
|
||
|
|
placeholder="우편번호"
|
||
|
|
className="w-32"
|
||
|
|
/>
|
||
|
|
<Button type="button" variant="outline" size="sm">
|
||
|
|
우편번호 찾기
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
<Input
|
||
|
|
value={formData.address.address1}
|
||
|
|
onChange={(e) => handleChange('address', { ...formData.address, address1: e.target.value })}
|
||
|
|
placeholder="기본주소"
|
||
|
|
/>
|
||
|
|
<Input
|
||
|
|
value={formData.address.address2}
|
||
|
|
onChange={(e) => handleChange('address', { ...formData.address, address2: e.target.value })}
|
||
|
|
placeholder="상세주소"
|
||
|
|
/>
|
||
|
|
</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 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)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{fieldSettings.showEmploymentType && (
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="employmentType">고용형태</Label>
|
||
|
|
<Select
|
||
|
|
value={formData.employmentType}
|
||
|
|
onValueChange={(value) => handleChange('employmentType', value)}
|
||
|
|
>
|
||
|
|
<SelectTrigger>
|
||
|
|
<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="직급 입력"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{fieldSettings.showStatus && (
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="status">상태</Label>
|
||
|
|
<Select
|
||
|
|
value={formData.status}
|
||
|
|
onValueChange={(value) => handleChange('status', value)}
|
||
|
|
>
|
||
|
|
<SelectTrigger>
|
||
|
|
<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>
|
||
|
|
<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">
|
||
|
|
부서/직책을 추가해주세요
|
||
|
|
</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"
|
||
|
|
/>
|
||
|
|
<Input
|
||
|
|
value={dp.positionName}
|
||
|
|
onChange={(e) => handleDepartmentPositionChange(dp.id, 'positionName', e.target.value)}
|
||
|
|
placeholder="직책"
|
||
|
|
className="flex-1"
|
||
|
|
/>
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => handleRemoveDepartmentPosition(dp.id)}
|
||
|
|
>
|
||
|
|
<Trash2 className="w-4 h-4 text-destructive" />
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* 사용자 정보 */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader className="bg-black text-white rounded-t-lg">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<CardTitle className="text-base font-medium">사용자 정보</CardTitle>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<Switch
|
||
|
|
id="hasUserAccount"
|
||
|
|
checked={formData.hasUserAccount}
|
||
|
|
onCheckedChange={(checked) => handleChange('hasUserAccount', checked)}
|
||
|
|
className="data-[state=checked]:bg-white data-[state=checked]:text-black"
|
||
|
|
/>
|
||
|
|
<Label htmlFor="hasUserAccount" className="text-sm font-normal text-white">
|
||
|
|
사용자 계정 생성
|
||
|
|
</Label>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</CardHeader>
|
||
|
|
{formData.hasUserAccount && (
|
||
|
|
<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="사용자 아이디"
|
||
|
|
required={formData.hasUserAccount}
|
||
|
|
/>
|
||
|
|
</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="비밀번호"
|
||
|
|
required={formData.hasUserAccount}
|
||
|
|
/>
|
||
|
|
</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="비밀번호 확인"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="role">권한</Label>
|
||
|
|
<Select
|
||
|
|
value={formData.role}
|
||
|
|
onValueChange={(value) => handleChange('role', value)}
|
||
|
|
>
|
||
|
|
<SelectTrigger>
|
||
|
|
<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)}
|
||
|
|
>
|
||
|
|
<SelectTrigger>
|
||
|
|
<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>
|
||
|
|
<Button type="submit">
|
||
|
|
<Save className="w-4 h-4 mr-2" />
|
||
|
|
{mode === 'create' ? '등록' : '저장'}
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</form>
|
||
|
|
</PageLayout>
|
||
|
|
);
|
||
|
|
}
|