지도 위젯 별 polling 설정 구현

This commit is contained in:
dohyeons 2025-11-14 10:26:09 +09:00
parent b3e217c1de
commit a3503c0b9f
5 changed files with 68 additions and 77 deletions

View File

@ -17,6 +17,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { DatabaseConfig } from "./data-sources/DatabaseConfig";
import { ApiConfig } from "./data-sources/ApiConfig";
import { QueryEditor } from "./QueryEditor";
@ -146,6 +147,9 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
// 커스텀 메트릭 설정
const [customMetricConfig, setCustomMetricConfig] = useState<CustomMetricConfig>({});
// 자동 새로고침 간격 (지도 위젯용)
const [refreshInterval, setRefreshInterval] = useState<number>(5);
// 사이드바 열릴 때 초기화
useEffect(() => {
if (isOpen && element) {
@ -155,6 +159,8 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
// dataSources는 element.dataSources 또는 chartConfig.dataSources에서 가져옴
setDataSources(element.dataSources || element.chartConfig?.dataSources || []);
setQueryResult(null);
// 자동 새로고침 간격 초기화
setRefreshInterval(element.chartConfig?.refreshInterval ?? 5);
// 리스트 위젯 설정 초기화
if (element.subtype === "list-v2" && element.listConfig) {
@ -290,6 +296,24 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
element.subtype === "custom-metric-v2" ||
element.subtype === "risk-alert-v2";
// chartConfig 구성 (위젯 타입별로 다르게 처리)
let finalChartConfig = { ...chartConfig };
if (isMultiDataSourceWidget) {
finalChartConfig = {
...finalChartConfig,
dataSources: dataSources,
};
}
// 지도 위젯인 경우 refreshInterval 추가
if (element.subtype === "map-summary-v2") {
finalChartConfig = {
...finalChartConfig,
refreshInterval,
};
}
const updatedElement: DashboardElement = {
...element,
customTitle: customTitle.trim() || undefined,
@ -302,8 +326,6 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
...(isMultiDataSourceWidget
? {
dataSources: dataSources,
// chartConfig에도 dataSources 포함 (일부 위젯은 chartConfig에서 읽음)
chartConfig: { ...chartConfig, dataSources: dataSources },
}
: {}),
}
@ -314,21 +336,10 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
listConfig,
}
: {}),
// 차트 설정 (차트 타입이거나 차트 기능이 있는 위젯)
...(element.type === "chart" ||
element.subtype === "chart" ||
["bar", "horizontal-bar", "pie", "line", "area", "stacked-bar", "donut", "combo"].includes(element.subtype)
// 차트 설정 (모든 위젯 공통)
...(needsDataSource(element.subtype)
? {
// 다중 데이터 소스 위젯은 chartConfig에 dataSources 포함 (빈 배열도 허용 - 연결 해제)
chartConfig: isMultiDataSourceWidget
? { ...chartConfig, dataSources: dataSources }
: chartConfig,
// 프론트엔드 호환성을 위해 dataSources도 element에 직접 포함 (빈 배열도 허용 - 연결 해제)
...(isMultiDataSourceWidget
? {
dataSources: dataSources,
}
: {}),
chartConfig: finalChartConfig,
}
: {}),
// 커스텀 메트릭 설정
@ -356,6 +367,7 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
listConfig,
chartConfig,
customMetricConfig,
refreshInterval,
onApply,
onClose,
]);
@ -432,6 +444,40 @@ export function WidgetConfigSidebar({ element, isOpen, onClose, onApply }: Widge
<Switch id="show-header" checked={showHeader} onCheckedChange={setShowHeader} />
</div>
</div>
{/* 자동 새로고침 설정 (지도 위젯 전용) */}
{element.subtype === "map-summary-v2" && (
<div className="bg-background rounded-lg p-3 shadow-sm">
<Label htmlFor="refresh-interval" className="mb-2 block text-xs font-semibold">
</Label>
<Select value={refreshInterval.toString()} onValueChange={(value) => setRefreshInterval(parseInt(value))}>
<SelectTrigger id="refresh-interval" className="h-9 text-sm">
<SelectValue placeholder="간격 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="0" className="text-sm">
</SelectItem>
<SelectItem value="5" className="text-sm">
5
</SelectItem>
<SelectItem value="10" className="text-sm">
10
</SelectItem>
<SelectItem value="30" className="text-sm">
30
</SelectItem>
<SelectItem value="60" className="text-sm">
1
</SelectItem>
</SelectContent>
</Select>
<p className="text-muted-foreground mt-1.5 text-xs">
</p>
</div>
)}
</div>
</TabsContent>

View File

@ -530,30 +530,6 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
))}
</div>
{/* 마커 polling 간격 설정 (MapTestWidgetV2 전용) */}
<div className="space-y-2">
<Label htmlFor="marker-refresh-interval" className="text-xs">
</Label>
<Select
value={(dataSource.refreshInterval ?? 5).toString()}
onValueChange={(value) => onChange({ refreshInterval: parseInt(value) })}
>
<SelectTrigger id="marker-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>
{/* 마커 종류 선택 (MapTestWidgetV2 전용) */}
<div className="space-y-2">

