feat: schoolName field, 입사일 label, password reset for admin

This commit is contained in:
pray4skylark
2026-06-08 09:05:32 +09:00
parent 40415fc6dc
commit c0969bcd90
10 changed files with 98 additions and 9 deletions
+1 -1
View File
@@ -238,7 +238,7 @@ export default function EmployeesPage() {
{emp.education && (
<div className="flex items-center gap-2 text-text-muted">
<GraduationCap className="w-3.5 h-3.5 flex-shrink-0" />
<span>{educationLabel[emp.education] || emp.education}{emp.major ? ` · ${emp.major}` : ''}</span>
<span>{educationLabel[emp.education] || emp.education}{emp.schoolName ? ` · ${emp.schoolName}` : ''}{emp.major ? ` · ${emp.major}` : ''}</span>
</div>
)}
{emp.graduationDate && (
+8
View File
@@ -379,6 +379,14 @@ export default function ProfilePage() {
<p className="text-text-main font-medium">{employee.education ? (EDUCATION_LABELS[employee.education] || employee.education) : '-'}</p>
</div>
<div>
<div className="flex items-center gap-2 text-text-muted text-xs mb-1">
<GraduationCap className="w-4 h-4" />
<span>학교</span>
</div>
<p className="text-text-main font-medium">{employee.schoolName || '-'}</p>
</div>
<div>
<div className="flex items-center gap-2 text-text-muted text-xs mb-1">
<GraduationCap className="w-4 h-4" />
+57 -5
View File
@@ -55,6 +55,7 @@ interface CreateForm {
joinedDate: string;
birthDate: string;
education: string;
schoolName: string;
major: string;
graduationDate: string;
residence: string;
@@ -70,6 +71,7 @@ interface EditForm {
leftDate: string;
birthDate: string;
education: string;
schoolName: string;
major: string;
graduationDate: string;
residence: string;
@@ -78,7 +80,7 @@ interface EditForm {
const emptyCreateForm: CreateForm = {
loginId: '', name: '', password: '', email: '', phone: '',
roleCode: 'EMPLOYEE', empStatus: 'ACTIVE', joinedDate: '',
birthDate: '', education: '', major: '', graduationDate: '', residence: '',
birthDate: '', education: '', schoolName: '', major: '', graduationDate: '', residence: '',
};
export default function AccountsPage() {
@@ -93,11 +95,14 @@ export default function AccountsPage() {
const [createForm, setCreateForm] = useState<CreateForm>({ ...emptyCreateForm });
const [editForm, setEditForm] = useState<EditForm>({
name: '', email: '', phone: '', roleCode: 'EMPLOYEE', empStatus: 'ACTIVE',
joinedDate: '', leftDate: '', birthDate: '', education: '', major: '', graduationDate: '', residence: '',
joinedDate: '', leftDate: '', birthDate: '', education: '', schoolName: '', major: '', graduationDate: '', residence: '',
});
const [createError, setCreateError] = useState<string | null>(null);
const [editError, setEditError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [showPasswordReset, setShowPasswordReset] = useState(false);
const [newPassword, setNewPassword] = useState('');
const [resetSubmitting, setResetSubmitting] = useState(false);
const fetchEmployees = async () => {
try {
@@ -168,6 +173,7 @@ export default function AccountsPage() {
leftDate: emp.leftDate || '',
birthDate: emp.birthDate || '',
education: emp.education || '',
schoolName: emp.schoolName || '',
major: emp.major || '',
graduationDate: emp.graduationDate || '',
residence: emp.residence || '',
@@ -264,7 +270,7 @@ export default function AccountsPage() {
<th className="px-4 sm:px-6 py-3 hidden lg:table-cell">생년월일</th>
<th className="px-4 sm:px-6 py-3 hidden lg:table-cell">학력</th>
<th className="px-4 sm:px-6 py-3 hidden xl:table-cell">거주지</th>
<th className="px-4 sm:px-6 py-3 hidden sm:table-cell">가입일</th>
<th className="px-4 sm:px-6 py-3 hidden sm:table-cell">입사일</th>
<th className="px-4 sm:px-6 py-3 text-right">액션</th>
</tr>
</thead>
@@ -380,7 +386,7 @@ export default function AccountsPage() {
</div>
</div>
<div>
<label className={labelClass}>가입일</label>
<label className={labelClass}>입사일</label>
<input type="date" value={createForm.joinedDate} onChange={(e) => setCreateForm((f) => ({ ...f, joinedDate: e.target.value }))} className={fieldClass} />
</div>
@@ -406,6 +412,10 @@ export default function AccountsPage() {
</select>
</div>
</div>
<div>
<label className={labelClass}>학교명</label>
<input value={createForm.schoolName} onChange={(e) => setCreateForm((f) => ({ ...f, schoolName: e.target.value }))} className={fieldClass} placeholder="서울대학교" />
</div>
<div>
<label className={labelClass}>학과</label>
<input value={createForm.major} onChange={(e) => setCreateForm((f) => ({ ...f, major: e.target.value }))} className={fieldClass} placeholder="컴퓨터공학" />
@@ -485,7 +495,7 @@ export default function AccountsPage() {
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className={labelClass}>가입일</label>
<label className={labelClass}>입사일</label>
<input type="date" value={editForm.joinedDate} onChange={(e) => setEditForm((f) => ({ ...f, joinedDate: e.target.value }))} className={fieldClass} />
</div>
<div>
@@ -556,6 +566,10 @@ export default function AccountsPage() {
</select>
</div>
</div>
<div>
<label className={labelClass}>학교명</label>
<input value={editForm.schoolName} onChange={(e) => setEditForm((f) => ({ ...f, schoolName: e.target.value }))} className={fieldClass} placeholder="서울대학교" />
</div>
<div>
<label className={labelClass}>학과</label>
<input value={editForm.major} onChange={(e) => setEditForm((f) => ({ ...f, major: e.target.value }))} className={fieldClass} placeholder="컴퓨터공학" />
@@ -571,6 +585,15 @@ export default function AccountsPage() {
</div>
</div>
{/* Password Reset */}
<div className="border-t border-border pt-3 mt-3">
<p className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-3">비밀번호</p>
<button type="button" onClick={() => setShowPasswordReset(true)}
className="flex items-center gap-2 px-4 py-2 rounded-xl border border-border text-sm font-medium text-text-muted hover:bg-gray-50 transition-colors">
<span>🔑</span> 비밀번호 초기화
</button>
</div>
{editError && (
<div className="bg-status-critical-bg border border-status-critical text-status-critical-text rounded-lg px-3 py-2 text-sm">
{editError}
@@ -589,6 +612,35 @@ export default function AccountsPage() {
</div>
)}
{/* Password Reset Dialog */}
{showPasswordReset && (
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm" onClick={() => setShowPasswordReset(false)}>
<div className="bg-white rounded-2xl shadow-xl w-full max-w-sm mx-4 p-6" onClick={(e) => e.stopPropagation()}>
<h3 className="text-lg font-bold text-text-main mb-1">비밀번호 초기화</h3>
<p className="text-sm text-text-muted mb-4">새 비밀번호를 입력하세요. 초기화 후 직원에게 전달해주세요.</p>
<input type="text" value={newPassword} onChange={(e) => setNewPassword(e.target.value)}
className="w-full px-3 py-2 border border-border rounded-lg text-sm mb-4 focus:outline-none focus:ring-2 focus:ring-primary" placeholder="새 비밀번호 입력" />
<div className="flex gap-3">
<button onClick={() => { setShowPasswordReset(false); setNewPassword(''); }}
className="flex-1 px-4 py-2 rounded-xl border border-border text-sm font-medium text-text-muted hover:bg-gray-100">취소</button>
<button onClick={async () => {
if (!newPassword.trim()) return;
setResetSubmitting(true);
try {
await employeeService.resetPassword(targetEmployee!.empId, newPassword);
alert('비밀번호가 초기화되었습니다.');
setShowPasswordReset(false);
setNewPassword('');
} catch (err: any) {
alert(err.response?.data?.message || '초기화 실패');
} finally { setResetSubmitting(false); }
}} disabled={resetSubmitting || !newPassword.trim()}
className="flex-1 px-4 py-2 rounded-xl bg-primary text-white text-sm font-semibold hover:bg-primary-hover disabled:opacity-50">초기화</button>
</div>
</div>
</div>
)}
{/* Delete Confirmation */}
{showDeleteDialog && targetEmployee && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm" onClick={() => setShowDeleteDialog(false)}>