ERP-node/frontend/stores/tabStore.ts

225 lines
6.9 KiB
TypeScript

"use client";
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
import { clearTabCache } from "@/lib/tabStateCache";
// --- 타입 정의 ---
export type AppMode = "user" | "admin";
export interface Tab {
id: string;
type: "screen" | "admin";
title: string;
screenId?: number;
menuObjid?: number;
adminUrl?: string;
}
interface ModeTabData {
tabs: Tab[];
activeTabId: string | null;
}
interface TabState {
mode: AppMode;
user: ModeTabData;
admin: ModeTabData;
refreshKeys: Record<string, number>;
setMode: (mode: AppMode) => void;
openTab: (tab: Omit<Tab, "id">, insertIndex?: number) => void;
closeTab: (tabId: string) => void;
switchTab: (tabId: string) => void;
refreshTab: (tabId: string) => void;
closeOtherTabs: (tabId: string) => void;
closeTabsToLeft: (tabId: string) => void;
closeTabsToRight: (tabId: string) => void;
closeAllTabs: () => void;
updateTabOrder: (fromIndex: number, toIndex: number) => void;
}
// --- 헬퍼 함수 ---
function generateTabId(tab: Omit<Tab, "id">): string {
if (tab.type === "screen" && tab.screenId != null) {
return `tab-screen-${tab.screenId}-${tab.menuObjid ?? 0}`;
}
if (tab.type === "admin" && tab.adminUrl) {
return `tab-admin-${tab.adminUrl.replace(/[^a-zA-Z0-9]/g, "-")}`;
}
return `tab-${Date.now()}`;
}
function findDuplicateTab(tabs: Tab[], newTab: Omit<Tab, "id">): Tab | undefined {
if (newTab.type === "screen" && newTab.screenId != null) {
return tabs.find(
(t) => t.type === "screen" && t.screenId === newTab.screenId && t.menuObjid === newTab.menuObjid,
);
}
if (newTab.type === "admin" && newTab.adminUrl) {
return tabs.find((t) => t.type === "admin" && t.adminUrl === newTab.adminUrl);
}
return undefined;
}
function getNextActiveTabId(tabs: Tab[], closedTabId: string, currentActiveId: string | null): string | null {
if (currentActiveId !== closedTabId) return currentActiveId;
const idx = tabs.findIndex((t) => t.id === closedTabId);
if (idx === -1) return null;
const remaining = tabs.filter((t) => t.id !== closedTabId);
if (remaining.length === 0) return null;
if (idx > 0) return remaining[Math.min(idx - 1, remaining.length - 1)].id;
return remaining[0].id;
}
// 현재 모드의 데이터 키 반환
function modeKey(state: TabState): AppMode {
return state.mode;
}
// --- 셀렉터 (컴포넌트에서 사용) ---
export function selectTabs(state: TabState): Tab[] {
return state[state.mode].tabs;
}
export function selectActiveTabId(state: TabState): string | null {
return state[state.mode].activeTabId;
}
// --- Store ---
const EMPTY_MODE: ModeTabData = { tabs: [], activeTabId: null };
export const useTabStore = create<TabState>()(
devtools(
persist(
(set, get) => ({
mode: "user" as AppMode,
user: { ...EMPTY_MODE },
admin: { ...EMPTY_MODE },
refreshKeys: {},
setMode: (mode) => {
set({ mode });
},
openTab: (tabData, insertIndex) => {
const mk = modeKey(get());
const modeData = get()[mk];
const existing = findDuplicateTab(modeData.tabs, tabData);
if (existing) {
set({ [mk]: { ...modeData, activeTabId: existing.id } });
return;
}
const id = generateTabId(tabData);
const newTab: Tab = { ...tabData, id };
const newTabs = [...modeData.tabs];
if (insertIndex != null && insertIndex >= 0 && insertIndex <= newTabs.length) {
newTabs.splice(insertIndex, 0, newTab);
} else {
newTabs.push(newTab);
}
set({ [mk]: { tabs: newTabs, activeTabId: id } });
},
closeTab: (tabId) => {
clearTabCache(tabId);
const mk = modeKey(get());
const modeData = get()[mk];
const nextActive = getNextActiveTabId(modeData.tabs, tabId, modeData.activeTabId);
const newTabs = modeData.tabs.filter((t) => t.id !== tabId);
const { [tabId]: _, ...restKeys } = get().refreshKeys;
set({ [mk]: { tabs: newTabs, activeTabId: nextActive }, refreshKeys: restKeys });
},
switchTab: (tabId) => {
const mk = modeKey(get());
const modeData = get()[mk];
set({ [mk]: { ...modeData, activeTabId: tabId } });
},
refreshTab: (tabId) => {
set((state) => ({
refreshKeys: { ...state.refreshKeys, [tabId]: (state.refreshKeys[tabId] || 0) + 1 },
}));
},
closeOtherTabs: (tabId) => {
const mk = modeKey(get());
const modeData = get()[mk];
modeData.tabs.filter((t) => t.id !== tabId).forEach((t) => clearTabCache(t.id));
set({ [mk]: { tabs: modeData.tabs.filter((t) => t.id === tabId), activeTabId: tabId } });
},
closeTabsToLeft: (tabId) => {
const mk = modeKey(get());
const modeData = get()[mk];
const idx = modeData.tabs.findIndex((t) => t.id === tabId);
if (idx === -1) return;
modeData.tabs.slice(0, idx).forEach((t) => clearTabCache(t.id));
set({ [mk]: { tabs: modeData.tabs.slice(idx), activeTabId: tabId } });
},
closeTabsToRight: (tabId) => {
const mk = modeKey(get());
const modeData = get()[mk];
const idx = modeData.tabs.findIndex((t) => t.id === tabId);
if (idx === -1) return;
modeData.tabs.slice(idx + 1).forEach((t) => clearTabCache(t.id));
set({ [mk]: { tabs: modeData.tabs.slice(0, idx + 1), activeTabId: tabId } });
},
closeAllTabs: () => {
const mk = modeKey(get());
const modeData = get()[mk];
modeData.tabs.forEach((t) => clearTabCache(t.id));
set({ [mk]: { tabs: [], activeTabId: null } });
},
updateTabOrder: (fromIndex, toIndex) => {
const mk = modeKey(get());
const modeData = get()[mk];
const newTabs = [...modeData.tabs];
const [moved] = newTabs.splice(fromIndex, 1);
newTabs.splice(toIndex, 0, moved);
set({ [mk]: { ...modeData, tabs: newTabs } });
},
}),
{
name: "erp-tab-store",
storage: {
getItem: (name) => {
if (typeof window === "undefined") return null;
const raw = sessionStorage.getItem(name);
return raw ? JSON.parse(raw) : null;
},
setItem: (name, value) => {
if (typeof window === "undefined") return;
sessionStorage.setItem(name, JSON.stringify(value));
},
removeItem: (name) => {
if (typeof window === "undefined") return;
sessionStorage.removeItem(name);
},
},
partialize: (state) => ({
mode: state.mode,
user: state.user,
admin: state.admin,
}) as unknown as TabState,
},
),
{ name: "TabStore" },
),
);