256 lines
6.9 KiB
TypeScript
256 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useCallback } from "react";
|
|
import { ProfileFormData, ProfileModalState } from "@/types/profile";
|
|
import { LAYOUT_CONFIG, MESSAGES } from "@/constants/layout";
|
|
|
|
// 알림 모달 상태 타입
|
|
interface AlertModalState {
|
|
isOpen: boolean;
|
|
title: string;
|
|
message: string;
|
|
type: "success" | "error" | "info";
|
|
}
|
|
|
|
/**
|
|
* 프로필 관련 비즈니스 로직을 관리하는 커스텀 훅
|
|
*/
|
|
export const useProfile = (user: any, refreshUserData: () => 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 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 openProfileModal = useCallback(() => {
|
|
if (user) {
|
|
setModalState((prev) => ({
|
|
...prev,
|
|
isOpen: true,
|
|
formData: {
|
|
userName: user.userName || "",
|
|
email: user.email || "",
|
|
deptName: user.deptName || "",
|
|
positionName: user.positionName || "",
|
|
locale: user.locale || "",
|
|
},
|
|
selectedImage: user.photo || "",
|
|
selectedFile: null,
|
|
isSaving: false,
|
|
}));
|
|
}
|
|
}, [user]);
|
|
|
|
/**
|
|
* 프로필 모달 닫기
|
|
*/
|
|
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 userSaveData = {
|
|
userId: user.userId,
|
|
userName: modalState.formData.userName,
|
|
email: modalState.formData.email,
|
|
deptName: modalState.formData.deptName,
|
|
positionName: modalState.formData.positionName,
|
|
locale: modalState.formData.locale,
|
|
photo: photoData,
|
|
};
|
|
|
|
console.log("사용자 정보 저장 요청:", userSaveData);
|
|
|
|
const userResponse = await fetch(`${LAYOUT_CONFIG.API_BASE_URL}${LAYOUT_CONFIG.ENDPOINTS.USER_SAVE}`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(userSaveData),
|
|
});
|
|
|
|
if (userResponse.ok) {
|
|
const userResult = await userResponse.json();
|
|
console.log("사용자 정보 저장 응답:", userResult);
|
|
|
|
if (userResult.result) {
|
|
// 성공: 세션 정보 새로고침
|
|
await refreshUserData();
|
|
setModalState((prev) => ({
|
|
...prev,
|
|
selectedFile: null,
|
|
isOpen: false,
|
|
}));
|
|
showAlert("저장 완료", MESSAGES.PROFILE_SAVE_SUCCESS, "success");
|
|
} else {
|
|
throw new Error(userResult.msg || "사용자 정보 저장 실패");
|
|
}
|
|
} else {
|
|
const errorText = await userResponse.text();
|
|
console.error("API 응답 오류:", errorText);
|
|
throw new Error(`사용자 정보 저장 실패: ${userResponse.status} ${userResponse.statusText}`);
|
|
}
|
|
} 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,
|
|
|
|
// 알림 모달 상태
|
|
alertModal,
|
|
closeAlert,
|
|
|
|
// 액션
|
|
openProfileModal,
|
|
closeProfileModal,
|
|
updateFormData,
|
|
selectImage,
|
|
removeImage,
|
|
saveProfile,
|
|
};
|
|
};
|