캔버스 크기 조절
This commit is contained in:
parent
39e3aa14cb
commit
3672bbd997
|
|
@ -36,30 +36,6 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
const [cellSize, setCellSize] = useState(60);
|
|
||||||
|
|
||||||
// 화면 크기에 따라 셀 크기 동적 계산
|
|
||||||
useEffect(() => {
|
|
||||||
const updateCellSize = () => {
|
|
||||||
if (!ref || typeof ref === "function" || !ref.current) return;
|
|
||||||
|
|
||||||
const container = ref.current.parentElement;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
// 컨테이너 너비에서 여백 제외
|
|
||||||
const availableWidth = container.clientWidth - 32; // 좌우 패딩
|
|
||||||
|
|
||||||
// 12 컬럼 + 11개 gap을 고려한 셀 크기 계산
|
|
||||||
const calculatedCellSize = Math.floor((availableWidth - 11 * GRID_CONFIG.GAP) / GRID_CONFIG.COLUMNS);
|
|
||||||
|
|
||||||
setCellSize(calculatedCellSize);
|
|
||||||
};
|
|
||||||
|
|
||||||
updateCellSize();
|
|
||||||
window.addEventListener("resize", updateCellSize);
|
|
||||||
|
|
||||||
return () => window.removeEventListener("resize", updateCellSize);
|
|
||||||
}, [ref]);
|
|
||||||
|
|
||||||
// 드래그 오버 처리
|
// 드래그 오버 처리
|
||||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||||
|
|
@ -93,16 +69,16 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
|
||||||
const rawX = e.clientX - rect.left + (ref.current?.scrollLeft || 0);
|
const rawX = e.clientX - rect.left + (ref.current?.scrollLeft || 0);
|
||||||
const rawY = e.clientY - rect.top + (ref.current?.scrollTop || 0);
|
const rawY = e.clientY - rect.top + (ref.current?.scrollTop || 0);
|
||||||
|
|
||||||
// 그리드에 스냅 (동적 셀 크기 사용)
|
// 그리드에 스냅 (고정 셀 크기 사용)
|
||||||
const snappedX = snapToGrid(rawX, cellSize);
|
const snappedX = snapToGrid(rawX, GRID_CONFIG.CELL_SIZE);
|
||||||
const snappedY = snapToGrid(rawY, cellSize);
|
const snappedY = snapToGrid(rawY, GRID_CONFIG.CELL_SIZE);
|
||||||
|
|
||||||
onCreateElement(dragData.type, dragData.subtype, snappedX, snappedY);
|
onCreateElement(dragData.type, dragData.subtype, snappedX, snappedY);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('드롭 데이터 파싱 오류:', error);
|
// console.error('드롭 데이터 파싱 오류:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ref, onCreateElement, cellSize],
|
[ref, onCreateElement],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 캔버스 클릭 시 선택 해제
|
// 캔버스 클릭 시 선택 해제
|
||||||
|
|
@ -115,19 +91,20 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
|
||||||
[onSelectElement],
|
[onSelectElement],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 그리드 크기 계산 (동적)
|
// 고정 그리드 크기
|
||||||
const cellWithGap = cellSize + GRID_CONFIG.GAP;
|
const cellWithGap = GRID_CONFIG.CELL_SIZE + GRID_CONFIG.GAP;
|
||||||
const gridSize = `${cellWithGap}px ${cellWithGap}px`;
|
const gridSize = `${cellWithGap}px ${cellWithGap}px`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={`relative min-h-full w-full bg-gray-50 ${isDragOver ? "bg-blue-50/50" : ""} `}
|
className={`relative min-h-screen rounded-lg bg-gray-50 shadow-inner ${isDragOver ? "bg-blue-50/50" : ""} `}
|
||||||
style={{
|
style={{
|
||||||
// 12 컬럼 그리드 배경 (동적 크기)
|
width: `${GRID_CONFIG.CANVAS_WIDTH}px`,
|
||||||
|
// 12 컬럼 그리드 배경
|
||||||
backgroundImage: `
|
backgroundImage: `
|
||||||
linear-gradient(rgba(59, 130, 246, 0.1) 1px, transparent 1px),
|
linear-gradient(rgba(59, 130, 246, 0.15) 1px, transparent 1px),
|
||||||
linear-gradient(90deg, rgba(59, 130, 246, 0.1) 1px, transparent 1px)
|
linear-gradient(90deg, rgba(59, 130, 246, 0.15) 1px, transparent 1px)
|
||||||
`,
|
`,
|
||||||
backgroundSize: gridSize,
|
backgroundSize: gridSize,
|
||||||
backgroundPosition: "0 0",
|
backgroundPosition: "0 0",
|
||||||
|
|
@ -144,7 +121,7 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
|
||||||
key={element.id}
|
key={element.id}
|
||||||
element={element}
|
element={element}
|
||||||
isSelected={selectedElement === element.id}
|
isSelected={selectedElement === element.id}
|
||||||
cellSize={cellSize}
|
cellSize={GRID_CONFIG.CELL_SIZE}
|
||||||
onUpdate={onUpdateElement}
|
onUpdate={onUpdateElement}
|
||||||
onRemove={onRemoveElement}
|
onRemove={onRemoveElement}
|
||||||
onSelect={onSelectElement}
|
onSelect={onSelectElement}
|
||||||
|
|
|
||||||
|
|
@ -23,32 +23,8 @@ export default function DashboardDesigner() {
|
||||||
const [dashboardId, setDashboardId] = useState<string | null>(null);
|
const [dashboardId, setDashboardId] = useState<string | null>(null);
|
||||||
const [dashboardTitle, setDashboardTitle] = useState<string>("");
|
const [dashboardTitle, setDashboardTitle] = useState<string>("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [cellSize, setCellSize] = useState(60);
|
|
||||||
const canvasRef = useRef<HTMLDivElement>(null);
|
const canvasRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// 화면 크기에 따라 셀 크기 동적 계산
|
|
||||||
useEffect(() => {
|
|
||||||
const updateCellSize = () => {
|
|
||||||
if (!canvasRef.current) return;
|
|
||||||
|
|
||||||
const container = canvasRef.current.parentElement;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
// 컨테이너 너비에서 여백 제외
|
|
||||||
const availableWidth = container.clientWidth - 32;
|
|
||||||
|
|
||||||
// 12 컬럼 + 11개 gap을 고려한 셀 크기 계산
|
|
||||||
const calculatedCellSize = Math.floor((availableWidth - 11 * GRID_CONFIG.GAP) / GRID_CONFIG.COLUMNS);
|
|
||||||
|
|
||||||
setCellSize(calculatedCellSize);
|
|
||||||
};
|
|
||||||
|
|
||||||
updateCellSize();
|
|
||||||
window.addEventListener("resize", updateCellSize);
|
|
||||||
|
|
||||||
return () => window.removeEventListener("resize", updateCellSize);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// URL 파라미터에서 대시보드 ID 읽기 및 데이터 로드
|
// URL 파라미터에서 대시보드 ID 읽기 및 데이터 로드
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -100,12 +76,12 @@ export default function DashboardDesigner() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 새로운 요소 생성 (그리드 기반 기본 크기)
|
// 새로운 요소 생성 (고정 그리드 기반 기본 크기)
|
||||||
const createElement = useCallback(
|
const createElement = useCallback(
|
||||||
(type: ElementType, subtype: ElementSubtype, x: number, y: number) => {
|
(type: ElementType, subtype: ElementSubtype, x: number, y: number) => {
|
||||||
// 기본 크기: 차트는 4x3 셀, 위젯은 2x2 셀
|
// 기본 크기: 차트는 4x3 셀, 위젯은 2x2 셀
|
||||||
const defaultCells = type === "chart" ? { width: 4, height: 3 } : { width: 2, height: 2 };
|
const defaultCells = type === "chart" ? { width: 4, height: 3 } : { width: 2, height: 2 };
|
||||||
const cellWithGap = cellSize + GRID_CONFIG.GAP;
|
const cellWithGap = GRID_CONFIG.CELL_SIZE + GRID_CONFIG.GAP;
|
||||||
|
|
||||||
const defaultWidth = defaultCells.width * cellWithGap - GRID_CONFIG.GAP;
|
const defaultWidth = defaultCells.width * cellWithGap - GRID_CONFIG.GAP;
|
||||||
const defaultHeight = defaultCells.height * cellWithGap - GRID_CONFIG.GAP;
|
const defaultHeight = defaultCells.height * cellWithGap - GRID_CONFIG.GAP;
|
||||||
|
|
@ -124,7 +100,7 @@ export default function DashboardDesigner() {
|
||||||
setElementCounter((prev) => prev + 1);
|
setElementCounter((prev) => prev + 1);
|
||||||
setSelectedElement(newElement.id);
|
setSelectedElement(newElement.id);
|
||||||
},
|
},
|
||||||
[elementCounter, cellSize],
|
[elementCounter],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 요소 업데이트
|
// 요소 업데이트
|
||||||
|
|
@ -253,25 +229,29 @@ export default function DashboardDesigner() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full bg-gray-50">
|
<div className="flex h-full bg-gray-50">
|
||||||
{/* 캔버스 영역 */}
|
{/* 캔버스 영역 */}
|
||||||
<div className="relative flex-1 overflow-auto border-r-2 border-gray-300">
|
<div className="relative flex-1 overflow-auto border-r-2 border-gray-300 bg-gray-100">
|
||||||
{/* 편집 중인 대시보드 표시 */}
|
{/* 편집 중인 대시보드 표시 */}
|
||||||
{dashboardTitle && (
|
{dashboardTitle && (
|
||||||
<div className="bg-accent0 absolute top-2 left-2 z-10 rounded-lg px-3 py-1 text-sm font-medium text-white shadow-lg">
|
<div className="bg-accent0 absolute top-6 left-6 z-10 rounded-lg px-3 py-1 text-sm font-medium text-white shadow-lg">
|
||||||
📝 편집 중: {dashboardTitle}
|
📝 편집 중: {dashboardTitle}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DashboardToolbar onClearCanvas={clearCanvas} onSaveLayout={saveLayout} />
|
<DashboardToolbar onClearCanvas={clearCanvas} onSaveLayout={saveLayout} />
|
||||||
<DashboardCanvas
|
|
||||||
ref={canvasRef}
|
{/* 캔버스 중앙 정렬 컨테이너 */}
|
||||||
elements={elements}
|
<div className="flex justify-center p-4">
|
||||||
selectedElement={selectedElement}
|
<DashboardCanvas
|
||||||
onCreateElement={createElement}
|
ref={canvasRef}
|
||||||
onUpdateElement={updateElement}
|
elements={elements}
|
||||||
onRemoveElement={removeElement}
|
selectedElement={selectedElement}
|
||||||
onSelectElement={setSelectedElement}
|
onCreateElement={createElement}
|
||||||
onConfigureElement={openConfigModal}
|
onUpdateElement={updateElement}
|
||||||
/>
|
onRemoveElement={removeElement}
|
||||||
|
onSelectElement={setSelectedElement}
|
||||||
|
onConfigureElement={openConfigModal}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 사이드바 */}
|
{/* 사이드바 */}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export function DashboardSidebar() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[348px] overflow-y-auto border-l border-gray-200 bg-white p-6">
|
<div className="w-[370px] overflow-y-auto border-l border-gray-200 bg-white p-6">
|
||||||
{/* 차트 섹션 */}
|
{/* 차트 섹션 */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h3 className="mb-4 border-b-2 border-green-500 pb-3 text-lg font-semibold text-gray-800">📊 차트 종류</h3>
|
<h3 className="mb-4 border-b-2 border-green-500 pb-3 text-lg font-semibold text-gray-800">📊 차트 종류</h3>
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,16 @@
|
||||||
* - 스냅 기능
|
* - 스냅 기능
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 그리드 설정
|
// 그리드 설정 (고정 크기)
|
||||||
export const GRID_CONFIG = {
|
export const GRID_CONFIG = {
|
||||||
COLUMNS: 12,
|
COLUMNS: 12,
|
||||||
CELL_SIZE: 60, // 60px 정사각형 셀
|
CELL_SIZE: 132, // 고정 셀 크기
|
||||||
GAP: 8, // 셀 간격
|
GAP: 8, // 셀 간격
|
||||||
SNAP_THRESHOLD: 15, // 스냅 임계값 (px)
|
SNAP_THRESHOLD: 15, // 스냅 임계값 (px)
|
||||||
ELEMENT_PADDING: 4, // 요소 주위 여백 (px)
|
ELEMENT_PADDING: 4, // 요소 주위 여백 (px)
|
||||||
|
CANVAS_WIDTH: 1682, // 고정 캔버스 너비 (실제 측정값)
|
||||||
|
// 계산식: (132 + 8) × 12 - 8 = 1672px (그리드)
|
||||||
|
// 추가 여백 10px 포함 = 1682px
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue