미리보기 기능 수정
This commit is contained in:
parent
13fe9c97fe
commit
e8c02fef5e
|
|
@ -48,6 +48,8 @@ import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRendere
|
||||||
import { DynamicWebTypeRenderer } from "@/lib/registry";
|
import { DynamicWebTypeRenderer } from "@/lib/registry";
|
||||||
import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeUtils";
|
import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeUtils";
|
||||||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||||
|
import { RealtimePreview } from "./RealtimePreviewDynamic";
|
||||||
|
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
|
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
|
||||||
const InteractiveScreenViewer = dynamic(
|
const InteractiveScreenViewer = dynamic(
|
||||||
|
|
@ -1316,8 +1318,9 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>화면 미리보기 - {screenToPreview?.screenName}</DialogTitle>
|
<DialogTitle>화면 미리보기 - {screenToPreview?.screenName}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<TableOptionsProvider>
|
<ScreenPreviewProvider isPreviewMode={true}>
|
||||||
<div className="flex flex-1 items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-slate-100 p-6">
|
<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 ? (
|
{isLoadingPreview ? (
|
||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|
@ -1331,10 +1334,24 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||||
const screenHeight = previewLayout.screenResolution?.height || 800;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -1416,115 +1433,61 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 라벨 표시 여부 계산
|
// 일반 컴포넌트 렌더링 - RealtimePreview 사용 (실제 화면과 동일)
|
||||||
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";
|
|
||||||
|
|
||||||
// 일반 컴포넌트 렌더링
|
|
||||||
return (
|
return (
|
||||||
<div key={component.id}>
|
<RealtimePreview
|
||||||
{/* 라벨을 외부에 별도로 렌더링 */}
|
key={component.id}
|
||||||
{shouldShowLabel && (
|
component={component}
|
||||||
<div
|
isSelected={false}
|
||||||
style={{
|
isDesignMode={false}
|
||||||
position: "absolute",
|
onClick={() => {}}
|
||||||
left: `${component.position.x}px`,
|
screenId={screenToPreview!.screenId}
|
||||||
top: `${component.position.y - 25}px`, // 컴포넌트 위쪽에 라벨 배치
|
tableName={screenToPreview?.tableName}
|
||||||
zIndex: (component.position.z || 1) + 1,
|
formData={previewFormData}
|
||||||
...labelStyle,
|
onFormDataChange={(fieldName, value) => {
|
||||||
}}
|
setPreviewFormData((prev) => ({
|
||||||
>
|
...prev,
|
||||||
{labelText}
|
[fieldName]: value,
|
||||||
{component.required && <span style={{ color: "#f97316", marginLeft: "2px" }}>*</span>}
|
}));
|
||||||
</div>
|
}}
|
||||||
)}
|
>
|
||||||
|
{/* 자식 컴포넌트들 */}
|
||||||
|
{(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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
{/* 실제 컴포넌트 */}
|
return (
|
||||||
<div
|
<RealtimePreview
|
||||||
style={(() => {
|
key={child.id}
|
||||||
const style = {
|
component={relativeChildComponent}
|
||||||
position: "absolute" as const,
|
isSelected={false}
|
||||||
left: `${component.position.x}px`,
|
isDesignMode={false}
|
||||||
top: `${component.position.y}px`,
|
onClick={() => {}}
|
||||||
width: component.style?.width || `${component.size.width}px`,
|
screenId={screenToPreview!.screenId}
|
||||||
height: component.style?.height || `${component.size.height}px`,
|
tableName={screenToPreview?.tableName}
|
||||||
zIndex: component.position.z || 1,
|
formData={previewFormData}
|
||||||
};
|
onFormDataChange={(fieldName, value) => {
|
||||||
|
setPreviewFormData((prev) => ({
|
||||||
return style;
|
...prev,
|
||||||
})()}
|
[fieldName]: value,
|
||||||
>
|
}));
|
||||||
{/* 위젯 컴포넌트가 아닌 경우 DynamicComponentRenderer 사용 */}
|
}}
|
||||||
{component.type !== "widget" ? (
|
/>
|
||||||
<DynamicComponentRenderer
|
);
|
||||||
component={{
|
})}
|
||||||
...component,
|
</RealtimePreview>
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1538,8 +1501,9 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TableOptionsProvider>
|
</TableOptionsProvider>
|
||||||
|
</ScreenPreviewProvider>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setPreviewDialogOpen(false)}>
|
<Button variant="outline" onClick={() => setPreviewDialogOpen(false)}>
|
||||||
닫기
|
닫기
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { GroupingPanel } from "@/components/screen/table-options/GroupingPanel";
|
||||||
import { TableFilter } from "@/types/table-options";
|
import { TableFilter } from "@/types/table-options";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { ModernDatePicker } from "@/components/screen/filters/ModernDatePicker";
|
import { ModernDatePicker } from "@/components/screen/filters/ModernDatePicker";
|
||||||
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
interface PresetFilter {
|
interface PresetFilter {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -44,6 +45,7 @@ interface TableSearchWidgetProps {
|
||||||
|
|
||||||
export function TableSearchWidget({ component, screenId, onHeightChange }: TableSearchWidgetProps) {
|
export function TableSearchWidget({ component, screenId, onHeightChange }: TableSearchWidgetProps) {
|
||||||
const { registeredTables, selectedTableId, setSelectedTableId, getTable } = useTableOptions();
|
const { registeredTables, selectedTableId, setSelectedTableId, getTable } = useTableOptions();
|
||||||
|
const { isPreviewMode } = useScreenPreview(); // 미리보기 모드 확인
|
||||||
|
|
||||||
// 높이 관리 context (실제 화면에서만 사용)
|
// 높이 관리 context (실제 화면에서만 사용)
|
||||||
let setWidgetHeight:
|
let setWidgetHeight:
|
||||||
|
|
@ -445,14 +447,14 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 동적 모드일 때만 설정 버튼들 표시 */}
|
{/* 동적 모드일 때만 설정 버튼들 표시 (미리보기에서는 비활성화) */}
|
||||||
{filterMode === "dynamic" && (
|
{filterMode === "dynamic" && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setColumnVisibilityOpen(true)}
|
onClick={() => !isPreviewMode && setColumnVisibilityOpen(true)}
|
||||||
disabled={!selectedTableId}
|
disabled={!selectedTableId || isPreviewMode}
|
||||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
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" />
|
<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
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setFilterOpen(true)}
|
onClick={() => !isPreviewMode && setFilterOpen(true)}
|
||||||
disabled={!selectedTableId}
|
disabled={!selectedTableId || isPreviewMode}
|
||||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
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" />
|
<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
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setGroupingOpen(true)}
|
onClick={() => !isPreviewMode && setGroupingOpen(true)}
|
||||||
disabled={!selectedTableId}
|
disabled={!selectedTableId || isPreviewMode}
|
||||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
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" />
|
<Layers className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue