"use client"; import React, { useState, useEffect } from "react"; import dynamic from "next/dynamic"; import { RefreshCw, AlertCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; 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 } ); interface MapMarker { id: string | number; latitude: number; longitude: number; label?: string; status?: string; additionalInfo?: Record; } interface MapComponentProps { component: { id: string; config?: { dataSource?: { type?: "internal" | "external"; connectionId?: number | null; tableName?: string; latColumn?: string; lngColumn?: string; labelColumn?: string; statusColumn?: string; additionalColumns?: string[]; whereClause?: string; }; mapConfig?: { center?: { lat: number; lng: number }; zoom?: number; minZoom?: number; maxZoom?: number; }; markerConfig?: { showLabel?: boolean; showPopup?: boolean; statusColors?: Record; }; refreshInterval?: number; }; }; } export default function MapComponent({ component }: MapComponentProps) { const [markers, setMarkers] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [lastUpdate, setLastUpdate] = useState(null); const dataSource = component.config?.dataSource; const mapConfig = component.config?.mapConfig; const markerConfig = component.config?.markerConfig; const refreshInterval = component.config?.refreshInterval || 0; // 데이터 로드 const loadMapData = async () => { if (!dataSource?.tableName || !dataSource?.latColumn || !dataSource?.lngColumn) { setError("테이블명, 위도 컬럼, 경도 컬럼을 설정해주세요."); return; } setIsLoading(true); setError(null); try { // API URL 구성 const isExternal = dataSource.type === "external" && dataSource.connectionId; const baseUrl = isExternal ? `/api/map-data/external/${dataSource.connectionId}` : `/api/map-data/internal`; const params = new URLSearchParams({ tableName: dataSource.tableName, latColumn: dataSource.latColumn, lngColumn: dataSource.lngColumn, }); if (dataSource.labelColumn) { params.append("labelColumn", dataSource.labelColumn); } if (dataSource.statusColumn) { params.append("statusColumn", dataSource.statusColumn); } if (dataSource.additionalColumns && dataSource.additionalColumns.length > 0) { params.append("additionalColumns", dataSource.additionalColumns.join(",")); } if (dataSource.whereClause) { params.append("whereClause", dataSource.whereClause); } const response = await fetch(`${baseUrl}?${params.toString()}`); const result = await response.json(); if (!result.success) { throw new Error(result.message || "데이터 조회 실패"); } setMarkers(result.data.markers || []); setLastUpdate(new Date()); } catch (err: any) { console.error("지도 데이터 로드 오류:", err); setError(err.message || "데이터를 불러올 수 없습니다."); } finally { setIsLoading(false); } }; // 초기 로드 및 자동 새로고침 useEffect(() => { loadMapData(); if (refreshInterval > 0) { const interval = setInterval(loadMapData, refreshInterval); return () => clearInterval(interval); } }, [ dataSource?.type, dataSource?.connectionId, dataSource?.tableName, dataSource?.latColumn, dataSource?.lngColumn, dataSource?.whereClause, refreshInterval, ]); // 마커 색상 가져오기 const getMarkerColor = (status?: string): string => { if (!status || !markerConfig?.statusColors) { return markerConfig?.statusColors?.default || "#3b82f6"; } return markerConfig.statusColors[status] || markerConfig.statusColors.default || "#3b82f6"; }; // 커스텀 마커 아이콘 생성 const createMarkerIcon = (status?: string) => { if (typeof window === "undefined") return undefined; const L = require("leaflet"); const color = getMarkerColor(status); return new L.Icon({ iconUrl: `data:image/svg+xml;base64,${btoa(` `)}`, shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png", iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [0, -41], }); }; if (error) { return (

{error}

); } return (
{/* 지도 */} {typeof window !== "undefined" && ( {/* 마커 렌더링 */} {markers.map((marker) => ( {markerConfig?.showPopup !== false && (
{marker.label && (
{marker.label}
)}
위도: {marker.latitude.toFixed(6)}
경도: {marker.longitude.toFixed(6)}
{marker.status && (
상태: {marker.status}
)} {marker.additionalInfo && Object.entries(marker.additionalInfo).map(([key, value]) => (
{key}: {String(value)}
))}
)}
))}
)} {/* 상단 정보 바 */}
마커: {markers.length}개 {lastUpdate && ( {lastUpdate.toLocaleTimeString()} )}
); }