ERP-node/frontend/components/layout/TabContent.tsx

114 lines
3.4 KiB
TypeScript

"use client";
import React, { Suspense, useRef, useState } from "react";
import { Loader2, Inbox } from "lucide-react";
import { useTab, TabItem } from "@/contexts/TabContext";
import { DialogPortalContainerContext } from "@/contexts/DialogPortalContext";
import { ScreenViewPageEmbeddable } from "@/app/(main)/screens/[screenId]/page";
function TabLoadingFallback() {
return (
<div className="flex h-full min-h-[400px] w-full items-center justify-center">
<div className="rounded-xl border border-slate-200 bg-white p-8 text-center shadow-lg">
<Loader2 className="text-primary mx-auto h-10 w-10 animate-spin" />
<p className="mt-4 font-medium text-slate-900"> ...</p>
</div>
</div>
);
}
function EmptyTabState() {
return (
<div className="flex h-full w-full items-center justify-center bg-white">
<div className="flex flex-col items-center py-12 text-center">
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-slate-100">
<Inbox className="h-8 w-8 text-slate-400" />
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-700"> </h3>
<p className="max-w-sm text-sm text-slate-500">
.
</p>
</div>
</div>
);
}
/**
* 개별 탭 패널 - 탭별 Dialog 포탈 컨테이너를 제공
* 탭이 display:none이면 포탈 컨테이너도 숨겨져 모달이 자동으로 가려짐
*/
function TabPanel({
isActive,
isMounted,
children,
}: {
isActive: boolean;
isMounted: boolean;
children: React.ReactNode;
}) {
const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(null);
return (
<div
className="h-full w-full"
style={{ display: isActive ? "block" : "none" }}
>
<div ref={setPortalContainer} />
<DialogPortalContainerContext.Provider value={portalContainer}>
{isMounted ? children : null}
</DialogPortalContainerContext.Provider>
</div>
);
}
export function TabContent() {
const { tabs, activeTabId, refreshKeys } = useTab();
const stableOrderRef = useRef<TabItem[]>([]);
const mountedTabIdsRef = useRef<Set<string>>(
new Set(activeTabId ? [activeTabId] : [])
);
if (activeTabId && !mountedTabIdsRef.current.has(activeTabId)) {
mountedTabIdsRef.current.add(activeTabId);
}
const currentIds = new Set(tabs.map(t => t.id));
const stableIds = new Set(stableOrderRef.current.map(t => t.id));
stableOrderRef.current = stableOrderRef.current.filter(t => currentIds.has(t.id));
for (const id of mountedTabIdsRef.current) {
if (!currentIds.has(id)) mountedTabIdsRef.current.delete(id);
}
for (const tab of tabs) {
if (!stableIds.has(tab.id)) {
stableOrderRef.current.push(tab);
}
}
const stableTabs = stableOrderRef.current;
if (stableTabs.length === 0) {
return <EmptyTabState />;
}
return (
<>
{stableTabs.map((tab) => (
<TabPanel
key={`${tab.id}-${refreshKeys[tab.id] || 0}`}
isActive={tab.id === activeTabId}
isMounted={mountedTabIdsRef.current.has(tab.id)}
>
<Suspense fallback={<TabLoadingFallback />}>
<ScreenViewPageEmbeddable
screenId={tab.screenId}
menuObjid={tab.menuObjid}
/>
</Suspense>
</TabPanel>
))}
</>
);
}