ERP-node/frontend/lib/utils/slotAdjustment.ts

898 lines
28 KiB
TypeScript
Raw Normal View History

/**
*
*
* :
* 1.
* 2.
* 3. 우선순위:
*/
import { ComponentData, GridSettings, Position } from "@/types/screen";
import {
SlotMap,
GridInfo,
getComponentColumns,
areSlotsEmpty,
getComponentsInSlots,
countEmptySlots,
findComponentSlots,
slotToPosition,
positionToSlot,
rowToPosition,
buildSlotMap,
positionToRow,
} from "./slotCalculations";
import { calculateWidthFromColumns } from "./gridUtils";
/**
*
*/
export interface PlacementCheck {
canPlace: boolean;
strategy: "EMPTY_SPACE" | "SHRINK_COMPONENTS" | "MOVE_DOWN" | "IMPOSSIBLE";
reason?: string;
affectedComponents: string[]; // 영향받는 컴포넌트 ID
requiredSpace: number; // 필요한 슬롯 수
availableSpace: number; // 사용 가능한 빈 슬롯 수
}
/**
*
*/
export interface LayoutAdjustment {
success: boolean;
adjustedComponents: ComponentData[];
resizedComponents: Array<{
id: string;
oldColumns: number;
newColumns: number;
oldSlots: number[];
newSlots: number[];
}>;
movedComponents: Array<{
id: string;
oldRow: number;
newRow: number;
oldPosition: Position;
newPosition: Position;
}>;
placement: Position; // 드래그된 컴포넌트의 최종 배치 위치
}
/**
*
*/
export function canPlaceInSlot(
targetSlot: number,
columns: number,
rowIndex: number,
slotMap: SlotMap,
allComponents: ComponentData[],
minColumns: number = 2,
): PlacementCheck {
const endSlot = Math.min(11, targetSlot + columns - 1);
console.log("🔍 배치 가능 여부 체크:", {
targetSlot,
endSlot,
columns,
rowIndex,
});
// 슬롯이 비어있는지 체크
const isEmpty = areSlotsEmpty(targetSlot, endSlot, rowIndex, slotMap);
if (isEmpty) {
return {
canPlace: true,
strategy: "EMPTY_SPACE",
affectedComponents: [],
requiredSpace: columns,
availableSpace: columns,
};
}
// 겹치는 컴포넌트 찾기
const affectedComponents = getComponentsInSlots(targetSlot, endSlot, rowIndex, slotMap);
// 빈 슬롯 수 계산
const emptySlots = countEmptySlots(rowIndex, slotMap);
console.log("📊 행 분석:", {
emptySlots,
affectedComponents,
requiredSpace: columns,
});
// 컴포넌트를 축소해서 공간 확보 가능한지 먼저 체크
const canShrink = checkIfCanShrink(affectedComponents, columns, rowIndex, slotMap, allComponents, minColumns);
console.log("✂️ 축소 가능 여부:", {
possible: canShrink.possible,
availableSpace: canShrink.availableSpace,
emptySlots,
totalAvailable: emptySlots + canShrink.availableSpace,
required: columns,
});
// 빈 공간 + 축소 가능 공간이 충분하면 축소 전략 사용
if (emptySlots + canShrink.availableSpace >= columns) {
return {
canPlace: true,
strategy: "SHRINK_COMPONENTS",
affectedComponents,
requiredSpace: columns,
availableSpace: emptySlots + canShrink.availableSpace,
};
}
// 축소로도 불가능하면 아래로 이동
return {
canPlace: true,
strategy: "MOVE_DOWN",
affectedComponents,
requiredSpace: columns,
availableSpace: emptySlots,
};
}
/**
*
*/
function checkIfCanShrink(
componentIds: string[],
requiredSpace: number,
rowIndex: number,
slotMap: SlotMap,
allComponents: ComponentData[],
minColumns: number,
): { possible: boolean; availableSpace: number } {
let totalShrinkable = 0;
for (const componentId of componentIds) {
const component = allComponents.find((c) => c.id === componentId);
if (!component) continue;
const currentColumns = getComponentColumns(component);
const shrinkable = Math.max(0, currentColumns - minColumns);
totalShrinkable += shrinkable;
}
console.log("✂️ 축소 가능 공간:", {
componentIds,
totalShrinkable,
requiredSpace,
possible: totalShrinkable >= requiredSpace,
});
return {
possible: totalShrinkable >= requiredSpace,
availableSpace: totalShrinkable,
};
}
/**
*
*/
export function calculateSlotPlacement(
targetSlot: number,
draggedColumns: number,
draggedComponentId: string,
rowIndex: number,
slotMap: SlotMap,
allComponents: ComponentData[],
gridInfo: GridInfo,
gridSettings: GridSettings,
minColumns: number = 2,
): LayoutAdjustment {
const endSlot = Math.min(11, targetSlot + draggedColumns - 1);
console.log("🎯 레이아웃 조정 계산 시작:", {
targetSlot,
endSlot,
draggedColumns,
rowIndex,
});
// 배치 가능 여부 체크
const check = canPlaceInSlot(targetSlot, draggedColumns, rowIndex, slotMap, allComponents, minColumns);
if (!check.canPlace) {
return {
success: false,
adjustedComponents: allComponents,
resizedComponents: [],
movedComponents: [],
placement: { x: 0, y: 0, z: 1 },
};
}
// 전략별 조정 수행
switch (check.strategy) {
case "EMPTY_SPACE":
return placeInEmptySpace(targetSlot, draggedColumns, draggedComponentId, rowIndex, allComponents, gridInfo);
case "SHRINK_COMPONENTS":
return placeWithShrinking(
targetSlot,
draggedColumns,
draggedComponentId,
rowIndex,
slotMap,
allComponents,
gridInfo,
gridSettings,
minColumns,
);
case "MOVE_DOWN":
return placeWithMovingDown(
targetSlot,
draggedColumns,
draggedComponentId,
rowIndex,
slotMap,
allComponents,
gridInfo,
gridSettings,
);
default:
return {
success: false,
adjustedComponents: allComponents,
resizedComponents: [],
movedComponents: [],
placement: { x: 0, y: 0, z: 1 },
};
}
}
/**
* 1: ( )
*/
function placeInEmptySpace(
targetSlot: number,
draggedColumns: number,
draggedComponentId: string,
rowIndex: number,
allComponents: ComponentData[],
gridInfo: GridInfo,
): LayoutAdjustment {
console.log("✅ 빈 공간에 배치 (조정 불필요)");
const x = slotToPosition(targetSlot, gridInfo);
const y = rowToPosition(rowIndex, gridInfo);
return {
success: true,
adjustedComponents: allComponents,
resizedComponents: [],
movedComponents: [],
placement: { x, y, z: 1 },
};
}
/**
* 2: 컴포넌트
*/
function placeWithShrinking(
targetSlot: number,
draggedColumns: number,
draggedComponentId: string,
rowIndex: number,
slotMap: SlotMap,
allComponents: ComponentData[],
gridInfo: GridInfo,
gridSettings: GridSettings,
minColumns: number,
): LayoutAdjustment {
console.log("✂️ 컴포넌트 축소하여 배치");
const { columnWidth, gap } = gridInfo;
const endSlot = Math.min(11, targetSlot + draggedColumns - 1);
// 겹치는 컴포넌트들 (드롭 위치에 직접 겹치는 컴포넌트)
const directlyAffected = getComponentsInSlots(targetSlot, endSlot, rowIndex, slotMap);
// 같은 행의 모든 컴포넌트들 (왼쪽/오른쪽 모두)
const allComponentsInRow = allComponents.filter((c) => {
const compRow = positionToRow(c.position.y, gridInfo);
return compRow === rowIndex && c.id !== draggedComponentId;
});
let adjustedComponents = [...allComponents];
const resizedComponents: LayoutAdjustment["resizedComponents"] = [];
// 같은 행의 모든 컴포넌트를 슬롯 순서대로 정렬
const componentsWithSlots = allComponentsInRow
.map((c) => {
const slots = findComponentSlots(c.id, rowIndex, slotMap);
return {
component: c,
startSlot: slots?.startSlot ?? 0,
columns: getComponentColumns(c),
};
})
.sort((a, b) => a.startSlot - b.startSlot);
// targetSlot 이전/이후 컴포넌트 분리
// 컴포넌트의 끝이 targetSlot 이전이면 beforeTarget
const beforeTarget = componentsWithSlots.filter((c) => c.startSlot + c.columns - 1 < targetSlot);
// 컴포넌트의 시작이 targetSlot 이후거나, targetSlot과 겹치면 atOrAfterTarget
const atOrAfterTarget = componentsWithSlots.filter((c) => c.startSlot + c.columns - 1 >= targetSlot);
// 이전 컴포넌트들의 총 컬럼 수
const beforeColumns = beforeTarget.reduce((sum, c) => sum + c.columns, 0);
// 이후 컴포넌트들의 총 컬럼 수
const afterColumns = atOrAfterTarget.reduce((sum, c) => sum + c.columns, 0);
// 필요한 총 컬럼: 이전 + 드래그 + 이후
const totalNeeded = beforeColumns + draggedColumns + afterColumns;
console.log("📊 레이아웃 분석:", {
targetSlot,
beforeColumns,
draggedColumns,
afterColumns,
totalNeeded,
spaceNeeded: totalNeeded - 12,
before: beforeTarget.map((c) => `${c.component.id}:${c.columns}`),
after: atOrAfterTarget.map((c) => `${c.component.id}:${c.columns}`),
});
// atOrAfterTarget 컴포넌트들 중 targetSlot과 겹치는 컴포넌트가 있는지 체크
const overlappingComponents = atOrAfterTarget.filter((c) => {
const endSlot = c.startSlot + c.columns - 1;
const targetEndSlot = Math.min(11, targetSlot + draggedColumns - 1);
// 겹침 조건: 컴포넌트 범위와 목표 범위가 겹치는지
return !(endSlot < targetSlot || c.startSlot > targetEndSlot);
});
console.log("🔍 겹침 컴포넌트:", {
overlapping: overlappingComponents.map((c) => `${c.component.id}:${c.startSlot}-${c.startSlot + c.columns - 1}`),
});
// 실제로 겹치는 슬롯 수 계산
let overlapSlots = 0;
if (overlappingComponents.length > 0) {
const targetEndSlot = Math.min(11, targetSlot + draggedColumns - 1);
// 각 겹치는 컴포넌트의 겹침 범위를 계산하여 최대값 사용
// (여러 컴포넌트와 겹칠 경우, 전체 겹침 범위)
const allOverlapSlots = new Set<number>();
for (const compInfo of overlappingComponents) {
const compEndSlot = compInfo.startSlot + compInfo.columns - 1;
// 겹치는 슬롯 범위
const overlapStart = Math.max(compInfo.startSlot, targetSlot);
const overlapEnd = Math.min(compEndSlot, targetEndSlot);
for (let slot = overlapStart; slot <= overlapEnd; slot++) {
allOverlapSlots.add(slot);
}
}
overlapSlots = allOverlapSlots.size;
}
// 필요한 공간 = 실제 겹치는 슬롯 수
let spaceNeeded = overlapSlots;
// 12컬럼 초과 체크 (겹침과는 별개로 전체 공간 부족)
if (totalNeeded > 12) {
spaceNeeded = Math.max(spaceNeeded, totalNeeded - 12);
}
console.log("📊 공간 분석:", {
hasOverlap: overlappingComponents.length > 0,
overlapSlots,
draggedColumns,
totalNeeded,
spaceNeeded,
overlapping: overlappingComponents.map(
(c) => `${c.component.id}:슬롯${c.startSlot}-${c.startSlot + c.columns - 1}`,
),
});
// 필요한 만큼 축소
if (spaceNeeded > 0) {
// atOrAfterTarget 컴포넌트들을 축소
for (const compInfo of atOrAfterTarget) {
if (spaceNeeded <= 0) break;
const oldColumns = compInfo.columns;
const maxShrink = oldColumns - minColumns;
if (maxShrink > 0) {
const shrinkAmount = Math.min(spaceNeeded, maxShrink);
const newColumns = oldColumns - shrinkAmount;
const newWidth = newColumns * columnWidth + (newColumns - 1) * gap;
const componentIndex = adjustedComponents.findIndex((c) => c.id === compInfo.component.id);
if (componentIndex !== -1) {
adjustedComponents[componentIndex] = {
...adjustedComponents[componentIndex],
gridColumns: newColumns,
columnSpan: newColumns,
size: {
...adjustedComponents[componentIndex].size,
width: newWidth,
},
} as ComponentData;
resizedComponents.push({
id: compInfo.component.id,
oldColumns,
newColumns,
oldSlots: Array.from({ length: oldColumns }, (_, i) => compInfo.startSlot + i),
newSlots: Array.from({ length: newColumns }, (_, i) => compInfo.startSlot + i),
});
spaceNeeded -= shrinkAmount;
console.log(`✂️ ${compInfo.component.id} 축소:`, {
oldColumns,
newColumns,
shrinkAmount,
remainingNeed: spaceNeeded,
});
}
}
}
}
console.log("📋 초기 축소 완료:", {
totalResized: resizedComponents.length,
resizedComponents: resizedComponents.map((r) => ({
id: r.id,
oldColumns: r.oldColumns,
newColumns: r.newColumns,
})),
});
// 배치 위치 계산
let x = slotToPosition(targetSlot, gridInfo);
let y = rowToPosition(rowIndex, gridInfo);
// 드래그될 컴포넌트가 차지할 슬롯 범위 (예약)
const reservedStartSlot = targetSlot;
const reservedEndSlot = Math.min(11, targetSlot + draggedColumns - 1);
console.log("🎯 드래그 컴포넌트 슬롯 예약:", {
targetSlot,
draggedColumns,
reservedSlots: `${reservedStartSlot}-${reservedEndSlot}`,
currentSpaceNeeded: spaceNeeded,
});
// 같은 행의 모든 컴포넌트들의 X 위치를 재계산하여 슬롯에 맞게 정렬
// 드래그될 컴포넌트의 슬롯은 건너뛰기
const componentsInRowSorted = adjustedComponents
.filter((c) => {
const compRow = positionToRow(c.position.y, gridInfo);
return compRow === rowIndex && c.id !== draggedComponentId;
})
.sort((a, b) => a.position.x - b.position.x);
let currentSlot = 0;
const movedToNextRow: string[] = []; // 다음 행으로 이동한 컴포넌트들
for (const comp of componentsInRowSorted) {
const compIndex = adjustedComponents.findIndex((c) => c.id === comp.id);
if (compIndex !== -1) {
let compColumns = getComponentColumns(adjustedComponents[compIndex]);
// 현재 슬롯이 예약된 범위와 겹치는지 체크
const wouldOverlap = currentSlot <= reservedEndSlot && currentSlot + compColumns - 1 >= reservedStartSlot;
if (wouldOverlap) {
// 예약된 슬롯을 건너뛰고 그 다음부터 배치
currentSlot = reservedEndSlot + 1;
console.log(`⏭️ ${comp.id} 예약된 슬롯 건너뛰기, 새 시작 슬롯: ${currentSlot}`);
}
// 화면 밖으로 나가는지 체크 (12컬럼 초과)
if (currentSlot + compColumns > 12) {
const overflow = currentSlot + compColumns - 12;
const oldColumns = compColumns;
const maxShrink = compColumns - minColumns;
// 추가 축소 가능하면 축소
if (maxShrink >= overflow) {
compColumns -= overflow;
const newWidth = compColumns * columnWidth + (compColumns - 1) * gap;
adjustedComponents[compIndex] = {
...adjustedComponents[compIndex],
gridColumns: compColumns,
columnSpan: compColumns,
size: {
...adjustedComponents[compIndex].size,
width: newWidth,
},
} as ComponentData;
resizedComponents.push({
id: comp.id,
oldColumns,
newColumns: compColumns,
oldSlots: [],
newSlots: [],
});
console.log(`✂️ ${comp.id} 추가 축소 (화면 경계):`, {
oldColumns,
newColumns: compColumns,
overflow,
});
} else {
// 축소로도 안되면 다음 행으로 이동
console.log(`⚠️ ${comp.id} 화면 밖으로 나감! (슬롯 ${currentSlot}, 컬럼 ${compColumns})`);
const newY = rowToPosition(rowIndex + 1, gridInfo);
adjustedComponents[compIndex] = {
...adjustedComponents[compIndex],
position: {
x: 0,
y: newY,
z: adjustedComponents[compIndex].position.z,
},
} as ComponentData;
movedToNextRow.push(comp.id);
console.log(`⬇️ ${comp.id} 다음 행으로 이동 (y: ${newY})`);
continue;
}
}
const newX = slotToPosition(currentSlot, gridInfo);
adjustedComponents[compIndex] = {
...adjustedComponents[compIndex],
position: {
...adjustedComponents[compIndex].position,
x: newX,
},
} as ComponentData;
console.log(`📍 ${comp.id} 위치 재조정:`, {
oldX: comp.position.x,
newX,
slot: currentSlot,
columns: compColumns,
wasOverlapping: wouldOverlap,
});
currentSlot += compColumns;
}
}
// 오른쪽 축소 효과: resizedComponents 중 targetSlot 오른쪽에 있는 컴포넌트들을 오른쪽으로 밀기
for (const resized of resizedComponents) {
const compIndex = adjustedComponents.findIndex((c) => c.id === resized.id);
if (compIndex !== -1) {
const comp = adjustedComponents[compIndex];
const compSlot = positionToSlot(comp.position.x, gridInfo);
// targetSlot 오른쪽에 있고 축소된 컴포넌트만 처리
if (compSlot >= targetSlot && resized.oldColumns > resized.newColumns) {
const shrinkAmount = resized.oldColumns - resized.newColumns;
const newX = comp.position.x + shrinkAmount * (columnWidth + gap);
adjustedComponents[compIndex] = {
...adjustedComponents[compIndex],
position: {
...adjustedComponents[compIndex].position,
x: newX,
},
} as ComponentData;
console.log(`➡️ ${resized.id} 오른쪽으로 이동:`, {
oldX: comp.position.x,
newX,
shrinkAmount,
oldColumns: resized.oldColumns,
newColumns: resized.newColumns,
});
}
}
}
// 축소된 컴포넌트 복구 로직
// 조건:
// 1. 축소된 컴포넌트가 있고
// 2. 컴포넌트가 다음 행으로 이동했거나
// 3. 드래그 컴포넌트가 현재 행에서 이미 축소된 컴포넌트와 겹치지 않는 위치에 배치되는 경우
if (resizedComponents.length > 0) {
console.log("🔍 축소된 컴포넌트 복구 가능 여부 확인:", {
movedToNextRow: movedToNextRow.length,
resizedComponents: resizedComponents.map((r) => r.id),
targetSlot,
draggedColumns,
});
// 🔧 중요: 업데이트된 컴포넌트 기준으로 새로운 슬롯맵 생성
const updatedSlotMap = buildSlotMap(adjustedComponents, gridInfo);
// 드래그 컴포넌트가 차지할 슬롯 범위
const draggedStartSlot = targetSlot;
const draggedEndSlot = Math.min(11, targetSlot + draggedColumns - 1);
// 축소된 컴포넌트들이 차지하는 슬롯 범위 (원본 크기 기준)
let canRestoreAll = true;
const restoredSlotRanges: Array<{ id: string; startSlot: number; endSlot: number }> = [];
for (const resizeInfo of resizedComponents) {
const comp = adjustedComponents.find((c) => c.id === resizeInfo.id);
if (!comp || movedToNextRow.includes(resizeInfo.id)) continue;
// 현재 위치를 슬롯으로 변환 (업데이트된 슬롯맵 사용)
const compSlots = findComponentSlots(resizeInfo.id, rowIndex, updatedSlotMap);
if (!compSlots) {
console.log(`⚠️ ${resizeInfo.id}의 슬롯을 찾을 수 없음`);
canRestoreAll = false;
break;
}
// 원본 크기로 복구했을 때의 슬롯 범위 계산
const currentStartSlot = compSlots.startSlot;
const restoredEndSlot = currentStartSlot + resizeInfo.oldColumns - 1;
console.log(`📍 ${resizeInfo.id} 슬롯 정보:`, {
currentSlots: `${currentStartSlot}-${compSlots.endSlot} (${compSlots.columns}컬럼)`,
restoredSlots: `${currentStartSlot}-${restoredEndSlot} (${resizeInfo.oldColumns}컬럼)`,
});
// 드래그 컴포넌트와 겹치는지 확인
const wouldOverlapWithDragged = !(restoredEndSlot < draggedStartSlot || currentStartSlot > draggedEndSlot);
if (wouldOverlapWithDragged) {
console.log(`⚠️ ${resizeInfo.id} 복구 시 드래그 컴포넌트와 겹침:`, {
restoredRange: `${currentStartSlot}-${restoredEndSlot}`,
draggedRange: `${draggedStartSlot}-${draggedEndSlot}`,
});
canRestoreAll = false;
break;
}
restoredSlotRanges.push({
id: resizeInfo.id,
startSlot: currentStartSlot,
endSlot: restoredEndSlot,
});
}
// 복구된 컴포넌트들끼리도 겹치는지 확인
if (canRestoreAll) {
for (let i = 0; i < restoredSlotRanges.length; i++) {
for (let j = i + 1; j < restoredSlotRanges.length; j++) {
const range1 = restoredSlotRanges[i];
const range2 = restoredSlotRanges[j];
const overlap = !(range1.endSlot < range2.startSlot || range1.startSlot > range2.endSlot);
if (overlap) {
console.log(`⚠️ 복구 시 컴포넌트끼리 겹침: ${range1.id} vs ${range2.id}`);
canRestoreAll = false;
break;
}
}
if (!canRestoreAll) break;
}
}
// 총 컬럼 수 체크 (12컬럼 초과 방지)
if (canRestoreAll) {
let totalColumnsInRow = draggedColumns;
const componentsInCurrentRow = adjustedComponents.filter((c) => {
const compRow = positionToRow(c.position.y, gridInfo);
return compRow === rowIndex && c.id !== draggedComponentId && !movedToNextRow.includes(c.id);
});
for (const comp of componentsInCurrentRow) {
const resizeInfo = resizedComponents.find((r) => r.id === comp.id);
if (resizeInfo) {
totalColumnsInRow += resizeInfo.oldColumns; // 원본 크기
} else {
totalColumnsInRow += getComponentColumns(comp); // 현재 크기
}
}
if (totalColumnsInRow > 12) {
console.log(`⚠️ 복구하면 12컬럼 초과: ${totalColumnsInRow}`);
canRestoreAll = false;
}
}
// 복구 가능하면 복구 실행
if (canRestoreAll) {
console.log("✅ 공간이 충분하여 축소된 컴포넌트 복구");
for (const resizeInfo of resizedComponents) {
if (!movedToNextRow.includes(resizeInfo.id)) {
const compIndex = adjustedComponents.findIndex((c) => c.id === resizeInfo.id);
if (compIndex !== -1) {
const originalColumns = resizeInfo.oldColumns;
const originalWidth = calculateWidthFromColumns(originalColumns, gridInfo, gridSettings);
adjustedComponents[compIndex] = {
...adjustedComponents[compIndex],
gridColumns: originalColumns,
columnSpan: originalColumns,
size: {
...adjustedComponents[compIndex].size,
width: originalWidth,
},
} as ComponentData;
console.log(`🔄 ${resizeInfo.id} 원래 크기로 복구:`, {
from: resizeInfo.newColumns,
to: originalColumns,
});
}
}
}
// 복구 후 위치 재조정 (슬롯 기반)
// 드래그 컴포넌트는 targetSlot에 배치하고, 나머지는 슬롯 순서대로 배치
console.log("🔄 복구 후 슬롯 기반 재정렬 시작:", {
targetSlot,
draggedColumns,
restoredComponents: resizedComponents.map((r) => `${r.id}:${r.oldColumns}컬럼`),
});
// 1. 현재 행의 모든 컴포넌트를 슬롯 기준으로 수집
const componentsInRow = adjustedComponents.filter((c) => {
const compRow = positionToRow(c.position.y, gridInfo);
return compRow === rowIndex && c.id !== draggedComponentId;
});
// 2. 각 컴포넌트의 시작 슬롯 계산 (업데이트된 슬롯맵 기준)
const updatedSlotMap2 = buildSlotMap(adjustedComponents, gridInfo);
const componentsForReposition = componentsInRow
.map((c) => {
const slots = findComponentSlots(c.id, rowIndex, updatedSlotMap2);
return {
component: c,
startSlot: slots?.startSlot ?? 0,
columns: getComponentColumns(c),
};
})
.sort((a, b) => a.startSlot - b.startSlot);
// 3. 드래그 컴포넌트를 targetSlot에 삽입
const draggedSlotInfo = {
component: null as any, // 임시
startSlot: targetSlot,
columns: draggedColumns,
isDragged: true,
};
// 4. 슬롯 순서대로 전체 배열 구성
const allSlotsSorted = [...componentsForReposition, draggedSlotInfo].sort((a, b) => a.startSlot - b.startSlot);
console.log("📋 슬롯 순서:", {
components: allSlotsSorted.map((s) =>
s.isDragged
? `[드래그:${s.startSlot}슬롯,${s.columns}컬럼]`
: `${s.component.id}:${s.startSlot}슬롯,${s.columns}컬럼`,
),
});
// 5. 슬롯 순서대로 X 좌표 재배치
let currentSlot = 0;
for (const slotInfo of allSlotsSorted) {
const posX = slotToPosition(currentSlot, gridInfo);
const compColumns = slotInfo.columns;
const compWidth = calculateWidthFromColumns(compColumns, gridInfo, gridSettings);
if (slotInfo.isDragged) {
// 드래그 컴포넌트
x = posX;
console.log(` 📍 드래그 컴포넌트: 슬롯${currentSlot}, x=${posX}, ${compColumns}컬럼`);
} else {
// 일반 컴포넌트
const compIndex = adjustedComponents.findIndex((c) => c.id === slotInfo.component.id);
if (compIndex !== -1) {
adjustedComponents[compIndex] = {
...adjustedComponents[compIndex],
position: {
...adjustedComponents[compIndex].position,
x: posX,
},
} as ComponentData;
console.log(` 📍 ${slotInfo.component.id}: 슬롯${currentSlot}, x=${posX}, ${compColumns}컬럼`);
}
}
currentSlot += compColumns;
}
console.log("📐 복구 후 위치 재조정 완료", { finalDraggedX: x, finalDraggedSlot: targetSlot });
} else {
console.log("⚠️ 복구 조건 불만족, 축소 상태 유지");
}
// 복구 여부와 관계없이 resizedComponents 초기화
resizedComponents.length = 0;
}
return {
success: true,
adjustedComponents,
resizedComponents,
movedComponents: [],
placement: { x, y, z: 1 },
};
}
/**
* 3: 겹치는
*/
function placeWithMovingDown(
targetSlot: number,
draggedColumns: number,
draggedComponentId: string,
rowIndex: number,
slotMap: SlotMap,
allComponents: ComponentData[],
gridInfo: GridInfo,
gridSettings: GridSettings,
): LayoutAdjustment {
console.log("⬇️ 겹치는 컴포넌트를 아래로 이동");
const endSlot = Math.min(11, targetSlot + draggedColumns - 1);
// 겹치는 컴포넌트들
const affectedComponentIds = getComponentsInSlots(targetSlot, endSlot, rowIndex, slotMap);
let adjustedComponents = [...allComponents];
const movedComponents: LayoutAdjustment["movedComponents"] = [];
// 다음 행으로 이동
const newRowIndex = rowIndex + 1;
const newY = rowToPosition(newRowIndex, gridInfo);
for (const componentId of affectedComponentIds) {
const component = allComponents.find((c) => c.id === componentId);
if (!component) continue;
const componentIndex = adjustedComponents.findIndex((c) => c.id === componentId);
if (componentIndex !== -1) {
const oldPosition = adjustedComponents[componentIndex].position;
const newPosition = { ...oldPosition, y: newY };
adjustedComponents[componentIndex] = {
...adjustedComponents[componentIndex],
position: newPosition,
};
movedComponents.push({
id: componentId,
oldRow: rowIndex,
newRow: newRowIndex,
oldPosition,
newPosition,
});
console.log(`⬇️ ${componentId} 이동:`, {
from: `${rowIndex}`,
to: `${newRowIndex}`,
});
}
}
// 배치 위치 계산
const x = slotToPosition(targetSlot, gridInfo);
const y = rowToPosition(rowIndex, gridInfo);
return {
success: true,
adjustedComponents,
resizedComponents: [],
movedComponents,
placement: { x, y, z: 1 },
};
}