Merge branch 'dev' of http://39.117.244.52:3000/kjs/ERP-node into userMng

This commit is contained in:
dohyeons
2025-08-26 09:58:13 +09:00
11 changed files with 1610 additions and 406 deletions

View File

@@ -110,6 +110,34 @@ export const useAuth = () => {
if (response.success && response.data) {
console.log("사용자 정보 조회 성공:", response.data);
// 사용자 로케일 정보도 함께 조회하여 전역 저장
try {
const localeResponse = await apiCall<string>("GET", "/admin/user-locale");
if (localeResponse.success && localeResponse.data) {
const userLocale = localeResponse.data;
console.log("✅ 사용자 로케일 조회 성공:", userLocale);
// 전역 상태에 저장 (다른 컴포넌트에서 사용)
(window as any).__GLOBAL_USER_LANG = userLocale;
(window as any).__GLOBAL_USER_LOCALE_LOADED = true;
// localStorage에도 저장 (새 창에서 공유)
localStorage.setItem("userLocale", userLocale);
localStorage.setItem("userLocaleLoaded", "true");
console.log("🌐 전역 사용자 로케일 저장됨:", userLocale);
}
} catch (localeError) {
console.warn("⚠️ 사용자 로케일 조회 실패, 기본값 사용:", localeError);
(window as any).__GLOBAL_USER_LANG = "KR";
(window as any).__GLOBAL_USER_LOCALE_LOADED = true;
// localStorage에도 저장
localStorage.setItem("userLocale", "KR");
localStorage.setItem("userLocaleLoaded", "true");
}
return response.data;
}
@@ -348,6 +376,12 @@ export const useAuth = () => {
// JWT 토큰 제거
TokenManager.removeToken();
// 로케일 정보도 제거
localStorage.removeItem("userLocale");
localStorage.removeItem("userLocaleLoaded");
(window as any).__GLOBAL_USER_LANG = undefined;
(window as any).__GLOBAL_USER_LOCALE_LOADED = undefined;
// 로그아웃 API 호출 성공 여부와 관계없이 클라이언트 상태 초기화
setUser(null);
setAuthStatus({
@@ -365,6 +399,13 @@ export const useAuth = () => {
// 오류가 발생해도 JWT 토큰 제거 및 클라이언트 상태 초기화
TokenManager.removeToken();
// 로케일 정보도 제거
localStorage.removeItem("userLocale");
localStorage.removeItem("userLocaleLoaded");
(window as any).__GLOBAL_USER_LANG = undefined;
(window as any).__GLOBAL_USER_LOCALE_LOADED = undefined;
setUser(null);
setAuthStatus({
isLoggedIn: false,

View File

@@ -6,75 +6,87 @@ let globalUserLang = "KR";
let globalChangeLangCallback: ((lang: string) => void) | null = null;
export const useMultiLang = (options: { companyCode?: string } = {}) => {
const [userLang, setUserLang] = useState<string>("KR");
const [userLang, setUserLang] = useState<string | null>(null); // null로 시작
const companyCode = options.companyCode || "*";
// 전역 언어 상태 동기화
// 전역 언어 상태 동기화 (무한 루프 방지)
useEffect(() => {
if (globalUserLang !== userLang) {
// 초기 로딩 시에만 동기화
if (globalUserLang && globalUserLang !== userLang) {
setUserLang(globalUserLang);
}
}, [globalUserLang]);
}, []); // 의존성 배열을 비워서 한 번만 실행
// 언어 변경 시 전역 콜백 호출
// 언어 변경 시 전역 콜백 호출 (무한 루프 방지)
useEffect(() => {
if (globalChangeLangCallback) {
// 언어가 설정된 경우에만 콜백 호출
if (globalChangeLangCallback && userLang) {
globalChangeLangCallback(userLang);
}
}, [userLang]);
// 사용자 로케일 조회 (한 번만 실행)
useEffect(() => {
// localStorage에서 로케일 확인 (새 창에서도 공유)
const storedLocale = localStorage.getItem("userLocale");
const storedLocaleLoaded = localStorage.getItem("userLocaleLoaded");
if (storedLocaleLoaded === "true" && storedLocale) {
console.log("🌐 localStorage에서 사용자 로케일 사용:", storedLocale);
setUserLang(storedLocale);
globalUserLang = storedLocale;
// 전역 상태도 동기화
(window as any).__GLOBAL_USER_LANG = storedLocale;
(window as any).__GLOBAL_USER_LOCALE_LOADED = true;
return;
}
// 전역에서 이미 로케일이 로드되었는지 확인
if ((window as any).__GLOBAL_USER_LOCALE_LOADED) {
const globalLocale = (window as any).__GLOBAL_USER_LANG;
console.log("🌐 전역에서 사용자 로케일 사용:", globalLocale);
setUserLang(globalLocale);
globalUserLang = globalLocale;
return;
}
// 이미 로케일이 설정되어 있으면 중복 호출 방지
if (globalUserLang && globalUserLang !== "KR") {
if (globalUserLang) {
setUserLang(globalUserLang);
return;
}
const fetchUserLocale = async () => {
try {
console.log("🔍 사용자 로케일 조회 시작");
const response = await apiClient.get("/admin/user-locale");
// 전역 로케일이 아직 로드되지 않았으면 대기
console.log("⏳ 전역 로케일 로드 대기 중...");
if (response.data.success && response.data.data) {
const userLocale = response.data.data;
console.log("✅ 사용자 로케일 조회 성공:", userLocale);
// 데이터베이스의 locale 값을 그대로 사용 (매핑 없음)
setUserLang(userLocale);
globalUserLang = userLocale; // 전역 상태도 업데이트
return;
}
// API 호출 실패 시 브라우저 언어 사용
console.warn("⚠️ 사용자 로케일 조회 실패, 브라우저 언어 사용");
const browserLang = navigator.language.split("-")[0];
// 브라우저 언어를 그대로 사용 (매핑 없음)
if (["ko", "en", "ja", "zh"].includes(browserLang)) {
setUserLang(browserLang);
globalUserLang = browserLang;
}
} catch (error) {
console.error("❌ 사용자 로케일 조회 중 오류:", error);
// 오류 시 브라우저 언어 사용
const browserLang = navigator.language.split("-")[0];
// 브라우저 언어를 그대로 사용 (매핑 없음)
if (["ko", "en", "ja", "zh"].includes(browserLang)) {
setUserLang(browserLang);
globalUserLang = browserLang;
}
// 주기적으로 전역 로케일 확인 (더 빠른 간격으로)
const checkInterval = setInterval(() => {
if ((window as any).__GLOBAL_USER_LOCALE_LOADED) {
const globalLocale = (window as any).__GLOBAL_USER_LANG;
console.log("🌐 전역에서 사용자 로케일 확인됨:", globalLocale);
setUserLang(globalLocale);
globalUserLang = globalLocale;
clearInterval(checkInterval);
}
};
}, 50); // 50ms로 단축
fetchUserLocale();
// 3초 후 타임아웃 (더 빠른 타임아웃)
setTimeout(() => {
clearInterval(checkInterval);
if (!userLang) {
console.warn("⚠️ 전역 로케일 로드 타임아웃, 기본값 사용");
setUserLang("KR");
globalUserLang = "KR";
}
}, 3000); // 3초로 단축
return () => clearInterval(checkInterval);
}, []);
// 다국어 텍스트 가져오기 (배치 조회 방식)
const getText = async (menuCode: string, langKey: string, fallback?: string): Promise<string> => {
console.log(`🔍 다국어 텍스트 요청 (배치 방식):`, { menuCode, langKey, userLang, companyCode });
console.log("🔍 다국어 텍스트 요청 (배치 방식):", { menuCode, langKey, userLang, companyCode });
try {
// 배치 조회 API 사용
@@ -92,7 +104,7 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
},
);
console.log(`📡 배치 API 응답 상태:`, response.status, response.statusText);
console.log("📡 배치 API 응답 상태:", response.status, response.statusText);
if (response.data.success && response.data.data && response.data.data[langKey]) {
// 번역 텍스트를 캐시에 저장
@@ -105,26 +117,36 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
}
// 실패 시 fallback 또는 키 반환
console.log(`🔄 배치 API 성공했지만 데이터 없음, fallback 반환:`, fallback || langKey);
console.log("🔄 배치 API 성공했지만 데이터 없음, fallback 반환:", fallback || langKey);
return fallback || langKey;
} catch (error) {
console.error("❌ 다국어 텍스트 배치 조회 실패:", error);
console.log(`🔄 에러 시 fallback 반환:`, fallback || langKey);
console.log("🔄 에러 시 fallback 반환:", fallback || langKey);
return fallback || langKey;
}
};
// 언어 변경
// 언어 변경 (무한 루프 방지)
const changeLang = async (newLang: string) => {
// 같은 언어로 변경하려는 경우 무시
if (newLang === userLang) {
console.log("🔄 같은 언어로 변경 시도 무시:", newLang);
return;
}
try {
console.log("🔄 언어 변경 시작:", { from: userLang, to: newLang });
// 백엔드에 사용자 로케일 설정 요청
const response = await apiClient.post("/admin/user-locale", {
locale: newLang,
});
if (response.data.success) {
setUserLang(newLang);
// 전역 상태 먼저 업데이트
globalUserLang = newLang;
// 로컬 상태 업데이트
setUserLang(newLang);
console.log("✅ 사용자 로케일 변경 성공:", newLang);
} else {
console.error("❌ 사용자 로케일 변경 실패:", response.data.message);
@@ -132,8 +154,8 @@ export const useMultiLang = (options: { companyCode?: string } = {}) => {
} catch (error) {
console.error("❌ 사용자 로케일 변경 중 오류:", error);
// 오류 시에도 로컬 상태는 변경
setUserLang(newLang);
globalUserLang = newLang;
setUserLang(newLang);
}
};