285 lines
12 KiB
JavaScript
285 lines
12 KiB
JavaScript
// assets/js/pages/admin/tabs/ClaimsTabAdmin.js - WITH FULL CRUD
|
||
|
||
const ClaimsTabAdmin = ({ state, handlers }) => {
|
||
const {
|
||
claims,
|
||
filteredClaims,
|
||
claimStatusFilter,
|
||
setClaimStatusFilter,
|
||
claimSearchTerm,
|
||
setClaimSearchTerm,
|
||
showCreateClaimModal,
|
||
setShowCreateClaimModal,
|
||
showEditClaimModal,
|
||
setShowEditClaimModal,
|
||
} = state;
|
||
|
||
const {
|
||
handleViewClaimDetail,
|
||
handleDeleteClaim,
|
||
handleEditClaimClick,
|
||
loadClaims,
|
||
} = handlers;
|
||
|
||
// Filter claims locally
|
||
React.useEffect(() => {
|
||
let filtered = claims.filter((claim) => {
|
||
const matchesSearch =
|
||
claim.item_name.toLowerCase().includes(claimSearchTerm.toLowerCase()) ||
|
||
claim.user_name.toLowerCase().includes(claimSearchTerm.toLowerCase());
|
||
const matchesStatus =
|
||
!claimStatusFilter || claim.status === claimStatusFilter;
|
||
return matchesSearch && matchesStatus;
|
||
});
|
||
state.setFilteredClaims(filtered);
|
||
}, [claimSearchTerm, claimStatusFilter, claims]);
|
||
|
||
return (
|
||
<div className="bg-gradient-to-br from-slate-800 to-slate-900 rounded-xl shadow-xl border border-slate-700">
|
||
<div className="p-6 border-b border-slate-700">
|
||
<div className="flex justify-between items-center mb-4">
|
||
<h2 className="text-xl font-semibold text-white">Kelola Klaim</h2>
|
||
<div className="flex gap-3">
|
||
<button
|
||
onClick={() => setShowCreateClaimModal(true)}
|
||
className="px-4 py-2 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-lg font-semibold hover:from-green-700 hover:to-green-800 transition shadow-lg"
|
||
>
|
||
➕ Tambah Klaim Manual
|
||
</button>
|
||
<button
|
||
onClick={loadClaims}
|
||
className="px-4 py-2 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg font-semibold hover:from-blue-700 hover:to-blue-800 transition shadow-lg"
|
||
>
|
||
🔄 Refresh
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-3 flex-wrap">
|
||
<input
|
||
type="text"
|
||
placeholder="Cari klaim..."
|
||
value={claimSearchTerm}
|
||
onChange={(e) => setClaimSearchTerm(e.target.value)}
|
||
className="flex-1 min-w-[200px] px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||
/>
|
||
<select
|
||
value={claimStatusFilter}
|
||
onChange={(e) => setClaimStatusFilter(e.target.value)}
|
||
className="px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||
>
|
||
<option value="">Semua Status</option>
|
||
<option value="pending">Pending</option>
|
||
<option value="approved">Approved</option>
|
||
<option value="rejected">Rejected</option>
|
||
</select>
|
||
</div>
|
||
|
||
{/* Stats Summary */}
|
||
<div className="grid grid-cols-3 gap-4 mt-4">
|
||
<div className="bg-yellow-500/10 border border-yellow-500/30 p-3 rounded-lg text-center">
|
||
<div className="text-yellow-400 font-bold text-2xl">
|
||
{claims.filter((c) => c.status === "pending").length}
|
||
</div>
|
||
<div className="text-slate-400 text-sm">Pending</div>
|
||
</div>
|
||
<div className="bg-green-500/10 border border-green-500/30 p-3 rounded-lg text-center">
|
||
<div className="text-green-400 font-bold text-2xl">
|
||
{claims.filter((c) => c.status === "approved").length}
|
||
</div>
|
||
<div className="text-slate-400 text-sm">Approved</div>
|
||
</div>
|
||
<div className="bg-red-500/10 border border-red-500/30 p-3 rounded-lg text-center">
|
||
<div className="text-red-400 font-bold text-2xl">
|
||
{claims.filter((c) => c.status === "rejected").length}
|
||
</div>
|
||
<div className="text-slate-400 text-sm">Rejected</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-6">
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
{filteredClaims.map((claim) => (
|
||
<div
|
||
key={claim.id}
|
||
className="bg-gradient-to-br from-slate-700 to-slate-800 border-2 border-slate-600 rounded-xl p-5 hover:border-blue-500 hover:shadow-xl hover:shadow-blue-500/20 transition-all"
|
||
>
|
||
{/* Header */}
|
||
<div className="flex justify-between items-start mb-4 border-b border-slate-600 pb-3">
|
||
<div className="flex-1">
|
||
<h3 className="text-lg font-bold text-white">
|
||
{claim.item_name}
|
||
</h3>
|
||
<p className="text-xs text-slate-400 mt-1">
|
||
Pengklaim:{" "}
|
||
<span className="text-white font-medium">
|
||
{claim.user_name}
|
||
</span>{" "}
|
||
• {claim.contact}
|
||
</p>
|
||
</div>
|
||
<span
|
||
className={`px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider ${Helpers.getStatusBadgeClass(
|
||
claim.status
|
||
)}`}
|
||
>
|
||
{claim.status}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Match Percentage */}
|
||
{claim.match_percentage && (
|
||
<div
|
||
className={`mb-4 p-3 rounded-lg text-center ${
|
||
claim.match_percentage >= 70
|
||
? "bg-green-500/10 border border-green-500/30"
|
||
: "bg-yellow-500/10 border border-yellow-500/30"
|
||
}`}
|
||
>
|
||
<div className="text-xs text-slate-400 mb-1">
|
||
Similarity Match
|
||
</div>
|
||
<div
|
||
className={`text-2xl font-bold ${
|
||
claim.match_percentage >= 70
|
||
? "text-green-400"
|
||
: "text-yellow-400"
|
||
}`}
|
||
>
|
||
{claim.match_percentage}%
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Description */}
|
||
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-600 mb-4">
|
||
<strong className="text-sm text-slate-300">
|
||
Deskripsi Klaim:
|
||
</strong>
|
||
<p className="text-sm text-slate-400 mt-1 line-clamp-3">
|
||
{claim.description}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Timestamps */}
|
||
<div className="space-y-1 text-xs text-slate-400 mb-4">
|
||
<div>
|
||
📅 Diajukan: {Helpers.formatDateTime(claim.created_at)}
|
||
</div>
|
||
{claim.verified_at && (
|
||
<div>
|
||
✅ Diverifikasi: {Helpers.formatDateTime(claim.verified_at)}
|
||
</div>
|
||
)}
|
||
{claim.verified_by_name && (
|
||
<div>👤 Oleh: {claim.verified_by_name}</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Notes */}
|
||
{claim.notes && (
|
||
<div
|
||
className={`p-3 rounded-lg border-2 mb-4 ${
|
||
claim.status === "approved"
|
||
? "bg-green-500/10 border-green-500/30"
|
||
: "bg-red-500/10 border-red-500/30"
|
||
}`}
|
||
>
|
||
<strong className="text-sm text-slate-300">
|
||
Catatan Manager:
|
||
</strong>
|
||
<p className="text-sm text-slate-400 mt-1">{claim.notes}</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Case Closed Info */}
|
||
{claim.status === "approved" && claim.berita_acara_no && (
|
||
<div className="bg-green-500/10 border-2 border-green-500/30 p-4 rounded-lg mb-4">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<span className="text-2xl">📋</span>
|
||
<strong className="text-green-400">Case Closed</strong>
|
||
</div>
|
||
<div className="space-y-1 text-sm text-slate-300">
|
||
<div>
|
||
No. BA:{" "}
|
||
<strong className="text-white">
|
||
{claim.berita_acara_no}
|
||
</strong>
|
||
</div>
|
||
{claim.case_closed_at && (
|
||
<div>
|
||
Ditutup: {Helpers.formatDateTime(claim.case_closed_at)}
|
||
</div>
|
||
)}
|
||
{claim.case_closed_by_name && (
|
||
<div>Oleh: {claim.case_closed_by_name}</div>
|
||
)}
|
||
</div>
|
||
{claim.bukti_serah_terima && (
|
||
<a
|
||
href={claim.bukti_serah_terima}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="inline-block mt-2 px-3 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition"
|
||
>
|
||
📄 Lihat Bukti
|
||
</a>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Actions */}
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={() => handleViewClaimDetail(claim)}
|
||
className="flex-1 px-3 py-2 bg-gradient-to-r from-blue-600 to-blue-700 text-white text-sm rounded-lg hover:from-blue-700 hover:to-blue-800 transition shadow-lg"
|
||
>
|
||
📋 Detail
|
||
</button>
|
||
|
||
{/* Edit button - only for pending claims */}
|
||
{claim.status === "pending" && (
|
||
<button
|
||
onClick={() => handleEditClaimClick(claim)}
|
||
className="px-3 py-2 bg-gradient-to-r from-yellow-600 to-yellow-700 text-white text-sm rounded-lg hover:from-yellow-700 hover:to-yellow-800 transition shadow-lg"
|
||
>
|
||
✏️
|
||
</button>
|
||
)}
|
||
|
||
{/* Delete button */}
|
||
<button
|
||
onClick={() => {
|
||
if (
|
||
confirm(
|
||
`⚠️ Yakin ingin menghapus klaim dari "${claim.user_name}"?\n\nBarang: ${claim.item_name}`
|
||
)
|
||
) {
|
||
handleDeleteClaim(claim.id);
|
||
}
|
||
}}
|
||
className="px-3 py-2 bg-gradient-to-r from-red-600 to-red-700 text-white text-sm rounded-lg hover:from-red-700 hover:to-red-800 transition shadow-lg"
|
||
>
|
||
🗑️
|
||
</button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{filteredClaims.length === 0 && (
|
||
<div className="text-center py-12 text-slate-400">
|
||
<div className="text-6xl mb-4">🤝</div>
|
||
<p>
|
||
{claimSearchTerm || claimStatusFilter
|
||
? "Tidak ada klaim yang sesuai filter"
|
||
: "Belum ada klaim"}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|