fix(WEB): 마스터데이터 캐시 테넌트 격리 및 상세 템플릿 개선
- masterDataStore: 테넌트별 캐시 격리 로직 강화 - AuthContext: 인증 컨텍스트 안정성 개선 - IntegratedDetailTemplate: 상세 템플릿 동작 수정 - VendorDetail: 거래처 상세 수정 - AttendanceManagement: 타입 정의 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,7 @@ const createEmptyPageConfigs = (): Record<PageType, PageConfig | null> => ({
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
currentTenantId: null as number | null,
|
||||
pageConfigs: createEmptyPageConfigs(),
|
||||
loading: {} as Record<PageType, boolean>,
|
||||
errors: {} as Record<PageType, string | null>,
|
||||
@@ -92,15 +93,27 @@ const STORAGE_PREFIX = 'page_config_';
|
||||
const STORAGE_TIMESTAMP_SUFFIX = '_timestamp';
|
||||
const CACHE_TTL = 10 * 60 * 1000; // 10분
|
||||
|
||||
/**
|
||||
* 테넌트별 캐시 키 생성
|
||||
* tenantId가 있으면: page_config_{tenantId}_{pageType}
|
||||
* tenantId가 없으면: page_config_{pageType} (하위 호환)
|
||||
*/
|
||||
function getStorageKey(tenantId: number | null, pageType: PageType): string {
|
||||
return tenantId != null
|
||||
? `${STORAGE_PREFIX}${tenantId}_${pageType}`
|
||||
: `${STORAGE_PREFIX}${pageType}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionStorage에서 페이지 구성 가져오기
|
||||
*/
|
||||
function getConfigFromSessionStorage(pageType: PageType): PageConfig | null {
|
||||
function getConfigFromSessionStorage(tenantId: number | null, pageType: PageType): PageConfig | null {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
try {
|
||||
const cachedData = window.sessionStorage.getItem(`${STORAGE_PREFIX}${pageType}`);
|
||||
const timestamp = window.sessionStorage.getItem(`${STORAGE_PREFIX}${pageType}${STORAGE_TIMESTAMP_SUFFIX}`);
|
||||
const key = getStorageKey(tenantId, pageType);
|
||||
const cachedData = window.sessionStorage.getItem(key);
|
||||
const timestamp = window.sessionStorage.getItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`);
|
||||
|
||||
if (!cachedData || !timestamp) return null;
|
||||
|
||||
@@ -108,8 +121,8 @@ function getConfigFromSessionStorage(pageType: PageType): PageConfig | null {
|
||||
const cacheAge = Date.now() - parseInt(timestamp, 10);
|
||||
if (cacheAge > CACHE_TTL) {
|
||||
// 만료된 캐시 삭제
|
||||
window.sessionStorage.removeItem(`${STORAGE_PREFIX}${pageType}`);
|
||||
window.sessionStorage.removeItem(`${STORAGE_PREFIX}${pageType}${STORAGE_TIMESTAMP_SUFFIX}`);
|
||||
window.sessionStorage.removeItem(key);
|
||||
window.sessionStorage.removeItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -123,12 +136,13 @@ function getConfigFromSessionStorage(pageType: PageType): PageConfig | null {
|
||||
/**
|
||||
* sessionStorage에 페이지 구성 저장
|
||||
*/
|
||||
function setConfigToSessionStorage(pageType: PageType, config: PageConfig): void {
|
||||
function setConfigToSessionStorage(tenantId: number | null, pageType: PageType, config: PageConfig): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
window.sessionStorage.setItem(`${STORAGE_PREFIX}${pageType}`, JSON.stringify(config));
|
||||
window.sessionStorage.setItem(`${STORAGE_PREFIX}${pageType}${STORAGE_TIMESTAMP_SUFFIX}`, Date.now().toString());
|
||||
const key = getStorageKey(tenantId, pageType);
|
||||
window.sessionStorage.setItem(key, JSON.stringify(config));
|
||||
window.sessionStorage.setItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`, Date.now().toString());
|
||||
} catch (error) {
|
||||
console.error(`[sessionStorage] Failed to set config for ${pageType}:`, error);
|
||||
}
|
||||
@@ -137,12 +151,13 @@ function setConfigToSessionStorage(pageType: PageType, config: PageConfig): void
|
||||
/**
|
||||
* sessionStorage에서 페이지 구성 삭제
|
||||
*/
|
||||
function removeConfigFromSessionStorage(pageType: PageType): void {
|
||||
function removeConfigFromSessionStorage(tenantId: number | null, pageType: PageType): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
window.sessionStorage.removeItem(`${STORAGE_PREFIX}${pageType}`);
|
||||
window.sessionStorage.removeItem(`${STORAGE_PREFIX}${pageType}${STORAGE_TIMESTAMP_SUFFIX}`);
|
||||
const key = getStorageKey(tenantId, pageType);
|
||||
window.sessionStorage.removeItem(key);
|
||||
window.sessionStorage.removeItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`);
|
||||
} catch (error) {
|
||||
console.error(`[sessionStorage] Failed to remove config for ${pageType}:`, error);
|
||||
}
|
||||
@@ -156,10 +171,15 @@ export const useMasterDataStore = create<MasterDataStore>()(
|
||||
// Initial state
|
||||
...initialState,
|
||||
|
||||
// ===== 테넌트 관리 =====
|
||||
|
||||
setCurrentTenantId: (tenantId: number | null) =>
|
||||
set({ currentTenantId: tenantId }, false, 'setCurrentTenantId'),
|
||||
|
||||
// ===== 하이브리드 로딩 전략 =====
|
||||
|
||||
fetchPageConfig: async (pageType: PageType) => {
|
||||
const { pageConfigs, loading, errors } = get();
|
||||
const { pageConfigs, loading, errors, currentTenantId } = get();
|
||||
|
||||
// 🔒 이미 로딩 중이면 중복 요청 방지
|
||||
if (loading[pageType]) {
|
||||
@@ -180,7 +200,7 @@ export const useMasterDataStore = create<MasterDataStore>()(
|
||||
}
|
||||
|
||||
// 2️⃣ sessionStorage 확인
|
||||
const cachedConfig = getConfigFromSessionStorage(pageType);
|
||||
const cachedConfig = getConfigFromSessionStorage(currentTenantId, pageType);
|
||||
if (cachedConfig) {
|
||||
console.log(`✅ [Cache Hit - Session] ${pageType}`);
|
||||
|
||||
@@ -239,7 +259,7 @@ export const useMasterDataStore = create<MasterDataStore>()(
|
||||
'fetchPageConfig/success'
|
||||
);
|
||||
|
||||
setConfigToSessionStorage(pageType, config);
|
||||
setConfigToSessionStorage(currentTenantId, pageType, config);
|
||||
|
||||
console.log(`✅ [Config Loaded] ${pageType}`, config);
|
||||
return config;
|
||||
@@ -270,7 +290,7 @@ export const useMasterDataStore = create<MasterDataStore>()(
|
||||
// ===== 캐시 무효화 =====
|
||||
|
||||
invalidateConfig: (pageType: PageType) => {
|
||||
const { pageConfigs } = get();
|
||||
const { pageConfigs, currentTenantId } = get();
|
||||
const newConfigs = { ...pageConfigs };
|
||||
delete newConfigs[pageType];
|
||||
|
||||
@@ -280,16 +300,17 @@ export const useMasterDataStore = create<MasterDataStore>()(
|
||||
'invalidateConfig'
|
||||
);
|
||||
|
||||
removeConfigFromSessionStorage(pageType);
|
||||
removeConfigFromSessionStorage(currentTenantId, pageType);
|
||||
|
||||
console.log(`🗑️ [Cache Invalidated] ${pageType}`);
|
||||
},
|
||||
|
||||
invalidateAllConfigs: () => {
|
||||
const { currentTenantId } = get();
|
||||
const pageTypes: PageType[] = ['item-master', 'quotation', 'sales-order', 'formula', 'pricing'];
|
||||
|
||||
pageTypes.forEach((pageType) => {
|
||||
removeConfigFromSessionStorage(pageType);
|
||||
removeConfigFromSessionStorage(currentTenantId, pageType);
|
||||
});
|
||||
|
||||
set(
|
||||
@@ -376,11 +397,12 @@ export const useMasterDataStore = create<MasterDataStore>()(
|
||||
// 1. 메모리 캐시 초기화
|
||||
set(initialState, false, 'reset');
|
||||
|
||||
// 2. sessionStorage 캐시도 정리
|
||||
// 2. sessionStorage 캐시도 정리 (프리픽스 기반으로 모든 테넌트 캐시 제거)
|
||||
if (typeof window !== 'undefined') {
|
||||
const pageTypes: PageType[] = ['item-master', 'quotation', 'sales-order', 'formula', 'pricing'];
|
||||
pageTypes.forEach((pageType) => {
|
||||
removeConfigFromSessionStorage(pageType);
|
||||
Object.keys(window.sessionStorage).forEach(key => {
|
||||
if (key.startsWith(STORAGE_PREFIX)) {
|
||||
window.sessionStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
console.log('[masterDataStore] Reset: cleared memory and sessionStorage cache');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user