606 lines
27 KiB
TypeScript
606 lines
27 KiB
TypeScript
"use client";
|
||
|
||
import React, { useEffect, useState } from "react";
|
||
import { useParams } from "next/navigation";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Loader2 } from "lucide-react";
|
||
import { screenApi } from "@/lib/api/screen";
|
||
import { ScreenDefinition, LayoutData } from "@/types/screen";
|
||
import { useRouter } from "next/navigation";
|
||
import { toast } from "sonner";
|
||
import { initializeComponents } from "@/lib/registry/components";
|
||
import { EditModal } from "@/components/screen/EditModal";
|
||
import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic";
|
||
import { FlowButtonGroup } from "@/components/screen/widgets/FlowButtonGroup";
|
||
import { FlowVisibilityConfig } from "@/types/control-management";
|
||
import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils";
|
||
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
||
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||
import { useAuth } from "@/hooks/useAuth"; // 🆕 사용자 정보
|
||
import { useResponsive } from "@/lib/hooks/useResponsive"; // 🆕 반응형 감지
|
||
|
||
export default function ScreenViewPage() {
|
||
const params = useParams();
|
||
const router = useRouter();
|
||
const screenId = parseInt(params.screenId as string);
|
||
|
||
// 🆕 현재 로그인한 사용자 정보
|
||
const { user, userName, companyCode } = useAuth();
|
||
|
||
// 🆕 모바일 환경 감지
|
||
const { isMobile } = useResponsive();
|
||
|
||
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 [flowSelectedData, setFlowSelectedData] = useState<any[]>([]);
|
||
const [flowSelectedStepId, setFlowSelectedStepId] = useState<number | null>(null);
|
||
|
||
// 테이블 새로고침을 위한 키 (값이 변경되면 테이블이 리렌더링됨)
|
||
const [tableRefreshKey, setTableRefreshKey] = useState(0);
|
||
|
||
// 플로우 새로고침을 위한 키 (값이 변경되면 플로우 데이터가 리렌더링됨)
|
||
const [flowRefreshKey, setFlowRefreshKey] = useState(0);
|
||
|
||
// 편집 모달 상태
|
||
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 containerRef = React.useRef<HTMLDivElement>(null);
|
||
const [scale, setScale] = useState(1);
|
||
const [containerWidth, setContainerWidth] = useState(0);
|
||
|
||
useEffect(() => {
|
||
const initComponents = async () => {
|
||
try {
|
||
await initializeComponents();
|
||
} catch (error) {
|
||
console.error("❌ 할당된 화면에서 컴포넌트 시스템 초기화 실패:", error);
|
||
}
|
||
};
|
||
|
||
initComponents();
|
||
}, []);
|
||
|
||
// 편집 모달 이벤트 리스너 등록
|
||
useEffect(() => {
|
||
const handleOpenEditModal = (event: CustomEvent) => {
|
||
console.log("🎭 편집 모달 열기 이벤트 수신:", event.detail);
|
||
|
||
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);
|
||
};
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const loadScreen = async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
// 화면 정보 로드
|
||
const screenData = await screenApi.getScreen(screenId);
|
||
setScreen(screenData);
|
||
|
||
// 레이아웃 로드
|
||
try {
|
||
const layoutData = await screenApi.getLayout(screenId);
|
||
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("화면을 불러오는데 실패했습니다.");
|
||
toast.error("화면을 불러오는데 실패했습니다.");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
if (screenId) {
|
||
loadScreen();
|
||
}
|
||
}, [screenId]);
|
||
|
||
// 캔버스 비율 조정 (사용자 화면에 맞게 자동 스케일) - 모바일에서는 비활성화
|
||
useEffect(() => {
|
||
// 모바일 환경에서는 스케일 조정 비활성화 (반응형만 작동)
|
||
if (isMobile) {
|
||
setScale(1);
|
||
return;
|
||
}
|
||
|
||
const updateScale = () => {
|
||
if (containerRef.current && layout) {
|
||
const designWidth = layout?.screenResolution?.width || 1200;
|
||
const designHeight = layout?.screenResolution?.height || 800;
|
||
|
||
// containerRef는 이미 패딩이 적용된 영역 내부이므로 offsetWidth는 패딩을 제외한 크기입니다
|
||
const containerWidth = containerRef.current.offsetWidth;
|
||
const containerHeight = containerRef.current.offsetHeight;
|
||
|
||
// 가로/세로 비율 중 작은 것을 선택하여 화면에 맞게 스케일 조정
|
||
// 하지만 화면이 컨테이너 전체 너비를 차지하도록 하기 위해 가로를 우선시
|
||
const scaleX = containerWidth / designWidth;
|
||
const scaleY = containerHeight / designHeight;
|
||
// 가로를 우선으로 하되, 세로가 넘치지 않도록 제한
|
||
const newScale = Math.min(scaleX, scaleY);
|
||
|
||
setScale(newScale);
|
||
// 컨테이너 너비 업데이트
|
||
setContainerWidth(containerWidth);
|
||
}
|
||
};
|
||
|
||
// 초기 측정
|
||
const timer = setTimeout(updateScale, 100);
|
||
|
||
window.addEventListener("resize", updateScale);
|
||
return () => {
|
||
clearTimeout(timer);
|
||
window.removeEventListener("resize", updateScale);
|
||
};
|
||
}, [layout, isMobile]);
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="from-muted to-muted/50 flex h-full min-h-[400px] w-full items-center justify-center bg-gradient-to-br">
|
||
<div className="border-border bg-background rounded-xl border p-8 text-center shadow-lg">
|
||
<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="from-muted to-muted/50 flex h-full min-h-[400px] w-full items-center justify-center bg-gradient-to-br">
|
||
<div className="border-border bg-background max-w-md rounded-xl border p-8 text-center shadow-lg">
|
||
<div className="from-destructive/20 to-warning/20 mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br shadow-sm">
|
||
<span className="text-3xl">⚠️</span>
|
||
</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>
|
||
);
|
||
}
|
||
|
||
// 화면 해상도 정보가 있으면 해당 크기로, 없으면 기본 크기 사용
|
||
const screenWidth = layout?.screenResolution?.width || 1200;
|
||
const screenHeight = layout?.screenResolution?.height || 800;
|
||
|
||
return (
|
||
<ScreenPreviewProvider isPreviewMode={false}>
|
||
<div ref={containerRef} className="bg-background h-full w-full overflow-hidden">
|
||
{/* 절대 위치 기반 렌더링 */}
|
||
{layout && layout.components.length > 0 ? (
|
||
<div
|
||
className="bg-background relative flex h-full origin-top-left items-start justify-start"
|
||
style={{
|
||
transform: `scale(${scale})`,
|
||
transformOrigin: "top left",
|
||
width: containerWidth > 0 ? `${containerWidth / scale}px` : "100%",
|
||
minWidth: containerWidth > 0 ? `${containerWidth / scale}px` : "100%",
|
||
}}
|
||
>
|
||
{/* 최상위 컴포넌트들 렌더링 */}
|
||
{(() => {
|
||
// 🆕 플로우 버튼 그룹 감지 및 처리
|
||
const topLevelComponents = layout.components.filter((component) => !component.parentId);
|
||
|
||
// 버튼은 scale에 맞춰 위치만 조정하면 됨 (scale = 1.0이면 그대로, scale < 1.0이면 왼쪽으로)
|
||
// 하지만 x=0 컴포넌트는 width: 100%로 확장되므로, 그만큼 버튼을 오른쪽으로 이동
|
||
const leftmostComponent = topLevelComponents.find((c) => c.position.x === 0);
|
||
let widthOffset = 0;
|
||
|
||
if (leftmostComponent && containerWidth > 0) {
|
||
const originalWidth = leftmostComponent.size?.width || screenWidth;
|
||
const actualWidth = containerWidth / scale;
|
||
widthOffset = Math.max(0, actualWidth - originalWidth);
|
||
|
||
console.log("📊 widthOffset 계산:", {
|
||
containerWidth,
|
||
scale,
|
||
screenWidth,
|
||
originalWidth,
|
||
actualWidth,
|
||
widthOffset,
|
||
leftmostType: leftmostComponent.type,
|
||
});
|
||
}
|
||
|
||
const buttonGroups: Record<string, any[]> = {};
|
||
const processedButtonIds = new Set<string>();
|
||
// 🔍 전체 버튼 목록 확인
|
||
const allButtons = topLevelComponents.filter((component) => {
|
||
const isButton =
|
||
(component.type === "component" &&
|
||
["button-primary", "button-secondary"].includes((component as any).componentType)) ||
|
||
(component.type === "widget" && (component as any).widgetType === "button");
|
||
return isButton;
|
||
});
|
||
|
||
console.log(
|
||
"🔍 메뉴에서 발견된 전체 버튼:",
|
||
allButtons.map((b) => ({
|
||
id: b.id,
|
||
label: b.label,
|
||
positionX: b.position.x,
|
||
positionY: b.position.y,
|
||
})),
|
||
);
|
||
|
||
topLevelComponents.forEach((component) => {
|
||
const isButton =
|
||
(component.type === "component" &&
|
||
["button-primary", "button-secondary"].includes((component as any).componentType)) ||
|
||
(component.type === "widget" && (component as any).widgetType === "button");
|
||
|
||
if (isButton) {
|
||
const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig as
|
||
| FlowVisibilityConfig
|
||
| undefined;
|
||
|
||
// 🔧 임시: 버튼 그룹 기능 완전 비활성화
|
||
// TODO: 사용자가 명시적으로 그룹을 원하는 경우에만 활성화하도록 UI 개선 필요
|
||
const DISABLE_BUTTON_GROUPS = true;
|
||
|
||
if (
|
||
!DISABLE_BUTTON_GROUPS &&
|
||
flowConfig?.enabled &&
|
||
flowConfig.layoutBehavior === "auto-compact" &&
|
||
flowConfig.groupId
|
||
) {
|
||
if (!buttonGroups[flowConfig.groupId]) {
|
||
buttonGroups[flowConfig.groupId] = [];
|
||
}
|
||
buttonGroups[flowConfig.groupId].push(component);
|
||
processedButtonIds.add(component.id);
|
||
}
|
||
// else: 모든 버튼을 개별 렌더링
|
||
}
|
||
});
|
||
|
||
const regularComponents = topLevelComponents.filter((c) => !processedButtonIds.has(c.id));
|
||
|
||
return (
|
||
<>
|
||
{/* 일반 컴포넌트들 */}
|
||
{regularComponents.map((component) => {
|
||
// 버튼인 경우 위치 조정 (테이블이 늘어난 만큼 오른쪽으로 이동)
|
||
const isButton =
|
||
(component.type === "component" &&
|
||
["button-primary", "button-secondary"].includes((component as any).componentType)) ||
|
||
(component.type === "widget" && (component as any).widgetType === "button");
|
||
|
||
const adjustedComponent =
|
||
isButton && widthOffset > 0
|
||
? {
|
||
...component,
|
||
position: {
|
||
...component.position,
|
||
x: component.position.x + widthOffset,
|
||
},
|
||
}
|
||
: component;
|
||
|
||
// 버튼일 경우 로그 출력
|
||
if (isButton) {
|
||
console.log("🔘 버튼 위치 조정:", {
|
||
label: component.label,
|
||
originalX: component.position.x,
|
||
adjustedX: component.position.x + widthOffset,
|
||
widthOffset,
|
||
});
|
||
}
|
||
|
||
return (
|
||
<RealtimePreview
|
||
key={component.id}
|
||
component={adjustedComponent}
|
||
isSelected={false}
|
||
isDesignMode={false}
|
||
onClick={() => {}}
|
||
screenId={screenId}
|
||
tableName={screen?.tableName}
|
||
userId={user?.userId}
|
||
userName={userName}
|
||
companyCode={companyCode}
|
||
selectedRowsData={selectedRowsData}
|
||
onSelectedRowsChange={(_, selectedData) => {
|
||
console.log("🔍 화면에서 선택된 행 데이터:", selectedData);
|
||
setSelectedRowsData(selectedData);
|
||
}}
|
||
flowSelectedData={flowSelectedData}
|
||
flowSelectedStepId={flowSelectedStepId}
|
||
onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => {
|
||
setFlowSelectedData(selectedData);
|
||
setFlowSelectedStepId(stepId);
|
||
}}
|
||
refreshKey={tableRefreshKey}
|
||
onRefresh={() => {
|
||
setTableRefreshKey((prev) => prev + 1);
|
||
setSelectedRowsData([]); // 선택 해제
|
||
}}
|
||
flowRefreshKey={flowRefreshKey}
|
||
onFlowRefresh={() => {
|
||
setFlowRefreshKey((prev) => prev + 1);
|
||
setFlowSelectedData([]); // 선택 해제
|
||
setFlowSelectedStepId(null);
|
||
}}
|
||
formData={formData}
|
||
onFormDataChange={(fieldName, value) => {
|
||
setFormData((prev) => ({ ...prev, [fieldName]: value }));
|
||
}}
|
||
>
|
||
{/* 자식 컴포넌트들 */}
|
||
{(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}
|
||
component={relativeChildComponent}
|
||
isSelected={false}
|
||
isDesignMode={false}
|
||
onClick={() => {}}
|
||
screenId={screenId}
|
||
tableName={screen?.tableName}
|
||
userId={user?.userId}
|
||
userName={userName}
|
||
companyCode={companyCode}
|
||
selectedRowsData={selectedRowsData}
|
||
onSelectedRowsChange={(_, selectedData) => {
|
||
console.log("🔍 화면에서 선택된 행 데이터 (자식):", selectedData);
|
||
setSelectedRowsData(selectedData);
|
||
}}
|
||
refreshKey={tableRefreshKey}
|
||
onRefresh={() => {
|
||
console.log("🔄 테이블 새로고침 요청됨 (자식)");
|
||
setTableRefreshKey((prev) => prev + 1);
|
||
setSelectedRowsData([]); // 선택 해제
|
||
}}
|
||
formData={formData}
|
||
onFormDataChange={(fieldName, value) => {
|
||
setFormData((prev) => ({ ...prev, [fieldName]: value }));
|
||
}}
|
||
/>
|
||
);
|
||
})}
|
||
</RealtimePreview>
|
||
);
|
||
})}
|
||
|
||
{/* 🆕 플로우 버튼 그룹들 */}
|
||
{Object.entries(buttonGroups).map(([groupId, buttons]) => {
|
||
if (buttons.length === 0) return null;
|
||
|
||
const firstButton = buttons[0];
|
||
const groupConfig = (firstButton as any).webTypeConfig
|
||
?.flowVisibilityConfig as FlowVisibilityConfig;
|
||
|
||
// 그룹의 위치는 모든 버튼 중 가장 왼쪽/위쪽 버튼의 위치 사용
|
||
const groupPosition = buttons.reduce(
|
||
(min, button) => ({
|
||
x: Math.min(min.x, button.position.x),
|
||
y: Math.min(min.y, button.position.y),
|
||
z: min.z,
|
||
}),
|
||
{ x: buttons[0].position.x, y: buttons[0].position.y, z: buttons[0].position.z || 2 },
|
||
);
|
||
|
||
// 버튼 그룹 위치에도 widthOffset 적용 (테이블이 늘어난 만큼 오른쪽으로 이동)
|
||
const adjustedGroupPosition = {
|
||
...groupPosition,
|
||
x: groupPosition.x + widthOffset,
|
||
};
|
||
|
||
// 그룹의 크기 계산: 버튼들의 실제 크기 + 간격을 기준으로 계산
|
||
const direction = groupConfig.groupDirection || "horizontal";
|
||
const gap = groupConfig.groupGap ?? 8;
|
||
|
||
let groupWidth = 0;
|
||
let groupHeight = 0;
|
||
|
||
if (direction === "horizontal") {
|
||
groupWidth = buttons.reduce((total, button, index) => {
|
||
const buttonWidth = button.size?.width || 100;
|
||
const gapWidth = index < buttons.length - 1 ? gap : 0;
|
||
return total + buttonWidth + gapWidth;
|
||
}, 0);
|
||
groupHeight = Math.max(...buttons.map((b) => b.size?.height || 40));
|
||
} else {
|
||
groupWidth = Math.max(...buttons.map((b) => b.size?.width || 100));
|
||
groupHeight = buttons.reduce((total, button, index) => {
|
||
const buttonHeight = button.size?.height || 40;
|
||
const gapHeight = index < buttons.length - 1 ? gap : 0;
|
||
return total + buttonHeight + gapHeight;
|
||
}, 0);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
key={`flow-button-group-${groupId}`}
|
||
style={{
|
||
position: "absolute",
|
||
left: `${groupPosition.x}px`,
|
||
top: `${groupPosition.y}px`,
|
||
zIndex: groupPosition.z,
|
||
width: `${groupWidth}px`,
|
||
height: `${groupHeight}px`,
|
||
}}
|
||
>
|
||
<FlowButtonGroup
|
||
buttons={buttons}
|
||
groupConfig={groupConfig}
|
||
isDesignMode={false}
|
||
renderButton={(button) => {
|
||
const relativeButton = {
|
||
...button,
|
||
position: { x: 0, y: 0, z: button.position.z || 1 },
|
||
};
|
||
|
||
return (
|
||
<div
|
||
key={button.id}
|
||
style={{
|
||
position: "relative",
|
||
display: "inline-block",
|
||
width: button.size?.width || 100,
|
||
height: button.size?.height || 40,
|
||
}}
|
||
>
|
||
<div style={{ width: "100%", height: "100%" }}>
|
||
<DynamicComponentRenderer
|
||
component={relativeButton}
|
||
isDesignMode={false}
|
||
isInteractive={true}
|
||
formData={formData}
|
||
onDataflowComplete={() => {}}
|
||
screenId={screenId}
|
||
tableName={screen?.tableName}
|
||
userId={user?.userId}
|
||
userName={userName}
|
||
companyCode={companyCode}
|
||
selectedRowsData={selectedRowsData}
|
||
onSelectedRowsChange={(_, selectedData) => {
|
||
setSelectedRowsData(selectedData);
|
||
}}
|
||
flowSelectedData={flowSelectedData}
|
||
flowSelectedStepId={flowSelectedStepId}
|
||
onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => {
|
||
setFlowSelectedData(selectedData);
|
||
setFlowSelectedStepId(stepId);
|
||
}}
|
||
refreshKey={tableRefreshKey}
|
||
onRefresh={() => {
|
||
setTableRefreshKey((prev) => prev + 1);
|
||
setSelectedRowsData([]);
|
||
}}
|
||
flowRefreshKey={flowRefreshKey}
|
||
onFlowRefresh={() => {
|
||
setFlowRefreshKey((prev) => prev + 1);
|
||
setFlowSelectedData([]);
|
||
setFlowSelectedStepId(null);
|
||
}}
|
||
onFormDataChange={(fieldName, value) => {
|
||
setFormData((prev) => ({ ...prev, [fieldName]: value }));
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
}}
|
||
/>
|
||
</div>
|
||
);
|
||
})}
|
||
</>
|
||
);
|
||
})()}
|
||
</div>
|
||
) : (
|
||
// 빈 화면일 때
|
||
<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 shadow-sm">
|
||
<span className="text-2xl">📄</span>
|
||
</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) => {
|
||
console.log("📝 EditModal에서 데이터 변경 수신:", changedFormData);
|
||
// 변경된 데이터를 메인 폼에 반영
|
||
setFormData((prev) => {
|
||
const updatedFormData = {
|
||
...prev,
|
||
...changedFormData, // 변경된 필드들만 업데이트
|
||
};
|
||
console.log("📊 메인 폼 데이터 업데이트:", updatedFormData);
|
||
return updatedFormData;
|
||
});
|
||
}}
|
||
/>
|
||
</div>
|
||
</ScreenPreviewProvider>
|
||
);
|
||
}
|