diff --git a/backend/src/main/java/com/srsystem/controller/EmployeeController.java b/backend/src/main/java/com/srsystem/controller/EmployeeController.java index 8e2dfc1..fea4ca3 100644 --- a/backend/src/main/java/com/srsystem/controller/EmployeeController.java +++ b/backend/src/main/java/com/srsystem/controller/EmployeeController.java @@ -257,6 +257,15 @@ public class EmployeeController { return ResponseEntity.ok(ApiResponse.ok("비밀번호가 변경되었습니다", null)); } + @Operation(summary = "비밀번호 초기화", description = "관리자가 직원 비밀번호를 초기화합니다 (ADMIN 전용)") + @PreAuthorize("hasRole('ADMIN')") + @PutMapping("/{empId}/reset-password") + public ResponseEntity> resetPassword( + @PathVariable Long empId, @RequestBody Map body) { + employeeService.resetPassword(empId, body.get("newPassword")); + return ResponseEntity.ok(ApiResponse.ok("비밀번호가 초기화되었습니다", null)); + } + // ---- Batch ---- @Operation(summary = "직원 일괄 등록", description = "엑셀 파일로 직원을 일괄 등록합니다") @@ -279,6 +288,7 @@ public class EmployeeController { emp.setPassword("batch1234!"); if (row.containsKey("생년월일")) emp.setBirthDate(java.time.LocalDate.parse(row.get("생년월일"))); if (row.containsKey("학력")) emp.setEducation(row.get("학력")); + if (row.containsKey("학교명")) emp.setSchoolName(row.get("학교명")); if (row.containsKey("학과")) emp.setMajor(row.get("학과")); if (row.containsKey("입사일")) emp.setJoinedDate(java.time.LocalDate.parse(row.get("입사일"))); return emp; @@ -318,7 +328,7 @@ public class EmployeeController { @PreAuthorize("hasRole('ADMIN')") @GetMapping("/batch/template") public ResponseEntity downloadTemplate() throws Exception { - String[] headers = {"로그인ID", "이름", "이메일", "전화번호", "권한", "재직상태", "파견상태", "생년월일", "학력", "학과", "입사일"}; + String[] headers = {"로그인ID", "이름", "이메일", "전화번호", "권한", "재직상태", "파견상태", "생년월일", "학력", "학교명", "학과", "입사일"}; Map dropdowns = Map.of( 4, new String[]{"ADMIN", "MANAGER", "EMPLOYEE"}, 5, new String[]{"ACTIVE", "ON_LEAVE", "RESIGNED"}, diff --git a/backend/src/main/java/com/srsystem/dto/EmployeeSummary.java b/backend/src/main/java/com/srsystem/dto/EmployeeSummary.java index c34c07b..ca05009 100644 --- a/backend/src/main/java/com/srsystem/dto/EmployeeSummary.java +++ b/backend/src/main/java/com/srsystem/dto/EmployeeSummary.java @@ -21,5 +21,6 @@ public record EmployeeSummary( String major, LocalDate graduationDate, String residence, - String profileImage + String profileImage, + String schoolName ) {} diff --git a/backend/src/main/java/com/srsystem/entity/Employee.java b/backend/src/main/java/com/srsystem/entity/Employee.java index c3c276d..ae84409 100644 --- a/backend/src/main/java/com/srsystem/entity/Employee.java +++ b/backend/src/main/java/com/srsystem/entity/Employee.java @@ -74,6 +74,9 @@ public class Employee extends BaseEntity { @Column(name = "major", length = 100) private String major; + @Column(name = "school_name", length = 200) + private String schoolName; + @Column(name = "graduation_date") private LocalDate graduationDate; diff --git a/backend/src/main/java/com/srsystem/service/EmployeeService.java b/backend/src/main/java/com/srsystem/service/EmployeeService.java index c578a6c..5990809 100644 --- a/backend/src/main/java/com/srsystem/service/EmployeeService.java +++ b/backend/src/main/java/com/srsystem/service/EmployeeService.java @@ -113,6 +113,7 @@ public class EmployeeService { employee.setGraduationDate(updateRequest.getGraduationDate()); employee.setResidence(updateRequest.getResidence()); employee.setProfileImage(updateRequest.getProfileImage()); + employee.setSchoolName(updateRequest.getSchoolName()); return employee; } @@ -144,6 +145,13 @@ public class EmployeeService { return employeeRepository.save(employee); } + @Transactional + public void resetPassword(Long empId, String newPassword) { + Employee employee = findById(empId); + employee.setPassword(passwordEncoder.encode(newPassword)); + employeeRepository.save(employee); + } + @Transactional public void delete(Long empId) { Employee employee = findById(empId); @@ -171,7 +179,8 @@ public class EmployeeService { employee.getMajor(), employee.getGraduationDate(), employee.getResidence(), - employee.getProfileImage() + employee.getProfileImage(), + employee.getSchoolName() ); } diff --git a/backend/src/main/resources/db/migration/V15__add_school_name.sql b/backend/src/main/resources/db/migration/V15__add_school_name.sql new file mode 100644 index 0000000..495972a --- /dev/null +++ b/backend/src/main/resources/db/migration/V15__add_school_name.sql @@ -0,0 +1 @@ +ALTER TABLE tb_employee ADD COLUMN IF NOT EXISTS school_name VARCHAR(200); diff --git a/src/app/(main)/employees/page.tsx b/src/app/(main)/employees/page.tsx index 4171b07..41b618a 100644 --- a/src/app/(main)/employees/page.tsx +++ b/src/app/(main)/employees/page.tsx @@ -238,7 +238,7 @@ export default function EmployeesPage() { {emp.education && (
- {educationLabel[emp.education] || emp.education}{emp.major ? ` · ${emp.major}` : ''} + {educationLabel[emp.education] || emp.education}{emp.schoolName ? ` · ${emp.schoolName}` : ''}{emp.major ? ` · ${emp.major}` : ''}
)} {emp.graduationDate && ( diff --git a/src/app/(main)/profile/page.tsx b/src/app/(main)/profile/page.tsx index be5f574..62bf6a0 100644 --- a/src/app/(main)/profile/page.tsx +++ b/src/app/(main)/profile/page.tsx @@ -379,6 +379,14 @@ export default function ProfilePage() {

{employee.education ? (EDUCATION_LABELS[employee.education] || employee.education) : '-'}

+
+
+ + 학교 +
+

{employee.schoolName || '-'}

+
+
diff --git a/src/app/(main)/settings/accounts/page.tsx b/src/app/(main)/settings/accounts/page.tsx index 428d788..ef330ea 100644 --- a/src/app/(main)/settings/accounts/page.tsx +++ b/src/app/(main)/settings/accounts/page.tsx @@ -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({ ...emptyCreateForm }); const [editForm, setEditForm] = useState({ 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(null); const [editError, setEditError] = useState(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() { 생년월일 학력 거주지 - 가입일 + 입사일 액션 @@ -380,7 +386,7 @@ export default function AccountsPage() {
- + setCreateForm((f) => ({ ...f, joinedDate: e.target.value }))} className={fieldClass} />
@@ -406,6 +412,10 @@ export default function AccountsPage() { +
+ + setCreateForm((f) => ({ ...f, schoolName: e.target.value }))} className={fieldClass} placeholder="서울대학교" /> +
setCreateForm((f) => ({ ...f, major: e.target.value }))} className={fieldClass} placeholder="컴퓨터공학" /> @@ -485,7 +495,7 @@ export default function AccountsPage() {
- + setEditForm((f) => ({ ...f, joinedDate: e.target.value }))} className={fieldClass} />
@@ -556,6 +566,10 @@ export default function AccountsPage() {
+
+ + setEditForm((f) => ({ ...f, schoolName: e.target.value }))} className={fieldClass} placeholder="서울대학교" /> +
setEditForm((f) => ({ ...f, major: e.target.value }))} className={fieldClass} placeholder="컴퓨터공학" /> @@ -571,6 +585,15 @@ export default function AccountsPage() {
+ {/* Password Reset */} +
+

비밀번호

+ +
+ {editError && (
{editError} @@ -589,6 +612,35 @@ export default function AccountsPage() {
)} + {/* Password Reset Dialog */} + {showPasswordReset && ( +
setShowPasswordReset(false)}> +
e.stopPropagation()}> +

비밀번호 초기화

+

새 비밀번호를 입력하세요. 초기화 후 직원에게 전달해주세요.

+ 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="새 비밀번호 입력" /> +
+ + +
+
+
+ )} + {/* Delete Confirmation */} {showDeleteDialog && targetEmployee && (
setShowDeleteDialog(false)}> diff --git a/src/services/employeeService.ts b/src/services/employeeService.ts index 2f49620..f000b69 100644 --- a/src/services/employeeService.ts +++ b/src/services/employeeService.ts @@ -68,6 +68,10 @@ const employeeService = { return apiClient.put>('/employees/me/password', { currentPassword, newPassword }); }, + resetPassword(empId: number, newPassword: string) { + return apiClient.put>(`/employees/${empId}/reset-password`, { newPassword }); + }, + getEmergencyContacts(empId: number) { return apiClient.get>(`/employees/${empId}/emergency-contacts`); }, diff --git a/src/types/index.ts b/src/types/index.ts index 01b4931..f6c3377 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -57,6 +57,7 @@ export interface Employee { leftDate: string | null; birthDate: string | null; education: string | null; + schoolName: string | null; major: string | null; graduationDate: string | null; residence: string | null;