'use client'; import React, { useState, useRef, useCallback } from 'react'; import { DashboardCanvas } from './DashboardCanvas'; import { DashboardSidebar } from './DashboardSidebar'; import { DashboardToolbar } from './DashboardToolbar'; import { ElementConfigModal } from './ElementConfigModal'; import { DashboardElement, ElementType, ElementSubtype } from './types'; /** * 대시보드 설계 도구 메인 컴포넌트 * - 드래그 앤 드롭으로 차트/위젯 배치 * - 요소 이동, 크기 조절, 삭제 기능 * - 레이아웃 저장/불러오기 기능 */ export default function DashboardDesigner() { const [elements, setElements] = useState([]); const [selectedElement, setSelectedElement] = useState(null); const [elementCounter, setElementCounter] = useState(0); const [configModalElement, setConfigModalElement] = useState(null); const [dashboardId, setDashboardId] = useState(null); const [dashboardTitle, setDashboardTitle] = useState(''); const [isLoading, setIsLoading] = useState(false); const canvasRef = useRef(null); // URL 파라미터에서 대시보드 ID 읽기 및 데이터 로드 React.useEffect(() => { const params = new URLSearchParams(window.location.search); const loadId = params.get('load'); if (loadId) { loadDashboard(loadId); } }, []); // 대시보드 데이터 로드 const loadDashboard = async (id: string) => { setIsLoading(true); try { // console.log('🔄 대시보드 로딩:', id); const { dashboardApi } = await import('@/lib/api/dashboard'); const dashboard = await dashboardApi.getDashboard(id); // console.log('✅ 대시보드 로딩 완료:', dashboard); // 대시보드 정보 설정 setDashboardId(dashboard.id); setDashboardTitle(dashboard.title); // 요소들 설정 if (dashboard.elements && dashboard.elements.length > 0) { setElements(dashboard.elements); // elementCounter를 가장 큰 ID 번호로 설정 const maxId = dashboard.elements.reduce((max, el) => { const match = el.id.match(/element-(\d+)/); if (match) { const num = parseInt(match[1]); return num > max ? num : max; } return max; }, 0); setElementCounter(maxId); } } catch (error) { // console.error('❌ 대시보드 로딩 오류:', error); alert('대시보드를 불러오는 중 오류가 발생했습니다.\n\n' + (error instanceof Error ? error.message : '알 수 없는 오류')); } finally { setIsLoading(false); } }; // 새로운 요소 생성 const createElement = useCallback(( type: ElementType, subtype: ElementSubtype, x: number, y: number ) => { const newElement: DashboardElement = { id: `element-${elementCounter + 1}`, type, subtype, position: { x, y }, size: { width: 250, height: 200 }, title: getElementTitle(type, subtype), content: getElementContent(type, subtype) }; setElements(prev => [...prev, newElement]); setElementCounter(prev => prev + 1); setSelectedElement(newElement.id); }, [elementCounter]); // 요소 업데이트 const updateElement = useCallback((id: string, updates: Partial) => { setElements(prev => prev.map(el => el.id === id ? { ...el, ...updates } : el )); }, []); // 요소 삭제 const removeElement = useCallback((id: string) => { setElements(prev => prev.filter(el => el.id !== id)); if (selectedElement === id) { setSelectedElement(null); } }, [selectedElement]); // 전체 삭제 const clearCanvas = useCallback(() => { if (window.confirm('모든 요소를 삭제하시겠습니까?')) { setElements([]); setSelectedElement(null); setElementCounter(0); } }, []); // 요소 설정 모달 열기 const openConfigModal = useCallback((element: DashboardElement) => { setConfigModalElement(element); }, []); // 요소 설정 모달 닫기 const closeConfigModal = useCallback(() => { setConfigModalElement(null); }, []); // 요소 설정 저장 const saveElementConfig = useCallback((updatedElement: DashboardElement) => { updateElement(updatedElement.id, updatedElement); }, [updateElement]); // 레이아웃 저장 const saveLayout = useCallback(async () => { if (elements.length === 0) { alert('저장할 요소가 없습니다. 차트나 위젯을 추가해주세요.'); return; } try { // 실제 API 호출 const { dashboardApi } = await import('@/lib/api/dashboard'); const elementsData = elements.map(el => ({ id: el.id, type: el.type, subtype: el.subtype, position: el.position, size: el.size, title: el.title, content: el.content, dataSource: el.dataSource, chartConfig: el.chartConfig })); let savedDashboard; if (dashboardId) { // 기존 대시보드 업데이트 // console.log('🔄 대시보드 업데이트:', dashboardId); savedDashboard = await dashboardApi.updateDashboard(dashboardId, { elements: elementsData }); alert(`대시보드 "${savedDashboard.title}"이 업데이트되었습니다!`); // 뷰어 페이지로 이동 window.location.href = `/dashboard/${savedDashboard.id}`; } else { // 새 대시보드 생성 const title = prompt('대시보드 제목을 입력하세요:', '새 대시보드'); if (!title) return; const description = prompt('대시보드 설명을 입력하세요 (선택사항):', ''); const dashboardData = { title, description: description || undefined, isPublic: false, elements: elementsData }; savedDashboard = await dashboardApi.createDashboard(dashboardData); // console.log('✅ 대시보드 생성 완료:', savedDashboard); const viewDashboard = confirm(`대시보드 "${title}"이 저장되었습니다!\n\n지금 확인해보시겠습니까?`); if (viewDashboard) { window.location.href = `/dashboard/${savedDashboard.id}`; } } } catch (error) { // console.error('❌ 저장 오류:', error); const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류'; alert(`대시보드 저장 중 오류가 발생했습니다.\n\n오류: ${errorMessage}\n\n관리자에게 문의하세요.`); } }, [elements, dashboardId]); // 로딩 중이면 로딩 화면 표시 if (isLoading) { return (
대시보드 로딩 중...
잠시만 기다려주세요
); } return (
{/* 캔버스 영역 */}
{/* 편집 중인 대시보드 표시 */} {dashboardTitle && (
📝 편집 중: {dashboardTitle}
)}
{/* 사이드바 */} {/* 요소 설정 모달 */} {configModalElement && ( )}
); } // 요소 제목 생성 헬퍼 함수 function getElementTitle(type: ElementType, subtype: ElementSubtype): string { if (type === 'chart') { switch (subtype) { case 'bar': return '📊 바 차트'; case 'pie': return '🥧 원형 차트'; case 'line': return '📈 꺾은선 차트'; default: return '📊 차트'; } } else if (type === 'widget') { switch (subtype) { case 'exchange': return '💱 환율 위젯'; case 'weather': return '☁️ 날씨 위젯'; default: return '🔧 위젯'; } } return '요소'; } // 요소 내용 생성 헬퍼 함수 function getElementContent(type: ElementType, subtype: ElementSubtype): string { if (type === 'chart') { switch (subtype) { case 'bar': return '바 차트가 여기에 표시됩니다'; case 'pie': return '원형 차트가 여기에 표시됩니다'; case 'line': return '꺾은선 차트가 여기에 표시됩니다'; default: return '차트가 여기에 표시됩니다'; } } else if (type === 'widget') { switch (subtype) { case 'exchange': return 'USD: ₩1,320\nJPY: ₩900\nEUR: ₩1,450'; case 'weather': return '서울\n23°C\n구름 많음'; default: return '위젯 내용이 여기에 표시됩니다'; } } return '내용이 여기에 표시됩니다'; }