Compare commits

...

2 Commits

Author SHA1 Message Date
leeheejin 1995adf245 중간저장 2025-10-15 17:14:42 +09:00
leeheejin 0b5b140625 대시보드 위젯 렌더링 수정 및 외부 API 키 통합
- DashboardViewer에 ListSummaryWidget 연결
- list 위젯이 실제 DB 데이터 표시하도록 수정
- ITS_API_KEY (국토교통부 교통사고 API) 추가
- KMA_API_KEY (기상청 특보 API) 재적용
- dashboard.ts API URL 수정 (/api로 통일)
2025-10-15 17:02:06 +09:00
4 changed files with 137 additions and 31 deletions

View File

@ -300,23 +300,31 @@ export class DashboardService {
const elementsResult = await PostgreSQLService.query(elementsQuery, [dashboardId]);
// 3. 요소 데이터 변환
const elements: DashboardElement[] = elementsResult.rows.map((row: any) => ({
id: row.id,
type: row.element_type,
subtype: row.element_subtype,
position: {
x: row.position_x,
y: row.position_y
},
size: {
width: row.width,
height: row.height
},
title: row.title,
content: row.content,
dataSource: JSON.parse(row.data_source_config || '{}'),
chartConfig: JSON.parse(row.chart_config || '{}')
}));
console.log('📊 대시보드 요소 개수:', elementsResult.rows.length);
const elements: DashboardElement[] = elementsResult.rows.map((row: any, index: number) => {
const element = {
id: row.id,
type: row.element_type,
subtype: row.element_subtype,
position: {
x: row.position_x,
y: row.position_y
},
size: {
width: row.width,
height: row.height
},
title: row.title,
content: row.content,
dataSource: JSON.parse(row.data_source_config || '{}'),
chartConfig: JSON.parse(row.chart_config || '{}')
};
console.log(`📊 위젯 #${index + 1}: type="${element.type}", subtype="${element.subtype}", title="${element.title}"`);
return element;
});
return {
id: dashboard.id,

View File

@ -20,7 +20,7 @@ services:
- LOG_LEVEL=debug
- ENCRYPTION_KEY=ilshin-plm-mail-encryption-key-32characters-2024-secure
- KMA_API_KEY=ogdXr2e9T4iHV69nvV-IwA
- ITS_API_KEY=${ITS_API_KEY:-}
- ITS_API_KEY=d6b9befec3114d648284674b8fddcc32
- EXPRESSWAY_API_KEY=${EXPRESSWAY_API_KEY:-}
volumes:
- ../../backend-node:/app # 개발 모드: 코드 변경 시 자동 반영

View File

@ -3,6 +3,115 @@
import React, { useState, useEffect, useCallback } from "react";
import { DashboardElement, QueryResult } from "@/components/admin/dashboard/types";
import { ChartRenderer } from "@/components/admin/dashboard/charts/ChartRenderer";
import dynamic from "next/dynamic";
// 위젯 동적 import - 모든 위젯
const ListSummaryWidget = dynamic(() => import("./widgets/ListSummaryWidget"), { ssr: false });
const MapSummaryWidget = dynamic(() => import("./widgets/MapSummaryWidget"), { ssr: false });
const StatusSummaryWidget = dynamic(() => import("./widgets/StatusSummaryWidget"), { ssr: false });
const RiskAlertWidget = dynamic(() => import("./widgets/RiskAlertWidget"), { ssr: false });
const WeatherWidget = dynamic(() => import("./widgets/WeatherWidget"), { ssr: false });
const ExchangeWidget = dynamic(() => import("./widgets/ExchangeWidget"), { ssr: false });
const VehicleStatusWidget = dynamic(() => import("./widgets/VehicleStatusWidget"), { ssr: false });
const VehicleListWidget = dynamic(() => import("./widgets/VehicleListWidget"), { ssr: false });
const VehicleMapOnlyWidget = dynamic(() => import("./widgets/VehicleMapOnlyWidget"), { ssr: false });
const CargoListWidget = dynamic(() => import("./widgets/CargoListWidget"), { ssr: false });
const CustomerIssuesWidget = dynamic(() => import("./widgets/CustomerIssuesWidget"), { ssr: false });
const DeliveryStatusWidget = dynamic(() => import("./widgets/DeliveryStatusWidget"), { ssr: false });
const DeliveryStatusSummaryWidget = dynamic(() => import("./widgets/DeliveryStatusSummaryWidget"), { ssr: false });
const DeliveryTodayStatsWidget = dynamic(() => import("./widgets/DeliveryTodayStatsWidget"), { ssr: false });
const TodoWidget = dynamic(() => import("./widgets/TodoWidget"), { ssr: false });
const DocumentWidget = dynamic(() => import("./widgets/DocumentWidget"), { ssr: false });
const BookingAlertWidget = dynamic(() => import("./widgets/BookingAlertWidget"), { ssr: false });
const MaintenanceWidget = dynamic(() => import("./widgets/MaintenanceWidget"), { ssr: false });
const CalculatorWidget = dynamic(() => import("./widgets/CalculatorWidget"), { ssr: false });
/**
* - DashboardSidebar의 subtype
* ViewerElement에서
*/
function renderWidget(element: DashboardElement) {
switch (element.subtype) {
// 차트는 ChartRenderer에서 처리됨 (이 함수 호출 안됨)
// === 위젯 종류 ===
case "exchange":
return <ExchangeWidget />;
case "weather":
return <WeatherWidget />;
case "calculator":
return <CalculatorWidget />;
case "clock":
return (
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-blue-400 to-purple-600 p-4 text-white">
<div className="text-center">
<div className="mb-2 text-3xl"></div>
<div className="text-sm"> ( )</div>
</div>
</div>
);
case "map-summary":
return <MapSummaryWidget element={element} />;
case "list-summary":
return <ListSummaryWidget element={element} />;
case "risk-alert":
return <RiskAlertWidget />;
case "calendar":
return (
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-green-400 to-teal-600 p-4 text-white">
<div className="text-center">
<div className="mb-2 text-3xl">📅</div>
<div className="text-sm"> ( )</div>
</div>
</div>
);
case "status-summary":
return <StatusSummaryWidget element={element} />;
// === 운영/작업 지원 ===
case "todo":
return <TodoWidget />;
case "booking-alert":
return <BookingAlertWidget />;
case "maintenance":
return <MaintenanceWidget />;
case "document":
return <DocumentWidget />;
case "list":
return <ListSummaryWidget element={element} />;
// === 차량 관련 (추가 위젯) ===
case "vehicle-status":
return <VehicleStatusWidget />;
case "vehicle-list":
return <VehicleListWidget />;
case "vehicle-map":
return <VehicleMapOnlyWidget element={element} />;
// === 배송 관련 (추가 위젯) ===
case "delivery-status":
return <DeliveryStatusWidget />;
case "delivery-status-summary":
return <DeliveryStatusSummaryWidget />;
case "delivery-today-stats":
return <DeliveryTodayStatsWidget />;
case "cargo-list":
return <CargoListWidget />;
case "customer-issues":
return <CustomerIssuesWidget />;
// === 기본 fallback ===
default:
return (
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-gray-400 to-gray-600 p-4 text-white">
<div className="text-center">
<div className="mb-2 text-3xl"></div>
<div className="text-sm"> : {element.subtype}</div>
</div>
</div>
);
}
}
interface DashboardViewerProps {
elements: DashboardElement[];
@ -198,18 +307,7 @@ function ViewerElement({ element, data, isLoading, onRefresh }: ViewerElementPro
<div className="h-[calc(100%-57px)]">
{element.type === "chart" ? (
<ChartRenderer element={element} data={data} width={element.size.width} height={element.size.height - 57} />
) : (
// 위젯 렌더링
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-blue-400 to-purple-600 p-4 text-white">
<div className="text-center">
<div className="mb-2 text-3xl">
{element.subtype === "exchange" && "💱"}
{element.subtype === "weather" && "☁️"}
</div>
<div className="text-sm whitespace-pre-line">{element.content}</div>
</div>
</div>
)}
) : renderWidget(element)}
</div>
{/* 로딩 오버레이 */}

View File

@ -5,7 +5,7 @@
import { DashboardElement } from "@/components/admin/dashboard/types";
// API 기본 설정
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api";
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "/api";
// 토큰 가져오기 (실제 인증 시스템에 맞게 수정)
function getAuthToken(): string | null {