PlatDetect / frontend /src /components /HistoryTable.jsx
haisyam's picture
Add delete history (single + clear all)
9aae0b0
Raw History Blame Contribute Delete
7.71 kB
import { useEffect, useState } from "react";
import { History, RefreshCw, CheckCircle, AlertTriangle, Trash2, Trash } from "lucide-react";
import { getHistory, deleteHistoryRecord, clearAllHistory } from "../services/api";
import { formatConfidence, formatDateTime } from "../utils/formatters";
export default function HistoryTable() {
const [history, setHistory] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [deletingIndex, setDeletingIndex] = useState(null);
const [confirmClear, setConfirmClear] = useState(false);
async function fetchHistory() {
setLoading(true);
setError("");
try {
const res = await getHistory();
if (res.success) {
setHistory(res.data || []);
} else {
setError(res.message || "Gagal memuat riwayat");
}
} catch {
setError("Riwayat belum tersedia.");
} finally {
setLoading(false);
}
}
useEffect(() => {
fetchHistory();
}, []);
async function handleDelete(index) {
try {
const res = await deleteHistoryRecord(index);
if (res.success) {
setDeletingIndex(null);
fetchHistory();
} else {
setError(res.message);
}
} catch {
setError("Gagal menghapus riwayat");
}
}
async function handleClearAll() {
try {
const res = await clearAllHistory();
if (res.success) {
setConfirmClear(false);
fetchHistory();
} else {
setError(res.message);
}
} catch {
setError("Gagal menghapus semua riwayat");
}
}
return (
<div className="bg-white border border-slate-200 rounded-2xl shadow-sm overflow-hidden">
<div className="p-4 border-b border-slate-100 flex items-center justify-between">
<div className="flex items-center gap-2">
<History className="w-4 h-4 text-blue-600" />
<h3 className="font-semibold text-slate-800 text-sm">Riwayat Deteksi</h3>
</div>
<div className="flex items-center gap-2">
{history.length > 0 && (
confirmClear ? (
<div className="flex items-center gap-1">
<button
onClick={handleClearAll}
className="text-xs bg-red-600 text-white px-2 py-1 rounded cursor-pointer hover:bg-red-700"
>
Hapus Semua
</button>
<button
onClick={() => setConfirmClear(false)}
className="text-xs text-slate-500 px-2 py-1 cursor-pointer hover:text-slate-700"
>
Batal
</button>
</div>
) : (
<button
onClick={() => setConfirmClear(true)}
className="text-slate-400 hover:text-red-500 transition-colors cursor-pointer"
title="Hapus semua riwayat"
>
<Trash className="w-4 h-4" />
</button>
)
)}
<button
onClick={fetchHistory}
disabled={loading}
className="text-slate-400 hover:text-blue-600 transition-colors cursor-pointer disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${loading ? "animate-spin" : ""}`} />
</button>
</div>
</div>
{error && (
<div className="px-4 py-3 text-sm text-yellow-700 bg-yellow-50">
{error}
</div>
)}
{loading ? (
<div className="px-4 py-8 text-center text-sm text-slate-400">
Memuat riwayat...
</div>
) : history.length === 0 ? (
<div className="px-4 py-8 text-center text-sm text-slate-400">
Belum ada riwayat deteksi.
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-slate-50 text-slate-500">
<th className="px-4 py-2.5 text-left font-medium whitespace-nowrap">Waktu</th>
<th className="px-4 py-2.5 text-left font-medium">File</th>
<th className="px-4 py-2.5 text-left font-medium">Plat</th>
<th className="px-4 py-2.5 text-left font-medium">Status</th>
<th className="px-4 py-2.5 text-left font-medium">Pemilik</th>
<th className="px-4 py-2.5 text-left font-medium">Confidence</th>
<th className="px-4 py-2.5 text-center font-medium">Aksi</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{history.map((h, i) => (
<tr key={i} className="hover:bg-slate-50 transition-colors">
<td className="px-4 py-2.5 text-slate-500 whitespace-nowrap text-xs">
{formatDateTime(h.waktu || h.time)}
</td>
<td className="px-4 py-2.5 text-slate-600 truncate max-w-[120px]">
{h.nama_file || h.file_name || "-"}
</td>
<td className="px-4 py-2.5 font-mono font-medium text-slate-800">
{h.plate || "-"}
</td>
<td className="px-4 py-2.5">
{h.status === "Terdaftar" ? (
<span className="inline-flex items-center gap-1 text-green-700 text-xs bg-green-100 px-2 py-0.5 rounded-full">
<CheckCircle className="w-3 h-3" />
Terdaftar
</span>
) : (
<span className="inline-flex items-center gap-1 text-yellow-700 text-xs bg-yellow-100 px-2 py-0.5 rounded-full">
<AlertTriangle className="w-3 h-3" />
Tidak Terdaftar
</span>
)}
</td>
<td className="px-4 py-2.5 text-slate-700">
{h.owner_name || h.owner || "-"}
</td>
<td className="px-4 py-2.5 text-slate-600">
{formatConfidence(h.confidence_yolo)}
</td>
<td className="px-4 py-2.5">
<div className="flex justify-center">
{deletingIndex === i ? (
<div className="flex items-center gap-1">
<button
onClick={() => handleDelete(i)}
className="text-xs bg-red-600 text-white px-2 py-1 rounded cursor-pointer hover:bg-red-700"
>
Hapus
</button>
<button
onClick={() => setDeletingIndex(null)}
className="text-xs text-slate-500 px-2 py-1 cursor-pointer hover:text-slate-700"
>
Batal
</button>
</div>
) : (
<button
onClick={() => setDeletingIndex(i)}
className="text-slate-400 hover:text-red-500 p-1 rounded hover:bg-red-50 transition-colors cursor-pointer"
title="Hapus"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}