diff --git a/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx b/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx index f16e8436..c72cb18e 100644 --- a/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx +++ b/frontend/components/admin/dashboard/data-sources/MultiApiConfig.tsx @@ -911,6 +911,128 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M

)} + + {/* 지도 팝업 필드 설정 (MapTestWidgetV2 전용) */} + {availableColumns.length > 0 && ( +
+ + + {/* 기존 팝업 필드 목록 */} + {dataSource.popupFields && dataSource.popupFields.length > 0 && ( +
+ {dataSource.popupFields.map((field, index) => ( +
+
+ 필드 {index + 1} + +
+ + {/* 필드명 선택 */} +
+ + +
+ + {/* 라벨 입력 */} +
+ + { + const newFields = [...(dataSource.popupFields || [])]; + newFields[index].label = e.target.value; + onChange({ popupFields: newFields }); + }} + placeholder="예: 차량 번호" + className="h-8 w-full text-xs" + dir="ltr" + /> +
+ + {/* 포맷 선택 */} +
+ + +
+
+ ))} +
+ )} + + {/* 필드 추가 버튼 */} + + +

+ 마커 클릭 시 팝업에 표시할 필드를 선택하고 한글 라벨을 지정하세요 +

+
+ )} ); } diff --git a/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx b/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx index 76986718..be4377b9 100644 --- a/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx +++ b/frontend/components/admin/dashboard/data-sources/MultiDatabaseConfig.tsx @@ -8,7 +8,7 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Loader2, CheckCircle, XCircle } from "lucide-react"; +import { Loader2, CheckCircle, XCircle, Plus, Trash2 } from "lucide-react"; interface MultiDatabaseConfigProps { dataSource: ChartDataSource; @@ -673,6 +673,128 @@ ORDER BY 하위부서수 DESC`,

)} + + {/* 지도 팝업 필드 설정 (MapTestWidgetV2 전용) */} + {availableColumns.length > 0 && ( +
+ + + {/* 기존 팝업 필드 목록 */} + {dataSource.popupFields && dataSource.popupFields.length > 0 && ( +
+ {dataSource.popupFields.map((field, index) => ( +
+
+ 필드 {index + 1} + +
+ + {/* 필드명 선택 */} +
+ + +
+ + {/* 라벨 입력 */} +
+ + { + const newFields = [...(dataSource.popupFields || [])]; + newFields[index].label = e.target.value; + onChange({ popupFields: newFields }); + }} + placeholder="예: 차량 번호" + className="h-8 w-full text-xs" + dir="ltr" + /> +
+ + {/* 포맷 선택 */} +
+ + +
+
+ ))} +
+ )} + + {/* 필드 추가 버튼 */} + + +

+ 마커 클릭 시 팝업에 표시할 필드를 선택하고 한글 라벨을 지정하세요 +

+
+ )} ); } diff --git a/frontend/components/admin/dashboard/types.ts b/frontend/components/admin/dashboard/types.ts index 61fa60ae..f5490dbf 100644 --- a/frontend/components/admin/dashboard/types.ts +++ b/frontend/components/admin/dashboard/types.ts @@ -171,6 +171,13 @@ export interface ChartDataSource { // 메트릭 설정 (CustomMetricTestWidget용) selectedColumns?: string[]; // 표시할 컬럼 선택 (빈 배열이면 전체 표시) + + // 지도 팝업 설정 (MapTestWidgetV2용) + popupFields?: { + fieldName: string; // DB 컬럼명 (예: vehicle_number) + label: string; // 표시할 한글명 (예: 차량 번호) + format?: "text" | "date" | "datetime" | "number" | "url"; // 표시 포맷 + }[]; } export interface ChartConfig { diff --git a/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx b/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx index e1faded8..dafc40fa 100644 --- a/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx +++ b/frontend/components/dashboard/widgets/MapTestWidgetV2.tsx @@ -919,7 +919,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) { // 첫 번째 데이터 소스의 새로고침 간격 사용 (초) const firstDataSource = dataSources[0]; const refreshInterval = firstDataSource?.refreshInterval ?? 5; - + // 0이면 자동 새로고침 비활성화 if (refreshInterval === 0) { return; @@ -1123,12 +1123,12 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) { // 첫 번째 데이터 소스의 마커 종류 가져오기 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; - + if (markerType === "arrow") { // 화살표 마커 markerIcon = L.divIcon({ @@ -1216,63 +1216,117 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) { return ( -
- {/* 제목 */} -
-
{marker.name}
- {marker.source &&
📡 {marker.source}
} -
+
+ {/* 데이터 소스명만 표시 */} + {marker.source && ( +
+
📡 {marker.source}
+
+ )} {/* 상세 정보 */}
- {marker.description && ( -
-
상세 정보
-
- {(() => { - try { - const parsed = JSON.parse(marker.description); - return ( -
- {parsed.incidenteTypeCd === "1" && ( -
🚨 교통사고
- )} - {parsed.incidenteTypeCd === "2" && ( -
🚧 도로공사
- )} - {parsed.addressJibun &&
📍 {parsed.addressJibun}
} - {parsed.addressNew && parsed.addressNew !== parsed.addressJibun && ( -
📍 {parsed.addressNew}
- )} - {parsed.roadName &&
🛣️ {parsed.roadName}
} - {parsed.linkName &&
🔗 {parsed.linkName}
} - {parsed.incidentMsg && ( -
💬 {parsed.incidentMsg}
- )} - {parsed.eventContent && ( -
📝 {parsed.eventContent}
- )} - {parsed.startDate &&
🕐 {parsed.startDate}
} - {parsed.endDate &&
🕐 종료: {parsed.endDate}
} -
- ); - } catch { - return marker.description; - } - })()} -
-
- )} + {marker.description && + (() => { + const firstDataSource = dataSources?.[0]; + const popupFields = firstDataSource?.popupFields; - {marker.status && ( -
- 상태: {marker.status} -
- )} + // popupFields가 설정되어 있으면 설정된 필드만 표시 + if (popupFields && popupFields.length > 0) { + try { + const parsed = JSON.parse(marker.description); + return ( +
+
상세 정보
+
+ {popupFields.map((field, idx) => { + const value = parsed[field.fieldName]; + if (value === undefined || value === null) return null; + + // 포맷팅 적용 + let formattedValue = value; + if (field.format === "date" && value) { + formattedValue = new Date(value).toLocaleDateString("ko-KR"); + } else if (field.format === "datetime" && value) { + formattedValue = new Date(value).toLocaleString("ko-KR"); + } else if (field.format === "number" && typeof value === "number") { + formattedValue = value.toLocaleString(); + } else if ( + field.format === "url" && + typeof value === "string" && + value.startsWith("http") + ) { + return ( +
+ {field.label}:{" "} + + 링크 열기 + +
+ ); + } + + return ( +
+ {field.label}:{" "} + {String(formattedValue)} +
+ ); + })} +
+
+ ); + } catch (error) { + return ( +
+
상세 정보
+
{marker.description}
+
+ ); + } + } + + // popupFields가 없으면 전체 데이터 표시 (기본 동작) + try { + const parsed = JSON.parse(marker.description); + return ( +
+
상세 정보
+
+ {Object.entries(parsed).map(([key, value], idx) => { + if (value === undefined || value === null) return null; + + // 좌표 필드는 제외 (하단에 별도 표시) + if (["lat", "lng", "latitude", "longitude", "x", "y"].includes(key)) return null; + + return ( +
+ {key}:{" "} + {String(value)} +
+ ); + })} +
+
+ ); + } catch (error) { + return ( +
+
상세 정보
+
{marker.description}
+
+ ); + } + })()} {/* 좌표 */}
- 📍 {marker.lat.toFixed(6)}, {marker.lng.toFixed(6)} + {marker.lat.toFixed(6)}, {marker.lng.toFixed(6)}
@@ -1280,7 +1334,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) { ); })} - )}