diff --git a/frontend/components/admin/dashboard/WidgetConfigSidebar.tsx b/frontend/components/admin/dashboard/WidgetConfigSidebar.tsx index 710abbe6..e79c6446 100644 --- a/frontend/components/admin/dashboard/WidgetConfigSidebar.tsx +++ b/frontend/components/admin/dashboard/WidgetConfigSidebar.tsx @@ -520,7 +520,39 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge )} {/* 지도 설정 */} - {element.subtype === "map-summary-v2" && } + {element.subtype === "map-summary-v2" && ( + { + setElement((prev) => + prev + ? { + ...prev, + chartConfig: { + ...prev.chartConfig, + refreshInterval: interval, + }, + } + : prev + ); + }} + onMarkerTypeChange={(type) => { + setElement((prev) => + prev + ? { + ...prev, + chartConfig: { + ...prev.chartConfig, + markerType: type, + }, + } + : prev + ); + }} + /> + )} {/* 리스크 알림 설정 */} {element.subtype === "risk-alert-v2" && } diff --git a/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx b/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx index bbbf7d4d..f16e8436 100644 --- a/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx +++ b/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx @@ -530,31 +530,50 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M ))} - {/* 자동 새로고침 설정 */} + {/* 마커 polling 간격 설정 (MapTestWidgetV2 전용) */}
-
+ + {/* 마커 종류 선택 (MapTestWidgetV2 전용) */} +
+ + +

+ 지도에 표시할 마커의 모양을 선택합니다

diff --git a/frontend/components/admin/dashboard/types.ts b/frontend/components/admin/dashboard/types.ts index 8b8ff2b4..61fa60ae 100644 --- a/frontend/components/admin/dashboard/types.ts +++ b/frontend/components/admin/dashboard/types.ts @@ -52,7 +52,7 @@ export type ElementSubtype = | "yard-management-3d" // 야드 관리 3D 위젯 | "work-history" // 작업 이력 위젯 | "transport-stats"; // 커스텀 통계 카드 위젯 - // | "custom-metric"; // (구버전 - 주석 처리: 2025-10-28, custom-metric-v2로 대체) +// | "custom-metric"; // (구버전 - 주석 처리: 2025-10-28, custom-metric-v2로 대체) // 차트 분류 export type ChartCategory = "axis-based" | "circular"; @@ -164,6 +164,7 @@ export interface ChartDataSource { markerColor?: string; // 마커 색상 (예: "#ff0000") polygonColor?: string; // 폴리곤 색상 (예: "#0000ff") polygonOpacity?: number; // 폴리곤 투명도 (0.0 ~ 1.0, 기본값: 0.5) + markerType?: string; // 마커 종류 (circle, arrow) // 컬럼 매핑 (다중 데이터 소스 통합용) columnMapping?: Record; // { 원본컬럼: 표시이름 } (예: { "name": "product" }) diff --git a/frontend/components/admin/dashboard/widget-sections/MapConfigSection.tsx b/frontend/components/admin/dashboard/widget-sections/MapConfigSection.tsx index ae75f7cb..3ed5fe24 100644 --- a/frontend/components/admin/dashboard/widget-sections/MapConfigSection.tsx +++ b/frontend/components/admin/dashboard/widget-sections/MapConfigSection.tsx @@ -3,20 +3,30 @@ import React from "react"; import { QueryResult } from "../types"; import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { AlertCircle } from "lucide-react"; interface MapConfigSectionProps { queryResult: QueryResult | null; + refreshInterval?: number; + markerType?: string; + onRefreshIntervalChange?: (interval: number) => void; + onMarkerTypeChange?: (type: string) => void; } /** * 지도 위젯 설정 섹션 - * - 위도/경도 매핑 - * - * TODO: 상세 설정 UI 추가 필요 + * - 자동 새로고침 간격 설정 + * - 마커 종류 선택 */ -export function MapConfigSection({ queryResult }: MapConfigSectionProps) { +export function MapConfigSection({ + queryResult, + refreshInterval = 5, + markerType = "circle", + onRefreshIntervalChange, + onMarkerTypeChange +}: MapConfigSectionProps) { // 쿼리 결과가 없으면 안내 메시지 표시 if (!queryResult || !queryResult.columns || queryResult.columns.length === 0) { return ( @@ -34,13 +44,56 @@ export function MapConfigSection({ queryResult }: MapConfigSectionProps) { return (
- - - - - 지도 상세 설정 UI는 추후 추가 예정입니다. - - + + +
+ {/* 자동 새로고침 간격 */} +
+ + +

