레이아웃 컴포넌트 단순화
This commit is contained in:
@@ -28,7 +28,9 @@ interface RealtimePreviewProps {
|
||||
onDragEnd?: () => void;
|
||||
onGroupToggle?: (groupId: string) => void; // 그룹 접기/펼치기
|
||||
children?: React.ReactNode; // 그룹 내 자식 컴포넌트들
|
||||
selectedScreen?: any; // 선택된 화면 정보
|
||||
selectedScreen?: any;
|
||||
onZoneComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void; // 존별 드롭 핸들러
|
||||
onZoneClick?: (zoneId: string) => void; // 존 클릭 핸들러
|
||||
}
|
||||
|
||||
// 동적 위젯 타입 아이콘 (레지스트리에서 조회)
|
||||
@@ -67,6 +69,8 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
onGroupToggle,
|
||||
children,
|
||||
selectedScreen,
|
||||
onZoneComponentDrop,
|
||||
onZoneClick,
|
||||
}) => {
|
||||
const { id, type, position, size, style: componentStyle } = component;
|
||||
|
||||
@@ -79,13 +83,13 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
}
|
||||
: {};
|
||||
|
||||
// 컴포넌트 기본 스타일
|
||||
// 컴포넌트 기본 스타일 - 레이아웃은 항상 맨 아래
|
||||
const baseStyle = {
|
||||
left: `${position.x}px`,
|
||||
top: `${position.y}px`,
|
||||
width: `${size.width}px`,
|
||||
height: `${size.height}px`,
|
||||
zIndex: position.z || 1,
|
||||
width: `${size?.width || 100}px`,
|
||||
height: `${size?.height || 36}px`,
|
||||
zIndex: component.type === "layout" ? 1 : position.z || 2, // 레이아웃은 z-index 1, 다른 컴포넌트는 2 이상
|
||||
...componentStyle,
|
||||
};
|
||||
|
||||
@@ -123,6 +127,8 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
onDragEnd={onDragEnd}
|
||||
children={children}
|
||||
selectedScreen={selectedScreen}
|
||||
onZoneComponentDrop={onZoneComponentDrop}
|
||||
onZoneClick={onZoneClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1345,9 +1345,81 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
[layout, gridInfo, screenResolution, snapToGrid, saveToHistory, openPanel],
|
||||
);
|
||||
|
||||
// 컴포넌트 드래그 처리
|
||||
// handleZoneComponentDrop은 handleComponentDrop으로 대체됨
|
||||
|
||||
// 존 클릭 핸들러
|
||||
const handleZoneClick = useCallback((zoneId: string) => {
|
||||
console.log("🎯 존 클릭:", zoneId);
|
||||
// 필요시 존 선택 로직 추가
|
||||
}, []);
|
||||
|
||||
// 웹타입별 기본 설정 생성 함수를 상위로 이동
|
||||
const getDefaultWebTypeConfig = useCallback((webType: string) => {
|
||||
switch (webType) {
|
||||
case "button":
|
||||
return {
|
||||
actionType: "custom",
|
||||
variant: "default",
|
||||
confirmationMessage: "",
|
||||
popupTitle: "",
|
||||
popupContent: "",
|
||||
navigateUrl: "",
|
||||
};
|
||||
case "date":
|
||||
return {
|
||||
format: "YYYY-MM-DD",
|
||||
showTime: false,
|
||||
placeholder: "날짜를 선택하세요",
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
format: "integer",
|
||||
placeholder: "숫자를 입력하세요",
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
options: [
|
||||
{ label: "옵션 1", value: "option1" },
|
||||
{ label: "옵션 2", value: "option2" },
|
||||
{ label: "옵션 3", value: "option3" },
|
||||
],
|
||||
multiple: false,
|
||||
searchable: false,
|
||||
placeholder: "옵션을 선택하세요",
|
||||
};
|
||||
case "file":
|
||||
return {
|
||||
accept: ["*/*"],
|
||||
maxSize: 10485760, // 10MB
|
||||
multiple: false,
|
||||
showPreview: true,
|
||||
autoUpload: false,
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 컴포넌트 드래그 처리 (캔버스 레벨 드롭)
|
||||
const handleComponentDrop = useCallback(
|
||||
(e: React.DragEvent, component: any) => {
|
||||
(e: React.DragEvent, component?: any, zoneId?: string, layoutId?: string) => {
|
||||
// 존별 드롭인 경우 dragData에서 컴포넌트 정보 추출
|
||||
if (!component) {
|
||||
const dragData = e.dataTransfer.getData("application/json");
|
||||
if (!dragData) return;
|
||||
|
||||
try {
|
||||
const parsedData = JSON.parse(dragData);
|
||||
if (parsedData.type === "component") {
|
||||
component = parsedData.component;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("드래그 데이터 파싱 오류:", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const rect = canvasRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
||||
@@ -1377,53 +1449,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
snappedPosition,
|
||||
});
|
||||
|
||||
// 웹타입별 기본 설정 생성
|
||||
const getDefaultWebTypeConfig = (webType: string) => {
|
||||
switch (webType) {
|
||||
case "button":
|
||||
return {
|
||||
actionType: "custom",
|
||||
variant: "default",
|
||||
confirmationMessage: "",
|
||||
popupTitle: "",
|
||||
popupContent: "",
|
||||
navigateUrl: "",
|
||||
};
|
||||
case "date":
|
||||
return {
|
||||
format: "YYYY-MM-DD",
|
||||
showTime: false,
|
||||
placeholder: "날짜를 선택하세요",
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
format: "integer",
|
||||
placeholder: "숫자를 입력하세요",
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
options: [
|
||||
{ label: "옵션 1", value: "option1" },
|
||||
{ label: "옵션 2", value: "option2" },
|
||||
{ label: "옵션 3", value: "option3" },
|
||||
],
|
||||
multiple: false,
|
||||
searchable: false,
|
||||
placeholder: "옵션을 선택하세요",
|
||||
};
|
||||
case "file":
|
||||
return {
|
||||
accept: ["*/*"],
|
||||
maxSize: 10485760, // 10MB
|
||||
multiple: false,
|
||||
showPreview: true,
|
||||
autoUpload: false,
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// 새 컴포넌트 생성
|
||||
console.log("🔍 ScreenDesigner handleComponentDrop:", {
|
||||
componentName: component.name,
|
||||
@@ -3105,8 +3130,10 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
onDragStart={(e) => startComponentDrag(component, e)}
|
||||
onDragEnd={endDrag}
|
||||
selectedScreen={selectedScreen}
|
||||
// onZoneComponentDrop 제거
|
||||
onZoneClick={handleZoneClick}
|
||||
>
|
||||
{/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 */}
|
||||
{/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 (레이아웃은 독립적으로 렌더링) */}
|
||||
{(component.type === "group" || component.type === "container" || component.type === "area") &&
|
||||
layout.components
|
||||
.filter((child) => child.parentId === component.id)
|
||||
@@ -3182,6 +3209,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
onDragStart={(e) => startComponentDrag(child, e)}
|
||||
onDragEnd={endDrag}
|
||||
selectedScreen={selectedScreen}
|
||||
// onZoneComponentDrop 제거
|
||||
onZoneClick={handleZoneClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -3290,7 +3319,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
id="layouts"
|
||||
title="레이아웃"
|
||||
isOpen={panelStates.layouts?.isOpen || false}
|
||||
onClose={() => closePanelState("layouts")}
|
||||
onClose={() => closePanel("layouts")}
|
||||
position="left"
|
||||
width={380}
|
||||
height={700}
|
||||
@@ -3304,6 +3333,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
};
|
||||
e.dataTransfer.setData("application/json", JSON.stringify(dragData));
|
||||
}}
|
||||
gridSettings={layout.gridSettings || { columns: 12, gap: 16, padding: 16, snapToGrid: true }}
|
||||
screenResolution={screenResolution}
|
||||
/>
|
||||
</FloatingPanel>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Grid, Layout, LayoutDashboard, Table, Navigation, FileText, Building, Search, Plus } from "lucide-react";
|
||||
import { LAYOUT_CATEGORIES, LayoutCategory } from "@/types/layout";
|
||||
import { LayoutRegistry } from "@/lib/registry/LayoutRegistry";
|
||||
import { calculateGridInfo, calculateWidthFromColumns } from "@/lib/utils/gridUtils";
|
||||
|
||||
// 카테고리 아이콘 매핑
|
||||
const CATEGORY_ICONS = {
|
||||
@@ -36,9 +37,25 @@ interface LayoutsPanelProps {
|
||||
onDragStart: (e: React.DragEvent, layoutData: any) => void;
|
||||
onLayoutSelect?: (layoutDefinition: any) => void;
|
||||
className?: string;
|
||||
gridSettings?: {
|
||||
columns: number;
|
||||
gap: number;
|
||||
padding: number;
|
||||
snapToGrid: boolean;
|
||||
};
|
||||
screenResolution?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }: LayoutsPanelProps) {
|
||||
export default function LayoutsPanel({
|
||||
onDragStart,
|
||||
onLayoutSelect,
|
||||
className,
|
||||
gridSettings,
|
||||
screenResolution,
|
||||
}: LayoutsPanelProps) {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>("all");
|
||||
|
||||
@@ -78,6 +95,29 @@ export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }:
|
||||
|
||||
// 레이아웃 드래그 시작 핸들러
|
||||
const handleDragStart = (e: React.DragEvent, layoutDefinition: any) => {
|
||||
// 격자 기반 동적 크기 계산
|
||||
let calculatedSize = layoutDefinition.defaultSize || { width: 400, height: 300 };
|
||||
|
||||
if (gridSettings && screenResolution && layoutDefinition.id === "card-layout") {
|
||||
// 카드 레이아웃의 경우 8그리드 컬럼에 맞는 너비 계산
|
||||
const gridInfo = calculateGridInfo(screenResolution.width, screenResolution.height, gridSettings);
|
||||
const calculatedWidth = calculateWidthFromColumns(8, gridInfo, gridSettings);
|
||||
|
||||
calculatedSize = {
|
||||
width: Math.max(calculatedWidth, 400), // 최소 400px 보장
|
||||
height: 400, // 높이는 고정
|
||||
};
|
||||
|
||||
console.log("🎯 카드 레이아웃 동적 크기 계산:", {
|
||||
gridColumns: 8,
|
||||
screenResolution,
|
||||
gridSettings,
|
||||
gridInfo,
|
||||
calculatedWidth,
|
||||
finalSize: calculatedSize,
|
||||
});
|
||||
}
|
||||
|
||||
// 새 레이아웃 컴포넌트 데이터 생성
|
||||
const layoutData = {
|
||||
id: `layout_${Date.now()}`,
|
||||
@@ -88,8 +128,9 @@ export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }:
|
||||
children: [],
|
||||
allowedComponentTypes: [],
|
||||
position: { x: 0, y: 0 },
|
||||
size: layoutDefinition.defaultSize || { width: 400, height: 300 },
|
||||
size: calculatedSize,
|
||||
label: layoutDefinition.name,
|
||||
gridColumns: layoutDefinition.id === "card-layout" ? 8 : 1, // 카드 레이아웃은 기본 8그리드
|
||||
};
|
||||
|
||||
// 드래그 데이터 설정
|
||||
@@ -192,4 +233,3 @@ export default function LayoutsPanel({ onDragStart, onLayoutSelect, className }:
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -191,9 +191,11 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
||||
positionX: currentPosition.x.toString(),
|
||||
positionY: currentPosition.y.toString(),
|
||||
positionZ: selectedComponent?.position.z?.toString() || "1",
|
||||
width: selectedComponent?.size.width?.toString() || "0",
|
||||
height: selectedComponent?.size.height?.toString() || "0",
|
||||
gridColumns: selectedComponent?.gridColumns?.toString() || "1",
|
||||
width: selectedComponent?.size?.width?.toString() || "0",
|
||||
height: selectedComponent?.size?.height?.toString() || "0",
|
||||
gridColumns:
|
||||
selectedComponent?.gridColumns?.toString() ||
|
||||
(selectedComponent?.type === "layout" && (selectedComponent as any)?.layoutType === "card-layout" ? "8" : "1"),
|
||||
labelText: selectedComponent?.style?.labelText || selectedComponent?.label || "",
|
||||
labelFontSize: selectedComponent?.style?.labelFontSize || "12px",
|
||||
labelColor: selectedComponent?.style?.labelColor || "#374151",
|
||||
@@ -244,14 +246,18 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
||||
description: area?.description || "",
|
||||
positionX: currentPos.x.toString(),
|
||||
positionY: currentPos.y.toString(),
|
||||
positionZ: selectedComponent.position.z?.toString() || "1",
|
||||
width: selectedComponent.size.width?.toString() || "0",
|
||||
height: selectedComponent.size.height?.toString() || "0",
|
||||
gridColumns: selectedComponent.gridColumns?.toString() || "1",
|
||||
labelText: selectedComponent.style?.labelText || selectedComponent.label || "",
|
||||
labelFontSize: selectedComponent.style?.labelFontSize || "12px",
|
||||
labelColor: selectedComponent.style?.labelColor || "#374151",
|
||||
labelMarginBottom: selectedComponent.style?.labelMarginBottom || "4px",
|
||||
positionZ: selectedComponent?.position?.z?.toString() || "1",
|
||||
width: selectedComponent?.size?.width?.toString() || "0", // 안전한 접근
|
||||
height: selectedComponent?.size?.height?.toString() || "0", // 안전한 접근
|
||||
gridColumns:
|
||||
selectedComponent?.gridColumns?.toString() ||
|
||||
(selectedComponent?.type === "layout" && (selectedComponent as any)?.layoutType === "card-layout"
|
||||
? "8"
|
||||
: "1"),
|
||||
labelText: selectedComponent?.style?.labelText || selectedComponent?.label || "",
|
||||
labelFontSize: selectedComponent?.style?.labelFontSize || "12px",
|
||||
labelColor: selectedComponent?.style?.labelColor || "#374151",
|
||||
labelMarginBottom: selectedComponent?.style?.labelMarginBottom || "4px",
|
||||
required: widget?.required || false,
|
||||
readonly: widget?.readonly || false,
|
||||
labelDisplay: selectedComponent.style?.labelDisplay !== false,
|
||||
@@ -584,7 +590,7 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, width: newValue }));
|
||||
onUpdateProperty("size", { ...selectedComponent.size, width: Number(newValue) });
|
||||
onUpdateProperty("size", { ...(selectedComponent.size || {}), width: Number(newValue) });
|
||||
}}
|
||||
className="mt-1"
|
||||
/>
|
||||
@@ -601,7 +607,7 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, height: newValue }));
|
||||
onUpdateProperty("size", { ...selectedComponent.size, height: Number(newValue) });
|
||||
onUpdateProperty("size", { ...(selectedComponent.size || {}), height: Number(newValue) });
|
||||
}}
|
||||
className="mt-1"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user