"use client"; import React, { useEffect, useState } from "react"; import dynamic from "next/dynamic"; import { DashboardElement } from "@/components/admin/dashboard/types"; import { getApiUrl } from "@/lib/utils/apiUrl"; import "leaflet/dist/leaflet.css"; // Leaflet 아이콘 경로 설정 (엑박 방지) if (typeof window !== "undefined") { const L = require("leaflet"); delete (L.Icon.Default.prototype as any)._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png", iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png", shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png", }); } // Leaflet 동적 import (SSR 방지) const MapContainer = dynamic(() => import("react-leaflet").then((mod) => mod.MapContainer), { ssr: false }); const TileLayer = dynamic(() => import("react-leaflet").then((mod) => mod.TileLayer), { ssr: false }); const Marker = dynamic(() => import("react-leaflet").then((mod) => mod.Marker), { ssr: false }); const Popup = dynamic(() => import("react-leaflet").then((mod) => mod.Popup), { ssr: false }); // 브이월드 API 키 const VWORLD_API_KEY = "97AD30D5-FDC4-3481-99C3-158E36422033"; interface MapSummaryWidgetProps { element: DashboardElement; } interface MarkerData { lat: number; lng: number; name: string; info: any; } // 테이블명 한글 번역 const translateTableName = (name: string): string => { const tableTranslations: { [key: string]: string } = { "vehicle_locations": "차량", "vehicles": "차량", "warehouses": "창고", "warehouse": "창고", "customers": "고객", "customer": "고객", "deliveries": "배송", "delivery": "배송", "drivers": "기사", "driver": "기사", "stores": "매장", "store": "매장", }; return tableTranslations[name.toLowerCase()] || tableTranslations[name.replace(/_/g, '').toLowerCase()] || name; }; /** * 범용 지도 위젯 (커스텀 지도 카드) * - 위도/경도가 있는 모든 데이터를 지도에 표시 * - 차량, 창고, 고객, 배송 등 모든 위치 데이터 지원 * - Leaflet + 브이월드 지도 사용 */ export default function MapSummaryWidget({ element }: MapSummaryWidgetProps) { const [markers, setMarkers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [tableName, setTableName] = useState(null); useEffect(() => { if (element?.dataSource?.query) { loadMapData(); } // 자동 새로고침 (30초마다) const interval = setInterval(() => { if (element?.dataSource?.query) { loadMapData(); } }, 30000); return () => clearInterval(interval); }, [element]); const loadMapData = async () => { if (!element?.dataSource?.query) { return; } // 쿼리에서 테이블 이름 추출 const extractTableName = (query: string): string | null => { const fromMatch = query.match(/FROM\s+([a-zA-Z0-9_가-힣]+)/i); if (fromMatch) { return fromMatch[1]; } return null; }; try { setLoading(true); const extractedTableName = extractTableName(element.dataSource.query); setTableName(extractedTableName); const token = localStorage.getItem("authToken"); const response = await fetch(getApiUrl("/api/dashboards/execute-query"), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query: element.dataSource.query, connectionType: element.dataSource.connectionType || "current", connectionId: element.dataSource.connectionId, }), }); if (!response.ok) throw new Error("데이터 로딩 실패"); const result = await response.json(); if (result.success && result.data?.rows) { const rows = result.data.rows; // 위도/경도 컬럼 찾기 const latCol = element.chartConfig?.latitudeColumn || "latitude"; const lngCol = element.chartConfig?.longitudeColumn || "longitude"; // 유효한 좌표 필터링 및 마커 데이터 생성 const markerData = rows .filter((row: any) => row[latCol] && row[lngCol]) .map((row: any) => ({ lat: parseFloat(row[latCol]), lng: parseFloat(row[lngCol]), name: row.name || row.vehicle_number || row.warehouse_name || row.customer_name || "알 수 없음", info: row, })); setMarkers(markerData); } setError(null); } catch (err) { setError(err instanceof Error ? err.message : "데이터 로딩 실패"); } finally { setLoading(false); } }; // customTitle이 있으면 사용, 없으면 테이블명으로 자동 생성 const displayTitle = element.customTitle || (tableName ? `${translateTableName(tableName)} 위치` : "위치 지도"); return (
{/* 헤더 */}

{displayTitle}

{element?.dataSource?.query ? (

총 {markers.length.toLocaleString()}개 마커

) : (

⚙️ 톱니바퀴 버튼을 눌러 데이터를 연결하세요

)}
{/* 에러 메시지 (지도 위에 오버레이) */} {error && (
⚠️ {error}
)} {/* 지도 (항상 표시) */}
{/* 브이월드 타일맵 */} {/* 마커 표시 */} {markers.map((marker, idx) => (
{marker.name}
{Object.entries(marker.info) .filter(([key]) => !["latitude", "longitude", "lat", "lng"].includes(key.toLowerCase())) .map(([key, value]) => (
{key}: {String(value)}
))}
))}
); }