Files
vexplor_dev/frontend/components/admin/dashboard/DashboardCanvas.tsx
hjjeong d8f73c1136 feat: 대시보드 관리 시스템 구현
 새로운 기능:
- 드래그 앤 드롭 대시보드 설계 도구
- SQL 쿼리 에디터 및 실시간 실행
- Recharts 기반 차트 컴포넌트 (Bar, Pie, Line)
- 차트 데이터 매핑 및 설정 UI
- 요소 이동, 크기 조절, 삭제 기능
- 레이아웃 저장 기능

📦 추가된 컴포넌트:
- DashboardDesigner: 메인 설계 도구
- QueryEditor: SQL 쿼리 작성 및 실행
- ChartConfigPanel: 차트 설정 패널
- ChartRenderer: 실제 차트 렌더링
- CanvasElement: 드래그 가능한 캔버스 요소

🔧 기술 스택:
- Recharts 라이브러리 추가
- TypeScript 타입 정의 완비
- 독립적 컴포넌트 구조로 설계

🎯 접속 경로: /admin/dashboard
2025-09-30 13:23:22 +09:00

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';