"use client"; import React, { useState, useEffect, useMemo } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { X, Loader2 } from "lucide-react"; import type { TabsComponent, TabItem } from "@/types/screen-management"; import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic"; import { cn } from "@/lib/utils"; import { useActiveTab } from "@/contexts/ActiveTabContext"; interface TabsWidgetProps { component: TabsComponent; className?: string; style?: React.CSSProperties; menuObjid?: number; // 부모 화면의 메뉴 OBJID } export function TabsWidget({ component, className, style, menuObjid }: TabsWidgetProps) { // ActiveTab context 사용 const { setActiveTab, removeTabsComponent } = useActiveTab(); const { tabs = [], defaultTab, orientation = "horizontal", variant = "default", allowCloseable = false, persistSelection = false, } = component; const storageKey = `tabs-${component.id}-selected`; // 초기 선택 탭 결정 const getInitialTab = () => { if (persistSelection && typeof window !== "undefined") { const saved = localStorage.getItem(storageKey); if (saved && tabs.some((t) => t.id === saved)) { return saved; } } return defaultTab || tabs[0]?.id || ""; }; const [selectedTab, setSelectedTab] = useState(getInitialTab()); const [visibleTabs, setVisibleTabs] = useState(tabs); const [loadingScreens, setLoadingScreens] = useState>({}); const [screenLayouts, setScreenLayouts] = useState>({}); // 🆕 한 번이라도 선택된 탭 추적 (지연 로딩 + 캐싱) const [mountedTabs, setMountedTabs] = useState>(() => new Set([getInitialTab()])); // 컴포넌트 탭 목록 변경 시 동기화 useEffect(() => { setVisibleTabs(tabs.filter((tab) => !tab.disabled)); }, [tabs]); // 선택된 탭 변경 시 localStorage에 저장 + ActiveTab Context 업데이트 useEffect(() => { if (persistSelection && typeof window !== "undefined") { localStorage.setItem(storageKey, selectedTab); } // ActiveTab Context에 현재 활성 탭 정보 등록 const currentTabInfo = visibleTabs.find(t => t.id === selectedTab); if (currentTabInfo) { setActiveTab(component.id, { tabId: selectedTab, tabsComponentId: component.id, screenId: currentTabInfo.screenId, label: currentTabInfo.label, }); } }, [selectedTab, persistSelection, storageKey, component.id, visibleTabs, setActiveTab]); // 컴포넌트 언마운트 시 ActiveTab Context에서 제거 useEffect(() => { return () => { removeTabsComponent(component.id); }; }, [component.id, removeTabsComponent]); // 초기 로드 시 선택된 탭의 화면 불러오기 useEffect(() => { const currentTab = visibleTabs.find((t) => t.id === selectedTab); if (currentTab && currentTab.screenId && !screenLayouts[currentTab.screenId]) { loadScreenLayout(currentTab.screenId); } }, [selectedTab, visibleTabs]); // 화면 레이아웃 로드 const loadScreenLayout = async (screenId: number) => { if (screenLayouts[screenId]) { return; // 이미 로드됨 } setLoadingScreens((prev) => ({ ...prev, [screenId]: true })); try { const { apiClient } = await import("@/lib/api/client"); const response = await apiClient.get(`/screen-management/screens/${screenId}/layout`); if (response.data.success && response.data.data) { setScreenLayouts((prev) => ({ ...prev, [screenId]: response.data.data })); } } catch (error) { console.error(`화면 레이아웃 로드 실패 ${screenId}:`, error); } finally { setLoadingScreens((prev) => ({ ...prev, [screenId]: false })); } }; // 탭 변경 핸들러 const handleTabChange = (tabId: string) => { setSelectedTab(tabId); // 마운트된 탭 목록에 추가 (한 번 마운트되면 유지) setMountedTabs(prev => { if (prev.has(tabId)) return prev; const newSet = new Set(prev); newSet.add(tabId); return newSet; }); // 해당 탭의 화면 로드 const tab = visibleTabs.find((t) => t.id === tabId); if (tab && tab.screenId && !screenLayouts[tab.screenId]) { loadScreenLayout(tab.screenId); } }; // 탭 닫기 핸들러 const handleCloseTab = (tabId: string, e: React.MouseEvent) => { e.stopPropagation(); const updatedTabs = visibleTabs.filter((tab) => tab.id !== tabId); setVisibleTabs(updatedTabs); // 닫은 탭이 선택된 탭이었다면 다음 탭 선택 if (selectedTab === tabId && updatedTabs.length > 0) { setSelectedTab(updatedTabs[0].id); } }; // 탭 스타일 클래스 const getTabsListClass = () => { const baseClass = orientation === "vertical" ? "flex-col" : ""; const variantClass = variant === "pills" ? "bg-muted p-1 rounded-lg" : variant === "underline" ? "border-b" : "bg-muted p-1"; return `${baseClass} ${variantClass}`; }; if (visibleTabs.length === 0) { return (

탭이 없습니다

); } return (
{visibleTabs.map((tab) => (
{tab.label} {allowCloseable && ( )}
))}
{/* 🆕 forceMount + CSS 숨김으로 탭 전환 시 리렌더링 방지 */}
{visibleTabs.map((tab) => { // 한 번도 선택되지 않은 탭은 렌더링하지 않음 (지연 로딩) const shouldRender = mountedTabs.has(tab.id); const isActive = selectedTab === tab.id; return ( {/* 한 번 마운트된 탭만 내용 렌더링 */} {shouldRender && ( <> {tab.screenId ? ( loadingScreens[tab.screenId] ? (
화면 로딩 중...
) : screenLayouts[tab.screenId] ? ( (() => { const layoutData = screenLayouts[tab.screenId]; const { components = [], screenResolution } = layoutData; const designWidth = screenResolution?.width || 1920; const designHeight = screenResolution?.height || 1080; return (
{components.map((comp: any) => ( ))}
); })() ) : (

화면을 불러올 수 없습니다

) ) : (

연결된 화면이 없습니다

)} )}
); })}
); }