/** * 운송 통계 위젯 * - 총 운송량 (톤) * - 누적 거리 (km) * - 정시 도착률 (%) * - 쿼리 결과 기반 통계 계산 */ "use client"; import { useState, useEffect } from "react"; import { DashboardElement } from "@/components/admin/dashboard/types"; interface TransportStatsWidgetProps { element?: DashboardElement; refreshInterval?: number; } interface StatsData { total_count: number; total_weight: number; total_distance: number; on_time_rate: number; } export default function TransportStatsWidget({ element, refreshInterval = 60000 }: TransportStatsWidgetProps) { const [stats, setStats] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // 데이터 로드 const loadData = async () => { try { setIsLoading(true); setError(null); // 쿼리가 설정되어 있지 않으면 안내 메시지만 표시 if (!element?.dataSource?.query) { setError("쿼리를 설정해주세요"); setIsLoading(false); return; } // 쿼리 실행하여 통계 계산 const token = localStorage.getItem("authToken"); const response = await fetch("/api/dashboards/execute-query", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query: element.dataSource.query, connectionType: element.dataSource.connectionType || "current", connectionId: element.dataSource.connectionId, }), }); if (!response.ok) throw new Error("데이터 로딩 실패"); const result = await response.json(); if (!result.success || !result.data?.rows) { throw new Error(result.message || "데이터 로드 실패"); } const data = result.data.rows || []; if (data.length === 0) { setStats({ total_count: 0, total_weight: 0, total_distance: 0, on_time_rate: 0 }); return; } // 자동으로 숫자 컬럼 감지 및 합계 계산 const firstRow = data[0]; const numericColumns: { [key: string]: number } = {}; // 모든 컬럼을 순회하며 숫자 컬럼 찾기 Object.keys(firstRow).forEach((key) => { const value = firstRow[key]; // 숫자로 변환 가능한 컬럼만 선택 if (value !== null && !isNaN(parseFloat(value))) { numericColumns[key] = data.reduce((sum: number, item: any) => { return sum + (parseFloat(item[key]) || 0); }, 0); } }); // 특정 키워드를 포함한 컬럼 자동 매핑 const weightKeys = ["weight", "cargo_weight", "total_weight", "중량", "무게"]; const distanceKeys = ["distance", "total_distance", "거리", "주행거리"]; const onTimeKeys = ["is_on_time", "on_time", "onTime", "정시", "정시도착"]; // 총 운송량 찾기 let total_weight = 0; for (const key of Object.keys(numericColumns)) { if (weightKeys.some((keyword) => key.toLowerCase().includes(keyword.toLowerCase()))) { total_weight = numericColumns[key]; break; } } // 누적 거리 찾기 let total_distance = 0; for (const key of Object.keys(numericColumns)) { if (distanceKeys.some((keyword) => key.toLowerCase().includes(keyword.toLowerCase()))) { total_distance = numericColumns[key]; break; } } // 정시 도착률 계산 let on_time_rate = 0; for (const key of Object.keys(firstRow)) { if (onTimeKeys.some((keyword) => key.toLowerCase().includes(keyword.toLowerCase()))) { const onTimeItems = data.filter((item: any) => { const onTime = item[key]; return onTime !== null && onTime !== undefined; }); if (onTimeItems.length > 0) { const onTimeCount = onTimeItems.filter((item: any) => { const onTime = item[key]; return onTime === true || onTime === "true" || onTime === 1 || onTime === "1"; }).length; on_time_rate = (onTimeCount / onTimeItems.length) * 100; } break; } } const calculatedStats: StatsData = { total_count: data.length, // 총 건수 total_weight, total_distance, on_time_rate, }; setStats(calculatedStats); } catch (err) { console.error("통계 로드 실패:", err); setError(err instanceof Error ? err.message : "데이터를 불러올 수 없습니다"); } finally { setIsLoading(false); } }; useEffect(() => { loadData(); const interval = setInterval(loadData, refreshInterval); return () => clearInterval(interval); }, [refreshInterval, element?.dataSource]); if (isLoading && !stats) { return (
로딩 중...
); } if (error || !stats) { return (
⚠️
{error || "데이터 없음"}
{!element?.dataSource?.query && (
톱니바퀴 아이콘을 클릭하여 쿼리를 설정하세요
)}
); } return (
{/* 총 건수 */}
총 건수
{stats.total_count.toLocaleString()}
{/* 총 운송량 */}
총 운송량
{stats.total_weight.toFixed(1)}
{/* 누적 거리 */}
누적 거리
{stats.total_distance.toFixed(1)} km
{/* 정시 도착률 */}
정시 도착률
{stats.on_time_rate.toFixed(1)} %
); }