+ 마커 데이터를 자동으로 갱신하는 주기를 설정합니다 +

+
+ + {/* 마커 종류 선택 */} +
+ + +

+ 지도에 표시할 마커의 모양을 선택합니다 +

+
+
); } diff --git a/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx b/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx index 51419821..834275b3 100644 --- a/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx +++ b/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx @@ -65,10 +65,6 @@ interface PolygonData { } export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) { - console.log("🗺️ MapTestWidgetV2 컴포넌트 마운트/렌더링"); - console.log("📦 element:", element); - console.log("📊 dataSources:", element?.dataSources); - const [markers, setMarkers] = useState([]); const [prevMarkers, setPrevMarkers] = useState([]); // 이전 마커 위치 저장 const [polygons, setPolygons] = useState([]); @@ -911,31 +907,29 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) { // 초기 로드 및 자동 새로고침 (마커 데이터만 polling) useEffect(() => { - console.log("🔄 지도 위젯 초기화 useEffect 실행됨!"); - console.log("📊 dataSources 상태:", dataSources); - if (!dataSources || dataSources.length === 0) { - console.log("⚠️ dataSources가 없거나 비어있음 - polling 시작 안함"); setMarkers([]); setPolygons([]); return; } // 즉시 첫 로드 (마커 데이터) - console.log("📡 초기 마커 데이터 로드"); loadMultipleDataSources(); - // 5초마다 마커 데이터만 자동 새로고침 (지도 타일은 안 건드림) - const refreshInterval = 5; - console.log(`⏱️ 마커 polling 시작: ${refreshInterval}초마다`); + // 첫 번째 데이터 소스의 새로고침 간격 사용 (초) + const firstDataSource = dataSources[0]; + const refreshInterval = firstDataSource?.refreshInterval ?? 5; + + // 0이면 자동 새로고침 비활성화 + if (refreshInterval === 0) { + return; + } const intervalId = setInterval(() => { - console.log("🔄 마커 자동 새로고침 실행"); loadMultipleDataSources(); }, refreshInterval * 1000); return () => { - console.log("⏹️ 마커 polling 정리"); clearInterval(intervalId); }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -1124,59 +1118,103 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) { ))} - {/* 마커 렌더링 (화살표 모양) */} + {/* 마커 렌더링 */} {markers.map((marker) => { - // 화살표 아이콘 생성 (진행 방향으로 회전) - let arrowIcon: any; + // 첫 번째 데이터 소스의 마커 종류 가져오기 + const firstDataSource = dataSources?.[0]; + const markerType = firstDataSource?.markerType || "circle"; + + let markerIcon: any; if (typeof window !== "undefined") { const L = require("leaflet"); const heading = marker.heading || 0; - arrowIcon = L.divIcon({ - className: "custom-arrow-marker", - html: ` -
- - - - - - - - -
- `, - iconSize: [40, 40], - iconAnchor: [20, 20], - }); + + if (markerType === "arrow") { + // 화살표 마커 + markerIcon = L.divIcon({ + className: "custom-arrow-marker", + html: ` +
+ + + + + + + + +
+ `, + iconSize: [40, 40], + iconAnchor: [20, 20], + }); + } else { + // 동그라미 마커 (기본) + markerIcon = L.divIcon({ + className: "custom-circle-marker", + html: ` +
+ + + + + + +
+ `, + iconSize: [32, 32], + iconAnchor: [16, 16], + }); + } } return ( - +
{/* 제목 */}