ERP-node/frontend/app/(main)/screens/[screenId]/page.tsx

835 lines
31 KiB
TypeScript

"use client";
import React, { useEffect, useState, useMemo } from "react";
import { useParams, useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Loader2, FileQuestion, AlertTriangle } from "lucide-react";
import { screenApi } from "@/lib/api/screen";
import { ScreenDefinition, LayoutData, ComponentData } from "@/types/screen";
import { LayerDefinition } from "@/types/screen-management";
import { useRouter } from "next/navigation";
import { showErrorToast } from "@/lib/utils/toastUtils";
import { initializeComponents } from "@/lib/registry/components";
import { EditModal } from "@/components/screen/EditModal";
import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic";
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
import { useAuth } from "@/hooks/useAuth";
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
import { TableSearchWidgetHeightProvider } from "@/contexts/TableSearchWidgetHeightContext";
import { ScreenContextProvider } from "@/contexts/ScreenContext";
import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext";
import { ActiveTabProvider } from "@/contexts/ActiveTabContext";
import { evaluateConditional } from "@/lib/utils/conditionalEvaluator";
import { ScreenMultiLangProvider } from "@/contexts/ScreenMultiLangContext";
import { convertV2ToLegacy, isValidV2Layout } from "@/lib/utils/layoutV2Converter";
import { useScheduleGenerator, ScheduleConfirmDialog } from "@/lib/v2-core/services/ScheduleGeneratorService";
import { ResponsiveGridRenderer } from "@/components/screen/ResponsiveGridRenderer";
import { useTabId } from "@/contexts/TabIdContext";
import { useTabStore } from "@/stores/tabStore";
export interface ScreenViewPageProps {
screenIdProp?: number;
menuObjidProp?: number;
}
function ScreenViewPage({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {}) {
// 스케줄 자동 생성 서비스 활성화
const {
showConfirmDialog,
previewResult,
handleConfirm,
closeDialog,
isLoading: scheduleLoading,
} = useScheduleGenerator();
const params = useParams();
const searchParams = useSearchParams();
const router = useRouter();
const screenId = screenIdProp ?? parseInt(params.screenId as string);
// props 우선, 없으면 URL 쿼리에서 menuObjid 가져오기
const menuObjid =
menuObjidProp ?? (searchParams.get("menuObjid") ? parseInt(searchParams.get("menuObjid")!) : undefined);
// URL 쿼리에서 프리뷰용 company_code 가져오기
const previewCompanyCode = searchParams.get("company_code");
// 프리뷰 모드 감지 (iframe에서 로드될 때)
const isPreviewMode = searchParams.get("preview") === "true";
const { user, userName, companyCode: authCompanyCode } = useAuth();
// 프리뷰 모드에서는 URL 파라미터의 company_code 우선 사용
const companyCode = previewCompanyCode || authCompanyCode;
const [screen, setScreen] = useState<ScreenDefinition | null>(null);
const [layout, setLayout] = useState<LayoutData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [formData, setFormData] = useState<Record<string, unknown>>({});
// 테이블에서 선택된 행 데이터 (버튼 액션에 전달)
const [selectedRowsData, setSelectedRowsData] = useState<any[]>([]);
// 테이블 정렬 정보 (엑셀 다운로드용)
const [tableSortBy, setTableSortBy] = useState<string | undefined>();
const [tableSortOrder, setTableSortOrder] = useState<"asc" | "desc">("asc");
const [tableColumnOrder, setTableColumnOrder] = useState<string[] | undefined>();
// 플로우에서 선택된 데이터 (버튼 액션에 전달)
const [flowSelectedData, setFlowSelectedData] = useState<any[]>([]);
const [flowSelectedStepId, setFlowSelectedStepId] = useState<number | null>(null);
// 테이블 새로고침 키
const [tableRefreshKey, setTableRefreshKey] = useState(0);
// 플로우 새로고침 키
const [flowRefreshKey, setFlowRefreshKey] = useState(0);
// 조건부 컨테이너 높이 추적 (컴포넌트 ID → 높이)
const [conditionalContainerHeights, setConditionalContainerHeights] = useState<Record<string, number>>({});
// 레이어 시스템 지원
const [conditionalLayers, setConditionalLayers] = useState<LayerDefinition[]>([]);
// 조건부 영역(Zone) 목록
const [zones, setZones] = useState<import("@/types/screen-management").ConditionalZone[]>([]);
// 데이터 전달에 의해 강제 활성화된 레이어 ID 목록
const [forceActivatedLayerIds, setForceActivatedLayerIds] = useState<string[]>([]);
// 편집 모달 상태
const [editModalOpen, setEditModalOpen] = useState(false);
const [editModalConfig, setEditModalConfig] = useState<{
screenId?: number;
modalSize?: "sm" | "md" | "lg" | "xl" | "full";
editData?: Record<string, unknown>;
onSave?: () => void;
modalTitle?: string;
modalDescription?: string;
}>({});
// 레이아웃 준비 완료 상태
const [layoutReady, setLayoutReady] = useState(false);
const containerRef = React.useRef<HTMLDivElement>(null);
useEffect(() => {
const initComponents = async () => {
try {
await initializeComponents();
} catch (error) {
console.error("❌ 할당된 화면에서 컴포넌트 시스템 초기화 실패:", error);
}
};
initComponents();
}, []);
// 편집 모달 이벤트 리스너 등록 (활성 탭에서만 처리)
const tabId = useTabId();
useEffect(() => {
const handleOpenEditModal = (event: CustomEvent) => {
const state = useTabStore.getState();
const currentActiveTabId = state[state.mode].activeTabId;
if (tabId && tabId !== currentActiveTabId) return;
setEditModalConfig({
screenId: event.detail.screenId,
modalSize: event.detail.modalSize,
editData: event.detail.editData,
onSave: event.detail.onSave,
modalTitle: event.detail.modalTitle,
modalDescription: event.detail.modalDescription,
});
setEditModalOpen(true);
};
// @ts-expect-error - CustomEvent type
window.addEventListener("openEditModal", handleOpenEditModal);
return () => {
// @ts-expect-error - CustomEvent type
window.removeEventListener("openEditModal", handleOpenEditModal);
};
}, [tabId]);
useEffect(() => {
const loadScreen = async () => {
try {
setLoading(true);
setLayoutReady(false);
setError(null);
const screenData = await screenApi.getScreen(screenId);
setScreen(screenData);
// 레이아웃 로드 (V2 우선, Zod 기반 기본값 병합)
try {
const v2Response = await screenApi.getLayoutV2(screenId);
if (v2Response && isValidV2Layout(v2Response)) {
const convertedLayout = convertV2ToLegacy(v2Response);
if (convertedLayout) {
setLayout({
...convertedLayout,
screenResolution: v2Response.screenResolution || convertedLayout.screenResolution,
} as LayoutData);
} else {
throw new Error("V2 레이아웃 변환 실패");
}
} else {
const layoutData = await screenApi.getLayout(screenId);
if (layoutData?.components?.length > 0) {
setLayout(layoutData);
} else {
console.warn("[ScreenViewPage] getLayout 실패, getLayerLayout(1) fallback:", screenId);
const baseLayerData = await screenApi.getLayerLayout(screenId, 1);
if (baseLayerData && isValidV2Layout(baseLayerData)) {
const converted = convertV2ToLegacy(baseLayerData);
if (converted) {
setLayout({
...converted,
screenResolution: baseLayerData.screenResolution || converted.screenResolution,
} as LayoutData);
} else {
setLayout(layoutData);
}
} else {
setLayout(layoutData);
}
}
}
} catch (layoutError) {
console.warn("레이아웃 로드 실패, 빈 레이아웃 사용:", layoutError);
setLayout({
screenId,
components: [],
gridSettings: {
columns: 12,
gap: 16,
padding: 16,
enabled: true,
size: 8,
color: "#e0e0e0",
opacity: 0.5,
snapToGrid: true,
},
});
}
} catch (error) {
console.error("화면 로드 실패:", error);
setError("화면을 불러오는데 실패했습니다.");
showErrorToast("화면을 불러오는 데 실패했습니다", error, {
guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요.",
});
} finally {
setLoading(false);
setLayoutReady(true);
}
};
if (screenId) {
loadScreen();
}
}, [screenId]);
// 조건부 레이어 + Zone 로드
useEffect(() => {
const loadConditionalLayersAndZones = async () => {
if (!screenId || !layout) return;
try {
// Zone 로드
const loadedZones = await screenApi.getScreenZones(screenId);
setZones(loadedZones);
// 모든 레이어 목록 조회
const allLayers = await screenApi.getScreenLayers(screenId);
const nonBaseLayers = allLayers.filter((l: any) => l.layer_id > 1);
if (nonBaseLayers.length === 0) {
setConditionalLayers([]);
return;
}
// 각 레이어의 레이아웃 데이터 로드
const layerDefinitions: LayerDefinition[] = [];
for (const layerInfo of nonBaseLayers) {
try {
const layerData = await screenApi.getLayerLayout(screenId, layerInfo.layer_id);
const condConfig = layerInfo.condition_config || layerData?.conditionConfig || {};
let layerComponents: any[] = [];
const rawComponents = layerData?.components;
if (rawComponents && Array.isArray(rawComponents) && rawComponents.length > 0) {
const tempV2 = {
version: "2.0" as const,
components: rawComponents,
gridSettings: layerData.gridSettings,
screenResolution: layerData.screenResolution,
};
if (isValidV2Layout(tempV2)) {
const converted = convertV2ToLegacy(tempV2);
if (converted) {
layerComponents = converted.components || [];
}
}
}
const zoneId = condConfig.zone_id;
const conditionValue = condConfig.condition_value;
const zone = zoneId ? loadedZones.find((z: any) => z.zone_id === zoneId) : null;
const layerDef: LayerDefinition = {
id: String(layerInfo.layer_id),
name: layerInfo.layer_name || `레이어 ${layerInfo.layer_id}`,
type: "conditional",
zIndex: layerInfo.layer_id * 10,
isVisible: false,
isLocked: false,
condition: zone
? {
targetComponentId: zone.trigger_component_id || "",
operator: (zone.trigger_operator as "eq" | "neq" | "in") || "eq",
value: conditionValue,
}
: condConfig.targetComponentId
? {
targetComponentId: condConfig.targetComponentId,
operator: condConfig.operator || "eq",
value: condConfig.value,
}
: undefined,
zoneId: zoneId || undefined,
conditionValue: conditionValue || undefined,
displayRegion: zone
? { x: zone.x, y: zone.y, width: zone.width, height: zone.height }
: condConfig.displayRegion || undefined,
components: layerComponents,
};
layerDefinitions.push(layerDef);
} catch (layerError) {
console.warn(`레이어 ${layerInfo.layer_id} 로드 실패:`, layerError);
}
}
setConditionalLayers(layerDefinitions);
} catch (error) {
console.error("레이어/Zone 로드 실패:", error);
}
};
loadConditionalLayersAndZones();
}, [screenId, layout]);
// 조건부 레이어 조건 평가 (formData 변경 시 동기적으로 즉시 계산)
const activeLayerIds = useMemo(() => {
if (conditionalLayers.length === 0 || !layout) return [] as string[];
const allComponents = layout.components || [];
const newActiveIds: string[] = [];
conditionalLayers.forEach((layer) => {
if (layer.condition) {
const { targetComponentId, operator, value } = layer.condition;
if (!targetComponentId) return;
const targetComponent = allComponents.find((c) => c.id === targetComponentId);
const fieldKey =
(targetComponent as any)?.columnName ||
(targetComponent as any)?.componentConfig?.columnName ||
targetComponentId;
const targetValue = formData[fieldKey];
let isMatch = false;
switch (operator) {
case "eq":
isMatch = String(targetValue ?? "") === String(value ?? "");
break;
case "neq":
isMatch = String(targetValue ?? "") !== String(value ?? "");
break;
case "in":
if (Array.isArray(value)) {
isMatch = value.some((v) => String(v) === String(targetValue ?? ""));
} else if (typeof value === "string" && value.includes(",")) {
isMatch = value
.split(",")
.map((v) => v.trim())
.includes(String(targetValue ?? ""));
}
break;
}
if (isMatch) {
newActiveIds.push(layer.id);
}
}
});
for (const forcedId of forceActivatedLayerIds) {
if (!newActiveIds.includes(forcedId)) {
newActiveIds.push(forcedId);
}
}
return newActiveIds;
}, [formData, conditionalLayers, layout, forceActivatedLayerIds]);
// 데이터 전달에 의한 레이어 강제 활성화 이벤트 리스너
useEffect(() => {
const handleActivateLayer = (e: Event) => {
const { componentId, targetLayerId } = (e as CustomEvent).detail || {};
if (!componentId && !targetLayerId) return;
if (targetLayerId) {
setForceActivatedLayerIds((prev) => (prev.includes(targetLayerId) ? prev : [...prev, targetLayerId]));
return;
}
for (const layer of conditionalLayers) {
const found = layer.components.some((comp) => comp.id === componentId);
if (found) {
setForceActivatedLayerIds((prev) => (prev.includes(layer.id) ? prev : [...prev, layer.id]));
return;
}
}
};
window.addEventListener("activateLayerForComponent", handleActivateLayer);
return () => {
window.removeEventListener("activateLayerForComponent", handleActivateLayer);
};
}, [conditionalLayers]);
// 메인 테이블 데이터 자동 로드 (단일 레코드 폼)
useEffect(() => {
const loadMainTableData = async () => {
if (!screen || !layout || !layout.components || !companyCode) {
return;
}
const mainTableName = screen.tableName;
if (!mainTableName) {
return;
}
// 테이블 위젯이 있으면 자동 로드 건너뜀 (테이블 행 선택으로 데이터 로드)
const hasTableWidget = layout.components.some(
(comp: any) =>
comp.componentType === "table-list" || comp.componentType === "v2-table-list" || comp.widgetType === "table",
);
if (hasTableWidget) {
return;
}
const inputComponents = layout.components.filter((comp: any) => {
const compType = comp.componentType || comp.widgetType;
const isInputType =
compType?.includes("input") ||
compType?.includes("select") ||
compType?.includes("textarea") ||
compType?.includes("v2-input") ||
compType?.includes("v2-select") ||
compType?.includes("v2-media") ||
compType?.includes("file-upload");
const hasColumnName = !!(comp as any).columnName;
return isInputType && hasColumnName;
});
if (inputComponents.length === 0) {
return;
}
try {
const { tableTypeApi } = await import("@/lib/api/screen");
const result = await tableTypeApi.getTableRecord(mainTableName, "company_code", companyCode, "*");
if (result && result.record) {
const newFormData: Record<string, any> = {};
inputComponents.forEach((comp: any) => {
const columnName = comp.columnName;
if (columnName && result.record[columnName] !== undefined) {
newFormData[columnName] = result.record[columnName];
}
});
if (Object.keys(newFormData).length > 0) {
setFormData((prev) => ({
...prev,
...newFormData,
}));
}
}
} catch (error) {
console.log("메인 테이블 자동 로드 실패 (정상일 수 있음):", error);
}
};
loadMainTableData();
}, [screen, layout, companyCode]);
// 개별 autoFill 처리
useEffect(() => {
const initAutoFill = async () => {
if (!layout || !layout.components || !user) {
return;
}
for (const comp of layout.components) {
if (comp.type === "widget" || comp.type === "component") {
const widget = comp as any;
const fieldName = widget.columnName || widget.id;
if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) {
const autoFillConfig = widget.autoFill || (comp as any).autoFill;
const currentValue = formData[fieldName];
if (currentValue === undefined || currentValue === "") {
const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig;
const userValue = user?.[userField as keyof typeof user];
if (userValue && sourceTable && filterColumn && displayColumn) {
try {
const { tableTypeApi } = await import("@/lib/api/screen");
const result = await tableTypeApi.getTableRecord(sourceTable, filterColumn, userValue, displayColumn);
setFormData((prev) => ({
...prev,
[fieldName]: result.value,
}));
} catch (error) {
console.error(`autoFill 조회 실패: ${fieldName}`, error);
}
}
}
}
}
}
};
initAutoFill();
}, [layout, user]);
// 조건부 비활성화/숨김 시 해당 필드 값 초기화
const conditionalFieldValues = useMemo(() => {
if (!layout?.components) return "";
const conditionFields = new Set<string>();
layout.components.forEach((component) => {
const conditional = (component as any).conditional;
if (conditional?.enabled && conditional.field) {
conditionFields.add(conditional.field);
}
});
const values: Record<string, any> = {};
conditionFields.forEach((field) => {
values[field] = (formData as Record<string, any>)[field];
});
return JSON.stringify(values);
}, [layout?.components, formData]);
useEffect(() => {
if (!layout?.components) return;
const fieldsToReset: string[] = [];
layout.components.forEach((component) => {
const conditional = (component as any).conditional;
if (!conditional?.enabled) return;
const conditionalResult = evaluateConditional(conditional, formData as Record<string, any>, layout.components);
if (!conditionalResult.visible || conditionalResult.disabled) {
const fieldName = (component as any).columnName || component.id;
const currentValue = (formData as Record<string, any>)[fieldName];
if (currentValue !== undefined && currentValue !== "" && currentValue !== null) {
fieldsToReset.push(fieldName);
}
}
});
if (fieldsToReset.length > 0) {
setFormData((prev) => {
const updated = { ...prev };
fieldsToReset.forEach((fieldName) => {
updated[fieldName] = "";
});
return updated;
});
}
}, [conditionalFieldValues, layout?.components]);
// 화면 해상도 정보
const screenWidth = layout?.screenResolution?.width || 1200;
const screenHeight = layout?.screenResolution?.height || 800;
// RealtimePreview에 전달할 공통 props 빌더
const buildRealtimePreviewProps = (component: ComponentData, extraProps?: Record<string, any>) => ({
component,
isSelected: false,
isDesignMode: false,
onClick: () => {},
menuObjid,
screenId,
tableName: screen?.tableName,
userId: user?.userId,
userName,
companyCode,
selectedRowsData,
sortBy: tableSortBy,
sortOrder: tableSortOrder,
columnOrder: tableColumnOrder,
flowSelectedData,
flowSelectedStepId,
onFlowSelectedDataChange: (selectedData: any[], stepId: number | null) => {
setFlowSelectedData(selectedData);
setFlowSelectedStepId(stepId);
},
refreshKey: tableRefreshKey,
onRefresh: () => {
setTableRefreshKey((prev) => prev + 1);
setSelectedRowsData([]);
},
flowRefreshKey,
onFlowRefresh: () => {
setFlowRefreshKey((prev) => prev + 1);
setFlowSelectedData([]);
setFlowSelectedStepId(null);
},
formData,
onFormDataChange: (fieldName: string, value: any) => {
setFormData((prev) => ({ ...prev, [fieldName]: value }));
},
onSelectedRowsChange: (_: any[], selectedData: any[]) => {
setSelectedRowsData(selectedData);
},
...extraProps,
});
if (loading) {
return (
<div className="bg-muted/30 flex h-full min-h-[400px] w-full items-center justify-center">
<div className="border-border bg-background rounded-lg border p-8 text-center">
<Loader2 className="text-primary mx-auto h-10 w-10 animate-spin" />
<p className="text-foreground mt-4 font-medium"> ...</p>
</div>
</div>
);
}
if (error || !screen) {
return (
<div className="bg-muted/30 flex h-full min-h-[400px] w-full items-center justify-center">
<div className="border-border bg-background max-w-md rounded-lg border p-8 text-center">
<div className="bg-destructive/10 mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full">
<AlertTriangle className="text-destructive h-10 w-10" />
</div>
<h2 className="text-foreground mb-3 text-xl font-bold"> </h2>
<p className="text-muted-foreground mb-6 leading-relaxed">{error || "요청하신 화면이 존재하지 않습니다."}</p>
<Button onClick={() => router.back()} variant="outline" className="rounded-lg">
</Button>
</div>
</div>
);
}
return (
<ScreenPreviewProvider isPreviewMode={false}>
<ActiveTabProvider>
<TableOptionsProvider>
<div
ref={containerRef}
className={`bg-background h-full w-full ${isPreviewMode ? "overflow-hidden p-0" : "overflow-auto p-3"}`}
>
{/* 레이아웃 준비 중 로딩 표시 */}
{!layoutReady && (
<div className="bg-muted/30 flex h-full w-full items-center justify-center">
<div className="border-border bg-background rounded-lg border p-8 text-center">
<Loader2 className="text-primary mx-auto h-8 w-8 animate-spin" />
<p className="text-foreground mt-4 text-sm font-medium"> ...</p>
</div>
</div>
)}
{/* 반응형 그리드 렌더링 */}
{layoutReady && layout && layout.components.length > 0 ? (
<ScreenMultiLangProvider components={layout.components} companyCode={companyCode}>
{/* 기본 레이어: ResponsiveGridRenderer로 렌더링 */}
<ResponsiveGridRenderer
components={layout.components}
canvasWidth={screenWidth}
canvasHeight={screenHeight}
renderComponent={(component) => {
// 조건부 표시 평가
const conditional = (component as any).conditional;
let conditionalDisabled = false;
if (conditional?.enabled) {
const conditionalResult = evaluateConditional(
conditional,
formData as Record<string, any>,
layout?.components || [],
);
if (!conditionalResult.visible) {
return null;
}
conditionalDisabled = conditionalResult.disabled;
}
return (
<RealtimePreview
{...buildRealtimePreviewProps(component, {
conditionalDisabled,
onHeightChange: (componentId: string, newHeight: number) => {
setConditionalContainerHeights((prev) => ({
...prev,
[componentId]: newHeight,
}));
},
})}
>
{/* 자식 컴포넌트들 (그룹/컨테이너/영역) */}
{(component.type === "group" || component.type === "container" || component.type === "area") &&
layout.components
.filter((child) => child.parentId === component.id)
.map((child) => {
// 자식 컴포넌트의 위치를 부모 기준 상대 좌표로 조정
const relativeChildComponent = {
...child,
position: {
x: child.position.x - component.position.x,
y: child.position.y - component.position.y,
z: child.position.z || 1,
},
};
return (
<RealtimePreview
key={child.id}
{...buildRealtimePreviewProps(relativeChildComponent)}
/>
);
})}
</RealtimePreview>
);
}}
/>
{/* 조건부 레이어 (Zone 기반) */}
{conditionalLayers.map((layer) => {
const isActive = activeLayerIds.includes(layer.id);
if (!isActive || !layer.components || layer.components.length === 0) return null;
const zone = layer.zoneId ? zones.find((z) => z.zone_id === layer.zoneId) : null;
const region = zone
? { x: zone.x, y: zone.y, width: zone.width, height: zone.height }
: layer.displayRegion;
return (
<div
key={`conditional-layer-${layer.id}`}
data-conditional-layer="true"
style={{
position: "absolute",
left: region ? `${region.x}px` : "0px",
top: region ? `${region.y}px` : "0px",
width: region ? `${region.width}px` : "100%",
height: region ? `${region.height}px` : "auto",
zIndex: layer.zIndex || 20,
overflow: "hidden",
transition: "none",
}}
>
<ResponsiveGridRenderer
components={layer.components.filter((comp) => !comp.parentId)}
canvasWidth={region?.width || screenWidth}
canvasHeight={region?.height || screenHeight}
renderComponent={(comp) => (
<RealtimePreview key={comp.id} {...buildRealtimePreviewProps(comp)} />
)}
/>
</div>
);
})}
</ScreenMultiLangProvider>
) : (
// 빈 화면일 때
layoutReady && (
<div className="bg-background flex items-center justify-center" style={{ minHeight: screenHeight }}>
<div className="text-center">
<div className="bg-muted mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full">
<FileQuestion className="text-muted-foreground h-8 w-8" />
</div>
<h2 className="text-foreground mb-2 text-xl font-semibold"> </h2>
<p className="text-muted-foreground"> .</p>
</div>
</div>
)
)}
{/* 편집 모달 */}
<EditModal
isOpen={editModalOpen}
onClose={() => {
setEditModalOpen(false);
setEditModalConfig({});
}}
screenId={editModalConfig.screenId}
modalSize={editModalConfig.modalSize}
editData={editModalConfig.editData}
onSave={editModalConfig.onSave}
modalTitle={editModalConfig.modalTitle}
modalDescription={editModalConfig.modalDescription}
onDataChange={(changedFormData) => {
setFormData((prev) => ({
...prev,
...changedFormData,
}));
}}
/>
{/* 스케줄 생성 확인 다이얼로그 */}
<ScheduleConfirmDialog
open={showConfirmDialog}
onOpenChange={(open) => !open && closeDialog()}
preview={previewResult}
onConfirm={() => handleConfirm(true)}
onCancel={closeDialog}
isLoading={scheduleLoading}
/>
</div>
</TableOptionsProvider>
</ActiveTabProvider>
</ScreenPreviewProvider>
);
}
// 실제 컴포넌트를 Provider로 감싸기
function ScreenViewPageWrapper({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {}) {
return (
<TableSearchWidgetHeightProvider>
<ScreenContextProvider>
<SplitPanelProvider>
<ScreenViewPage screenIdProp={screenIdProp} menuObjidProp={menuObjidProp} />
</SplitPanelProvider>
</ScreenContextProvider>
</TableSearchWidgetHeightProvider>
);
}
export { ScreenViewPageWrapper };
export default ScreenViewPageWrapper;