2025-08-21 09:41:46 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2025-08-28 10:05:06 +09:00
|
|
|
import { useState, useCallback, useEffect } from "react";
|
2025-08-21 09:41:46 +09:00
|
|
|
import { ProfileFormData, ProfileModalState } from "@/types/profile";
|
|
|
|
|
import { LAYOUT_CONFIG, MESSAGES } from "@/constants/layout";
|
2025-08-27 17:32:41 +09:00
|
|
|
import { apiCall } from "@/lib/api/client";
|
2025-12-01 18:41:02 +09:00
|
|
|
import {
|
|
|
|
|
getDriverProfile,
|
|
|
|
|
updateDriverProfile,
|
|
|
|
|
updateDriverStatus,
|
|
|
|
|
deleteDriverAccount,
|
2025-12-01 19:03:43 +09:00
|
|
|
deleteDriverVehicle,
|
|
|
|
|
registerDriverVehicle,
|
2025-12-01 18:41:02 +09:00
|
|
|
DriverProfile,
|
2025-12-01 19:03:43 +09:00
|
|
|
VehicleRegisterData,
|
2025-12-01 18:41:02 +09:00
|
|
|
} from "@/lib/api/driver";
|
|
|
|
|
import { DriverInfo, DriverFormData } from "@/components/layout/ProfileModal";
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
// 알림 모달 상태 타입
|
|
|
|
|
interface AlertModalState {
|
|
|
|
|
isOpen: boolean;
|
|
|
|
|
title: string;
|
|
|
|
|
message: string;
|
|
|
|
|
type: "success" | "error" | "info";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 프로필 관련 비즈니스 로직을 관리하는 커스텀 훅
|
|
|
|
|
*/
|
2025-08-28 10:09:57 +09:00
|
|
|
export const useProfile = (user: any, refreshUserData: () => Promise<void>, refreshMenus?: () => Promise<void>) => {
|
2025-08-21 09:41:46 +09:00
|
|
|
// 상태 관리
|
|
|
|
|
const [modalState, setModalState] = useState<ProfileModalState>({
|
|
|
|
|
isOpen: false,
|
|
|
|
|
formData: {
|
|
|
|
|
userName: "",
|
|
|
|
|
email: "",
|
|
|
|
|
deptName: "",
|
|
|
|
|
positionName: "",
|
|
|
|
|
locale: "",
|
|
|
|
|
},
|
2025-09-30 15:45:21 +09:00
|
|
|
selectedImage: null,
|
2025-08-21 09:41:46 +09:00
|
|
|
selectedFile: null,
|
|
|
|
|
isSaving: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 알림 모달 상태
|
|
|
|
|
const [alertModal, setAlertModal] = useState<AlertModalState>({
|
|
|
|
|
isOpen: false,
|
|
|
|
|
title: "",
|
|
|
|
|
message: "",
|
|
|
|
|
type: "info",
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-28 10:05:06 +09:00
|
|
|
// 부서 목록 상태
|
|
|
|
|
const [departments, setDepartments] = useState<
|
|
|
|
|
Array<{
|
|
|
|
|
deptCode: string;
|
|
|
|
|
deptName: string;
|
|
|
|
|
}>
|
|
|
|
|
>([]);
|
|
|
|
|
|
2025-12-01 18:41:02 +09:00
|
|
|
// 운전자 정보 상태
|
|
|
|
|
const [isDriver, setIsDriver] = useState(false);
|
2025-12-01 19:03:43 +09:00
|
|
|
const [hasVehicle, setHasVehicle] = useState(false); // 차량 보유 여부
|
2025-12-01 18:41:02 +09:00
|
|
|
const [driverInfo, setDriverInfo] = useState<DriverInfo | null>(null);
|
|
|
|
|
const [driverFormData, setDriverFormData] = useState<DriverFormData>({
|
|
|
|
|
vehicleNumber: "",
|
|
|
|
|
vehicleType: "",
|
|
|
|
|
licenseNumber: "",
|
|
|
|
|
phoneNumber: "",
|
2025-12-01 19:03:43 +09:00
|
|
|
branchName: "",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 새 차량 등록 모달 상태
|
|
|
|
|
const [isVehicleRegisterModalOpen, setIsVehicleRegisterModalOpen] = useState(false);
|
|
|
|
|
const [newVehicleData, setNewVehicleData] = useState<VehicleRegisterData>({
|
|
|
|
|
vehicleNumber: "",
|
|
|
|
|
vehicleType: "",
|
|
|
|
|
branchName: "",
|
2025-12-01 18:41:02 +09:00
|
|
|
});
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
// 알림 모달 표시 함수
|
|
|
|
|
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 }));
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-08-28 10:05:06 +09:00
|
|
|
// 부서 목록 로드 함수
|
|
|
|
|
const loadDepartments = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await apiCall("GET", "/admin/departments");
|
|
|
|
|
if (response.success && response.data) {
|
2025-09-01 17:07:50 +09:00
|
|
|
setDepartments(response.data as Array<{ deptCode: string; deptName: string }>);
|
2025-08-28 10:05:06 +09:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("부서 목록 로드 실패:", error);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-12-01 18:41:02 +09:00
|
|
|
// 운전자 정보 로드 함수
|
|
|
|
|
const loadDriverInfo = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await getDriverProfile();
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
setIsDriver(true);
|
2025-12-01 19:03:43 +09:00
|
|
|
// 차량 보유 여부 확인
|
|
|
|
|
const vehicleExists = !!response.data.vehicleNumber;
|
|
|
|
|
setHasVehicle(vehicleExists);
|
2025-12-01 18:41:02 +09:00
|
|
|
setDriverInfo({
|
|
|
|
|
vehicleNumber: response.data.vehicleNumber,
|
|
|
|
|
vehicleType: response.data.vehicleType,
|
|
|
|
|
licenseNumber: response.data.licenseNumber,
|
|
|
|
|
phoneNumber: response.data.phoneNumber,
|
|
|
|
|
vehicleStatus: response.data.vehicleStatus,
|
2025-12-01 19:03:43 +09:00
|
|
|
branchName: response.data.branchName,
|
2025-12-01 18:41:02 +09:00
|
|
|
});
|
|
|
|
|
setDriverFormData({
|
|
|
|
|
vehicleNumber: response.data.vehicleNumber || "",
|
|
|
|
|
vehicleType: response.data.vehicleType || "",
|
|
|
|
|
licenseNumber: response.data.licenseNumber || "",
|
|
|
|
|
phoneNumber: response.data.phoneNumber || "",
|
2025-12-01 19:03:43 +09:00
|
|
|
branchName: response.data.branchName || "",
|
2025-12-01 18:41:02 +09:00
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
setIsDriver(false);
|
2025-12-01 19:03:43 +09:00
|
|
|
setHasVehicle(false);
|
2025-12-01 18:41:02 +09:00
|
|
|
setDriverInfo(null);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("운전자 정보 로드 실패:", error);
|
|
|
|
|
setIsDriver(false);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
/**
|
|
|
|
|
* 프로필 모달 열기
|
|
|
|
|
*/
|
|
|
|
|
const openProfileModal = useCallback(() => {
|
|
|
|
|
if (user) {
|
2025-08-28 10:05:06 +09:00
|
|
|
// 부서 목록 로드
|
|
|
|
|
loadDepartments();
|
2025-12-01 18:41:02 +09:00
|
|
|
// 운전자 정보 로드
|
|
|
|
|
loadDriverInfo();
|
2025-08-28 10:05:06 +09:00
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
setModalState((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
isOpen: true,
|
|
|
|
|
formData: {
|
|
|
|
|
userName: user.userName || "",
|
|
|
|
|
email: user.email || "",
|
|
|
|
|
deptName: user.deptName || "",
|
|
|
|
|
positionName: user.positionName || "",
|
2025-08-28 10:05:06 +09:00
|
|
|
locale: user.locale || "KR", // 기본값을 KR로 설정
|
2025-08-21 09:41:46 +09:00
|
|
|
},
|
2025-09-30 15:45:21 +09:00
|
|
|
selectedImage: user.photo || null,
|
2025-08-21 09:41:46 +09:00
|
|
|
selectedFile: null,
|
|
|
|
|
isSaving: false,
|
|
|
|
|
}));
|
|
|
|
|
}
|
2025-12-01 18:41:02 +09:00
|
|
|
}, [user, loadDepartments, loadDriverInfo]);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 프로필 모달 닫기
|
|
|
|
|
*/
|
|
|
|
|
const closeProfileModal = useCallback(() => {
|
|
|
|
|
setModalState((prev) => ({
|
|
|
|
|
...prev,
|
2025-09-30 15:45:21 +09:00
|
|
|
selectedImage: null,
|
|
|
|
|
selectedFile: null,
|
2025-08-21 09:41:46 +09:00
|
|
|
isOpen: false,
|
|
|
|
|
}));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 프로필 폼 데이터 변경
|
|
|
|
|
*/
|
|
|
|
|
const updateFormData = useCallback((field: keyof ProfileFormData, value: string) => {
|
|
|
|
|
setModalState((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
formData: {
|
|
|
|
|
...prev.formData,
|
|
|
|
|
[field]: value,
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-12-01 18:41:02 +09:00
|
|
|
/**
|
|
|
|
|
* 운전자 폼 데이터 변경
|
|
|
|
|
*/
|
|
|
|
|
const updateDriverFormData = useCallback((field: keyof DriverFormData, value: string) => {
|
|
|
|
|
setDriverFormData((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
[field]: value,
|
|
|
|
|
}));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 차량 상태 변경 (대기/정비)
|
|
|
|
|
*/
|
|
|
|
|
const handleDriverStatusChange = useCallback(
|
|
|
|
|
async (status: "off" | "maintenance") => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await updateDriverStatus(status);
|
|
|
|
|
if (response.success) {
|
|
|
|
|
showAlert("상태 변경", response.message || "차량 상태가 변경되었습니다.", "success");
|
|
|
|
|
// 운전자 정보 새로고침
|
|
|
|
|
await loadDriverInfo();
|
|
|
|
|
} else {
|
|
|
|
|
showAlert("상태 변경 실패", response.message || "상태 변경에 실패했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("차량 상태 변경 실패:", error);
|
|
|
|
|
showAlert("오류", "상태 변경 중 오류가 발생했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[showAlert, loadDriverInfo]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 회원 탈퇴
|
|
|
|
|
*/
|
|
|
|
|
const handleDriverAccountDelete = useCallback(async () => {
|
|
|
|
|
if (!confirm("정말로 탈퇴하시겠습니까?\n차량 정보가 함께 삭제되며, 이 작업은 되돌릴 수 없습니다.")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await deleteDriverAccount();
|
|
|
|
|
if (response.success) {
|
|
|
|
|
showAlert("탈퇴 완료", "회원 탈퇴가 완료되었습니다.", "success");
|
|
|
|
|
// 로그아웃 처리
|
|
|
|
|
window.location.href = "/login";
|
|
|
|
|
} else {
|
|
|
|
|
showAlert("탈퇴 실패", response.message || "회원 탈퇴에 실패했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("회원 탈퇴 실패:", error);
|
|
|
|
|
showAlert("오류", "회원 탈퇴 중 오류가 발생했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
}, [showAlert]);
|
|
|
|
|
|
2025-12-01 19:03:43 +09:00
|
|
|
/**
|
|
|
|
|
* 차량 삭제
|
|
|
|
|
*/
|
|
|
|
|
const handleDeleteVehicle = useCallback(async () => {
|
|
|
|
|
if (!confirm("이 차량을 더 이상 사용하지 않습니까?\n차량 정보가 삭제됩니다.")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await deleteDriverVehicle();
|
|
|
|
|
if (response.success) {
|
|
|
|
|
showAlert("삭제 완료", "차량이 삭제되었습니다.", "success");
|
|
|
|
|
// 운전자 정보 새로고침
|
|
|
|
|
await loadDriverInfo();
|
|
|
|
|
} else {
|
|
|
|
|
showAlert("삭제 실패", response.message || "차량 삭제에 실패했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("차량 삭제 실패:", error);
|
|
|
|
|
showAlert("오류", "차량 삭제 중 오류가 발생했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
}, [showAlert, loadDriverInfo]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 새 차량 등록 모달 열기
|
|
|
|
|
*/
|
|
|
|
|
const openVehicleRegisterModal = useCallback(() => {
|
|
|
|
|
setNewVehicleData({
|
|
|
|
|
vehicleNumber: "",
|
|
|
|
|
vehicleType: "",
|
|
|
|
|
branchName: driverFormData.branchName || "", // 기존 소속 지점 유지
|
|
|
|
|
});
|
|
|
|
|
setIsVehicleRegisterModalOpen(true);
|
|
|
|
|
}, [driverFormData.branchName]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 새 차량 등록 모달 닫기
|
|
|
|
|
*/
|
|
|
|
|
const closeVehicleRegisterModal = useCallback(() => {
|
|
|
|
|
setIsVehicleRegisterModalOpen(false);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 새 차량 데이터 변경
|
|
|
|
|
*/
|
|
|
|
|
const updateNewVehicleData = useCallback((field: keyof VehicleRegisterData, value: string) => {
|
|
|
|
|
setNewVehicleData((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
[field]: value,
|
|
|
|
|
}));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 새 차량 등록 처리
|
|
|
|
|
*/
|
|
|
|
|
const handleRegisterVehicle = useCallback(async () => {
|
|
|
|
|
if (!newVehicleData.vehicleNumber) {
|
|
|
|
|
showAlert("입력 오류", "차량번호는 필수입니다.", "error");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await registerDriverVehicle(newVehicleData);
|
|
|
|
|
if (response.success) {
|
|
|
|
|
showAlert("등록 완료", "차량이 등록되었습니다.", "success");
|
|
|
|
|
setIsVehicleRegisterModalOpen(false);
|
|
|
|
|
// 운전자 정보 새로고침
|
|
|
|
|
await loadDriverInfo();
|
|
|
|
|
} else {
|
|
|
|
|
showAlert("등록 실패", response.message || "차량 등록에 실패했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("차량 등록 실패:", error);
|
|
|
|
|
showAlert("오류", "차량 등록 중 오류가 발생했습니다.", "error");
|
|
|
|
|
}
|
|
|
|
|
}, [newVehicleData, showAlert, loadDriverInfo]);
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
/**
|
|
|
|
|
* 이미지 선택 처리
|
|
|
|
|
*/
|
|
|
|
|
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(() => {
|
|
|
|
|
// 파일 input 초기화
|
|
|
|
|
const fileInput = document.getElementById("profile-image-input") as HTMLInputElement;
|
|
|
|
|
if (fileInput) {
|
|
|
|
|
fileInput.value = "";
|
|
|
|
|
}
|
2025-09-30 15:45:21 +09:00
|
|
|
|
|
|
|
|
// 상태 업데이트 - 명시적으로 null로 설정하여 AvatarFallback이 확실히 표시되도록 함
|
|
|
|
|
setModalState((prev) => {
|
|
|
|
|
const newState = {
|
|
|
|
|
...prev,
|
|
|
|
|
selectedImage: null, // 빈 문자열 대신 null로 설정
|
|
|
|
|
selectedFile: null,
|
|
|
|
|
};
|
|
|
|
|
return newState;
|
|
|
|
|
});
|
2025-08-21 09:41:46 +09:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 프로필 저장
|
|
|
|
|
*/
|
|
|
|
|
const saveProfile = useCallback(async () => {
|
|
|
|
|
if (!user) return;
|
|
|
|
|
|
|
|
|
|
setModalState((prev) => ({ ...prev, isSaving: true }));
|
|
|
|
|
|
|
|
|
|
try {
|
2025-09-30 15:45:21 +09:00
|
|
|
// 이미지 데이터 결정 로직
|
|
|
|
|
let photoData: string | null | undefined = undefined;
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
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!);
|
|
|
|
|
});
|
2025-09-30 15:45:21 +09:00
|
|
|
} else if (modalState.selectedImage === null || modalState.selectedImage === "") {
|
|
|
|
|
// 이미지가 명시적으로 삭제된 경우 (X 버튼 클릭)
|
|
|
|
|
photoData = null;
|
2025-08-21 09:41:46 +09:00
|
|
|
} else if (modalState.selectedImage && modalState.selectedImage !== user.photo) {
|
2025-09-30 15:45:21 +09:00
|
|
|
// 미리보기 이미지가 변경된 경우
|
2025-08-21 09:41:46 +09:00
|
|
|
photoData = modalState.selectedImage;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 17:32:41 +09:00
|
|
|
// 사용자 정보 저장 데이터 준비
|
2025-09-30 15:45:21 +09:00
|
|
|
const updateData: any = {
|
2025-08-21 09:41:46 +09:00
|
|
|
userName: modalState.formData.userName,
|
|
|
|
|
email: modalState.formData.email,
|
|
|
|
|
locale: modalState.formData.locale,
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-30 15:45:21 +09:00
|
|
|
// photo가 변경된 경우에만 추가 (undefined가 아닌 경우)
|
|
|
|
|
if (photoData !== undefined) {
|
|
|
|
|
updateData.photo = photoData;
|
|
|
|
|
}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
2025-08-27 17:32:41 +09:00
|
|
|
// API 호출 (JWT 토큰 자동 포함)
|
|
|
|
|
const response = await apiCall("PUT", "/admin/profile", updateData);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
2025-12-01 18:41:02 +09:00
|
|
|
// 운전자 정보도 저장 (운전자인 경우)
|
|
|
|
|
if (isDriver) {
|
|
|
|
|
const driverResponse = await updateDriverProfile({
|
|
|
|
|
userName: modalState.formData.userName,
|
|
|
|
|
phoneNumber: driverFormData.phoneNumber,
|
|
|
|
|
licenseNumber: driverFormData.licenseNumber,
|
|
|
|
|
vehicleNumber: driverFormData.vehicleNumber,
|
|
|
|
|
vehicleType: driverFormData.vehicleType,
|
2025-12-01 19:03:43 +09:00
|
|
|
branchName: driverFormData.branchName,
|
2025-12-01 18:41:02 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!driverResponse.success) {
|
|
|
|
|
console.warn("운전자 정보 저장 실패:", driverResponse.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 17:07:50 +09:00
|
|
|
if (response.success || (response as any).result) {
|
2025-08-27 17:32:41 +09:00
|
|
|
// locale이 변경된 경우 전역 변수와 localStorage 업데이트
|
2025-08-28 10:09:57 +09:00
|
|
|
const localeChanged = modalState.formData.locale && modalState.formData.locale !== user.locale;
|
|
|
|
|
if (localeChanged) {
|
2025-09-01 17:07:50 +09:00
|
|
|
// 🎯 useMultiLang의 콜백 시스템을 사용하여 모든 컴포넌트에 즉시 알림
|
|
|
|
|
const { notifyLanguageChange } = await import("@/hooks/useMultiLang");
|
|
|
|
|
notifyLanguageChange(modalState.formData.locale);
|
2025-08-21 09:41:46 +09:00
|
|
|
}
|
2025-08-27 17:32:41 +09:00
|
|
|
|
|
|
|
|
// 성공: 사용자 정보 새로고침
|
|
|
|
|
await refreshUserData();
|
2025-08-28 10:09:57 +09:00
|
|
|
|
|
|
|
|
// locale이 변경된 경우 메뉴도 새로고침
|
|
|
|
|
if (localeChanged && refreshMenus) {
|
|
|
|
|
await refreshMenus();
|
|
|
|
|
}
|
2025-09-30 15:45:21 +09:00
|
|
|
|
|
|
|
|
// 모달 상태 초기화 (저장 후 즉시 반영을 위해)
|
2025-08-27 17:32:41 +09:00
|
|
|
setModalState((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
selectedFile: null,
|
2025-09-30 15:45:21 +09:00
|
|
|
selectedImage: null,
|
2025-08-27 17:32:41 +09:00
|
|
|
isOpen: false,
|
|
|
|
|
}));
|
2025-09-30 15:45:21 +09:00
|
|
|
|
2025-08-27 17:32:41 +09:00
|
|
|
showAlert("저장 완료", "프로필이 성공적으로 업데이트되었습니다.", "success");
|
2025-08-21 09:41:46 +09:00
|
|
|
} else {
|
2025-09-01 17:07:50 +09:00
|
|
|
throw new Error((response as any).message || "프로필 업데이트 실패");
|
2025-08-21 09:41:46 +09:00
|
|
|
}
|
|
|
|
|
} 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 }));
|
|
|
|
|
}
|
2025-12-01 18:41:02 +09:00
|
|
|
}, [user, modalState.selectedFile, modalState.selectedImage, modalState.formData, refreshUserData, showAlert, isDriver, driverFormData]);
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// 상태
|
|
|
|
|
isModalOpen: modalState.isOpen,
|
|
|
|
|
formData: modalState.formData,
|
|
|
|
|
selectedImage: modalState.selectedImage,
|
|
|
|
|
isSaving: modalState.isSaving,
|
2025-08-28 10:05:06 +09:00
|
|
|
departments,
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
// 알림 모달 상태
|
|
|
|
|
alertModal,
|
|
|
|
|
closeAlert,
|
|
|
|
|
|
2025-12-01 18:41:02 +09:00
|
|
|
// 운전자 관련 상태
|
|
|
|
|
isDriver,
|
2025-12-01 19:03:43 +09:00
|
|
|
hasVehicle,
|
2025-12-01 18:41:02 +09:00
|
|
|
driverInfo,
|
|
|
|
|
driverFormData,
|
|
|
|
|
|
2025-12-01 19:03:43 +09:00
|
|
|
// 새 차량 등록 모달 상태
|
|
|
|
|
isVehicleRegisterModalOpen,
|
|
|
|
|
newVehicleData,
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
// 액션
|
|
|
|
|
openProfileModal,
|
|
|
|
|
closeProfileModal,
|
|
|
|
|
updateFormData,
|
|
|
|
|
selectImage,
|
|
|
|
|
removeImage,
|
|
|
|
|
saveProfile,
|
2025-12-01 18:41:02 +09:00
|
|
|
|
|
|
|
|
// 운전자 관련 액션
|
|
|
|
|
updateDriverFormData,
|
|
|
|
|
handleDriverStatusChange,
|
|
|
|
|
handleDriverAccountDelete,
|
2025-12-01 19:03:43 +09:00
|
|
|
handleDeleteVehicle,
|
|
|
|
|
openVehicleRegisterModal,
|
|
|
|
|
closeVehicleRegisterModal,
|
|
|
|
|
updateNewVehicleData,
|
|
|
|
|
handleRegisterVehicle,
|
2025-08-21 09:41:46 +09:00
|
|
|
};
|
|
|
|
|
};
|