차량위치 위젯 기존꺼 분할 완료

This commit is contained in:
leeheejin
2025-10-15 10:29:15 +09:00
parent 9599d34ba9
commit 36aec28708
21 changed files with 2346 additions and 640 deletions

View File

@@ -27,40 +27,62 @@ interface CustomerIssue {
}
interface DeliveryStatusWidgetProps {
element?: any; // 대시보드 요소 (dataSource 포함)
refreshInterval?: number;
}
export default function DeliveryStatusWidget({ refreshInterval = 60000 }: DeliveryStatusWidgetProps) {
export default function DeliveryStatusWidget({ element, refreshInterval = 60000 }: DeliveryStatusWidgetProps) {
const [deliveries, setDeliveries] = useState<DeliveryItem[]>([]);
const [issues, setIssues] = useState<CustomerIssue[]>([]);
const [todayStats, setTodayStats] = useState({
shipped: 0,
delivered: 0,
});
const [isLoading, setIsLoading] = useState(false);
const [lastUpdate, setLastUpdate] = useState<Date>(new Date());
const [selectedStatus, setSelectedStatus] = useState<string>("all"); // 필터 상태 추가
const loadData = async () => {
setIsLoading(true);
// TODO: 실제 API 연동 시 아래 주석 해제
// try {
// const response = await fetch('/api/delivery/status', {
// headers: {
// 'Authorization': `Bearer ${localStorage.getItem('authToken')}`,
// },
// });
// const data = await response.json();
// setDeliveries(data.deliveries);
// setIssues(data.issues);
// setTodayStats(data.todayStats);
// setLastUpdate(new Date());
// } catch (error) {
// console.error('배송 데이터 로드 실패:', error);
// } finally {
// setIsLoading(false);
// }
// 설정된 쿼리가 없으면 로딩 중단 (더미 데이터 사용 안 함)
if (!element?.dataSource?.query) {
setIsLoading(false);
setDeliveries([]);
setIssues([]);
return;
}
try {
const response = await fetch("/api/dashboards/execute-query", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${typeof window !== "undefined" ? localStorage.getItem("authToken") || "" : ""}`,
},
body: JSON.stringify({ query: element.dataSource.query }),
});
if (response.ok) {
const result = await response.json();
if (result.success && result.data.rows.length > 0) {
// TODO: DB 데이터를 DeliveryItem 형식으로 변환
setDeliveries(result.data.rows);
setLastUpdate(new Date());
}
}
} catch (error) {
console.error("배송 데이터 로드 실패:", error);
}
setIsLoading(false);
};
// 데이터 로드 및 자동 새로고침
useEffect(() => {
loadData();
const interval = setInterval(loadData, refreshInterval);
return () => clearInterval(interval);
}, [element?.dataSource?.query, refreshInterval]);
// 더미 데이터 완전히 제거 (아래 코드 삭제)
/*
// 가상 배송 데이터 (개발용 - 실제 DB 연동 시 삭제)
const dummyDeliveries: DeliveryItem[] = [
{
@@ -148,23 +170,7 @@ export default function DeliveryStatusWidget({ refreshInterval = 60000 }: Delive
},
];
setTimeout(() => {
setDeliveries(dummyDeliveries);
setIssues(dummyIssues);
setTodayStats({
shipped: 24,
delivered: 18,
});
setLastUpdate(new Date());
setIsLoading(false);
}, 500);
};
useEffect(() => {
loadData();
const interval = setInterval(loadData, refreshInterval);
return () => clearInterval(interval);
}, [refreshInterval]);
*/
const getStatusColor = (status: DeliveryItem["status"]) => {
switch (status) {
@@ -259,7 +265,32 @@ export default function DeliveryStatusWidget({ refreshInterval = 60000 }: Delive
pickup_waiting: deliveries.filter((d) => d.status === "pickup_waiting").length,
};
const delayedDeliveries = deliveries.filter((d) => d.status === "delayed");
// 필터링된 배송 목록
const filteredDeliveries = selectedStatus === "all"
? deliveries
: deliveries.filter((d) => d.status === selectedStatus);
// 오늘 통계 계산
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayStats = {
// 오늘 발송 건수 (created_at이 오늘인 것)
shipped: deliveries.filter((d: any) => {
if (!d.created_at) return false;
const createdDate = new Date(d.created_at);
createdDate.setHours(0, 0, 0, 0);
return createdDate.getTime() === today.getTime();
}).length,
// 오늘 도착 건수 (status가 delivered이고 estimated_delivery가 오늘인 것)
delivered: deliveries.filter((d: any) => {
if (d.status !== "delivered" && d.status !== "delivered") return false;
if (!d.estimated_delivery && !d.estimatedDelivery) return false;
const deliveredDate = new Date(d.estimated_delivery || d.estimatedDelivery);
deliveredDate.setHours(0, 0, 0, 0);
return deliveredDate.getTime() === today.getTime();
}).length,
};
return (
<div className="h-full w-full bg-gradient-to-br from-slate-50 to-blue-50 p-4 overflow-auto">
@@ -284,24 +315,63 @@ export default function DeliveryStatusWidget({ refreshInterval = 60000 }: Delive
{/* 배송 상태 요약 */}
<div className="mb-3">
<h4 className="mb-2 text-sm font-semibold text-gray-700"> </h4>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
<div className="rounded-lg bg-white p-1.5 shadow-sm border-l-4 border-blue-500">
<h4 className="mb-2 text-sm font-semibold text-gray-700"> ( )</h4>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
<button
onClick={() => setSelectedStatus("all")}
className={`rounded-lg p-1.5 shadow-sm border-l-4 transition-all ${
selectedStatus === "all"
? "border-gray-900 bg-gray-100 ring-2 ring-gray-900"
: "border-gray-500 bg-white hover:bg-gray-50"
}`}
>
<div className="text-xs text-gray-600 mb-0.5"></div>
<div className="text-lg font-bold text-gray-900">{deliveries.length}</div>
</button>
<button
onClick={() => setSelectedStatus("in_transit")}
className={`rounded-lg p-1.5 shadow-sm border-l-4 transition-all ${
selectedStatus === "in_transit"
? "border-blue-900 bg-blue-100 ring-2 ring-blue-900"
: "border-blue-500 bg-white hover:bg-blue-50"
}`}
>
<div className="text-xs text-gray-600 mb-0.5"></div>
<div className="text-lg font-bold text-blue-600">{statusStats.in_transit}</div>
</div>
<div className="rounded-lg bg-white p-1.5 shadow-sm border-l-4 border-green-500">
</button>
<button
onClick={() => setSelectedStatus("delivered")}
className={`rounded-lg p-1.5 shadow-sm border-l-4 transition-all ${
selectedStatus === "delivered"
? "border-green-900 bg-green-100 ring-2 ring-green-900"
: "border-green-500 bg-white hover:bg-green-50"
}`}
>
<div className="text-xs text-gray-600 mb-0.5"></div>
<div className="text-lg font-bold text-green-600">{statusStats.delivered}</div>
</div>
<div className="rounded-lg bg-white p-1.5 shadow-sm border-l-4 border-red-500">
</button>
<button
onClick={() => setSelectedStatus("delayed")}
className={`rounded-lg p-1.5 shadow-sm border-l-4 transition-all ${
selectedStatus === "delayed"
? "border-red-900 bg-red-100 ring-2 ring-red-900"
: "border-red-500 bg-white hover:bg-red-50"
}`}
>
<div className="text-xs text-gray-600 mb-0.5"></div>
<div className="text-lg font-bold text-red-600">{statusStats.delayed}</div>
</div>
<div className="rounded-lg bg-white p-1.5 shadow-sm border-l-4 border-yellow-500">
</button>
<button
onClick={() => setSelectedStatus("pickup_waiting")}
className={`rounded-lg p-1.5 shadow-sm border-l-4 transition-all ${
selectedStatus === "pickup_waiting"
? "border-yellow-900 bg-yellow-100 ring-2 ring-yellow-900"
: "border-yellow-500 bg-white hover:bg-yellow-50"
}`}
>
<div className="text-xs text-gray-600 mb-0.5"> </div>
<div className="text-lg font-bold text-yellow-600">{statusStats.pickup_waiting}</div>
</div>
</button>
</div>
</div>
@@ -322,20 +392,24 @@ export default function DeliveryStatusWidget({ refreshInterval = 60000 }: Delive
</div>
</div>
{/* 지연 중인 화물 리스트 */}
{/* 필터링된 화물 리스트 */}
<div className="mb-3">
<h4 className="mb-2 text-sm font-semibold text-gray-700 flex items-center gap-2">
<AlertTriangle className="h-4 w-4 text-red-600" />
({delayedDeliveries.length})
<Package className="h-4 w-4 text-gray-600" />
{selectedStatus === "all" && `전체 화물 (${filteredDeliveries.length})`}
{selectedStatus === "in_transit" && `배송 중인 화물 (${filteredDeliveries.length})`}
{selectedStatus === "delivered" && `배송 완료 (${filteredDeliveries.length})`}
{selectedStatus === "delayed" && `지연 중인 화물 (${filteredDeliveries.length})`}
{selectedStatus === "pickup_waiting" && `픽업 대기 (${filteredDeliveries.length})`}
</h4>
<div className="rounded-lg bg-white shadow-sm border border-gray-200 overflow-hidden">
{delayedDeliveries.length === 0 ? (
{filteredDeliveries.length === 0 ? (
<div className="p-6 text-center text-sm text-gray-500">
{selectedStatus === "all" ? "화물이 없습니다" : "해당 상태의 화물이 없습니다"}
</div>
) : (
<div className="max-h-[200px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100">
{delayedDeliveries.map((delivery) => (
{filteredDeliveries.map((delivery) => (
<div
key={delivery.id}
className="p-3 border-b border-gray-200 last:border-b-0 hover:bg-gray-50 transition-colors"