컴포넌트 그룹화기능
This commit is contained in:
parent
ad988f2951
commit
e18c78f40d
|
|
@ -254,7 +254,7 @@ export const RealtimePreview: React.FC<RealtimePreviewProps> = ({
|
|||
<div className="flex items-center space-x-1">
|
||||
<Group className="h-3 w-3 text-blue-600" />
|
||||
<span className="text-xs font-medium">{label || "그룹"}</span>
|
||||
<span className="text-xs text-gray-500">({children.length}개)</span>
|
||||
<span className="text-xs text-gray-500">({children ? children.length : 0}개)</span>
|
||||
</div>
|
||||
{component.collapsible &&
|
||||
(component.collapsed ? (
|
||||
|
|
|
|||
|
|
@ -535,29 +535,34 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
// 경계 박스 계산
|
||||
const boundingBox = calculateBoundingBox(selectedComponents);
|
||||
|
||||
// 그룹 컴포넌트 생성
|
||||
// 그룹 컴포넌트 생성 (경계 박스 정보 전달)
|
||||
const groupComponent = createGroupComponent(
|
||||
componentIds,
|
||||
title,
|
||||
{ x: boundingBox.minX, y: boundingBox.minY },
|
||||
{ width: boundingBox.width, height: boundingBox.height },
|
||||
style,
|
||||
);
|
||||
|
||||
// 자식 컴포넌트들의 상대 위치 계산
|
||||
const relativeChildren = calculateRelativePositions(selectedComponents, {
|
||||
x: boundingBox.minX,
|
||||
y: boundingBox.minY,
|
||||
});
|
||||
const relativeChildren = calculateRelativePositions(
|
||||
selectedComponents,
|
||||
{
|
||||
x: boundingBox.minX,
|
||||
y: boundingBox.minY,
|
||||
},
|
||||
groupComponent.id,
|
||||
);
|
||||
|
||||
// 새 레이아웃 생성
|
||||
const newLayout = {
|
||||
...layout,
|
||||
components: [
|
||||
// 그룹이 아닌 기존 컴포넌트들
|
||||
...layout.components.filter((comp) => !componentIds.includes(comp.id) && comp.type !== "group"),
|
||||
// 그룹 컴포넌트
|
||||
// 그룹에 포함되지 않은 기존 컴포넌트들만 유지
|
||||
...layout.components.filter((comp) => !componentIds.includes(comp.id)),
|
||||
// 그룹 컴포넌트 추가
|
||||
groupComponent,
|
||||
// 상대 위치로 업데이트된 자식 컴포넌트들
|
||||
// 자식 컴포넌트들도 유지 (parentId로 그룹과 연결)
|
||||
...relativeChildren,
|
||||
],
|
||||
};
|
||||
|
|
@ -585,8 +590,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
const newLayout = {
|
||||
...layout,
|
||||
components: [
|
||||
// 그룹이 아닌 기존 컴포넌트들
|
||||
...layout.components.filter((comp) => comp.id !== groupId),
|
||||
// 그룹과 그룹의 자식 컴포넌트들을 제외한 기존 컴포넌트들
|
||||
...layout.components.filter((comp) => comp.id !== groupId && comp.parentId !== groupId),
|
||||
// 절대 위치로 복원된 자식 컴포넌트들
|
||||
...absoluteChildren,
|
||||
],
|
||||
|
|
@ -986,38 +991,45 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
</div>
|
||||
|
||||
{/* 컴포넌트들 - 실시간 미리보기 */}
|
||||
{layout.components.map((component) => {
|
||||
// 그룹 컴포넌트인 경우 자식 컴포넌트들 가져오기
|
||||
const children =
|
||||
component.type === "group"
|
||||
? layout.components.filter((child) => child.parentId === component.id)
|
||||
: [];
|
||||
{layout.components
|
||||
.filter((component) => !component.parentId) // 최상위 컴포넌트만 렌더링
|
||||
.map((component) => {
|
||||
// 그룹 컴포넌트인 경우 자식 컴포넌트들 가져오기
|
||||
const children =
|
||||
component.type === "group"
|
||||
? layout.components.filter((child) => child.parentId === component.id)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<RealtimePreview
|
||||
key={component.id}
|
||||
component={component}
|
||||
isSelected={
|
||||
selectedComponent?.id === component.id || groupState.selectedComponents.includes(component.id)
|
||||
}
|
||||
onClick={() => handleComponentClick(component)}
|
||||
onDragStart={(e) => startComponentDrag(component, e)}
|
||||
onDragEnd={endDrag}
|
||||
onGroupToggle={(groupId) => {
|
||||
// 그룹 접기/펼치기 토글
|
||||
const groupComp = component as GroupComponent;
|
||||
updateComponentProperty(groupId, "collapsed", !groupComp.collapsed);
|
||||
}}
|
||||
>
|
||||
{children.map((child) => (
|
||||
<div key={child.id} className="rounded border bg-white p-2 text-xs text-gray-600">
|
||||
<div className="font-medium">{child.label || (child as any).columnName || child.id}</div>
|
||||
<div className="text-gray-500">{child.type}</div>
|
||||
</div>
|
||||
))}
|
||||
</RealtimePreview>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<RealtimePreview
|
||||
key={component.id}
|
||||
component={component}
|
||||
isSelected={
|
||||
selectedComponent?.id === component.id ||
|
||||
groupState.selectedComponents.includes(component.id)
|
||||
}
|
||||
onClick={() => handleComponentClick(component)}
|
||||
onDragStart={(e) => startComponentDrag(component, e)}
|
||||
onDragEnd={endDrag}
|
||||
onGroupToggle={(groupId) => {
|
||||
// 그룹 접기/펼치기 토글
|
||||
const groupComp = component as GroupComponent;
|
||||
updateComponentProperty(groupId, "collapsed", !groupComp.collapsed);
|
||||
}}
|
||||
>
|
||||
{children.map((child) => (
|
||||
<RealtimePreview
|
||||
key={child.id}
|
||||
component={child}
|
||||
isSelected={groupState.selectedComponents.includes(child.id)}
|
||||
onClick={() => handleComponentClick(child)}
|
||||
onDragStart={(e) => startComponentDrag(child, e)}
|
||||
onDragEnd={endDrag}
|
||||
/>
|
||||
))}
|
||||
</RealtimePreview>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,14 +12,19 @@ export function createGroupComponent(
|
|||
componentIds: string[],
|
||||
title: string = "새 그룹",
|
||||
position: Position = { x: 0, y: 0 },
|
||||
boundingBox?: { width: number; height: number },
|
||||
style?: any,
|
||||
): GroupComponent {
|
||||
// 격자 기반 크기 계산
|
||||
const gridWidth = Math.max(6, Math.ceil(boundingBox?.width / 80) + 2); // 최소 6 그리드, 여백 2
|
||||
const gridHeight = Math.max(100, (boundingBox?.height || 200) + 40); // 최소 100px, 여백 40px
|
||||
|
||||
return {
|
||||
id: generateComponentId(),
|
||||
type: "group",
|
||||
position,
|
||||
size: { width: 12, height: 200 }, // 기본 크기
|
||||
title,
|
||||
size: { width: gridWidth, height: gridHeight },
|
||||
label: title, // title 대신 label 사용
|
||||
backgroundColor: "#f8f9fa",
|
||||
border: "1px solid #dee2e6",
|
||||
borderRadius: 8,
|
||||
|
|
@ -34,7 +39,7 @@ export function createGroupComponent(
|
|||
};
|
||||
}
|
||||
|
||||
// 선택된 컴포넌트들의 경계 박스 계산
|
||||
// 선택된 컴포넌트들의 경계 박스 계산 (격자 기반)
|
||||
export function calculateBoundingBox(components: ComponentData[]): {
|
||||
minX: number;
|
||||
minY: number;
|
||||
|
|
@ -49,7 +54,7 @@ export function calculateBoundingBox(components: ComponentData[]): {
|
|||
|
||||
const minX = Math.min(...components.map((c) => c.position.x));
|
||||
const minY = Math.min(...components.map((c) => c.position.y));
|
||||
const maxX = Math.max(...components.map((c) => c.position.x + (c.size.width * 80 - 16)));
|
||||
const maxX = Math.max(...components.map((c) => c.position.x + c.size.width * 80));
|
||||
const maxY = Math.max(...components.map((c) => c.position.y + c.size.height));
|
||||
|
||||
return {
|
||||
|
|
@ -63,14 +68,18 @@ export function calculateBoundingBox(components: ComponentData[]): {
|
|||
}
|
||||
|
||||
// 그룹 내 컴포넌트들의 상대 위치 계산
|
||||
export function calculateRelativePositions(components: ComponentData[], groupPosition: Position): ComponentData[] {
|
||||
export function calculateRelativePositions(
|
||||
components: ComponentData[],
|
||||
groupPosition: Position,
|
||||
groupId: string,
|
||||
): ComponentData[] {
|
||||
return components.map((component) => ({
|
||||
...component,
|
||||
position: {
|
||||
x: component.position.x - groupPosition.x,
|
||||
y: component.position.y - groupPosition.y,
|
||||
},
|
||||
parentId: components[0]?.id, // 임시로 첫 번째 컴포넌트 ID 사용
|
||||
parentId: groupId, // 그룹 ID를 부모로 설정
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue