diff --git a/frontend/components/admin/dashboard/CanvasElement.tsx b/frontend/components/admin/dashboard/CanvasElement.tsx index 6214c8cb..2ecf8245 100644 --- a/frontend/components/admin/dashboard/CanvasElement.tsx +++ b/frontend/components/admin/dashboard/CanvasElement.tsx @@ -4,7 +4,7 @@ import React, { useState, useCallback, useRef, useEffect } from "react"; import dynamic from "next/dynamic"; import { DashboardElement, QueryResult, Position } from "./types"; import { ChartRenderer } from "./charts/ChartRenderer"; -import { GRID_CONFIG, magneticSnap } from "./gridUtils"; +import { GRID_CONFIG, magneticSnap, snapSizeToGrid } from "./gridUtils"; // 위젯 동적 임포트 const WeatherWidget = dynamic(() => import("@/components/dashboard/widgets/WeatherWidget"), { @@ -378,9 +378,9 @@ export function CanvasElement({ const snappedX = magneticSnap(newX, verticalGuidelines); const snappedY = magneticSnap(newY, horizontalGuidelines); - // 크기는 12px 단위로 스냅 - const snappedWidth = Math.round(newWidth / 12) * 12; - const snappedHeight = Math.round(newHeight / 12) * 12; + // 크기는 그리드 박스 단위로 스냅 + const snappedWidth = snapSizeToGrid(newWidth, canvasWidth || 1560); + const snappedHeight = snapSizeToGrid(newHeight, canvasWidth || 1560); // 스냅 후 경계 체크 const finalSnappedX = Math.max(0, Math.min(snappedX, canvasWidth - snappedWidth)); diff --git a/frontend/components/admin/dashboard/DashboardDesigner.tsx b/frontend/components/admin/dashboard/DashboardDesigner.tsx index 36601623..83097678 100644 --- a/frontend/components/admin/dashboard/DashboardDesigner.tsx +++ b/frontend/components/admin/dashboard/DashboardDesigner.tsx @@ -7,7 +7,14 @@ import { DashboardTopMenu } from "./DashboardTopMenu"; import { ElementConfigSidebar } from "./ElementConfigSidebar"; import { DashboardSaveModal } from "./DashboardSaveModal"; import { DashboardElement, ElementType, ElementSubtype } from "./types"; -import { GRID_CONFIG, snapToGrid, snapSizeToGrid, calculateCellSize, calculateGridConfig } from "./gridUtils"; +import { + GRID_CONFIG, + snapToGrid, + snapSizeToGrid, + calculateCellSize, + calculateGridConfig, + calculateBoxSize, +} from "./gridUtils"; import { Resolution, RESOLUTIONS, detectScreenResolution } from "./ResolutionSelector"; import { DashboardProvider } from "@/contexts/DashboardContext"; import { useMenu } from "@/contexts/MenuContext"; @@ -89,8 +96,8 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D // 그리드에 스냅 (X, Y, 너비, 높이 모두) const snappedX = snapToGrid(scaledX, newCellSize); const snappedY = snapToGrid(el.position.y, newCellSize); - const snappedWidth = snapSizeToGrid(scaledWidth, 2, newCellSize); - const snappedHeight = snapSizeToGrid(el.size.height, 2, newCellSize); + const snappedWidth = snapSizeToGrid(scaledWidth, newConfig.width); + const snappedHeight = snapSizeToGrid(el.size.height, newConfig.width); return { ...el, @@ -215,22 +222,25 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D return; } - // 기본 크기 설정 (서브그리드 기준) - const gridConfig = calculateGridConfig(canvasConfig.width); - const subGridSize = gridConfig.SUB_GRID_SIZE; + // 기본 크기 설정 (그리드 박스 단위) + const boxSize = calculateBoxSize(canvasConfig.width); - // 서브그리드 기준 기본 크기 (픽셀) - let defaultWidth = subGridSize * 10; // 기본 위젯: 서브그리드 10칸 - let defaultHeight = subGridSize * 10; // 기본 위젯: 서브그리드 10칸 + // 그리드 박스 단위 기본 크기 + let boxesWidth = 3; // 기본 위젯: 박스 3개 + let boxesHeight = 3; // 기본 위젯: 박스 3개 if (type === "chart") { - defaultWidth = subGridSize * 20; // 차트: 서브그리드 20칸 - defaultHeight = subGridSize * 15; // 차트: 서브그리드 15칸 + boxesWidth = 4; // 차트: 박스 4개 + boxesHeight = 3; // 차트: 박스 3개 } else if (type === "widget" && subtype === "calendar") { - defaultWidth = subGridSize * 10; // 달력: 서브그리드 10칸 - defaultHeight = subGridSize * 15; // 달력: 서브그리드 15칸 + boxesWidth = 3; // 달력: 박스 3개 + boxesHeight = 4; // 달력: 박스 4개 } + // 박스 개수를 픽셀로 변환 (마지막 간격 제거) + const defaultWidth = boxesWidth * boxSize + (boxesWidth - 1) * GRID_CONFIG.GRID_BOX_GAP; + const defaultHeight = boxesHeight * boxSize + (boxesHeight - 1) * GRID_CONFIG.GRID_BOX_GAP; + // 크기 유효성 검사 if (isNaN(defaultWidth) || isNaN(defaultHeight) || defaultWidth <= 0 || defaultHeight <= 0) { // console.error("Invalid size calculated:", { diff --git a/frontend/components/admin/dashboard/gridUtils.ts b/frontend/components/admin/dashboard/gridUtils.ts index 083f819b..6094aa0e 100644 --- a/frontend/components/admin/dashboard/gridUtils.ts +++ b/frontend/components/admin/dashboard/gridUtils.ts @@ -54,9 +54,11 @@ export function calculateGridConfig(canvasWidth: number) { /** * 실제 그리드 셀 크기 계산 (gap 포함) + * @param canvasWidth - 캔버스 너비 */ -export const getCellWithGap = () => { - return GRID_CONFIG.CELL_SIZE + GRID_CONFIG.GAP; +export const getCellWithGap = (canvasWidth: number = 1560) => { + const boxSize = calculateBoxSize(canvasWidth); + return boxSize + GRID_CONFIG.GRID_BOX_GAP; }; /** @@ -70,14 +72,14 @@ export const getCanvasWidth = () => { /** * 좌표를 서브 그리드에 스냅 (세밀한 조정 가능) * @param value - 스냅할 좌표값 - * @param subGridSize - 서브 그리드 크기 (선택사항, 기본값: cellSize/3 ≈ 43px) + * @param subGridSize - 서브 그리드 크기 (선택사항) * @returns 스냅된 좌표값 */ export const snapToGrid = (value: number, subGridSize?: number): number => { - // 서브 그리드 크기가 지정되지 않으면 기본 그리드 크기의 1/3 사용 (3x3 서브그리드) - const snapSize = subGridSize ?? Math.floor(GRID_CONFIG.CELL_SIZE / 3); + // 서브 그리드 크기가 지정되지 않으면 기본 박스 크기 사용 + const snapSize = subGridSize ?? calculateBoxSize(1560); - // 서브 그리드 단위로 스냅 + // 그리드 단위로 스냅 const gridIndex = Math.round(value / snapSize); return gridIndex * snapSize; }; @@ -88,8 +90,9 @@ export const snapToGrid = (value: number, subGridSize?: number): number => { * @param cellSize - 셀 크기 (선택사항) * @returns 스냅된 좌표값 (임계값 내에 있으면 스냅, 아니면 원래 값) */ -export const snapToGridWithThreshold = (value: number, cellSize: number = GRID_CONFIG.CELL_SIZE): number => { - const snapped = snapToGrid(value, cellSize); +export const snapToGridWithThreshold = (value: number, cellSize?: number): number => { + const snapSize = cellSize ?? calculateBoxSize(1560); + const snapped = snapToGrid(value, snapSize); const distance = Math.abs(value - snapped); return distance <= GRID_CONFIG.SNAP_THRESHOLD ? snapped : value; @@ -102,15 +105,7 @@ export const snapToGridWithThreshold = (value: number, cellSize: number = GRID_C * @param cellSize - 셀 크기 (선택사항) * @returns 스냅된 크기 */ -export const snapSizeToGrid = ( - size: number, - minCells: number = 2, - cellSize: number = GRID_CONFIG.CELL_SIZE, -): number => { - const cellWithGap = cellSize + GRID_CONFIG.GAP; - const cells = Math.max(minCells, Math.round(size / cellWithGap)); - return cells * cellWithGap - GRID_CONFIG.GAP; -}; +// 기존 snapSizeToGrid 제거 - 새 버전은 269번 줄에 있음 /** * 위치와 크기를 모두 그리드에 스냅 @@ -142,9 +137,10 @@ export const snapBoundsToGrid = (bounds: GridBounds, canvasWidth?: number, canva let snappedX = snapToGrid(bounds.position.x); let snappedY = snapToGrid(bounds.position.y); - // 크기 스냅 - const snappedWidth = snapSizeToGrid(bounds.size.width); - const snappedHeight = snapSizeToGrid(bounds.size.height); + // 크기 스냅 (canvasWidth 기본값 1560) + const width = canvasWidth || 1560; + const snappedWidth = snapSizeToGrid(bounds.size.width, width); + const snappedHeight = snapSizeToGrid(bounds.size.height, width); // 캔버스 경계 체크 if (canvasWidth) { @@ -264,3 +260,16 @@ export function magneticSnap(value: number, guidelines: number[]): number { const { nearest } = findNearestGuideline(value, guidelines); return nearest; // 거리 체크 없이 무조건 스냅 } + +// 크기를 그리드 박스 단위로 스냅 (박스 크기의 배수로만 가능) +export function snapSizeToGrid(size: number, canvasWidth: number): number { + const boxSize = calculateBoxSize(canvasWidth); + const cellSize = boxSize + GRID_CONFIG.GRID_BOX_GAP; // 박스 + 간격 + + // 최소 1개 박스 크기 + const minBoxes = 1; + const boxes = Math.max(minBoxes, Math.round(size / cellSize)); + + // 박스 개수에서 마지막 간격 제거 + return boxes * boxSize + (boxes - 1) * GRID_CONFIG.GRID_BOX_GAP; +}