2025-10-28 13:38:22 +09:00
|
|
|
|
"use client";
|
2025-10-15 10:29:15 +09:00
|
|
|
|
|
2025-10-28 13:38:22 +09:00
|
|
|
|
import React, { useState, useCallback } from "react";
|
|
|
|
|
|
import { ChartConfig, QueryResult, MarkerColorRule } from "./types";
|
|
|
|
|
|
import { Plus, Trash2 } from "lucide-react";
|
|
|
|
|
|
import { v4 as uuidv4 } from "uuid";
|
2025-10-15 10:29:15 +09:00
|
|
|
|
|
|
|
|
|
|
interface VehicleMapConfigPanelProps {
|
|
|
|
|
|
config?: ChartConfig;
|
|
|
|
|
|
queryResult?: QueryResult;
|
|
|
|
|
|
onConfigChange: (config: ChartConfig) => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 차량 위치 지도 설정 패널
|
|
|
|
|
|
* - 위도/경도 컬럼 매핑
|
|
|
|
|
|
* - 라벨/상태 컬럼 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: VehicleMapConfigPanelProps) {
|
|
|
|
|
|
const [currentConfig, setCurrentConfig] = useState<ChartConfig>(config || {});
|
|
|
|
|
|
|
|
|
|
|
|
// 설정 업데이트
|
2025-10-28 13:38:22 +09:00
|
|
|
|
const updateConfig = useCallback(
|
|
|
|
|
|
(updates: Partial<ChartConfig>) => {
|
|
|
|
|
|
const newConfig = { ...currentConfig, ...updates };
|
|
|
|
|
|
setCurrentConfig(newConfig);
|
|
|
|
|
|
onConfigChange(newConfig);
|
|
|
|
|
|
},
|
|
|
|
|
|
[currentConfig, onConfigChange],
|
|
|
|
|
|
);
|
2025-10-15 10:29:15 +09:00
|
|
|
|
|
|
|
|
|
|
// 사용 가능한 컬럼 목록
|
|
|
|
|
|
const availableColumns = queryResult?.columns || [];
|
|
|
|
|
|
const sampleData = queryResult?.rows?.[0] || {};
|
|
|
|
|
|
|
2025-10-28 13:38:22 +09:00
|
|
|
|
// 마커 색상 모드 변경
|
|
|
|
|
|
const handleMarkerColorModeChange = useCallback(
|
|
|
|
|
|
(mode: "single" | "conditional") => {
|
|
|
|
|
|
if (mode === "single") {
|
|
|
|
|
|
updateConfig({
|
|
|
|
|
|
markerColorMode: "single",
|
|
|
|
|
|
markerColorColumn: undefined,
|
|
|
|
|
|
markerColorRules: undefined,
|
|
|
|
|
|
markerDefaultColor: "#3b82f6", // 파란색
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
updateConfig({
|
|
|
|
|
|
markerColorMode: "conditional",
|
|
|
|
|
|
markerColorRules: [],
|
|
|
|
|
|
markerDefaultColor: "#6b7280", // 회색
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
[updateConfig],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 마커 색상 규칙 추가
|
|
|
|
|
|
const addColorRule = useCallback(() => {
|
|
|
|
|
|
const newRule: MarkerColorRule = {
|
|
|
|
|
|
id: uuidv4(),
|
|
|
|
|
|
value: "",
|
|
|
|
|
|
color: "#3b82f6",
|
|
|
|
|
|
label: "",
|
|
|
|
|
|
};
|
|
|
|
|
|
const currentRules = currentConfig.markerColorRules || [];
|
|
|
|
|
|
updateConfig({ markerColorRules: [...currentRules, newRule] });
|
|
|
|
|
|
}, [currentConfig.markerColorRules, updateConfig]);
|
|
|
|
|
|
|
|
|
|
|
|
// 마커 색상 규칙 삭제
|
|
|
|
|
|
const deleteColorRule = useCallback(
|
|
|
|
|
|
(id: string) => {
|
|
|
|
|
|
const currentRules = currentConfig.markerColorRules || [];
|
|
|
|
|
|
updateConfig({ markerColorRules: currentRules.filter((rule) => rule.id !== id) });
|
|
|
|
|
|
},
|
|
|
|
|
|
[currentConfig.markerColorRules, updateConfig],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 마커 색상 규칙 업데이트
|
|
|
|
|
|
const updateColorRule = useCallback(
|
|
|
|
|
|
(id: string, updates: Partial<MarkerColorRule>) => {
|
|
|
|
|
|
const currentRules = currentConfig.markerColorRules || [];
|
|
|
|
|
|
updateConfig({
|
|
|
|
|
|
markerColorRules: currentRules.map((rule) => (rule.id === id ? { ...rule, ...updates } : rule)),
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
[currentConfig.markerColorRules, updateConfig],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-10-15 10:29:15 +09:00
|
|
|
|
return (
|
2025-10-22 13:40:15 +09:00
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
<h4 className="text-xs font-semibold text-gray-800">🗺️ 지도 설정</h4>
|
2025-10-15 10:29:15 +09:00
|
|
|
|
|
|
|
|
|
|
{/* 쿼리 결과가 없을 때 */}
|
|
|
|
|
|
{!queryResult && (
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-3">
|
|
|
|
|
|
<div className="text-xs text-yellow-800">
|
2025-10-15 10:29:15 +09:00
|
|
|
|
💡 먼저 SQL 쿼리를 실행하여 데이터를 가져온 후 지도를 설정할 수 있습니다.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 데이터 필드 매핑 */}
|
|
|
|
|
|
{queryResult && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* 지도 제목 */}
|
2025-10-22 13:40:15 +09:00
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">지도 제목</label>
|
2025-10-15 10:29:15 +09:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
2025-10-28 13:38:22 +09:00
|
|
|
|
value={currentConfig.title || ""}
|
2025-10-15 10:29:15 +09:00
|
|
|
|
onChange={(e) => updateConfig({ title: e.target.value })}
|
|
|
|
|
|
placeholder="차량 위치 지도"
|
2025-10-28 13:38:22 +09:00
|
|
|
|
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
|
2025-10-15 10:29:15 +09:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 위도 컬럼 설정 */}
|
2025-10-22 13:40:15 +09:00
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">
|
2025-10-15 10:29:15 +09:00
|
|
|
|
위도 컬럼 (Latitude)
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<span className="ml-1 text-red-500">*</span>
|
2025-10-15 10:29:15 +09:00
|
|
|
|
</label>
|
|
|
|
|
|
<select
|
2025-10-28 13:38:22 +09:00
|
|
|
|
value={currentConfig.latitudeColumn || ""}
|
2025-10-15 10:29:15 +09:00
|
|
|
|
onChange={(e) => updateConfig({ latitudeColumn: e.target.value })}
|
2025-10-28 13:38:22 +09:00
|
|
|
|
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
|
2025-10-15 10:29:15 +09:00
|
|
|
|
>
|
|
|
|
|
|
<option value="">선택하세요</option>
|
|
|
|
|
|
{availableColumns.map((col) => (
|
|
|
|
|
|
<option key={col} value={col}>
|
|
|
|
|
|
{col} {sampleData[col] && `(예: ${sampleData[col]})`}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 경도 컬럼 설정 */}
|
2025-10-22 13:40:15 +09:00
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">
|
2025-10-15 10:29:15 +09:00
|
|
|
|
경도 컬럼 (Longitude)
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<span className="ml-1 text-red-500">*</span>
|
2025-10-15 10:29:15 +09:00
|
|
|
|
</label>
|
|
|
|
|
|
<select
|
2025-10-28 13:38:22 +09:00
|
|
|
|
value={currentConfig.longitudeColumn || ""}
|
2025-10-15 10:29:15 +09:00
|
|
|
|
onChange={(e) => updateConfig({ longitudeColumn: e.target.value })}
|
2025-10-28 13:38:22 +09:00
|
|
|
|
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
|
2025-10-15 10:29:15 +09:00
|
|
|
|
>
|
|
|
|
|
|
<option value="">선택하세요</option>
|
|
|
|
|
|
{availableColumns.map((col) => (
|
|
|
|
|
|
<option key={col} value={col}>
|
|
|
|
|
|
{col} {sampleData[col] && `(예: ${sampleData[col]})`}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 라벨 컬럼 (선택사항) */}
|
2025-10-22 13:40:15 +09:00
|
|
|
|
<div className="space-y-1.5">
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<label className="block text-xs font-medium text-gray-700">라벨 컬럼 (마커 표시명)</label>
|
2025-10-15 10:29:15 +09:00
|
|
|
|
<select
|
2025-10-28 13:38:22 +09:00
|
|
|
|
value={currentConfig.labelColumn || ""}
|
2025-10-15 10:29:15 +09:00
|
|
|
|
onChange={(e) => updateConfig({ labelColumn: e.target.value })}
|
2025-10-28 13:38:22 +09:00
|
|
|
|
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
|
2025-10-15 10:29:15 +09:00
|
|
|
|
>
|
|
|
|
|
|
<option value="">선택하세요 (선택사항)</option>
|
|
|
|
|
|
{availableColumns.map((col) => (
|
|
|
|
|
|
<option key={col} value={col}>
|
|
|
|
|
|
{col}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-28 13:38:22 +09:00
|
|
|
|
{/* 마커 색상 설정 */}
|
|
|
|
|
|
<div className="space-y-2 border-t pt-3">
|
|
|
|
|
|
<h5 className="text-xs font-semibold text-gray-800">🎨 마커 색상 설정</h5>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 색상 모드 선택 */}
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">색상 모드</label>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => handleMarkerColorModeChange("single")}
|
|
|
|
|
|
className={`flex-1 rounded-lg border px-3 py-2 text-xs transition-colors ${
|
|
|
|
|
|
(currentConfig.markerColorMode || "single") === "single"
|
|
|
|
|
|
? "border-blue-300 bg-blue-50 font-medium text-blue-700"
|
|
|
|
|
|
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
단일 색상
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => handleMarkerColorModeChange("conditional")}
|
|
|
|
|
|
className={`flex-1 rounded-lg border px-3 py-2 text-xs transition-colors ${
|
|
|
|
|
|
currentConfig.markerColorMode === "conditional"
|
|
|
|
|
|
? "border-blue-300 bg-blue-50 font-medium text-blue-700"
|
|
|
|
|
|
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
조건부 색상
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 단일 색상 모드 */}
|
|
|
|
|
|
{(currentConfig.markerColorMode || "single") === "single" && (
|
|
|
|
|
|
<div className="space-y-1.5 rounded-lg bg-gray-50 p-3">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">마커 색상</label>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
value={currentConfig.markerDefaultColor || "#3b82f6"}
|
|
|
|
|
|
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
|
|
|
|
|
|
className="h-8 w-12 cursor-pointer rounded border border-gray-300"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={currentConfig.markerDefaultColor || "#3b82f6"}
|
|
|
|
|
|
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
|
|
|
|
|
|
placeholder="#3b82f6"
|
|
|
|
|
|
className="flex-1 rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-xs text-gray-500">모든 마커가 동일한 색상으로 표시됩니다</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 조건부 색상 모드 */}
|
|
|
|
|
|
{currentConfig.markerColorMode === "conditional" && (
|
|
|
|
|
|
<div className="space-y-2 rounded-lg bg-gray-50 p-3">
|
|
|
|
|
|
{/* 색상 조건 컬럼 선택 */}
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">
|
|
|
|
|
|
색상 조건 컬럼
|
|
|
|
|
|
<span className="ml-1 text-red-500">*</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={currentConfig.markerColorColumn || ""}
|
|
|
|
|
|
onChange={(e) => updateConfig({ markerColorColumn: e.target.value })}
|
|
|
|
|
|
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">선택하세요</option>
|
|
|
|
|
|
{availableColumns.map((col) => (
|
|
|
|
|
|
<option key={col} value={col}>
|
|
|
|
|
|
{col}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<p className="text-xs text-gray-500">이 컬럼의 값에 따라 마커 색상이 결정됩니다</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 기본 색상 */}
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">기본 색상</label>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
value={currentConfig.markerDefaultColor || "#6b7280"}
|
|
|
|
|
|
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
|
|
|
|
|
|
className="h-8 w-12 cursor-pointer rounded border border-gray-300"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={currentConfig.markerDefaultColor || "#6b7280"}
|
|
|
|
|
|
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
|
|
|
|
|
|
placeholder="#6b7280"
|
|
|
|
|
|
className="flex-1 rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-xs text-gray-500">규칙에 매칭되지 않는 경우 사용할 색상</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 색상 규칙 목록 */}
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700">색상 규칙</label>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={addColorRule}
|
|
|
|
|
|
className="flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1 text-xs text-white transition-colors hover:bg-blue-600"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Plus className="h-3 w-3" />
|
|
|
|
|
|
추가
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 규칙 리스트 */}
|
|
|
|
|
|
{(currentConfig.markerColorRules || []).length === 0 ? (
|
|
|
|
|
|
<div className="rounded-lg border border-gray-200 bg-white p-3 text-center">
|
|
|
|
|
|
<p className="text-xs text-gray-500">추가 버튼을 눌러 색상 규칙을 만드세요</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{(currentConfig.markerColorRules || []).map((rule) => (
|
|
|
|
|
|
<div key={rule.id} className="space-y-2 rounded-lg border border-gray-200 bg-white p-2">
|
|
|
|
|
|
{/* 규칙 헤더 */}
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<span className="text-xs font-medium text-gray-700">규칙</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => deleteColorRule(rule.id)}
|
|
|
|
|
|
className="text-red-500 transition-colors hover:text-red-700"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-3 w-3" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 조건 값 */}
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-600">값 (조건)</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={rule.value}
|
|
|
|
|
|
onChange={(e) => updateColorRule(rule.id, { value: e.target.value })}
|
|
|
|
|
|
placeholder="예: active, inactive"
|
|
|
|
|
|
className="w-full rounded border border-gray-300 px-2 py-1 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 색상 */}
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-600">색상</label>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
value={rule.color}
|
|
|
|
|
|
onChange={(e) => updateColorRule(rule.id, { color: e.target.value })}
|
|
|
|
|
|
className="h-8 w-12 cursor-pointer rounded border border-gray-300"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={rule.color}
|
|
|
|
|
|
onChange={(e) => updateColorRule(rule.id, { color: e.target.value })}
|
|
|
|
|
|
placeholder="#3b82f6"
|
|
|
|
|
|
className="flex-1 rounded border border-gray-300 px-2 py-1 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 라벨 (선택사항) */}
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-600">라벨 (선택)</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={rule.label || ""}
|
|
|
|
|
|
onChange={(e) => updateColorRule(rule.id, { label: e.target.value })}
|
|
|
|
|
|
placeholder="예: 활성, 비활성"
|
|
|
|
|
|
className="w-full rounded border border-gray-300 px-2 py-1 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-10-15 10:29:15 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-23 13:17:21 +09:00
|
|
|
|
{/* 날씨 정보 표시 옵션 */}
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-gray-700">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={currentConfig.showWeather || false}
|
|
|
|
|
|
onChange={(e) => updateConfig({ showWeather: e.target.checked })}
|
|
|
|
|
|
className="text-primary focus:ring-primary h-4 w-4 rounded border-gray-300 focus:ring-2"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>날씨 정보 표시</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<p className="ml-6 text-xs text-gray-500">마커 팝업에 해당 위치의 날씨 정보를 함께 표시합니다</p>
|
|
|
|
|
|
</div>
|
2025-10-23 13:17:21 +09:00
|
|
|
|
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-gray-700">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={currentConfig.showWeatherAlerts || false}
|
|
|
|
|
|
onChange={(e) => updateConfig({ showWeatherAlerts: e.target.checked })}
|
|
|
|
|
|
className="text-primary focus:ring-primary h-4 w-4 rounded border-gray-300 focus:ring-2"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>기상특보 영역 표시</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<p className="ml-6 text-xs text-gray-500">
|
|
|
|
|
|
현재 발효 중인 기상특보(주의보/경보)를 지도에 색상 영역으로 표시합니다
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2025-10-23 13:17:21 +09:00
|
|
|
|
|
2025-10-15 10:29:15 +09:00
|
|
|
|
{/* 설정 미리보기 */}
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<div className="rounded-lg bg-gray-50 p-3">
|
|
|
|
|
|
<div className="mb-2 text-xs font-medium text-gray-700">📋 설정 미리보기</div>
|
|
|
|
|
|
<div className="text-muted-foreground space-y-1 text-xs">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>위도:</strong> {currentConfig.latitudeColumn || "미설정"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>경도:</strong> {currentConfig.longitudeColumn || "미설정"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>라벨:</strong> {currentConfig.labelColumn || "없음"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>색상 모드:</strong> {currentConfig.markerColorMode === "conditional" ? "조건부" : "단일"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{currentConfig.markerColorMode === "conditional" && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>색상 조건 컬럼:</strong> {currentConfig.markerColorColumn || "미설정"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>색상 규칙 개수:</strong> {(currentConfig.markerColorRules || []).length}개
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>날씨 표시:</strong> {currentConfig.showWeather ? "활성화" : "비활성화"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>기상특보 표시:</strong> {currentConfig.showWeatherAlerts ? "활성화" : "비활성화"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>데이터 개수:</strong> {queryResult.rows.length}개
|
|
|
|
|
|
</div>
|
2025-10-15 10:29:15 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 필수 필드 확인 */}
|
|
|
|
|
|
{(!currentConfig.latitudeColumn || !currentConfig.longitudeColumn) && (
|
2025-10-28 13:38:22 +09:00
|
|
|
|
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
|
|
|
|
|
<div className="text-xs text-red-800">
|
2025-10-15 10:29:15 +09:00
|
|
|
|
⚠️ 위도와 경도 컬럼을 반드시 선택해야 지도에 표시할 수 있습니다.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|