2025-11-18 14:17:52 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 품목기준관리 Zustand Store
|
|
|
|
|
|
*
|
|
|
|
|
|
* 하이브리드 로딩 전략:
|
|
|
|
|
|
* 1단계: Zustand (메모리 캐시)
|
|
|
|
|
|
* 2단계: sessionStorage (브라우저 세션)
|
|
|
|
|
|
* 3단계: API/Redis (백엔드 캐시, 10분 TTL)
|
|
|
|
|
|
* 4단계: Database (영구 저장소)
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { create } from 'zustand';
|
|
|
|
|
|
import { devtools } from 'zustand/middleware';
|
2025-12-09 18:07:47 +09:00
|
|
|
|
import { useShallow } from 'zustand/react/shallow';
|
2025-11-18 14:17:52 +09:00
|
|
|
|
import type { PageConfig, PageType, DynamicFormData } from '@/types/master-data';
|
|
|
|
|
|
import { fetchPageConfigByType, invalidatePageConfigCache } from '@/lib/api/master-data';
|
|
|
|
|
|
|
|
|
|
|
|
// ===== Store 타입 정의 =====
|
|
|
|
|
|
|
|
|
|
|
|
interface MasterDataStore {
|
|
|
|
|
|
// === State ===
|
|
|
|
|
|
|
2025-12-15 09:21:43 +09:00
|
|
|
|
// 현재 테넌트 ID (캐시 격리용)
|
|
|
|
|
|
currentTenantId: number | null;
|
|
|
|
|
|
|
2025-11-18 14:17:52 +09:00
|
|
|
|
// 페이지 구성 캐시 (페이지 타입별)
|
|
|
|
|
|
pageConfigs: Record<PageType, PageConfig | null>;
|
|
|
|
|
|
|
|
|
|
|
|
// 로딩 상태
|
|
|
|
|
|
loading: Record<PageType, boolean>;
|
|
|
|
|
|
|
|
|
|
|
|
// 에러 상태
|
|
|
|
|
|
errors: Record<PageType, string | null>;
|
|
|
|
|
|
|
|
|
|
|
|
// 선택된 페이지 타입
|
|
|
|
|
|
selectedPageType: PageType | null;
|
|
|
|
|
|
|
|
|
|
|
|
// 폼 데이터 (임시 저장)
|
|
|
|
|
|
formData: Record<string, DynamicFormData>;
|
|
|
|
|
|
|
|
|
|
|
|
// === Actions ===
|
|
|
|
|
|
|
2025-12-15 09:21:43 +09:00
|
|
|
|
// 테넌트 ID 설정 (로그인 시 호출)
|
|
|
|
|
|
setCurrentTenantId: (tenantId: number | null) => void;
|
|
|
|
|
|
|
2025-11-18 14:17:52 +09:00
|
|
|
|
// 페이지 구성 가져오기 (하이브리드 로딩)
|
2025-12-15 09:21:43 +09:00
|
|
|
|
// tenantId 파라미터는 선택적 (없으면 currentTenantId 사용)
|
|
|
|
|
|
fetchPageConfig: (pageType: PageType, tenantId?: number) => Promise<PageConfig | null>;
|
2025-11-18 14:17:52 +09:00
|
|
|
|
|
|
|
|
|
|
// 페이지 구성 캐시 무효화
|
|
|
|
|
|
invalidateConfig: (pageType: PageType) => void;
|
|
|
|
|
|
|
|
|
|
|
|
// 모든 캐시 무효화
|
|
|
|
|
|
invalidateAllConfigs: () => void;
|
|
|
|
|
|
|
|
|
|
|
|
// 선택된 페이지 타입 설정
|
|
|
|
|
|
setSelectedPageType: (pageType: PageType | null) => void;
|
|
|
|
|
|
|
|
|
|
|
|
// 폼 데이터 관리
|
|
|
|
|
|
setFormData: (id: string, data: DynamicFormData) => void;
|
|
|
|
|
|
getFormData: (id: string) => DynamicFormData | undefined;
|
|
|
|
|
|
clearFormData: (id: string) => void;
|
|
|
|
|
|
|
|
|
|
|
|
// 로딩/에러 상태
|
|
|
|
|
|
setLoading: (pageType: PageType, loading: boolean) => void;
|
|
|
|
|
|
setError: (pageType: PageType, error: string | null) => void;
|
|
|
|
|
|
|
|
|
|
|
|
// 초기화
|
|
|
|
|
|
reset: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 초기 상태 =====
|
|
|
|
|
|
|
2025-12-09 18:07:47 +09:00
|
|
|
|
const createEmptyPageConfigs = (): Record<PageType, PageConfig | null> => ({
|
|
|
|
|
|
'item-master': null,
|
|
|
|
|
|
'quotation': null,
|
|
|
|
|
|
'sales-order': null,
|
|
|
|
|
|
'formula': null,
|
|
|
|
|
|
'pricing': null,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-18 14:17:52 +09:00
|
|
|
|
const initialState = {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
currentTenantId: null as number | null,
|
2025-12-09 18:07:47 +09:00
|
|
|
|
pageConfigs: createEmptyPageConfigs(),
|
2025-11-18 14:17:52 +09:00
|
|
|
|
loading: {} as Record<PageType, boolean>,
|
|
|
|
|
|
errors: {} as Record<PageType, string | null>,
|
2025-12-09 18:07:47 +09:00
|
|
|
|
selectedPageType: null as PageType | null,
|
|
|
|
|
|
formData: {} as Record<string, DynamicFormData>,
|
2025-11-18 14:17:52 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ===== sessionStorage 유틸리티 =====
|
|
|
|
|
|
|
|
|
|
|
|
const STORAGE_PREFIX = 'page_config_';
|
|
|
|
|
|
const STORAGE_TIMESTAMP_SUFFIX = '_timestamp';
|
|
|
|
|
|
const CACHE_TTL = 10 * 60 * 1000; // 10분
|
|
|
|
|
|
|
2026-01-29 16:57:49 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 테넌트별 캐시 키 생성
|
|
|
|
|
|
* 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}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-18 14:17:52 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* sessionStorage에서 페이지 구성 가져오기
|
|
|
|
|
|
*/
|
2026-01-29 16:57:49 +09:00
|
|
|
|
function getConfigFromSessionStorage(tenantId: number | null, pageType: PageType): PageConfig | null {
|
2025-11-18 14:17:52 +09:00
|
|
|
|
if (typeof window === 'undefined') return null;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
const key = getStorageKey(tenantId, pageType);
|
|
|
|
|
|
const cachedData = window.sessionStorage.getItem(key);
|
|
|
|
|
|
const timestamp = window.sessionStorage.getItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`);
|
2025-11-18 14:17:52 +09:00
|
|
|
|
|
|
|
|
|
|
if (!cachedData || !timestamp) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// TTL 확인
|
|
|
|
|
|
const cacheAge = Date.now() - parseInt(timestamp, 10);
|
|
|
|
|
|
if (cacheAge > CACHE_TTL) {
|
|
|
|
|
|
// 만료된 캐시 삭제
|
2026-01-29 16:57:49 +09:00
|
|
|
|
window.sessionStorage.removeItem(key);
|
|
|
|
|
|
window.sessionStorage.removeItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`);
|
2025-11-18 14:17:52 +09:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return JSON.parse(cachedData);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`[sessionStorage] Failed to get config for ${pageType}:`, error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* sessionStorage에 페이지 구성 저장
|
|
|
|
|
|
*/
|
2026-01-29 16:57:49 +09:00
|
|
|
|
function setConfigToSessionStorage(tenantId: number | null, pageType: PageType, config: PageConfig): void {
|
2025-11-18 14:17:52 +09:00
|
|
|
|
if (typeof window === 'undefined') return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
const key = getStorageKey(tenantId, pageType);
|
|
|
|
|
|
window.sessionStorage.setItem(key, JSON.stringify(config));
|
|
|
|
|
|
window.sessionStorage.setItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`, Date.now().toString());
|
2025-11-18 14:17:52 +09:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`[sessionStorage] Failed to set config for ${pageType}:`, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* sessionStorage에서 페이지 구성 삭제
|
|
|
|
|
|
*/
|
2026-01-29 16:57:49 +09:00
|
|
|
|
function removeConfigFromSessionStorage(tenantId: number | null, pageType: PageType): void {
|
2025-11-18 14:17:52 +09:00
|
|
|
|
if (typeof window === 'undefined') return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
const key = getStorageKey(tenantId, pageType);
|
|
|
|
|
|
window.sessionStorage.removeItem(key);
|
|
|
|
|
|
window.sessionStorage.removeItem(`${key}${STORAGE_TIMESTAMP_SUFFIX}`);
|
2025-11-18 14:17:52 +09:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`[sessionStorage] Failed to remove config for ${pageType}:`, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===== Store 생성 =====
|
|
|
|
|
|
|
|
|
|
|
|
export const useMasterDataStore = create<MasterDataStore>()(
|
|
|
|
|
|
devtools(
|
|
|
|
|
|
(set, get) => ({
|
|
|
|
|
|
// Initial state
|
|
|
|
|
|
...initialState,
|
|
|
|
|
|
|
2026-01-29 16:57:49 +09:00
|
|
|
|
// ===== 테넌트 관리 =====
|
|
|
|
|
|
|
|
|
|
|
|
setCurrentTenantId: (tenantId: number | null) =>
|
|
|
|
|
|
set({ currentTenantId: tenantId }, false, 'setCurrentTenantId'),
|
|
|
|
|
|
|
2025-11-18 14:17:52 +09:00
|
|
|
|
// ===== 하이브리드 로딩 전략 =====
|
|
|
|
|
|
|
|
|
|
|
|
fetchPageConfig: async (pageType: PageType) => {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
const { pageConfigs, loading, errors, currentTenantId } = get();
|
2025-11-18 14:17:52 +09:00
|
|
|
|
|
|
|
|
|
|
// 🔒 이미 로딩 중이면 중복 요청 방지
|
|
|
|
|
|
if (loading[pageType]) {
|
|
|
|
|
|
return pageConfigs[pageType] || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔒 최근에 에러가 발생했으면 재시도 방지 (캐시 무효화 전까지)
|
|
|
|
|
|
if (errors[pageType] && pageConfigs[pageType] === null) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1️⃣ 메모리 캐시 확인 (Zustand)
|
|
|
|
|
|
if (pageConfigs[pageType]) {
|
|
|
|
|
|
return pageConfigs[pageType];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2️⃣ sessionStorage 확인
|
2026-01-29 16:57:49 +09:00
|
|
|
|
const cachedConfig = getConfigFromSessionStorage(currentTenantId, pageType);
|
2025-11-18 14:17:52 +09:00
|
|
|
|
if (cachedConfig) {
|
|
|
|
|
|
|
|
|
|
|
|
// 메모리에 저장
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => ({
|
|
|
|
|
|
pageConfigs: {
|
|
|
|
|
|
...state.pageConfigs,
|
|
|
|
|
|
[pageType]: cachedConfig,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
false,
|
|
|
|
|
|
'fetchPageConfig/cacheHit'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return cachedConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3️⃣ API 요청 (Redis/Database)
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
get().setLoading(pageType, true);
|
|
|
|
|
|
get().setError(pageType, null);
|
|
|
|
|
|
|
|
|
|
|
|
const config = await fetchPageConfigByType(pageType);
|
|
|
|
|
|
|
|
|
|
|
|
if (!config) {
|
|
|
|
|
|
console.warn(`⚠️ [Config Not Found] ${pageType}`);
|
|
|
|
|
|
get().setError(pageType, '페이지 구성을 찾을 수 없습니다.');
|
|
|
|
|
|
|
|
|
|
|
|
// 에러 상태지만 null을 캐시에 저장하여 재시도 방지
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => ({
|
|
|
|
|
|
pageConfigs: {
|
|
|
|
|
|
...state.pageConfigs,
|
|
|
|
|
|
[pageType]: null,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
false,
|
|
|
|
|
|
'fetchPageConfig/notFound'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 캐시 저장 (메모리 + sessionStorage)
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => ({
|
|
|
|
|
|
pageConfigs: {
|
|
|
|
|
|
...state.pageConfigs,
|
|
|
|
|
|
[pageType]: config,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
false,
|
|
|
|
|
|
'fetchPageConfig/success'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-01-29 16:57:49 +09:00
|
|
|
|
setConfigToSessionStorage(currentTenantId, pageType, config);
|
2025-11-18 14:17:52 +09:00
|
|
|
|
|
|
|
|
|
|
return config;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : '페이지 구성 조회 실패';
|
|
|
|
|
|
console.error(`❌ [API Error] ${pageType}:`, error);
|
|
|
|
|
|
|
|
|
|
|
|
get().setError(pageType, errorMessage);
|
|
|
|
|
|
|
|
|
|
|
|
// 에러 상태를 캐시에 저장하여 재시도 방지
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => ({
|
|
|
|
|
|
pageConfigs: {
|
|
|
|
|
|
...state.pageConfigs,
|
|
|
|
|
|
[pageType]: null,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
false,
|
|
|
|
|
|
'fetchPageConfig/error'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
get().setLoading(pageType, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 캐시 무효화 =====
|
|
|
|
|
|
|
|
|
|
|
|
invalidateConfig: (pageType: PageType) => {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
const { pageConfigs, currentTenantId } = get();
|
2025-11-18 14:17:52 +09:00
|
|
|
|
const newConfigs = { ...pageConfigs };
|
|
|
|
|
|
delete newConfigs[pageType];
|
|
|
|
|
|
|
|
|
|
|
|
set(
|
|
|
|
|
|
{ pageConfigs: newConfigs },
|
|
|
|
|
|
false,
|
|
|
|
|
|
'invalidateConfig'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-01-29 16:57:49 +09:00
|
|
|
|
removeConfigFromSessionStorage(currentTenantId, pageType);
|
2025-11-18 14:17:52 +09:00
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
invalidateAllConfigs: () => {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
const { currentTenantId } = get();
|
2025-11-18 14:17:52 +09:00
|
|
|
|
const pageTypes: PageType[] = ['item-master', 'quotation', 'sales-order', 'formula', 'pricing'];
|
|
|
|
|
|
|
|
|
|
|
|
pageTypes.forEach((pageType) => {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
removeConfigFromSessionStorage(currentTenantId, pageType);
|
2025-11-18 14:17:52 +09:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
set(
|
2025-12-09 18:07:47 +09:00
|
|
|
|
{ pageConfigs: createEmptyPageConfigs() },
|
2025-11-18 14:17:52 +09:00
|
|
|
|
false,
|
|
|
|
|
|
'invalidateAllConfigs'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 서버 캐시도 무효화
|
|
|
|
|
|
invalidatePageConfigCache().catch((error) => {
|
|
|
|
|
|
console.error('[Cache Invalidation Error]:', error);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 페이지 타입 선택 =====
|
|
|
|
|
|
|
|
|
|
|
|
setSelectedPageType: (pageType) =>
|
|
|
|
|
|
set(
|
|
|
|
|
|
{ selectedPageType: pageType },
|
|
|
|
|
|
false,
|
|
|
|
|
|
'setSelectedPageType'
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 폼 데이터 관리 =====
|
|
|
|
|
|
|
|
|
|
|
|
setFormData: (id, data) =>
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => ({
|
|
|
|
|
|
formData: {
|
|
|
|
|
|
...state.formData,
|
|
|
|
|
|
[id]: data,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
false,
|
|
|
|
|
|
'setFormData'
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
getFormData: (id) => {
|
|
|
|
|
|
return get().formData[id];
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
clearFormData: (id) =>
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => {
|
|
|
|
|
|
const newFormData = { ...state.formData };
|
|
|
|
|
|
delete newFormData[id];
|
|
|
|
|
|
return { formData: newFormData };
|
|
|
|
|
|
},
|
|
|
|
|
|
false,
|
|
|
|
|
|
'clearFormData'
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 로딩/에러 상태 =====
|
|
|
|
|
|
|
|
|
|
|
|
setLoading: (pageType, loading) =>
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => ({
|
|
|
|
|
|
loading: {
|
|
|
|
|
|
...state.loading,
|
|
|
|
|
|
[pageType]: loading,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
false,
|
|
|
|
|
|
'setLoading'
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
setError: (pageType, error) =>
|
|
|
|
|
|
set(
|
|
|
|
|
|
(state) => ({
|
|
|
|
|
|
errors: {
|
|
|
|
|
|
...state.errors,
|
|
|
|
|
|
[pageType]: error,
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
false,
|
|
|
|
|
|
'setError'
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 초기화 =====
|
|
|
|
|
|
|
2025-12-15 09:21:43 +09:00
|
|
|
|
reset: () => {
|
|
|
|
|
|
// 1. 메모리 캐시 초기화
|
|
|
|
|
|
set(initialState, false, 'reset');
|
|
|
|
|
|
|
2026-01-29 16:57:49 +09:00
|
|
|
|
// 2. sessionStorage 캐시도 정리 (프리픽스 기반으로 모든 테넌트 캐시 제거)
|
2025-12-15 09:21:43 +09:00
|
|
|
|
if (typeof window !== 'undefined') {
|
2026-01-29 16:57:49 +09:00
|
|
|
|
Object.keys(window.sessionStorage).forEach(key => {
|
|
|
|
|
|
if (key.startsWith(STORAGE_PREFIX)) {
|
|
|
|
|
|
window.sessionStorage.removeItem(key);
|
|
|
|
|
|
}
|
2025-12-15 09:21:43 +09:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-11-18 14:17:52 +09:00
|
|
|
|
}),
|
|
|
|
|
|
{
|
|
|
|
|
|
name: 'MasterDataStore',
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// ===== Selector Hooks (성능 최적화) =====
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 특정 페이지 타입의 구성만 구독
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const usePageConfig = (pageType: PageType) =>
|
|
|
|
|
|
useMasterDataStore((state) => state.pageConfigs[pageType]);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 로딩 상태만 구독
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const usePageConfigLoading = (pageType: PageType) =>
|
|
|
|
|
|
useMasterDataStore((state) => state.loading[pageType]);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 에러 상태만 구독
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const usePageConfigError = (pageType: PageType) =>
|
|
|
|
|
|
useMasterDataStore((state) => state.errors[pageType]);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 선택된 페이지 타입만 구독
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const useSelectedPageType = () =>
|
|
|
|
|
|
useMasterDataStore((state) => state.selectedPageType);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 액션만 가져오기 (리렌더링 방지)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const useMasterDataActions = () =>
|
|
|
|
|
|
useMasterDataStore(
|
2025-12-09 18:07:47 +09:00
|
|
|
|
useShallow((state) => ({
|
2025-11-18 14:17:52 +09:00
|
|
|
|
fetchPageConfig: state.fetchPageConfig,
|
|
|
|
|
|
invalidateConfig: state.invalidateConfig,
|
|
|
|
|
|
invalidateAllConfigs: state.invalidateAllConfigs,
|
|
|
|
|
|
setSelectedPageType: state.setSelectedPageType,
|
|
|
|
|
|
setFormData: state.setFormData,
|
|
|
|
|
|
getFormData: state.getFormData,
|
|
|
|
|
|
clearFormData: state.clearFormData,
|
|
|
|
|
|
reset: state.reset,
|
2025-12-09 18:07:47 +09:00
|
|
|
|
}))
|
2025-11-18 14:17:52 +09:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// ===== 타입 추출 =====
|
|
|
|
|
|
|
|
|
|
|
|
export type { MasterDataStore };
|