"use client"; import React, { useRef, useEffect, useCallback } from "react"; import { useTabStore, selectTabs, selectActiveTabId } from "@/stores/tabStore"; import { ScreenViewPageWrapper } from "@/app/(main)/screens/[screenId]/page"; import { AdminPageRenderer } from "./AdminPageRenderer"; import { EmptyDashboard } from "./EmptyDashboard"; import { TabIdProvider } from "@/contexts/TabIdContext"; import { registerModalPortal } from "@/lib/modalPortalRef"; import ScreenModal from "@/components/common/ScreenModal"; import { saveTabCacheImmediate, loadTabCache, captureAllScrollPositions, restoreAllScrollPositions, getElementPath, captureFormState, restoreFormState, clearTabCache, } from "@/lib/tabStateCache"; export function TabContent() { const tabs = useTabStore(selectTabs); const activeTabId = useTabStore(selectActiveTabId); const refreshKeys = useTabStore((s) => s.refreshKeys); // 한 번이라도 활성화된 탭만 마운트 (지연 마운트) const mountedTabIdsRef = useRef>(new Set()); // 각 탭의 스크롤 컨테이너 ref const scrollRefsMap = useRef>(new Map()); // 이전 활성 탭 ID 추적 const prevActiveTabIdRef = useRef(null); // 활성 탭의 스크롤 위치를 실시간 추적 (display:none 전에 캡처하기 위함) // Map> - 탭 내 여러 스크롤 영역을 각각 추적 const lastScrollMapRef = useRef>>(new Map()); // 요소 → 경로 캐시 (매 스크롤 이벤트마다 경로를 재계산하지 않기 위함) const pathCacheRef = useRef>(new WeakMap()); if (activeTabId) { mountedTabIdsRef.current.add(activeTabId); } // 활성 탭의 scroll 이벤트를 감지하여 요소별 위치를 실시간 저장 useEffect(() => { if (!activeTabId) return; const container = scrollRefsMap.current.get(activeTabId); if (!container) return; const handleScroll = (e: Event) => { const target = e.target as HTMLElement; let path = pathCacheRef.current.get(target); if (path === undefined) { path = getElementPath(target, container); pathCacheRef.current.set(target, path); } if (path === null) return; let tabMap = lastScrollMapRef.current.get(activeTabId); if (!tabMap) { tabMap = new Map(); lastScrollMapRef.current.set(activeTabId, tabMap); } if (target.scrollTop > 0 || target.scrollLeft > 0) { tabMap.set(path, { top: target.scrollTop, left: target.scrollLeft }); } else { tabMap.delete(path); } }; container.addEventListener("scroll", handleScroll, true); return () => container.removeEventListener("scroll", handleScroll, true); }, [activeTabId]); // 복원 관련 cleanup ref const scrollRestoreCleanupRef = useRef<(() => void) | null>(null); const formRestoreCleanupRef = useRef<(() => void) | null>(null); // 탭 전환 시: 이전 탭 상태 캐싱, 새 탭 상태 복원 useEffect(() => { // 이전 복원 작업 취소 if (scrollRestoreCleanupRef.current) { scrollRestoreCleanupRef.current(); scrollRestoreCleanupRef.current = null; } if (formRestoreCleanupRef.current) { formRestoreCleanupRef.current(); formRestoreCleanupRef.current = null; } const prevId = prevActiveTabIdRef.current; // 이전 활성 탭의 스크롤 + 폼 상태 저장 // 키를 항상 포함하여 이전 캐시의 오래된 값이 병합으로 살아남지 않도록 함 if (prevId && prevId !== activeTabId) { const tabMap = lastScrollMapRef.current.get(prevId); const scrollPositions = tabMap && tabMap.size > 0 ? Array.from(tabMap.entries()).map(([path, pos]) => ({ path, ...pos })) : undefined; const prevEl = scrollRefsMap.current.get(prevId); const formFields = captureFormState(prevEl ?? null); saveTabCacheImmediate(prevId, { scrollPositions, domFormFields: formFields ?? undefined, }); } // 새 활성 탭의 스크롤 + 폼 상태 복원 if (activeTabId) { const cache = loadTabCache(activeTabId); if (cache) { const el = scrollRefsMap.current.get(activeTabId); if (cache.scrollPositions) { const cleanup = restoreAllScrollPositions(el ?? null, cache.scrollPositions); if (cleanup) scrollRestoreCleanupRef.current = cleanup; } if (cache.domFormFields) { const cleanup = restoreFormState(el ?? null, cache.domFormFields ?? null); if (cleanup) formRestoreCleanupRef.current = cleanup; } } } prevActiveTabIdRef.current = activeTabId; }, [activeTabId]); // F5 새로고침 직전에 활성 탭의 스크롤/폼 상태를 저장 useEffect(() => { const handleBeforeUnload = () => { const currentActiveId = prevActiveTabIdRef.current; if (!currentActiveId) return; const el = scrollRefsMap.current.get(currentActiveId); // 활성 탭은 display:block이므로 DOM에서 직접 캡처 (가장 정확) const scrollPositions = captureAllScrollPositions(el ?? null); // DOM 캡처 실패 시 실시간 추적 데이터 fallback const tabMap = lastScrollMapRef.current.get(currentActiveId); const trackedPositions = !scrollPositions && tabMap && tabMap.size > 0 ? Array.from(tabMap.entries()).map(([path, pos]) => ({ path, ...pos })) : undefined; const finalPositions = scrollPositions || trackedPositions; const formFields = captureFormState(el ?? null); saveTabCacheImmediate(currentActiveId, { scrollPositions: finalPositions, domFormFields: formFields ?? undefined, }); }; window.addEventListener("beforeunload", handleBeforeUnload); return () => { window.removeEventListener("beforeunload", handleBeforeUnload); if (scrollRestoreCleanupRef.current) scrollRestoreCleanupRef.current(); if (formRestoreCleanupRef.current) formRestoreCleanupRef.current(); }; }, []); // 탭 닫기 시 캐시 정리 (tabs 배열 변화 감지) useEffect(() => { const currentTabIds = new Set(tabs.map((t) => t.id)); const mountedIds = mountedTabIdsRef.current; mountedIds.forEach((id) => { if (!currentTabIds.has(id)) { clearTabCache(id); scrollRefsMap.current.delete(id); mountedIds.delete(id); } }); }, [tabs]); const setScrollRef = useCallback((tabId: string, el: HTMLDivElement | null) => { scrollRefsMap.current.set(tabId, el); }, []); // 포탈 컨테이너 ref callback: 전역 레퍼런스에 등록 const portalRefCallback = useCallback((el: HTMLDivElement | null) => { registerModalPortal(el); }, []); if (tabs.length === 0) { return ; } const tabLookup = new Map(tabs.map((t) => [t.id, t])); const stableIds = Array.from(mountedTabIdsRef.current); return (
{stableIds.map((tabId) => { const tab = tabLookup.get(tabId); if (!tab) return null; const isActive = tab.id === activeTabId; const refreshKey = refreshKeys[tab.id] || 0; return (
setScrollRef(tab.id, el)} className="absolute inset-0 overflow-hidden" style={{ display: isActive ? "block" : "none" }} >
); })}
); } function TabPageRenderer({ tab, refreshKey, }: { tab: { id: string; type: string; screenId?: number; menuObjid?: number; adminUrl?: string }; refreshKey: number; }) { if (tab.type === "screen" && tab.screenId != null) { return ( ); } if (tab.type === "admin" && tab.adminUrl) { return (
); } return null; }