사용자 화면 크기에 따라 화면 비율 조정
This commit is contained in:
parent
a0dde51109
commit
ac53b3c440
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
@ -34,6 +34,9 @@ export default function ScreenViewPage() {
|
||||||
// 테이블 새로고침을 위한 키 상태
|
// 테이블 새로고침을 위한 키 상태
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
|
||||||
|
// 스케일 상태
|
||||||
|
const [scale, setScale] = useState(1);
|
||||||
|
|
||||||
// 편집 모달 상태
|
// 편집 모달 상태
|
||||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||||
const [editModalConfig, setEditModalConfig] = useState<{
|
const [editModalConfig, setEditModalConfig] = useState<{
|
||||||
|
|
@ -119,6 +122,36 @@ export default function ScreenViewPage() {
|
||||||
}
|
}
|
||||||
}, [screenId]);
|
}, [screenId]);
|
||||||
|
|
||||||
|
// 가로폭 기준 자동 스케일 계산
|
||||||
|
useEffect(() => {
|
||||||
|
const updateScale = () => {
|
||||||
|
if (layout) {
|
||||||
|
// main 요소의 실제 너비를 직접 사용
|
||||||
|
const mainElement = document.querySelector("main");
|
||||||
|
const mainWidth = mainElement ? mainElement.clientWidth : window.innerWidth - 288;
|
||||||
|
|
||||||
|
// 좌우 마진 16px씩 제외
|
||||||
|
const margin = 32; // 16px * 2
|
||||||
|
const availableWidth = mainWidth - margin;
|
||||||
|
|
||||||
|
const screenWidth = layout?.screenResolution?.width || 1200;
|
||||||
|
const newScale = availableWidth / screenWidth;
|
||||||
|
|
||||||
|
console.log("🎯 스케일 계산 (마진 포함):", {
|
||||||
|
mainWidth,
|
||||||
|
margin,
|
||||||
|
availableWidth,
|
||||||
|
screenWidth,
|
||||||
|
newScale,
|
||||||
|
});
|
||||||
|
|
||||||
|
setScale(newScale);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateScale();
|
||||||
|
}, [layout]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-[400px] w-full items-center justify-center bg-gradient-to-br from-gray-50 to-slate-100">
|
<div className="flex h-full min-h-[400px] w-full items-center justify-center bg-gradient-to-br from-gray-50 to-slate-100">
|
||||||
|
|
@ -152,258 +185,277 @@ export default function ScreenViewPage() {
|
||||||
const screenHeight = layout?.screenResolution?.height || 800;
|
const screenHeight = layout?.screenResolution?.height || 800;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full overflow-auto bg-gradient-to-br from-gray-50 to-slate-100 p-10">
|
<div className="h-full w-full overflow-auto bg-white" style={{ padding: "16px 0" }}>
|
||||||
{layout && layout.components.length > 0 ? (
|
{layout && layout.components.length > 0 ? (
|
||||||
// 캔버스 컴포넌트들을 정확한 해상도로 표시
|
// 스케일링된 화면을 감싸는 래퍼 (실제 크기 조정 + 좌우 마진 16px)
|
||||||
<div
|
<div
|
||||||
className="relative mx-auto rounded-xl border border-gray-200/60 bg-white shadow-lg shadow-gray-900/5"
|
|
||||||
style={{
|
style={{
|
||||||
width: `${screenWidth}px`,
|
width: `${screenWidth * scale}px`,
|
||||||
height: `${screenHeight}px`,
|
height: `${screenHeight * scale}px`,
|
||||||
minWidth: `${screenWidth}px`,
|
marginLeft: "16px",
|
||||||
minHeight: `${screenHeight}px`,
|
marginRight: "16px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{layout.components
|
{/* 캔버스 컴포넌트들을 가로폭에 맞춰 스케일링하여 표시 */}
|
||||||
.filter((comp) => !comp.parentId) // 최상위 컴포넌트만 렌더링 (그룹 포함)
|
<div
|
||||||
.map((component) => {
|
className="relative bg-white"
|
||||||
// 그룹 컴포넌트인 경우 특별 처리
|
style={{
|
||||||
if (component.type === "group") {
|
width: `${screenWidth}px`,
|
||||||
const groupChildren = layout.components.filter((child) => child.parentId === component.id);
|
height: `${screenHeight}px`,
|
||||||
|
transform: `scale(${scale})`,
|
||||||
|
transformOrigin: "top left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{layout.components
|
||||||
|
.filter((comp) => !comp.parentId) // 최상위 컴포넌트만 렌더링 (그룹 포함)
|
||||||
|
.map((component) => {
|
||||||
|
// 그룹 컴포넌트인 경우 특별 처리
|
||||||
|
if (component.type === "group") {
|
||||||
|
const groupChildren = layout.components.filter((child) => child.parentId === component.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={component.id}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: `${component.position.x}px`,
|
||||||
|
top: `${component.position.y}px`,
|
||||||
|
width: component.style?.width || `${component.size.width}px`,
|
||||||
|
height: component.style?.height || `${component.size.height}px`,
|
||||||
|
zIndex: component.position.z || 1,
|
||||||
|
backgroundColor: (component as any).backgroundColor || "rgba(59, 130, 246, 0.05)",
|
||||||
|
border: (component as any).border || "1px solid rgba(59, 130, 246, 0.2)",
|
||||||
|
borderRadius: (component as any).borderRadius || "12px",
|
||||||
|
padding: "20px",
|
||||||
|
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 그룹 제목 */}
|
||||||
|
{(component as any).title && (
|
||||||
|
<div className="mb-3 inline-block rounded-lg bg-blue-50 px-3 py-1 text-sm font-semibold text-blue-700">
|
||||||
|
{(component as any).title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 그룹 내 자식 컴포넌트들 렌더링 */}
|
||||||
|
{groupChildren.map((child) => (
|
||||||
|
<div
|
||||||
|
key={child.id}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: `${child.position.x}px`,
|
||||||
|
top: `${child.position.y}px`,
|
||||||
|
width: child.style?.width || `${child.size.width}px`,
|
||||||
|
height: child.style?.height || `${child.size.height}px`,
|
||||||
|
zIndex: child.position.z || 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InteractiveScreenViewer
|
||||||
|
component={child}
|
||||||
|
allComponents={layout.components}
|
||||||
|
formData={formData}
|
||||||
|
onFormDataChange={(fieldName, value) => {
|
||||||
|
console.log("📝 폼 데이터 변경:", { fieldName, value });
|
||||||
|
setFormData((prev) => {
|
||||||
|
const newFormData = {
|
||||||
|
...prev,
|
||||||
|
[fieldName]: value,
|
||||||
|
};
|
||||||
|
console.log("📊 전체 폼 데이터:", newFormData);
|
||||||
|
return newFormData;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
screenInfo={{
|
||||||
|
id: screenId,
|
||||||
|
tableName: screen?.tableName,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 라벨 표시 여부 계산
|
||||||
|
const templateTypes = ["datatable"];
|
||||||
|
const shouldShowLabel =
|
||||||
|
component.style?.labelDisplay !== false &&
|
||||||
|
(component.label || component.style?.labelText) &&
|
||||||
|
!templateTypes.includes(component.type);
|
||||||
|
|
||||||
|
const labelText = component.style?.labelText || component.label || "";
|
||||||
|
const labelStyle = {
|
||||||
|
fontSize: component.style?.labelFontSize || "14px",
|
||||||
|
color: component.style?.labelColor || "#212121",
|
||||||
|
fontWeight: component.style?.labelFontWeight || "500",
|
||||||
|
backgroundColor: component.style?.labelBackgroundColor || "transparent",
|
||||||
|
padding: component.style?.labelPadding || "0",
|
||||||
|
borderRadius: component.style?.labelBorderRadius || "0",
|
||||||
|
marginBottom: component.style?.labelMarginBottom || "4px",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 일반 컴포넌트 렌더링
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={component.id}>
|
||||||
key={component.id}
|
{/* 라벨을 외부에 별도로 렌더링 */}
|
||||||
style={{
|
{shouldShowLabel && (
|
||||||
position: "absolute",
|
<div
|
||||||
left: `${component.position.x}px`,
|
style={{
|
||||||
top: `${component.position.y}px`,
|
position: "absolute",
|
||||||
width: component.style?.width || `${component.size.width}px`,
|
left: `${component.position.x}px`,
|
||||||
height: component.style?.height || `${component.size.height}px`,
|
top: `${component.position.y - 25}px`, // 컴포넌트 위쪽에 라벨 배치
|
||||||
zIndex: component.position.z || 1,
|
zIndex: (component.position.z || 1) + 1,
|
||||||
backgroundColor: (component as any).backgroundColor || "rgba(59, 130, 246, 0.05)",
|
...labelStyle,
|
||||||
border: (component as any).border || "1px solid rgba(59, 130, 246, 0.2)",
|
}}
|
||||||
borderRadius: (component as any).borderRadius || "12px",
|
>
|
||||||
padding: "20px",
|
{labelText}
|
||||||
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
{component.required && <span style={{ color: "#f97316", marginLeft: "2px" }}>*</span>}
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 그룹 제목 */}
|
|
||||||
{(component as any).title && (
|
|
||||||
<div className="mb-3 inline-block rounded-lg bg-blue-50 px-3 py-1 text-sm font-semibold text-blue-700">
|
|
||||||
{(component as any).title}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 그룹 내 자식 컴포넌트들 렌더링 */}
|
{/* 실제 컴포넌트 */}
|
||||||
{groupChildren.map((child) => (
|
|
||||||
<div
|
|
||||||
key={child.id}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
left: `${child.position.x}px`,
|
|
||||||
top: `${child.position.y}px`,
|
|
||||||
width: child.style?.width || `${child.size.width}px`,
|
|
||||||
height: child.style?.height || `${child.size.height}px`,
|
|
||||||
zIndex: child.position.z || 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<InteractiveScreenViewer
|
|
||||||
component={child}
|
|
||||||
allComponents={layout.components}
|
|
||||||
formData={formData}
|
|
||||||
onFormDataChange={(fieldName, value) => {
|
|
||||||
console.log("📝 폼 데이터 변경:", { fieldName, value });
|
|
||||||
setFormData((prev) => {
|
|
||||||
const newFormData = {
|
|
||||||
...prev,
|
|
||||||
[fieldName]: value,
|
|
||||||
};
|
|
||||||
console.log("📊 전체 폼 데이터:", newFormData);
|
|
||||||
return newFormData;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
screenInfo={{
|
|
||||||
id: screenId,
|
|
||||||
tableName: screen?.tableName,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 라벨 표시 여부 계산
|
|
||||||
const templateTypes = ["datatable"];
|
|
||||||
const shouldShowLabel =
|
|
||||||
component.style?.labelDisplay !== false &&
|
|
||||||
(component.label || component.style?.labelText) &&
|
|
||||||
!templateTypes.includes(component.type);
|
|
||||||
|
|
||||||
const labelText = component.style?.labelText || component.label || "";
|
|
||||||
const labelStyle = {
|
|
||||||
fontSize: component.style?.labelFontSize || "14px",
|
|
||||||
color: component.style?.labelColor || "#212121",
|
|
||||||
fontWeight: component.style?.labelFontWeight || "500",
|
|
||||||
backgroundColor: component.style?.labelBackgroundColor || "transparent",
|
|
||||||
padding: component.style?.labelPadding || "0",
|
|
||||||
borderRadius: component.style?.labelBorderRadius || "0",
|
|
||||||
marginBottom: component.style?.labelMarginBottom || "4px",
|
|
||||||
};
|
|
||||||
|
|
||||||
// 일반 컴포넌트 렌더링
|
|
||||||
return (
|
|
||||||
<div key={component.id}>
|
|
||||||
{/* 라벨을 외부에 별도로 렌더링 */}
|
|
||||||
{shouldShowLabel && (
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: `${component.position.x}px`,
|
left: `${component.position.x}px`,
|
||||||
top: `${component.position.y - 25}px`, // 컴포넌트 위쪽에 라벨 배치
|
top: `${component.position.y}px`,
|
||||||
zIndex: (component.position.z || 1) + 1,
|
width: component.style?.width || `${component.size.width}px`,
|
||||||
...labelStyle,
|
height: component.style?.height || `${component.size.height}px`,
|
||||||
|
zIndex: component.position.z || 1,
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
// console.log("🎯 할당된 화면 컴포넌트:", {
|
||||||
|
// id: component.id,
|
||||||
|
// type: component.type,
|
||||||
|
// position: component.position,
|
||||||
|
// size: component.size,
|
||||||
|
// styleWidth: component.style?.width,
|
||||||
|
// styleHeight: component.style?.height,
|
||||||
|
// finalWidth: `${component.size.width}px`,
|
||||||
|
// finalHeight: `${component.size.height}px`,
|
||||||
|
// });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{labelText}
|
{/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */}
|
||||||
{component.required && <span style={{ color: "#f97316", marginLeft: "2px" }}>*</span>}
|
{component.type !== "widget" ? (
|
||||||
</div>
|
<DynamicComponentRenderer
|
||||||
)}
|
component={{
|
||||||
|
...component,
|
||||||
{/* 실제 컴포넌트 */}
|
style: {
|
||||||
<div
|
...component.style,
|
||||||
style={{
|
labelDisplay: shouldShowLabel ? false : (component.style?.labelDisplay ?? true), // 상위에서 라벨을 표시했으면 컴포넌트 내부에서는 숨김
|
||||||
position: "absolute",
|
},
|
||||||
left: `${component.position.x}px`,
|
}}
|
||||||
top: `${component.position.y}px`,
|
isInteractive={true}
|
||||||
width: component.style?.width || `${component.size.width}px`,
|
formData={formData}
|
||||||
height: component.style?.height || `${component.size.height}px`,
|
onFormDataChange={(fieldName, value) => {
|
||||||
zIndex: component.position.z || 1,
|
|
||||||
}}
|
|
||||||
onMouseEnter={() => {
|
|
||||||
// console.log("🎯 할당된 화면 컴포넌트:", {
|
|
||||||
// id: component.id,
|
|
||||||
// type: component.type,
|
|
||||||
// position: component.position,
|
|
||||||
// size: component.size,
|
|
||||||
// styleWidth: component.style?.width,
|
|
||||||
// styleHeight: component.style?.height,
|
|
||||||
// finalWidth: `${component.size.width}px`,
|
|
||||||
// finalHeight: `${component.size.height}px`,
|
|
||||||
// });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */}
|
|
||||||
{component.type !== "widget" ? (
|
|
||||||
<DynamicComponentRenderer
|
|
||||||
component={{
|
|
||||||
...component,
|
|
||||||
style: {
|
|
||||||
...component.style,
|
|
||||||
labelDisplay: shouldShowLabel ? false : (component.style?.labelDisplay ?? true), // 상위에서 라벨을 표시했으면 컴포넌트 내부에서는 숨김
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
isInteractive={true}
|
|
||||||
formData={formData}
|
|
||||||
onFormDataChange={(fieldName, value) => {
|
|
||||||
setFormData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[fieldName]: value,
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
screenId={screenId}
|
|
||||||
tableName={screen?.tableName}
|
|
||||||
onRefresh={() => {
|
|
||||||
console.log("화면 새로고침 요청");
|
|
||||||
// 테이블 컴포넌트 강제 새로고침을 위한 키 업데이트
|
|
||||||
setRefreshKey((prev) => prev + 1);
|
|
||||||
// 선택된 행 상태도 초기화
|
|
||||||
setSelectedRows([]);
|
|
||||||
setSelectedRowsData([]);
|
|
||||||
}}
|
|
||||||
onClose={() => {
|
|
||||||
console.log("화면 닫기 요청");
|
|
||||||
}}
|
|
||||||
// 테이블 선택된 행 정보 전달
|
|
||||||
selectedRows={selectedRows}
|
|
||||||
selectedRowsData={selectedRowsData}
|
|
||||||
onSelectedRowsChange={(newSelectedRows, newSelectedRowsData) => {
|
|
||||||
setSelectedRows(newSelectedRows);
|
|
||||||
setSelectedRowsData(newSelectedRowsData);
|
|
||||||
}}
|
|
||||||
// 테이블 새로고침 키 전달
|
|
||||||
refreshKey={refreshKey}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<DynamicWebTypeRenderer
|
|
||||||
webType={(() => {
|
|
||||||
// 유틸리티 함수로 파일 컴포넌트 감지
|
|
||||||
if (isFileComponent(component)) {
|
|
||||||
console.log('🎯 page.tsx - 파일 컴포넌트 감지 → webType: "file"', {
|
|
||||||
componentId: component.id,
|
|
||||||
componentType: component.type,
|
|
||||||
originalWebType: component.webType,
|
|
||||||
});
|
|
||||||
return "file";
|
|
||||||
}
|
|
||||||
// 다른 컴포넌트는 유틸리티 함수로 webType 결정
|
|
||||||
return getComponentWebType(component) || "text";
|
|
||||||
})()}
|
|
||||||
config={component.webTypeConfig}
|
|
||||||
props={{
|
|
||||||
component: component,
|
|
||||||
value: formData[component.columnName || component.id] || "",
|
|
||||||
onChange: (value: any) => {
|
|
||||||
const fieldName = component.columnName || component.id;
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[fieldName]: value,
|
[fieldName]: value,
|
||||||
}));
|
}));
|
||||||
},
|
}}
|
||||||
onFormDataChange: (fieldName, value) => {
|
screenId={screenId}
|
||||||
console.log(`🎯 page.tsx onFormDataChange 호출: ${fieldName} = "${value}"`);
|
tableName={screen?.tableName}
|
||||||
console.log("📋 현재 formData:", formData);
|
onRefresh={() => {
|
||||||
setFormData((prev) => {
|
console.log("화면 새로고침 요청");
|
||||||
const newFormData = {
|
// 테이블 컴포넌트 강제 새로고침을 위한 키 업데이트
|
||||||
|
setRefreshKey((prev) => prev + 1);
|
||||||
|
// 선택된 행 상태도 초기화
|
||||||
|
setSelectedRows([]);
|
||||||
|
setSelectedRowsData([]);
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
console.log("화면 닫기 요청");
|
||||||
|
}}
|
||||||
|
// 테이블 선택된 행 정보 전달
|
||||||
|
selectedRows={selectedRows}
|
||||||
|
selectedRowsData={selectedRowsData}
|
||||||
|
onSelectedRowsChange={(newSelectedRows, newSelectedRowsData) => {
|
||||||
|
setSelectedRows(newSelectedRows);
|
||||||
|
setSelectedRowsData(newSelectedRowsData);
|
||||||
|
}}
|
||||||
|
// 테이블 새로고침 키 전달
|
||||||
|
refreshKey={refreshKey}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DynamicWebTypeRenderer
|
||||||
|
webType={(() => {
|
||||||
|
// 유틸리티 함수로 파일 컴포넌트 감지
|
||||||
|
if (isFileComponent(component)) {
|
||||||
|
console.log('🎯 page.tsx - 파일 컴포넌트 감지 → webType: "file"', {
|
||||||
|
componentId: component.id,
|
||||||
|
componentType: component.type,
|
||||||
|
originalWebType: component.webType,
|
||||||
|
});
|
||||||
|
return "file";
|
||||||
|
}
|
||||||
|
// 다른 컴포넌트는 유틸리티 함수로 webType 결정
|
||||||
|
return getComponentWebType(component) || "text";
|
||||||
|
})()}
|
||||||
|
config={component.webTypeConfig}
|
||||||
|
props={{
|
||||||
|
component: component,
|
||||||
|
value: formData[component.columnName || component.id] || "",
|
||||||
|
onChange: (value: any) => {
|
||||||
|
const fieldName = component.columnName || component.id;
|
||||||
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[fieldName]: value,
|
[fieldName]: value,
|
||||||
};
|
}));
|
||||||
console.log("📝 업데이트된 formData:", newFormData);
|
},
|
||||||
return newFormData;
|
onFormDataChange: (fieldName, value) => {
|
||||||
});
|
console.log(`🎯 page.tsx onFormDataChange 호출: ${fieldName} = "${value}"`);
|
||||||
},
|
console.log("📋 현재 formData:", formData);
|
||||||
isInteractive: true,
|
setFormData((prev) => {
|
||||||
formData: formData,
|
const newFormData = {
|
||||||
readonly: component.readonly,
|
...prev,
|
||||||
required: component.required,
|
[fieldName]: value,
|
||||||
placeholder: component.placeholder,
|
};
|
||||||
className: "w-full h-full",
|
console.log("📝 업데이트된 formData:", newFormData);
|
||||||
}}
|
return newFormData;
|
||||||
/>
|
});
|
||||||
)}
|
},
|
||||||
|
isInteractive: true,
|
||||||
|
formData: formData,
|
||||||
|
readonly: component.readonly,
|
||||||
|
required: component.required,
|
||||||
|
placeholder: component.placeholder,
|
||||||
|
className: "w-full h-full",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// 빈 화면일 때도 깔끔하게 표시
|
// 빈 화면일 때도 같은 스케일로 표시 + 좌우 마진 16px
|
||||||
<div
|
<div
|
||||||
className="mx-auto flex items-center justify-center rounded-xl border border-gray-200/60 bg-white shadow-lg shadow-gray-900/5"
|
|
||||||
style={{
|
style={{
|
||||||
width: `${screenWidth}px`,
|
width: `${screenWidth * scale}px`,
|
||||||
height: `${screenHeight}px`,
|
height: `${screenHeight * scale}px`,
|
||||||
minWidth: `${screenWidth}px`,
|
marginLeft: "16px",
|
||||||
minHeight: `${screenHeight}px`,
|
marginRight: "16px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-center">
|
<div
|
||||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-white shadow-sm">
|
className="flex items-center justify-center bg-white"
|
||||||
<span className="text-2xl">📄</span>
|
style={{
|
||||||
|
width: `${screenWidth}px`,
|
||||||
|
height: `${screenHeight}px`,
|
||||||
|
transform: `scale(${scale})`,
|
||||||
|
transformOrigin: "top left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-white shadow-sm">
|
||||||
|
<span className="text-2xl">📄</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="mb-2 text-xl font-semibold text-gray-900">화면이 비어있습니다</h2>
|
||||||
|
<p className="text-gray-600">이 화면에는 아직 설계된 컴포넌트가 없습니다.</p>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="mb-2 text-xl font-semibold text-gray-900">화면이 비어있습니다</h2>
|
|
||||||
<p className="text-gray-600">이 화면에는 아직 설계된 컴포넌트가 없습니다.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -900,12 +900,9 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||||
|
|
||||||
// 모달 내부 가용 공간 계산 (헤더, 푸터, 패딩 제외)
|
// 모달 내부 가용 공간 계산 (헤더, 푸터, 패딩 제외)
|
||||||
const availableWidth = typeof window !== "undefined" ? window.innerWidth * 0.95 - 100 : 1800; // 95vw - 패딩
|
const availableWidth = typeof window !== "undefined" ? window.innerWidth * 0.95 - 100 : 1800; // 95vw - 패딩
|
||||||
const availableHeight = typeof window !== "undefined" ? window.innerHeight * 0.95 - 200 : 800; // 95vh - 헤더/푸터
|
|
||||||
|
|
||||||
// 축소 비율 계산 (가로, 세로 중 더 작은 비율 사용)
|
// 가로폭 기준으로 스케일 계산 (가로폭에 맞춤)
|
||||||
const scaleX = availableWidth / screenWidth;
|
const scale = availableWidth / screenWidth;
|
||||||
const scaleY = availableHeight / screenHeight;
|
|
||||||
const scale = Math.min(scaleX, scaleY, 1); // 최대 1 (확대하지 않음)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue