에러 수정
This commit is contained in:
parent
cbdd9fef0f
commit
68184ac49f
|
|
@ -16,8 +16,8 @@ import {
|
|||
import dynamic from "next/dynamic";
|
||||
|
||||
// 위젯 동적 import - 모든 위젯
|
||||
const MapSummaryWidget = dynamic(() => import("./widgets/MapSummaryWidget"), { ssr: false });
|
||||
const MapTestWidget = dynamic(() => import("./widgets/MapTestWidget"), { ssr: false });
|
||||
// const MapSummaryWidget = dynamic(() => import("./widgets/MapSummaryWidget"), { ssr: false });
|
||||
// const MapTestWidget = dynamic(() => import("./widgets/MapTestWidget"), { ssr: false });
|
||||
const MapTestWidgetV2 = dynamic(() => import("./widgets/MapTestWidgetV2"), { ssr: false });
|
||||
const ChartTestWidget = dynamic(() => import("./widgets/ChartTestWidget"), { ssr: false });
|
||||
const ListTestWidget = dynamic(
|
||||
|
|
@ -27,7 +27,7 @@ const ListTestWidget = dynamic(
|
|||
const CustomMetricTestWidget = dynamic(() => import("./widgets/CustomMetricTestWidget"), { ssr: false });
|
||||
const RiskAlertTestWidget = dynamic(() => import("./widgets/RiskAlertTestWidget"), { ssr: false });
|
||||
const StatusSummaryWidget = dynamic(() => import("./widgets/StatusSummaryWidget"), { ssr: false });
|
||||
const RiskAlertWidget = dynamic(() => import("./widgets/RiskAlertWidget"), { ssr: false });
|
||||
// const RiskAlertWidget = dynamic(() => import("./widgets/RiskAlertWidget"), { ssr: false });
|
||||
const WeatherWidget = dynamic(() => import("./widgets/WeatherWidget"), { ssr: false });
|
||||
const WeatherMapWidget = dynamic(() => import("./widgets/WeatherMapWidget"), { ssr: false });
|
||||
const ExchangeWidget = dynamic(() => import("./widgets/ExchangeWidget"), { ssr: false });
|
||||
|
|
@ -51,10 +51,10 @@ const ClockWidget = dynamic(
|
|||
() => import("@/components/admin/dashboard/widgets/ClockWidget").then((mod) => ({ default: mod.ClockWidget })),
|
||||
{ ssr: false },
|
||||
);
|
||||
const ListWidget = dynamic(
|
||||
() => import("@/components/admin/dashboard/widgets/ListWidget").then((mod) => ({ default: mod.ListWidget })),
|
||||
{ ssr: false },
|
||||
);
|
||||
// const ListWidget = dynamic(
|
||||
// () => import("@/components/admin/dashboard/widgets/ListWidget").then((mod) => ({ default: mod.ListWidget })),
|
||||
// { ssr: false },
|
||||
// );
|
||||
|
||||
const YardManagement3DWidget = dynamic(() => import("@/components/admin/dashboard/widgets/YardManagement3DWidget"), {
|
||||
ssr: false,
|
||||
|
|
@ -68,9 +68,9 @@ const CustomStatsWidget = dynamic(() => import("./widgets/CustomStatsWidget"), {
|
|||
ssr: false,
|
||||
});
|
||||
|
||||
const CustomMetricWidget = dynamic(() => import("./widgets/CustomMetricWidget"), {
|
||||
ssr: false,
|
||||
});
|
||||
// const CustomMetricWidget = dynamic(() => import("./widgets/CustomMetricWidget"), {
|
||||
// ssr: false,
|
||||
// });
|
||||
|
||||
/**
|
||||
* 위젯 렌더링 함수 - DashboardSidebar의 모든 subtype 처리
|
||||
|
|
@ -91,10 +91,10 @@ function renderWidget(element: DashboardElement) {
|
|||
return <CalculatorWidget element={element} />;
|
||||
case "clock":
|
||||
return <ClockWidget element={element} />;
|
||||
case "map-summary":
|
||||
return <MapSummaryWidget element={element} />;
|
||||
case "map-test":
|
||||
return <MapTestWidget element={element} />;
|
||||
// case "map-summary":
|
||||
// return <MapSummaryWidget element={element} />;
|
||||
// case "map-test":
|
||||
// return <MapTestWidget element={element} />;
|
||||
case "map-summary-v2":
|
||||
return <MapTestWidgetV2 element={element} />;
|
||||
case "chart":
|
||||
|
|
@ -105,14 +105,14 @@ function renderWidget(element: DashboardElement) {
|
|||
return <CustomMetricTestWidget element={element} />;
|
||||
case "risk-alert-v2":
|
||||
return <RiskAlertTestWidget element={element} />;
|
||||
case "risk-alert":
|
||||
return <RiskAlertWidget element={element} />;
|
||||
// case "risk-alert":
|
||||
// return <RiskAlertWidget element={element} />;
|
||||
case "calendar":
|
||||
return <CalendarWidget element={element} />;
|
||||
case "status-summary":
|
||||
return <StatusSummaryWidget element={element} />;
|
||||
case "custom-metric":
|
||||
return <CustomMetricWidget element={element} />;
|
||||
// case "custom-metric":
|
||||
// return <CustomMetricWidget element={element} />;
|
||||
|
||||
// === 운영/작업 지원 ===
|
||||
case "todo":
|
||||
|
|
@ -122,8 +122,8 @@ function renderWidget(element: DashboardElement) {
|
|||
return <BookingAlertWidget element={element} />;
|
||||
case "document":
|
||||
return <DocumentWidget element={element} />;
|
||||
case "list":
|
||||
return <ListWidget element={element} />;
|
||||
// case "list":
|
||||
// return <ListWidget element={element} />;
|
||||
|
||||
case "yard-management-3d":
|
||||
// console.log("🏗️ 야드관리 위젯 렌더링:", {
|
||||
|
|
@ -171,7 +171,7 @@ function renderWidget(element: DashboardElement) {
|
|||
// === 기본 fallback ===
|
||||
default:
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-muted to-muted-foreground p-4 text-white">
|
||||
<div className="from-muted to-muted-foreground flex h-full w-full items-center justify-center bg-gradient-to-br p-4 text-white">
|
||||
<div className="text-center">
|
||||
<div className="mb-2 text-3xl">❓</div>
|
||||
<div className="text-sm">알 수 없는 위젯 타입: {element.subtype}</div>
|
||||
|
|
@ -212,7 +212,7 @@ export function DashboardViewer({
|
|||
dataUrl: string,
|
||||
format: "png" | "pdf",
|
||||
canvasWidth: number,
|
||||
canvasHeight: number
|
||||
canvasHeight: number,
|
||||
) => {
|
||||
if (format === "png") {
|
||||
console.log("💾 PNG 다운로드 시작...");
|
||||
|
|
@ -227,7 +227,7 @@ export function DashboardViewer({
|
|||
} else {
|
||||
console.log("📄 PDF 생성 중...");
|
||||
const jsPDF = (await import("jspdf")).default;
|
||||
|
||||
|
||||
// dataUrl에서 이미지 크기 계산
|
||||
const img = new Image();
|
||||
img.src = dataUrl;
|
||||
|
|
@ -274,40 +274,41 @@ export function DashboardViewer({
|
|||
|
||||
console.log("📸 html-to-image 로딩 중...");
|
||||
// html-to-image 동적 import
|
||||
// @ts-expect-error - html-to-image 타입 선언 누락
|
||||
const { toPng } = await import("html-to-image");
|
||||
|
||||
console.log("📸 캔버스 캡처 중...");
|
||||
|
||||
|
||||
// 3D/WebGL 렌더링 완료 대기
|
||||
console.log("⏳ 3D 렌더링 완료 대기 중...");
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
|
||||
// WebGL 캔버스를 이미지로 변환 (Three.js 캔버스 보존)
|
||||
console.log("🎨 WebGL 캔버스 처리 중...");
|
||||
const webglCanvases = canvas.querySelectorAll("canvas");
|
||||
const webglImages: { canvas: HTMLCanvasElement; dataUrl: string; rect: DOMRect }[] = [];
|
||||
|
||||
|
||||
webglCanvases.forEach((webglCanvas) => {
|
||||
try {
|
||||
const rect = webglCanvas.getBoundingClientRect();
|
||||
const dataUrl = webglCanvas.toDataURL("image/png");
|
||||
webglImages.push({ canvas: webglCanvas, dataUrl, rect });
|
||||
console.log("✅ WebGL 캔버스 캡처:", {
|
||||
width: rect.width,
|
||||
console.log("✅ WebGL 캔버스 캡처:", {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
bottom: rect.bottom
|
||||
bottom: rect.bottom,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("⚠️ WebGL 캔버스 캡처 실패:", error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 캔버스의 실제 크기와 위치 가져오기
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const canvasWidth = canvas.scrollWidth;
|
||||
|
||||
|
||||
// 실제 콘텐츠의 최하단 위치 계산
|
||||
// 뷰어 모드에서는 모든 자식 요소를 확인
|
||||
const children = canvas.querySelectorAll("*");
|
||||
|
|
@ -323,17 +324,17 @@ export function DashboardViewer({
|
|||
maxBottom = relativeBottom;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 실제 콘텐츠 높이 + 여유 공간 (50px)
|
||||
// maxBottom이 0이면 기본 캔버스 높이 사용
|
||||
const canvasHeight = maxBottom > 50 ? maxBottom + 50 : Math.max(canvas.scrollHeight, rect.height);
|
||||
|
||||
|
||||
console.log("📐 캔버스 정보:", {
|
||||
rect: { x: rect.x, y: rect.y, left: rect.left, top: rect.top, width: rect.width, height: rect.height },
|
||||
scroll: { width: canvasWidth, height: canvas.scrollHeight },
|
||||
calculated: { width: canvasWidth, height: canvasHeight },
|
||||
maxBottom: maxBottom,
|
||||
webglCount: webglImages.length
|
||||
webglCount: webglImages.length,
|
||||
});
|
||||
|
||||
// html-to-image로 캔버스 캡처 (WebGL 제외)
|
||||
|
|
@ -344,8 +345,8 @@ export function DashboardViewer({
|
|||
pixelRatio: 2, // 고해상도
|
||||
cacheBust: true,
|
||||
skipFonts: false,
|
||||
preferredFontFormat: 'woff2',
|
||||
filter: (node) => {
|
||||
preferredFontFormat: "woff2",
|
||||
filter: (node: Node) => {
|
||||
// WebGL 캔버스는 제외 (나중에 수동으로 합성)
|
||||
if (node instanceof HTMLCanvasElement) {
|
||||
return false;
|
||||
|
|
@ -353,7 +354,7 @@ export function DashboardViewer({
|
|||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// WebGL 캔버스를 이미지 위에 합성
|
||||
if (webglImages.length > 0) {
|
||||
console.log("🖼️ WebGL 이미지 합성 중...");
|
||||
|
|
@ -362,17 +363,17 @@ export function DashboardViewer({
|
|||
await new Promise((resolve) => {
|
||||
img.onload = resolve;
|
||||
});
|
||||
|
||||
|
||||
// 새 캔버스에 합성
|
||||
const compositeCanvas = document.createElement("canvas");
|
||||
compositeCanvas.width = img.width;
|
||||
compositeCanvas.height = img.height;
|
||||
const ctx = compositeCanvas.getContext("2d");
|
||||
|
||||
|
||||
if (ctx) {
|
||||
// 기본 이미지 그리기
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
|
||||
// WebGL 이미지들을 위치에 맞게 그리기
|
||||
for (const { dataUrl: webglDataUrl, rect: webglRect } of webglImages) {
|
||||
const webglImg = new Image();
|
||||
|
|
@ -380,28 +381,28 @@ export function DashboardViewer({
|
|||
await new Promise((resolve) => {
|
||||
webglImg.onload = resolve;
|
||||
});
|
||||
|
||||
|
||||
// 상대 위치 계산 (pixelRatio 2 고려)
|
||||
const relativeX = (webglRect.left - rect.left) * 2;
|
||||
const relativeY = (webglRect.top - rect.top) * 2;
|
||||
const width = webglRect.width * 2;
|
||||
const height = webglRect.height * 2;
|
||||
|
||||
|
||||
ctx.drawImage(webglImg, relativeX, relativeY, width, height);
|
||||
console.log("✅ WebGL 이미지 합성 완료:", { x: relativeX, y: relativeY, width, height });
|
||||
}
|
||||
|
||||
|
||||
// 합성된 이미지를 dataUrl로 변환
|
||||
const compositeDataUrl = compositeCanvas.toDataURL("image/png");
|
||||
console.log("✅ 최종 합성 완료");
|
||||
|
||||
|
||||
// 합성된 이미지로 다운로드
|
||||
return await handleDownloadWithDataUrl(compositeDataUrl, format, canvasWidth, canvasHeight);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("✅ 캡처 완료 (WebGL 없음)");
|
||||
|
||||
|
||||
// WebGL이 없는 경우 기본 다운로드
|
||||
await handleDownloadWithDataUrl(dataUrl, format, canvasWidth, canvasHeight);
|
||||
} catch (error) {
|
||||
|
|
@ -409,7 +410,8 @@ export function DashboardViewer({
|
|||
alert(`다운로드에 실패했습니다.\n\n에러: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
},
|
||||
[backgroundColor, dashboardTitle],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[backgroundColor, dashboardTitle, handleDownloadWithDataUrl],
|
||||
);
|
||||
|
||||
// 캔버스 설정 계산
|
||||
|
|
@ -528,11 +530,11 @@ export function DashboardViewer({
|
|||
// 요소가 없는 경우
|
||||
if (elements.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center bg-muted">
|
||||
<div className="bg-muted flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 text-6xl">📊</div>
|
||||
<div className="mb-2 text-xl font-medium text-foreground">표시할 요소가 없습니다</div>
|
||||
<div className="text-sm text-muted-foreground">대시보드 편집기에서 차트나 위젯을 추가해보세요</div>
|
||||
<div className="text-foreground mb-2 text-xl font-medium">표시할 요소가 없습니다</div>
|
||||
<div className="text-muted-foreground text-sm">대시보드 편집기에서 차트나 위젯을 추가해보세요</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -541,8 +543,8 @@ export function DashboardViewer({
|
|||
return (
|
||||
<DashboardProvider>
|
||||
{/* 데스크톱: 디자이너에서 설정한 위치 그대로 렌더링 (화면에 맞춰 비율 유지) */}
|
||||
<div className="hidden min-h-screen bg-muted py-8 lg:block" style={{ backgroundColor }}>
|
||||
<div className="mx-auto px-4" style={{ width: '100%', maxWidth: 'none' }}>
|
||||
<div className="bg-muted hidden min-h-screen py-8 lg:block" style={{ backgroundColor }}>
|
||||
<div className="mx-auto px-4" style={{ width: "100%", maxWidth: "none" }}>
|
||||
{/* 다운로드 버튼 */}
|
||||
<div className="mb-4 flex justify-end">
|
||||
<DropdownMenu>
|
||||
|
|
@ -584,7 +586,7 @@ export function DashboardViewer({
|
|||
</div>
|
||||
|
||||
{/* 태블릿 이하: 반응형 세로 정렬 */}
|
||||
<div className="block min-h-screen bg-muted p-4 lg:hidden" style={{ backgroundColor }}>
|
||||
<div className="bg-muted block min-h-screen p-4 lg:hidden" style={{ backgroundColor }}>
|
||||
<div className="mx-auto max-w-3xl space-y-4">
|
||||
{/* 다운로드 버튼 */}
|
||||
<div className="flex justify-end">
|
||||
|
|
@ -646,16 +648,16 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
|
|||
// 태블릿 이하: 세로 스택 카드 스타일
|
||||
return (
|
||||
<div
|
||||
className="relative overflow-hidden rounded-lg border border-border bg-background shadow-sm"
|
||||
className="border-border bg-background relative overflow-hidden rounded-lg border shadow-sm"
|
||||
style={{ minHeight: "300px" }}
|
||||
>
|
||||
{element.showHeader !== false && (
|
||||
<div className="flex items-center justify-between px-2 py-1">
|
||||
<h3 className="text-xs font-semibold text-foreground">{element.customTitle || element.title}</h3>
|
||||
<h3 className="text-foreground text-xs font-semibold">{element.customTitle || element.title}</h3>
|
||||
<button
|
||||
onClick={onRefresh}
|
||||
disabled={isLoading}
|
||||
className="text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50"
|
||||
title="새로고침"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -677,7 +679,7 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
|
|||
<div className={element.showHeader !== false ? "p-2" : "p-2"} style={{ minHeight: "250px" }}>
|
||||
{!isMounted ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||
<div className="border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
|
||||
</div>
|
||||
) : element.type === "chart" ? (
|
||||
<ChartRenderer element={element} data={data} width={undefined} height={250} />
|
||||
|
|
@ -686,10 +688,10 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
|
|||
)}
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className="bg-opacity-75 absolute inset-0 flex items-center justify-center bg-background">
|
||||
<div className="bg-opacity-75 bg-background absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||
<div className="text-sm text-foreground">업데이트 중...</div>
|
||||
<div className="border-primary mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
|
||||
<div className="text-foreground text-sm">업데이트 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -704,7 +706,7 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
|
|||
|
||||
return (
|
||||
<div
|
||||
className="absolute overflow-hidden rounded-lg border border-border bg-background shadow-sm"
|
||||
className="border-border bg-background absolute overflow-hidden rounded-lg border shadow-sm"
|
||||
style={{
|
||||
left: `${leftPercentage}%`,
|
||||
top: element.position.y,
|
||||
|
|
@ -714,11 +716,11 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
|
|||
>
|
||||
{element.showHeader !== false && (
|
||||
<div className="flex items-center justify-between px-2 py-1">
|
||||
<h3 className="text-xs font-semibold text-foreground">{element.customTitle || element.title}</h3>
|
||||
<h3 className="text-foreground text-xs font-semibold">{element.customTitle || element.title}</h3>
|
||||
<button
|
||||
onClick={onRefresh}
|
||||
disabled={isLoading}
|
||||
className="text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50"
|
||||
title="새로고침"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -740,7 +742,7 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
|
|||
<div className={element.showHeader !== false ? "h-[calc(100%-32px)] w-full" : "h-full w-full"}>
|
||||
{!isMounted ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||
<div className="border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
|
||||
</div>
|
||||
) : element.type === "chart" ? (
|
||||
<ChartRenderer
|
||||
|
|
@ -754,10 +756,10 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
|
|||
)}
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className="bg-opacity-75 absolute inset-0 flex items-center justify-center bg-background">
|
||||
<div className="bg-opacity-75 bg-background absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||
<div className="text-sm text-foreground">업데이트 중...</div>
|
||||
<div className="border-primary mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
|
||||
<div className="text-foreground text-sm">업데이트 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ 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[]>([]);
|
||||
|
|
@ -73,9 +77,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const [geoJsonData, setGeoJsonData] = useState<any>(null);
|
||||
const [lastRefreshTime, setLastRefreshTime] = useState<Date | null>(null);
|
||||
|
||||
// // console.log("🧪 MapTestWidgetV2 렌더링!", element);
|
||||
// // console.log("📍 마커:", markers.length, "🔷 폴리곤:", polygons.length);
|
||||
|
||||
// dataSources를 useMemo로 추출 (circular reference 방지)
|
||||
const dataSources = useMemo(() => {
|
||||
return element?.dataSources || element?.chartConfig?.dataSources;
|
||||
|
|
@ -908,36 +909,37 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
loadGeoJsonData();
|
||||
}, []);
|
||||
|
||||
// 초기 로드 및 자동 새로고침
|
||||
// 초기 로드 및 자동 새로고침 (마커 데이터만 polling)
|
||||
useEffect(() => {
|
||||
console.log("🔄 지도 위젯 초기화");
|
||||
console.log("🔄 지도 위젯 초기화 useEffect 실행됨!");
|
||||
console.log("📊 dataSources 상태:", dataSources);
|
||||
|
||||
if (!dataSources || dataSources.length === 0) {
|
||||
console.log("⚠️ dataSources가 없거나 비어있음");
|
||||
console.log("⚠️ dataSources가 없거나 비어있음 - polling 시작 안함");
|
||||
setMarkers([]);
|
||||
setPolygons([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 즉시 첫 로드
|
||||
console.log("📡 초기 데이터 로드");
|
||||
// 즉시 첫 로드 (마커 데이터)
|
||||
console.log("📡 초기 마커 데이터 로드");
|
||||
loadMultipleDataSources();
|
||||
|
||||
// 5초마다 자동 새로고침
|
||||
// 5초마다 마커 데이터만 자동 새로고침 (지도 타일은 안 건드림)
|
||||
const refreshInterval = 5;
|
||||
console.log(`⏱️ 자동 새로고침 설정: ${refreshInterval}초마다`);
|
||||
console.log(`⏱️ 마커 polling 시작: ${refreshInterval}초마다`);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
console.log("🔄 자동 새로고침 실행");
|
||||
console.log("🔄 마커 자동 새로고침 실행");
|
||||
loadMultipleDataSources();
|
||||
}, refreshInterval * 1000);
|
||||
|
||||
return () => {
|
||||
console.log("⏹️ 자동 새로고침 정리");
|
||||
console.log("⏹️ 마커 polling 정리");
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [dataSources]);
|
||||
|
||||
// 타일맵 URL (chartConfig에서 가져오기)
|
||||
const tileMapUrl =
|
||||
|
|
@ -986,10 +988,6 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-destructive text-sm">{error}</p>
|
||||
</div>
|
||||
) : !element?.dataSources || element.dataSources.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-muted-foreground text-sm">데이터 소스를 연결해주세요</p>
|
||||
</div>
|
||||
) : (
|
||||
<MapContainer center={center} zoom={13} style={{ width: "100%", height: "100%" }} className="z-0">
|
||||
<TileLayer url={tileMapUrl} attribution="© VWorld" maxZoom={19} />
|
||||
|
|
@ -1244,6 +1242,23 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
</Marker>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 데이터 소스 없을 때 안내 메시지 */}
|
||||
{(!element?.dataSources || element.dataSources.length === 0) && (
|
||||
<div
|
||||
className="pointer-events-none absolute left-1/2 top-1/2 z-[1000] -translate-x-1/2 -translate-y-1/2"
|
||||
style={{ zIndex: 1000 }}
|
||||
>
|
||||
<div className="rounded-lg border-2 border-dashed border-primary bg-background/95 p-4 shadow-lg backdrop-blur-sm">
|
||||
<p className="text-center text-sm font-medium">
|
||||
📍 지도가 표시되었습니다
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1 text-center text-xs">
|
||||
데이터 소스를 연결하면 마커가 표시됩니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</MapContainer>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue