프로필 이미지 기능 수정
This commit is contained in:
parent
00ce90a9f0
commit
49f812f444
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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 반환값)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ interface UserInfo {
|
|||
userTypeName?: string;
|
||||
authName?: string;
|
||||
partnerCd?: string;
|
||||
locale?: string;
|
||||
isAdmin: boolean;
|
||||
sabun?: string;
|
||||
photo?: string | null;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue