미리보기 기능 수정

This commit is contained in:
kjs
2025-11-26 14:58:18 +09:00
parent 13fe9c97fe
commit e8c02fef5e
2 changed files with 87 additions and 121 deletions

View File

@@ -48,6 +48,8 @@ import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRendere
import { DynamicWebTypeRenderer } from "@/lib/registry";
import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeUtils";
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
import { RealtimePreview } from "./RealtimePreviewDynamic";
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
const InteractiveScreenViewer = dynamic(
@@ -1316,8 +1318,9 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
<DialogHeader>
<DialogTitle> - {screenToPreview?.screenName}</DialogTitle>
</DialogHeader>
<TableOptionsProvider>
<div className="flex flex-1 items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-slate-100 p-6">
<ScreenPreviewProvider isPreviewMode={true}>
<TableOptionsProvider>
<div className="flex flex-1 items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-slate-100 p-6">
{isLoadingPreview ? (
<div className="flex h-full items-center justify-center">
<div className="text-center">
@@ -1331,10 +1334,24 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
const screenHeight = previewLayout.screenResolution?.height || 800;
// 모달 내부 가용 공간 계산 (헤더, 푸터, 패딩 제외)
const availableWidth = typeof window !== "undefined" ? window.innerWidth * 0.95 - 100 : 1800; // 95vw - 패딩
const modalPadding = 100; // 헤더 + 푸터 + 패딩
const availableWidth = typeof window !== "undefined" ? window.innerWidth * 0.95 - modalPadding : 1700;
const availableHeight = typeof window !== "undefined" ? window.innerHeight * 0.95 - modalPadding : 900;
// 가로폭 기준으로 스케일 계산 (가로폭에 맞춤)
const scale = availableWidth / screenWidth;
// 가로/세로 비율을 모두 고려하여 작은 쪽에 맞춤 (화면이 잘리지 않도록)
const scaleX = availableWidth / screenWidth;
const scaleY = availableHeight / screenHeight;
const scale = Math.min(scaleX, scaleY, 1); // 최대 1배율 (확대 방지)
console.log("📐 미리보기 스케일 계산:", {
screenWidth,
screenHeight,
availableWidth,
availableHeight,
scaleX,
scaleY,
finalScale: scale,
});
return (
<div
@@ -1416,115 +1433,61 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
);
}
// 라벨 표시 여부 계산
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",
};
const labelMarginBottom = component.style?.labelMarginBottom || "4px";
// 일반 컴포넌트 렌더링
// 일반 컴포넌트 렌더링 - RealtimePreview 사용 (실제 화면과 동일)
return (
<div key={component.id}>
{/* 라벨을 외부에 별도로 렌더링 */}
{shouldShowLabel && (
<div
style={{
position: "absolute",
left: `${component.position.x}px`,
top: `${component.position.y - 25}px`, // 컴포넌트 위쪽에 라벨 배치
zIndex: (component.position.z || 1) + 1,
...labelStyle,
}}
>
{labelText}
{component.required && <span style={{ color: "#f97316", marginLeft: "2px" }}>*</span>}
</div>
)}
<RealtimePreview
key={component.id}
component={component}
isSelected={false}
isDesignMode={false}
onClick={() => {}}
screenId={screenToPreview!.screenId}
tableName={screenToPreview?.tableName}
formData={previewFormData}
onFormDataChange={(fieldName, value) => {
setPreviewFormData((prev) => ({
...prev,
[fieldName]: value,
}));
}}
>
{/* 자식 컴포넌트들 */}
{(component.type === "group" ||
component.type === "container" ||
component.type === "area") &&
previewLayout.components
.filter((child: any) => child.parentId === component.id)
.map((child: any) => {
// 자식 컴포넌트의 위치를 부모 기준 상대 좌표로 조정
const relativeChildComponent = {
...child,
position: {
x: child.position.x - component.position.x,
y: child.position.y - component.position.y,
z: child.position.z || 1,
},
};
{/* 실제 컴포넌트 */}
<div
style={(() => {
const style = {
position: "absolute" as const,
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,
};
return style;
})()}
>
{/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */}
{component.type !== "widget" ? (
<DynamicComponentRenderer
component={{
...component,
style: {
...component.style,
labelDisplay: shouldShowLabel ? false : (component.style?.labelDisplay ?? true), // 상위에서 라벨을 표시했으면 컴포넌트 내부에서는 숨김
},
}}
isInteractive={true}
formData={previewFormData}
onFormDataChange={(fieldName, value) => {
setPreviewFormData((prev) => ({
...prev,
[fieldName]: value,
}));
}}
screenId={screenToPreview!.screenId}
tableName={screenToPreview?.tableName}
/>
) : (
<DynamicWebTypeRenderer
webType={(() => {
// 유틸리티 함수로 파일 컴포넌트 감지
if (isFileComponent(component)) {
return "file";
}
// 다른 컴포넌트는 유틸리티 함수로 webType 결정
return getComponentWebType(component) || "text";
})()}
config={component.webTypeConfig}
props={{
component: component,
value: previewFormData[component.columnName || component.id] || "",
onChange: (value: any) => {
const fieldName = component.columnName || component.id;
setPreviewFormData((prev) => ({
...prev,
[fieldName]: value,
}));
},
onFormDataChange: (fieldName, value) => {
setPreviewFormData((prev) => ({
...prev,
[fieldName]: value,
}));
},
isInteractive: true,
formData: previewFormData,
readonly: component.readonly,
required: component.required,
placeholder: component.placeholder,
className: "w-full h-full",
}}
/>
)}
</div>
</div>
return (
<RealtimePreview
key={child.id}
component={relativeChildComponent}
isSelected={false}
isDesignMode={false}
onClick={() => {}}
screenId={screenToPreview!.screenId}
tableName={screenToPreview?.tableName}
formData={previewFormData}
onFormDataChange={(fieldName, value) => {
setPreviewFormData((prev) => ({
...prev,
[fieldName]: value,
}));
}}
/>
);
})}
</RealtimePreview>
);
})}
</div>
@@ -1538,8 +1501,9 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
</div>
</div>
)}
</div>
</TableOptionsProvider>
</div>
</TableOptionsProvider>
</ScreenPreviewProvider>
<DialogFooter>
<Button variant="outline" onClick={() => setPreviewDialogOpen(false)}>

