ERP-node/frontend/components/dashboard/widgets/DeliveryTodayStatsWidget.tsx

161 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useState, useEffect } from "react";
import { DashboardElement } from "@/components/admin/dashboard/types";
import { getApiUrl } from "@/lib/utils/apiUrl";
interface DeliveryTodayStatsWidgetProps {
element: DashboardElement;
}
interface TodayStats {
shipped: number;
delivered: number;
}
/**
* 오늘 처리 현황 위젯
* - 오늘 발송 건수
* - 오늘 도착 건수
*/
export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStatsWidgetProps) {
const [todayStats, setTodayStats] = useState<TodayStats>({ shipped: 0, delivered: 0 });
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadData();
// 자동 새로고침 (30초마다)
const interval = setInterval(loadData, 30000);
return () => clearInterval(interval);
}, [element]);
const loadData = async () => {
if (!element?.dataSource?.query) {
setError("쿼리가 설정되지 않았습니다");
setLoading(false);
return;
}
try {
setLoading(true);
const token = localStorage.getItem("authToken");
const response = await fetch(getApiUrl("/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) {
const rows = result.data.rows;
const today = new Date().toISOString().split("T")[0];
// 오늘 발송 건수 (created_at 기준)
const shippedToday = rows.filter((row: any) => {
const createdDate = row.created_at?.split("T")[0] || row.createdAt?.split("T")[0];
return createdDate === today;
}).length;
// 오늘 도착 건수 (status === 'delivered' AND estimated_delivery 기준)
const deliveredToday = rows.filter((row: any) => {
const status = row.status || "";
const deliveryDate = row.estimated_delivery?.split("T")[0] || row.estimatedDelivery?.split("T")[0];
return (status === "delivered" || status === "완료") && deliveryDate === today;
}).length;
setTodayStats({
shipped: shippedToday,
delivered: deliveredToday,
});
}
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : "데이터 로딩 실패");
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="text-muted-foreground mt-2 text-sm"> ...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-destructive text-center">
<p className="text-sm"> {error}</p>
<button
onClick={loadData}
className="bg-destructive/10 text-destructive hover:bg-destructive/20 mt-2 rounded px-3 py-1 text-xs"
>
</button>
</div>
</div>
);
}
if (!element?.dataSource?.query) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-muted-foreground text-center">
<p className="text-sm"> </p>
</div>
</div>
);
}
return (
<div className="bg-background flex h-full flex-col overflow-hidden p-4">
{/* 헤더 */}
<div className="mb-4 flex items-center justify-between">
<h3 className="text-foreground text-lg font-semibold"> </h3>
<button onClick={loadData} className="text-muted-foreground hover:bg-muted rounded-full p-1" title="새로고침">
🔄
</button>
</div>
{/* 통계 카드 */}
<div className="flex flex-1 flex-col gap-4">
{/* 오늘 발송 */}
<div className="from-primary/10 to-primary/20 flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br p-6">
<div className="mb-2 text-4xl">📤</div>
<p className="text-primary text-sm font-medium"> </p>
<p className="text-primary mt-2 text-4xl font-bold">{todayStats.shipped.toLocaleString()}</p>
<p className="text-primary mt-1 text-xs"></p>
</div>
{/* 오늘 도착 */}
<div className="from-success/10 to-success/20 flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br p-6">
<div className="mb-2 text-4xl">📥</div>
<p className="text-success text-sm font-medium"> </p>
<p className="text-success mt-2 text-4xl font-bold">{todayStats.delivered.toLocaleString()}</p>
<p className="text-success mt-1 text-xs"></p>
</div>
</div>
</div>
);
}