diff --git a/backend-node/src/controllers/adminController.ts b/backend-node/src/controllers/adminController.ts index 667637c8..0ec055ba 100644 --- a/backend-node/src/controllers/adminController.ts +++ b/backend-node/src/controllers/adminController.ts @@ -1739,6 +1739,7 @@ export const getUserInfo = async (req: AuthenticatedRequest, res: Response) => { u.fax_no, u.partner_objid, u.rank, + u.photo, u.locale, u.company_code, u.data_type, @@ -1791,6 +1792,9 @@ export const getUserInfo = async (req: AuthenticatedRequest, res: Response) => { faxNo: user.fax_no, partnerObjid: user.partner_objid, rank: user.rank, + photo: user.photo + ? `data:image/jpeg;base64,${user.photo.toString("base64")}` + : null, locale: user.locale, companyCode: user.company_code, dataType: user.data_type, @@ -2724,7 +2728,24 @@ export const updateProfile = async ( if (email !== undefined) updateData.email = email; if (tel !== undefined) updateData.tel = tel; if (cellPhone !== undefined) updateData.cell_phone = cellPhone; - if (photo !== undefined) updateData.photo = photo; + + // photo 데이터 처리 (Base64를 Buffer로 변환하여 저장) + if (photo !== undefined) { + if (photo && typeof photo === "string") { + try { + // Base64 헤더 제거 (data:image/jpeg;base64, 등) + const base64Data = photo.replace(/^data:image\/[a-z]+;base64,/, ""); + // Base64를 Buffer로 변환 + updateData.photo = Buffer.from(base64Data, "base64"); + } catch (error) { + console.error("Base64 이미지 처리 오류:", error); + updateData.photo = null; + } + } else { + updateData.photo = null; // 빈 값이면 null로 설정 + } + } + if (locale !== undefined) updateData.locale = locale; // 업데이트할 데이터가 없으면 에러 @@ -2767,10 +2788,18 @@ export const updateProfile = async ( }, }); + // photo가 Buffer 타입인 경우 Base64로 변환 + const responseData = { + ...updatedUser, + photo: updatedUser?.photo + ? `data:image/jpeg;base64,${updatedUser.photo.toString("base64")}` + : null, + }; + res.json({ result: true, message: "프로필이 성공적으로 업데이트되었습니다.", - data: updatedUser, + data: responseData, }); } catch (error) { console.error("프로필 업데이트 오류:", error); diff --git a/backend-node/src/controllers/authController.ts b/backend-node/src/controllers/authController.ts index c9e5b6ce..43a82f2e 100644 --- a/backend-node/src/controllers/authController.ts +++ b/backend-node/src/controllers/authController.ts @@ -166,15 +166,33 @@ export class AuthController { const userInfo = JwtUtils.verifyToken(token); + // DB에서 최신 사용자 정보 조회 (locale 포함) + const dbUserInfo = await AuthService.getUserInfo(userInfo.userId); + + if (!dbUserInfo) { + res.status(401).json({ + success: false, + message: "사용자 정보를 찾을 수 없습니다.", + error: { + code: "USER_NOT_FOUND", + details: "사용자 정보가 삭제되었거나 존재하지 않습니다.", + }, + }); + return; + } + const userInfoResponse: UserInfo = { - userId: userInfo.userId, - userName: userInfo.userName || "", - deptName: userInfo.deptName || "", - companyCode: userInfo.companyCode || "ILSHIN", - userType: userInfo.userType || "USER", - userTypeName: userInfo.userTypeName || "일반사용자", + userId: dbUserInfo.userId, + userName: dbUserInfo.userName || "", + deptName: dbUserInfo.deptName || "", + companyCode: dbUserInfo.companyCode || "ILSHIN", + userType: dbUserInfo.userType || "USER", + userTypeName: dbUserInfo.userTypeName || "일반사용자", + email: dbUserInfo.email || "", + photo: dbUserInfo.photo, + locale: dbUserInfo.locale || "KR", // locale 정보 추가 isAdmin: - userInfo.userType === "ADMIN" || userInfo.userId === "plm_admin", + dbUserInfo.userType === "ADMIN" || dbUserInfo.userId === "plm_admin", }; res.status(200).json({ diff --git a/backend-node/src/services/authService.ts b/backend-node/src/services/authService.ts index 5aafc132..a7e32d5c 100644 --- a/backend-node/src/services/authService.ts +++ b/backend-node/src/services/authService.ts @@ -146,6 +146,8 @@ export class AuthService { user_type_name: true, partner_objid: true, company_code: true, + locale: true, + photo: true, }, }); @@ -189,6 +191,8 @@ export class AuthService { partnerObjid: userInfo.partner_objid || undefined, authName: authInfo.length > 0 ? authInfo[0].auth_name : undefined, companyCode: userInfo.company_code || "ILSHIN", + photo: userInfo.photo ? `data:image/jpeg;base64,${userInfo.photo.toString('base64')}` : undefined, + locale: userInfo.locale || "KR", }; logger.info(`사용자 정보 조회 완료: ${userId}`); diff --git a/backend-node/src/types/auth.ts b/backend-node/src/types/auth.ts index 785157f9..c1384b51 100644 --- a/backend-node/src/types/auth.ts +++ b/backend-node/src/types/auth.ts @@ -15,6 +15,9 @@ export interface UserInfo { companyCode: string; userType?: string; userTypeName?: string; + email?: string; + photo?: string; + locale?: string; isAdmin?: boolean; } @@ -47,6 +50,8 @@ export interface PersonBean { partnerObjid?: string; authName?: string; companyCode?: string; + photo?: string; + locale?: string; } // 로그인 결과 타입 (기존 LoginService.loginPwdCheck 반환값) diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index 35597eb3..9a4d6207 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -204,6 +204,7 @@ export function AppLayout({ children }: AppLayoutProps) { formData, selectedImage, isSaving, + departments, alertModal, closeAlert, openProfileModal, @@ -394,6 +395,7 @@ export function AppLayout({ children }: AppLayoutProps) { formData={formData} selectedImage={selectedImage} isSaving={isSaving} + departments={departments} alertModal={alertModal} onClose={closeProfileModal} onFormChange={updateFormData} diff --git a/frontend/components/layout/ProfileModal.tsx b/frontend/components/layout/ProfileModal.tsx index a99fd23d..d2467bac 100644 --- a/frontend/components/layout/ProfileModal.tsx +++ b/frontend/components/layout/ProfileModal.tsx @@ -53,6 +53,10 @@ interface ProfileModalProps { formData: ProfileFormData; selectedImage: string; isSaving: boolean; + departments: Array<{ + deptCode: string; + deptName: string; + }>; alertModal: { isOpen: boolean; title: string; @@ -76,6 +80,7 @@ export function ProfileModal({ formData, selectedImage, isSaving, + departments, alertModal, onClose, onFormChange, @@ -99,12 +104,14 @@ export function ProfileModal({ {selectedImage ? ( + ) : user?.photo ? ( + ) : ( {formData.userName?.substring(0, 1) || "U"} )} - {selectedImage && ( + {(selectedImage || user?.photo) && ( 부서 - onFormChange("deptName", e.target.value)} - placeholder="부서를 입력하세요" - /> + onFormChange("deptName", value)}> + + + + + {Array.isArray(departments) && departments.length > 0 ? ( + departments.map((department) => ( + + {department.deptName} + + )) + ) : ( + + 부서 정보가 없습니다 + + )} + + 직급 diff --git a/frontend/hooks/useAuth.ts b/frontend/hooks/useAuth.ts index 200ad11b..921cf21b 100644 --- a/frontend/hooks/useAuth.ts +++ b/frontend/hooks/useAuth.ts @@ -19,6 +19,7 @@ interface UserInfo { userTypeName?: string; authName?: string; partnerCd?: string; + locale?: string; isAdmin: boolean; sabun?: string; photo?: string | null; diff --git a/frontend/hooks/useProfile.ts b/frontend/hooks/useProfile.ts index adfe8d26..d723b895 100644 --- a/frontend/hooks/useProfile.ts +++ b/frontend/hooks/useProfile.ts @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import { ProfileFormData, ProfileModalState } from "@/types/profile"; import { LAYOUT_CONFIG, MESSAGES } from "@/constants/layout"; import { apiCall } from "@/lib/api/client"; @@ -40,6 +40,14 @@ export const useProfile = (user: any, refreshUserData: () => Promise) => { type: "info", }); + // 부서 목록 상태 + const [departments, setDepartments] = useState< + Array<{ + deptCode: string; + deptName: string; + }> + >([]); + // 알림 모달 표시 함수 const showAlert = useCallback((title: string, message: string, type: "success" | "error" | "info" = "info") => { setAlertModal({ @@ -55,11 +63,33 @@ export const useProfile = (user: any, refreshUserData: () => Promise) => { setAlertModal((prev) => ({ ...prev, isOpen: false })); }, []); + // 부서 목록 로드 함수 + const loadDepartments = useCallback(async () => { + try { + const response = await apiCall("GET", "/admin/departments"); + if (response.success && response.data) { + setDepartments(response.data); + } + } catch (error) { + console.error("부서 목록 로드 실패:", error); + } + }, []); + /** * 프로필 모달 열기 */ const openProfileModal = useCallback(() => { if (user) { + console.log("🔍 프로필 모달 열기 - 사용자 정보:", { + userName: user.userName, + email: user.email, + deptName: user.deptName, + locale: user.locale, + }); + + // 부서 목록 로드 + loadDepartments(); + setModalState((prev) => ({ ...prev, isOpen: true, @@ -68,14 +98,14 @@ export const useProfile = (user: any, refreshUserData: () => Promise) => { email: user.email || "", deptName: user.deptName || "", positionName: user.positionName || "", - locale: user.locale || "", + locale: user.locale || "KR", // 기본값을 KR로 설정 }, selectedImage: user.photo || "", selectedFile: null, isSaving: false, })); } - }, [user]); + }, [user, loadDepartments]); /** * 프로필 모달 닫기 @@ -235,6 +265,7 @@ export const useProfile = (user: any, refreshUserData: () => Promise) => { formData: modalState.formData, selectedImage: modalState.selectedImage, isSaving: modalState.isSaving, + departments, // 알림 모달 상태 alertModal,