View File

@ -321,38 +321,6 @@ ORDER BY 하위부서수 DESC`,
/>
</div>
{/* 마커 polling 간격 설정 (MapTestWidgetV2 전용) */}
<div className="space-y-2">
<Label htmlFor="marker-refresh-interval" className="text-xs">
</Label>
<Select
value={(dataSource.refreshInterval ?? 5).toString()}
onValueChange={(value) => onChange({ refreshInterval: parseInt(value) })}
>
<SelectTrigger id="marker-refresh-interval" className="h-8 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-muted-foreground text-[10px]"> </p>
</div>
{/* 지도 색상 설정 (MapTestWidgetV2 전용) */}
<div className="bg-muted/30 space-y-2 rounded-lg border p-2">

View File

@ -155,7 +155,6 @@ export interface ChartDataSource {
jsonPath?: string; // JSON 응답에서 데이터 추출 경로 (예: "data.results")
// 공통
refreshInterval?: number; // 자동 새로고침 (초, 0이면 수동)
lastExecuted?: string; // 마지막 실행 시간
lastError?: string; // 마지막 오류 메시지
mapDisplayType?: "auto" | "marker" | "polygon"; // 지도 표시 방식 (auto: 자동, marker: 마커, polygon: 영역)
@ -184,6 +183,9 @@ export interface ChartConfig {
// 다중 데이터 소스 (테스트 위젯용)
dataSources?: ChartDataSource[]; // 여러 데이터 소스 (REST API + Database 혼합 가능)
// 위젯 레벨 설정 (MapTestWidgetV2용)
refreshInterval?: number; // 위젯 전체 자동 새로고침 간격 (초, 0이면 수동)
// 멀티 차트 설정 (ChartTestWidget용)
chartType?: string; // 차트 타입 (line, bar, pie, etc.)
mergeMode?: boolean; // 데이터 병합 모드 (여러 데이터 소스를 하나의 라인/바로 합침)

View File

@ -916,9 +916,8 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
// 즉시 첫 로드 (마커 데이터)
loadMultipleDataSources();
// 첫 번째 데이터 소스의 새로고침 간격 사용 (초)
const firstDataSource = dataSources[0];
const refreshInterval = firstDataSource?.refreshInterval ?? 5;
// 위젯 레벨의 새로고침 간격 사용 (초)
const refreshInterval = element?.chartConfig?.refreshInterval ?? 5;
// 0이면 자동 새로고침 비활성화
if (refreshInterval === 0) {
@ -933,7 +932,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
clearInterval(intervalId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataSources]);
}, [dataSources, element?.chartConfig?.refreshInterval]);
// 타일맵 URL (chartConfig에서 가져오기)
const tileMapUrl =