View File

@@ -12,6 +12,7 @@ import { GroupingPanel } from "@/components/screen/table-options/GroupingPanel";
import { TableFilter } from "@/types/table-options";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { ModernDatePicker } from "@/components/screen/filters/ModernDatePicker";
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
interface PresetFilter {
id: string;
@@ -44,6 +45,7 @@ interface TableSearchWidgetProps {
export function TableSearchWidget({ component, screenId, onHeightChange }: TableSearchWidgetProps) {
const { registeredTables, selectedTableId, setSelectedTableId, getTable } = useTableOptions();
const { isPreviewMode } = useScreenPreview(); // 미리보기 모드 확인
// 높이 관리 context (실제 화면에서만 사용)
let setWidgetHeight:
@@ -445,14 +447,14 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
</div>
)}
{/* 동적 모드일 때만 설정 버튼들 표시 */}
{/* 동적 모드일 때만 설정 버튼들 표시 (미리보기에서는 비활성화) */}
{filterMode === "dynamic" && (
<>
<Button
variant="outline"
size="sm"
onClick={() => setColumnVisibilityOpen(true)}
disabled={!selectedTableId}
onClick={() => !isPreviewMode && setColumnVisibilityOpen(true)}
disabled={!selectedTableId || isPreviewMode}
className="h-8 text-xs sm:h-9 sm:text-sm"
>
<Settings className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
@@ -462,8 +464,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
<Button
variant="outline"
size="sm"
onClick={() => setFilterOpen(true)}
disabled={!selectedTableId}
onClick={() => !isPreviewMode && setFilterOpen(true)}
disabled={!selectedTableId || isPreviewMode}
className="h-8 text-xs sm:h-9 sm:text-sm"
>
<Filter className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
@@ -473,8 +475,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
<Button
variant="outline"
size="sm"
onClick={() => setGroupingOpen(true)}
disabled={!selectedTableId}
onClick={() => !isPreviewMode && setGroupingOpen(true)}
disabled={!selectedTableId || isPreviewMode}
className="h-8 text-xs sm:h-9 sm:text-sm"
>
<Layers className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />