ERP-node/frontend/hooks/useProfile.ts

288 lines
8.2 KiB
TypeScript

"use client";
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";
// 알림 모달 상태 타입
interface AlertModalState {
isOpen: boolean;
title: string;
message: string;
type: "success" | "error" | "info";
}
/**
* 프로필 관련 비즈니스 로직을 관리하는 커스텀 훅
*/
export const useProfile = (user: any, refreshUserData: () => Promise<void>, refreshMenus?: () => Promise<void>) => {
// 상태 관리
const [modalState, setModalState] = useState<ProfileModalState>({
isOpen: false,
formData: {
userName: "",
email: "",
deptName: "",
positionName: "",
locale: "",
},
selectedImage: "",
selectedFile: null,
isSaving: false,
});
// 알림 모달 상태
const [alertModal, setAlertModal] = useState<AlertModalState>({
isOpen: false,
title: "",
message: "",
type: "info",
});
// 부서 목록 상태
const [departments, setDepartments] = useState<
Array<{
deptCode: string;
deptName: string;
}>
>([]);
// 알림 모달 표시 함수
const showAlert = useCallback((title: string, message: string, type: "success" | "error" | "info" = "info") => {
setAlertModal({
isOpen: true,
title,
message,
type,
});
}, []);
// 알림 모달 닫기 함수
const closeAlert = useCallback(() => {
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 as Array<{ deptCode: string; deptName: string }>);
}
} 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,
formData: {
userName: user.userName || "",
email: user.email || "",
deptName: user.deptName || "",
positionName: user.positionName || "",
locale: user.locale || "KR", // 기본값을 KR로 설정
},
selectedImage: user.photo || "",
selectedFile: null,
isSaving: false,
}));
}
}, [user, loadDepartments]);
/**
* 프로필 모달 닫기
*/
const closeProfileModal = useCallback(() => {
setModalState((prev) => ({
...prev,
isOpen: false,
}));
}, []);
/**
* 프로필 폼 데이터 변경
*/
const updateFormData = useCallback((field: keyof ProfileFormData, value: string) => {
setModalState((prev) => ({
...prev,
formData: {
...prev.formData,
[field]: value,
},
}));
}, []);
/**
* 이미지 선택 처리
*/
const selectImage = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
// 파일 크기 검사
if (file.size > LAYOUT_CONFIG.FILE_UPLOAD.MAX_SIZE) {
showAlert("파일 크기 오류", MESSAGES.FILE_SIZE_ERROR, "error");
return;
}
// 이미지 파일인지 확인
if (!file.type.startsWith("image/")) {
showAlert("파일 형식 오류", MESSAGES.FILE_TYPE_ERROR, "error");
return;
}
// 선택된 파일 저장
setModalState((prev) => ({
...prev,
selectedFile: file,
}));
// 미리보기 이미지 생성
const reader = new FileReader();
reader.onload = (e) => {
setModalState((prev) => ({
...prev,
selectedImage: e.target?.result as string,
}));
};
reader.readAsDataURL(file);
}
},
[showAlert],
);
/**
* 이미지 삭제
*/
const removeImage = useCallback(() => {
setModalState((prev) => ({
...prev,
selectedImage: "",
selectedFile: null,
}));
// 파일 input 초기화
const fileInput = document.getElementById("profile-image-input") as HTMLInputElement;
if (fileInput) {
fileInput.value = "";
}
}, []);
/**
* 프로필 저장
*/
const saveProfile = useCallback(async () => {
if (!user) return;
setModalState((prev) => ({ ...prev, isSaving: true }));
try {
// 선택된 이미지가 있으면 Base64로 변환, 없으면 기존 이미지 유지
let photoData = user.photo || "";
if (modalState.selectedFile) {
// 새로 선택된 파일을 Base64로 변환
const reader = new FileReader();
photoData = await new Promise<string>((resolve) => {
reader.onload = (e) => {
resolve(e.target?.result as string);
};
reader.readAsDataURL(modalState.selectedFile!);
});
} else if (modalState.selectedImage && modalState.selectedImage !== user.photo) {
// 미리보기 이미지가 변경된 경우 사용
photoData = modalState.selectedImage;
}
// 사용자 정보 저장 데이터 준비
const updateData = {
userName: modalState.formData.userName,
email: modalState.formData.email,
locale: modalState.formData.locale,
photo: photoData !== user.photo ? photoData : undefined, // 변경된 경우만 전송
};
console.log("프로필 업데이트 요청:", updateData);
// API 호출 (JWT 토큰 자동 포함)
const response = await apiCall("PUT", "/admin/profile", updateData);
console.log("프로필 업데이트 응답:", response);
if (response.success || (response as any).result) {
// locale이 변경된 경우 전역 변수와 localStorage 업데이트
const localeChanged = modalState.formData.locale && modalState.formData.locale !== user.locale;
if (localeChanged) {
// 🎯 useMultiLang의 콜백 시스템을 사용하여 모든 컴포넌트에 즉시 알림
const { notifyLanguageChange } = await import("@/hooks/useMultiLang");
notifyLanguageChange(modalState.formData.locale);
console.log("🌍 사용자 locale 업데이트 (콜백 방식):", modalState.formData.locale);
}
// 성공: 사용자 정보 새로고침
await refreshUserData();
// locale이 변경된 경우 메뉴도 새로고침
if (localeChanged && refreshMenus) {
console.log("🔄 locale 변경으로 인한 메뉴 새로고침 시작");
await refreshMenus();
console.log("✅ 메뉴 새로고침 완료");
}
setModalState((prev) => ({
...prev,
selectedFile: null,
isOpen: false,
}));
showAlert("저장 완료", "프로필이 성공적으로 업데이트되었습니다.", "success");
} else {
throw new Error((response as any).message || "프로필 업데이트 실패");
}
} catch (error) {
console.error("프로필 저장 실패:", error);
const errorMessage = error instanceof Error ? error.message : MESSAGES.PROFILE_SAVE_ERROR;
showAlert("저장 실패", errorMessage, "error");
} finally {
setModalState((prev) => ({ ...prev, isSaving: false }));
}
}, [user, modalState.selectedFile, modalState.selectedImage, modalState.formData, refreshUserData, showAlert]);
return {
// 상태
isModalOpen: modalState.isOpen,
formData: modalState.formData,
selectedImage: modalState.selectedImage,
isSaving: modalState.isSaving,
departments,
// 알림 모달 상태
alertModal,
closeAlert,
// 액션
openProfileModal,
closeProfileModal,
updateFormData,
selectImage,
removeImage,
saveProfile,
};
};