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="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-destructive">
<p className="text-sm"> {error}</p>
<button
onClick={loadData}
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
</div>
</div>
);
}
if (!element?.dataSource?.query) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-muted-foreground">
<p className="text-sm"> </p>
</div>
</div>
);
}
return (
<div className="flex h-full flex-col overflow-hidden bg-background p-4">
{/* 헤더 */}
<div className="mb-4 flex items-center justify-between">
<h3 className="text-lg font-semibold text-foreground"> </h3>
<button onClick={loadData} className="rounded-full p-1 text-muted-foreground hover:bg-muted" title="새로고침">
🔄
</button>
</div>
{/* 통계 카드 */}
<div className="flex flex-1 flex-col gap-4">
{/* 오늘 발송 */}
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-primary/10 to-primary/20 p-6">
<div className="mb-2 text-4xl">📤</div>
<p className="text-sm font-medium text-primary"> </p>
<p className="mt-2 text-4xl font-bold text-primary">{todayStats.shipped.toLocaleString()}</p>
<p className="mt-1 text-xs text-primary"></p>
</div>
{/* 오늘 도착 */}
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-success/10 to-success/20 p-6">
<div className="mb-2 text-4xl">📥</div>
<p className="text-sm font-medium text-success"> </p>
<p className="mt-2 text-4xl font-bold text-success">{todayStats.delivered.toLocaleString()}</p>
<p className="mt-1 text-xs text-success"></p>
</div>
</div>
</div>
);
}