polling 및 마커 종류 설정 추가
This commit is contained in:
parent
68184ac49f
commit
54b7cae218
|
|
@ -520,7 +520,39 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
|
|||
)}
|
||||
|
||||
{/* 지도 설정 */}
|
||||
{element.subtype === "map-summary-v2" && <MapConfigSection queryResult={queryResult} />}
|
||||
{element.subtype === "map-summary-v2" && (
|
||||
<MapConfigSection
|
||||
queryResult={queryResult}
|
||||
refreshInterval={element.chartConfig?.refreshInterval || 5}
|
||||
markerType={element.chartConfig?.markerType || "circle"}
|
||||
onRefreshIntervalChange={(interval) => {
|
||||
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" && <RiskAlertSection queryResult={queryResult} />}
|
||||
|
|
|
|||
|
|
@ -530,31 +530,50 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
|
|||
))}
|
||||
</div>
|
||||
|
||||
{/* 자동 새로고침 설정 */}
|
||||
{/* 마커 polling 간격 설정 (MapTestWidgetV2 전용) */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`refresh-${dataSource.id}`} className="text-xs">
|
||||
자동 새로고침 간격
|
||||
<Label htmlFor="marker-refresh-interval" className="text-xs">
|
||||
마커 새로고침 간격
|
||||
</Label>
|
||||
<Select
|
||||
value={String(dataSource.refreshInterval || 0)}
|
||||
onValueChange={(value) => onChange({ refreshInterval: Number(value) })}
|
||||
value={(dataSource.refreshInterval ?? 5).toString()}
|
||||
onValueChange={(value) => onChange({ refreshInterval: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="새로고침 안 함" />
|
||||
<SelectTrigger id="marker-refresh-interval" className="h-9 text-xs">
|
||||
<SelectValue placeholder="간격 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">새로고침 안 함</SelectItem>
|
||||
<SelectItem value="10">10초마다</SelectItem>
|
||||
<SelectItem value="30">30초마다</SelectItem>
|
||||
<SelectItem value="60">1분마다</SelectItem>
|
||||
<SelectItem value="300">5분마다</SelectItem>
|
||||
<SelectItem value="600">10분마다</SelectItem>
|
||||
<SelectItem value="1800">30분마다</SelectItem>
|
||||
<SelectItem value="3600">1시간마다</SelectItem>
|
||||
<SelectItem value="0" className="text-xs">없음</SelectItem>
|
||||
<SelectItem value="5" className="text-xs">5초</SelectItem>
|
||||
<SelectItem value="10" className="text-xs">10초</SelectItem>
|
||||
<SelectItem value="30" className="text-xs">30초</SelectItem>
|
||||
<SelectItem value="60" className="text-xs">1분</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
설정한 간격마다 자동으로 데이터를 다시 불러옵니다
|
||||
마커 데이터를 자동으로 갱신하는 주기를 설정합니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 마커 종류 선택 (MapTestWidgetV2 전용) */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="marker-type" className="text-xs">
|
||||
마커 종류
|
||||
</Label>
|
||||
<Select
|
||||
value={dataSource.markerType || "circle"}
|
||||
onValueChange={(value) => onChange({ markerType: value })}
|
||||
>
|
||||
<SelectTrigger id="marker-type" className="h-9 text-xs">
|
||||
<SelectValue placeholder="마커 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="circle" className="text-xs">동그라미</SelectItem>
|
||||
<SelectItem value="arrow" className="text-xs">화살표</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
지도에 표시할 마커의 모양을 선택합니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string, string>; // { 원본컬럼: 표시이름 } (예: { "name": "product" })
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="rounded-lg bg-background p-3 shadow-sm">
|
||||
<Label className="mb-2 block text-xs font-semibold">지도 설정</Label>
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription className="text-xs">
|
||||
지도 상세 설정 UI는 추후 추가 예정입니다.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Label className="mb-3 block text-xs font-semibold">지도 설정</Label>
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* 자동 새로고침 간격 */}
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="refresh-interval" className="text-xs">
|
||||
자동 새로고침 간격
|
||||
</Label>
|
||||
<Select
|
||||
value={refreshInterval.toString()}
|
||||
onValueChange={(value) => onRefreshIntervalChange?.(parseInt(value))}
|
||||
>
|
||||
<SelectTrigger id="refresh-interval" className="h-9 text-xs">
|
||||
<SelectValue placeholder="새로고침 간격 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0" className="text-xs">없음</SelectItem>
|
||||
<SelectItem value="5" className="text-xs">5초</SelectItem>
|
||||
<SelectItem value="10" className="text-xs">10초</SelectItem>
|
||||
<SelectItem value="30" className="text-xs">30초</SelectItem>
|
||||
<SelectItem value="60" className="text-xs">1분</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
마커 데이터를 자동으로 갱신하는 주기를 설정합니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 마커 종류 선택 */}
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="marker-type" className="text-xs">
|
||||
마커 종류
|
||||
</Label>
|
||||
<Select
|
||||
value={markerType}
|
||||
onValueChange={(value) => onMarkerTypeChange?.(value)}
|
||||
>
|
||||
<SelectTrigger id="marker-type" className="h-9 text-xs">
|
||||
<SelectValue placeholder="마커 종류 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="circle" className="text-xs">동그라미</SelectItem>
|
||||
<SelectItem value="arrow" className="text-xs">화살표</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
지도에 표시할 마커의 모양을 선택합니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MarkerData[]>([]);
|
||||
const [prevMarkers, setPrevMarkers] = useState<MarkerData[]>([]); // 이전 마커 위치 저장
|
||||
const [polygons, setPolygons] = useState<PolygonData[]>([]);
|
||||
|
|
@ -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) {
|
|||
</Polygon>
|
||||
))}
|
||||
|
||||
{/* 마커 렌더링 (화살표 모양) */}
|
||||
{/* 마커 렌더링 */}
|
||||
{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: `
|
||||
<div style="
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%) rotate(${heading}deg);
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
|
||||
">
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 화살표 몸통 -->
|
||||
<polygon
|
||||
points="20,5 25,15 23,15 23,25 17,25 17,15 15,15"
|
||||
fill="${marker.color || "#3b82f6"}"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- 화살촉 -->
|
||||
<polygon
|
||||
points="20,2 28,12 12,12"
|
||||
fill="${marker.color || "#3b82f6"}"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- 중심점 -->
|
||||
<circle
|
||||
cx="20"
|
||||
cy="30"
|
||||
r="3"
|
||||
fill="white"
|
||||
stroke="${marker.color || "#3b82f6"}"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
`,
|
||||
iconSize: [40, 40],
|
||||
iconAnchor: [20, 20],
|
||||
});
|
||||
|
||||
if (markerType === "arrow") {
|
||||
// 화살표 마커
|
||||
markerIcon = L.divIcon({
|
||||
className: "custom-arrow-marker",
|
||||
html: `
|
||||
<div style="
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%) rotate(${heading}deg);
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
|
||||
">
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 화살표 몸통 -->
|
||||
<polygon
|
||||
points="20,5 25,15 23,15 23,25 17,25 17,15 15,15"
|
||||
fill="${marker.color || "#3b82f6"}"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- 화살촉 -->
|
||||
<polygon
|
||||
points="20,2 28,12 12,12"
|
||||
fill="${marker.color || "#3b82f6"}"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- 중심점 -->
|
||||
<circle
|
||||
cx="20"
|
||||
cy="30"
|
||||
r="3"
|
||||
fill="white"
|
||||
stroke="${marker.color || "#3b82f6"}"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
`,
|
||||
iconSize: [40, 40],
|
||||
iconAnchor: [20, 20],
|
||||
});
|
||||
} else {
|
||||
// 동그라미 마커 (기본)
|
||||
markerIcon = L.divIcon({
|
||||
className: "custom-circle-marker",
|
||||
html: `
|
||||
<div style="
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%);
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
|
||||
">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 외부 원 -->
|
||||
<circle
|
||||
cx="16"
|
||||
cy="16"
|
||||
r="14"
|
||||
fill="${marker.color || "#3b82f6"}"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<!-- 내부 점 -->
|
||||
<circle
|
||||
cx="16"
|
||||
cy="16"
|
||||
r="6"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
`,
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 16],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Marker key={marker.id} position={[marker.lat, marker.lng]} icon={arrowIcon}>
|
||||
<Marker key={marker.id} position={[marker.lat, marker.lng]} icon={markerIcon}>
|
||||
<Popup maxWidth={350}>
|
||||
<div className="max-w-[350px] min-w-[250px]">
|
||||
{/* 제목 */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue