프로필 이미지 기능 수정

This commit is contained in:
hyeonsu 2025-08-28 10:05:06 +09:00
parent 00ce90a9f0
commit 49f812f444
8 changed files with 128 additions and 19 deletions

View File

@ -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);

View File

@ -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({

View File

@ -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}`);

View File

@ -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 반환값)

View File

@ -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}

View File

@ -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({
<Avatar className="h-24 w-24">
{selectedImage ? (
<AvatarImage src={selectedImage} alt="프로필 사진 미리보기" />
) : user?.photo ? (
<AvatarImage src={user.photo} alt="기존 프로필 사진" />
) : (
<AvatarFallback className="text-lg">{formData.userName?.substring(0, 1) || "U"}</AvatarFallback>
)}
</Avatar>
{selectedImage && (
{(selectedImage || user?.photo) && (
<Button
type="button"
variant="destructive"
@ -171,12 +178,24 @@ export function ProfileModal({
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="deptName"></Label>
<Input
id="deptName"
value={formData.deptName}
onChange={(e) => onFormChange("deptName", e.target.value)}
placeholder="부서를 입력하세요"
/>
<Select value={formData.deptName} onValueChange={(value) => onFormChange("deptName", value)}>
<SelectTrigger>
<SelectValue placeholder="부서 선택" />
</SelectTrigger>
<SelectContent>
{Array.isArray(departments) && departments.length > 0 ? (
departments.map((department) => (
<SelectItem key={department.deptCode} value={department.deptName}>
{department.deptName}
</SelectItem>
))
) : (
<SelectItem value="no-data" disabled>
</SelectItem>
)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="positionName"></Label>

View File

@ -19,6 +19,7 @@ interface UserInfo {
userTypeName?: string;
authName?: string;
partnerCd?: string;
locale?: string;
isAdmin: boolean;
sabun?: string;
photo?: string | null;

View File

@ -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<void>) => {
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<void>) => {
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<void>) => {
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<void>) => {
formData: modalState.formData,
selectedImage: modalState.selectedImage,
isSaving: modalState.isSaving,
departments,
// 알림 모달 상태
alertModal,