feat: 멀티테넌시 지원을 위한 레이어 관리 기능 추가

- 레이어 목록 조회, 특정 레이어 레이아웃 조회, 레이어 삭제 및 조건 설정 업데이트 기능을 추가했습니다.
- 엔티티 참조 데이터 조회 및 공통 코드 데이터 조회에 멀티테넌시 필터를 적용하여 인증된 사용자의 회사 코드에 따라 데이터 접근을 제한했습니다.
- 레이어 관리 패널에서 기본 레이어와 조건부 레이어의 컴포넌트를 통합하여 조건부 영역의 표시를 개선했습니다.
- 레이아웃 저장 시 레이어 ID를 포함하여 레이어별로 저장할 수 있도록 변경했습니다.
This commit is contained in:
kjs
2026-02-09 13:21:56 +09:00
parent 84eb035069
commit 1c71b3aa83
14 changed files with 1571 additions and 598 deletions

View File

@@ -38,7 +38,7 @@ export const API_BASE_URL = getApiBaseUrl();
export const getFullImageUrl = (imagePath: string): string => {
// 빈 값 체크
if (!imagePath) return "";
// 이미 전체 URL인 경우 그대로 반환
if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
return imagePath;
@@ -49,18 +49,18 @@ export const getFullImageUrl = (imagePath: string): string => {
// 런타임에 현재 hostname 확인 (SSR 시점이 아닌 클라이언트에서 실행될 때)
if (typeof window !== "undefined") {
const currentHost = window.location.hostname;
// 프로덕션 환경: v1.vexplor.com → api.vexplor.com
if (currentHost === "v1.vexplor.com") {
return `https://api.vexplor.com${imagePath}`;
}
// 로컬 개발환경
if (currentHost === "localhost" || currentHost === "127.0.0.1") {
return `http://localhost:8080${imagePath}`;
}
}
// SSR 또는 알 수 없는 환경에서는 API_BASE_URL 사용 (fallback)
// 주의: 프로덕션 URL이 https://api.vexplor.com/api 이므로
// 단순 .replace("/api", "")는 호스트명의 /api까지 제거하는 버그 발생
@@ -69,7 +69,7 @@ export const getFullImageUrl = (imagePath: string): string => {
if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) {
return `${baseUrl}${imagePath}`;
}
// 최종 fallback
return imagePath;
}
@@ -157,7 +157,7 @@ const refreshToken = async (): Promise<string | null> => {
headers: {
Authorization: `Bearer ${currentToken}`,
},
}
},
);
if (response.data?.success && response.data?.data?.token) {
@@ -192,13 +192,16 @@ const startAutoRefresh = (): void => {
}
// 10분마다 토큰 상태 확인
tokenRefreshTimer = setInterval(async () => {
const token = TokenManager.getToken();
if (token && TokenManager.isTokenExpiringSoon(token)) {
console.log("[TokenManager] 토큰 만료 임박, 자동 갱신 시작...");
await refreshToken();
}
}, 10 * 60 * 1000); // 10분
tokenRefreshTimer = setInterval(
async () => {
const token = TokenManager.getToken();
if (token && TokenManager.isTokenExpiringSoon(token)) {
console.log("[TokenManager] 토큰 만료 임박, 자동 갱신 시작...");
await refreshToken();
}
},
10 * 60 * 1000,
); // 10분
// 페이지 로드 시 즉시 확인
const token = TokenManager.getToken();
@@ -230,14 +233,18 @@ const setupActivityBasedRefresh = (): void => {
["click", "keydown", "scroll", "mousemove"].forEach((event) => {
// 너무 잦은 호출 방지를 위해 throttle 적용
let throttleTimer: NodeJS.Timeout | null = null;
window.addEventListener(event, () => {
if (!throttleTimer) {
throttleTimer = setTimeout(() => {
handleActivity();
throttleTimer = null;
}, 1000); // 1초 throttle
}
}, { passive: true });
window.addEventListener(
event,
() => {
if (!throttleTimer) {
throttleTimer = setTimeout(() => {
handleActivity();
throttleTimer = null;
}, 1000); // 1초 throttle
}
},
{ passive: true },
);
});
};

View File

@@ -213,6 +213,28 @@ export const screenApi = {
await apiClient.post(`/screen-management/screens/${screenId}/layout-v2`, layoutData);
},
// 🆕 레이어 목록 조회
getScreenLayers: async (screenId: number): Promise<any[]> => {
const response = await apiClient.get(`/screen-management/screens/${screenId}/layers`);
return response.data.data || [];
},
// 🆕 특정 레이어 레이아웃 조회
getLayerLayout: async (screenId: number, layerId: number): Promise<any> => {
const response = await apiClient.get(`/screen-management/screens/${screenId}/layers/${layerId}/layout`);
return response.data.data;
},
// 🆕 레이어 삭제
deleteLayer: async (screenId: number, layerId: number): Promise<void> => {
await apiClient.delete(`/screen-management/screens/${screenId}/layers/${layerId}`);
},
// 🆕 레이어 조건 설정 업데이트
updateLayerCondition: async (screenId: number, layerId: number, conditionConfig: any, layerName?: string): Promise<void> => {
await apiClient.put(`/screen-management/screens/${screenId}/layers/${layerId}/condition`, { conditionConfig, layerName });
},
// 연결된 모달 화면 감지
detectLinkedModals: async (
screenId: number,