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

214 lines
8.2 KiB
TypeScript
Raw Normal View History

/**
*
* -
* -
* -
* -
*/
"use client";
import { useState, useEffect } from "react";
import { DashboardElement } from "@/components/admin/dashboard/types";
2025-10-24 16:08:57 +09:00
import { getApiUrl } from "@/lib/utils/apiUrl";
2025-10-22 13:40:15 +09:00
import { WORK_TYPE_LABELS, WORK_STATUS_LABELS, WORK_STATUS_COLORS, WorkType, WorkStatus } from "@/types/workHistory";
interface WorkHistoryWidgetProps {
element: DashboardElement;
refreshInterval?: number;
}
export default function WorkHistoryWidget({ element, refreshInterval = 60000 }: WorkHistoryWidgetProps) {
const [data, setData] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedType, setSelectedType] = useState<WorkType | "all">("all");
const [selectedStatus, setSelectedStatus] = useState<WorkStatus | "all">("all");
// 데이터 로드
const loadData = async () => {
try {
setIsLoading(true);
setError(null);
// 쿼리가 설정되어 있으면 쿼리 실행
if (element.dataSource?.query) {
const token = localStorage.getItem("authToken");
2025-10-24 16:08:57 +09:00
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) {
setData(result.data.rows);
} else {
throw new Error(result.message || "데이터 로드 실패");
}
} else {
// 쿼리 미설정 시 안내 메시지
setError("쿼리를 설정해주세요");
setData([]);
}
} catch (err) {
console.error("작업 이력 로드 실패:", err);
setError(err instanceof Error ? err.message : "데이터를 불러올 수 없습니다");
} finally {
setIsLoading(false);
}
};
useEffect(() => {
loadData();
const interval = setInterval(loadData, refreshInterval);
return () => clearInterval(interval);
}, [selectedType, selectedStatus, refreshInterval, element.dataSource]);
if (isLoading && data.length === 0) {
return (
2025-10-29 17:53:03 +09:00
<div className="flex h-full items-center justify-center bg-muted">
<div className="text-center">
2025-10-29 17:53:03 +09:00
<div className="mx-auto h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="mt-2 text-sm text-foreground"> ...</div>
</div>
</div>
);
}
if (error) {
return (
2025-10-29 17:53:03 +09:00
<div className="flex h-full items-center justify-center bg-muted p-4">
<div className="text-center">
<div className="mb-2 text-4xl"></div>
2025-10-29 17:53:03 +09:00
<div className="text-sm font-medium text-foreground">{error}</div>
{!element.dataSource?.query && <div className="mt-2 text-xs text-muted-foreground"> </div>}
<button
onClick={loadData}
className="mt-3 rounded-lg bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90"
>
</button>
</div>
</div>
);
}
return (
2025-10-29 17:53:03 +09:00
<div className="flex h-full flex-col bg-background">
{/* 필터 */}
<div className="flex gap-2 border-b p-3">
<select
value={selectedType}
onChange={(e) => setSelectedType(e.target.value as WorkType | "all")}
className="rounded border px-2 py-1 text-sm"
>
<option value="all"> </option>
<option value="inbound"></option>
<option value="outbound"></option>
<option value="transfer"></option>
<option value="maintenance"></option>
</select>
<select
value={selectedStatus}
onChange={(e) => setSelectedStatus(e.target.value as WorkStatus | "all")}
className="rounded border px-2 py-1 text-sm"
>
<option value="all"> </option>
<option value="pending"></option>
<option value="in_progress"></option>
<option value="completed"></option>
<option value="cancelled"></option>
</select>
<button
onClick={loadData}
className="ml-auto rounded bg-primary px-3 py-1 text-sm text-primary-foreground hover:bg-primary/90"
>
🔄
</button>
</div>
{/* 테이블 */}
<div className="flex-1 overflow-auto">
<table className="w-full text-sm">
2025-10-29 17:53:03 +09:00
<thead className="sticky top-0 bg-muted text-left">
<tr>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
</tr>
</thead>
<tbody>
{data.length === 0 ? (
<tr>
2025-10-29 17:53:03 +09:00
<td colSpan={8} className="py-8 text-center text-muted-foreground">
</td>
</tr>
) : (
data
.filter((item) => selectedType === "all" || item.work_type === selectedType)
.filter((item) => selectedStatus === "all" || item.status === selectedStatus)
.map((item, index) => (
2025-10-29 17:53:03 +09:00
<tr key={item.id || index} className="border-b hover:bg-muted">
<td className="px-3 py-2 font-mono text-xs">{item.work_number}</td>
<td className="px-3 py-2">
{item.work_date
? new Date(item.work_date).toLocaleString("ko-KR", {
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
})
: "-"}
</td>
<td className="px-3 py-2">
2025-10-29 17:53:03 +09:00
<span className="rounded bg-primary/10 px-2 py-1 text-xs font-medium text-primary">
{WORK_TYPE_LABELS[item.work_type as WorkType] || item.work_type}
</span>
</td>
<td className="px-3 py-2">{item.vehicle_number || "-"}</td>
<td className="px-3 py-2 text-xs">
{item.origin && item.destination ? `${item.origin}${item.destination}` : "-"}
</td>
<td className="px-3 py-2">{item.cargo_name || "-"}</td>
<td className="px-3 py-2">
{item.cargo_weight ? `${item.cargo_weight} ${item.cargo_unit || "ton"}` : "-"}
</td>
<td className="px-3 py-2">
<span
2025-10-29 17:53:03 +09:00
className={`rounded px-2 py-1 text-xs font-medium ${WORK_STATUS_COLORS[item.status as WorkStatus] || "bg-muted text-foreground"}`}
>
{WORK_STATUS_LABELS[item.status as WorkStatus] || item.status}
</span>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
{/* 푸터 */}
2025-10-29 17:53:03 +09:00
<div className="border-t bg-muted px-3 py-2 text-xs text-foreground"> {data.length}</div>
</div>
);
}