110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import React, { forwardRef, useState, useCallback } from 'react';
|
|
import { DashboardElement, ElementType, ElementSubtype, DragData } from './types';
|
|
import { CanvasElement } from './CanvasElement';
|
|
|
|
interface DashboardCanvasProps {
|
|
elements: DashboardElement[];
|
|
selectedElement: string | null;
|
|
onCreateElement: (type: ElementType, subtype: ElementSubtype, x: number, y: number) => void;
|
|
onUpdateElement: (id: string, updates: Partial<DashboardElement>) => void;
|
|
onRemoveElement: (id: string) => void;
|
|
onSelectElement: (id: string | null) => void;
|
|
onConfigureElement?: (element: DashboardElement) => void;
|
|
}
|
|
|
|
/**
|
|
* 대시보드 캔버스 컴포넌트
|
|
* - 드래그 앤 드롭 영역
|
|
* - 그리드 배경
|
|
* - 요소 배치 및 관리
|
|
*/
|
|
export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
|
|
({ elements, selectedElement, onCreateElement, onUpdateElement, onRemoveElement, onSelectElement, onConfigureElement }, ref) => {
|
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
|
|
// 드래그 오버 처리
|
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = 'copy';
|
|
setIsDragOver(true);
|
|
}, []);
|
|
|
|
// 드래그 리브 처리
|
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
if (e.currentTarget === e.target) {
|
|
setIsDragOver(false);
|
|
}
|
|
}, []);
|
|
|
|
// 드롭 처리
|
|
const handleDrop = useCallback((e: React.DragEvent) => {
|
|
e.preventDefault();
|
|
setIsDragOver(false);
|
|
|
|
try {
|
|
const dragData: DragData = JSON.parse(e.dataTransfer.getData('application/json'));
|
|
|
|
if (!ref || typeof ref === 'function') return;
|
|
|
|
const rect = ref.current?.getBoundingClientRect();
|
|
if (!rect) return;
|
|
|
|
// 캔버스 스크롤을 고려한 정확한 위치 계산
|
|
const x = e.clientX - rect.left + (ref.current?.scrollLeft || 0);
|
|
const y = e.clientY - rect.top + (ref.current?.scrollTop || 0);
|
|
|
|
onCreateElement(dragData.type, dragData.subtype, x, y);
|
|
} catch (error) {
|
|
// console.error('드롭 데이터 파싱 오류:', error);
|
|
}
|
|
}, [ref, onCreateElement]);
|
|
|
|
// 캔버스 클릭 시 선택 해제
|
|
const handleCanvasClick = useCallback((e: React.MouseEvent) => {
|
|
if (e.target === e.currentTarget) {
|
|
onSelectElement(null);
|
|
}
|
|
}, [onSelectElement]);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={`
|
|
w-full min-h-full relative
|
|
bg-gray-100
|
|
bg-grid-pattern
|
|
${isDragOver ? 'bg-blue-50' : ''}
|
|
`}
|
|
style={{
|
|
backgroundImage: `
|
|
linear-gradient(rgba(200, 200, 200, 0.3) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(200, 200, 200, 0.3) 1px, transparent 1px)
|
|
`,
|
|
backgroundSize: '20px 20px'
|
|
}}
|
|
onDragOver={handleDragOver}
|
|
onDragLeave={handleDragLeave}
|
|
onDrop={handleDrop}
|
|
onClick={handleCanvasClick}
|
|
>
|
|
{/* 배치된 요소들 렌더링 */}
|
|
{elements.map((element) => (
|
|
<CanvasElement
|
|
key={element.id}
|
|
element={element}
|
|
isSelected={selectedElement === element.id}
|
|
onUpdate={onUpdateElement}
|
|
onRemove={onRemoveElement}
|
|
onSelect={onSelectElement}
|
|
onConfigure={onConfigureElement}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
DashboardCanvas.displayName = 'DashboardCanvas';
|