2026-06-03 14:22:56 +05:30

356 lines
22 KiB
JavaScript

import React, { useState } from 'react';
import { Head, router } from '@inertiajs/react';
import AdminLayout from '@/Layouts/AdminLayout';
import {
CheckCircle,
XCircle,
Clock,
X,
AlertTriangle,
ShieldCheck,
Edit3,
History,
Sparkles
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
export default function Verifications({ verifications, status, summary, history = [] }) {
const [selectedItem, setSelectedItem] = useState(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
// Verification form overrides
const [ocrOverrides, setOcrOverrides] = useState({});
const [isEditingOcr, setIsEditingOcr] = useState(false);
const [rejectionReason, setRejectionReason] = useState('');
const [showRejectionForm, setShowRejectionForm] = useState(false);
const activeList = verifications?.data || [];
const openReview = (item) => {
setSelectedItem(item);
setOcrOverrides(item.ocr_data || {});
setIsEditingOcr(false);
setShowRejectionForm(false);
setRejectionReason('');
setIsDialogOpen(true);
};
const handleVerifySubmit = (action) => {
router.post(`/admin/workers/${selectedItem.id}/verify`, {
action,
rejection_reason: action === 'reject' ? rejectionReason : null,
ocr_overrides: ocrOverrides
}, {
onSuccess: () => {
setIsDialogOpen(false);
}
});
};
return (
<AdminLayout title="Verifications">
<Head title="Verifications" />
<div className="font-sans max-w-7xl mx-auto space-y-6 pb-12">
{/* Simplified Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-slate-200 pb-5">
<div>
<h1 className="text-xl font-black text-slate-800 uppercase tracking-tight">Worker Verifications</h1>
<p className="text-xs text-slate-500 mt-0.5 font-medium">Verify workers, check passports, and override data inputs.</p>
</div>
{/* Filter Tabs */}
<div className="flex bg-slate-100 p-1 rounded-xl w-fit border border-slate-200">
{[
{ label: 'All', value: 'all' },
{ label: 'Approved', value: 'approved' },
{ label: 'Flagged', value: 'rejected' },
].map((tab) => (
<button
key={tab.value}
type="button"
onClick={() => router.get('/admin/workers/verifications', { status: tab.value })}
className={`px-4 py-1.5 rounded-lg text-[10px] font-black uppercase tracking-wider transition-all ${
(status || 'all') === tab.value
? 'bg-white text-[#0F6E56] shadow-sm'
: 'text-slate-650 hover:text-slate-900'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
{/* Queue Summary widgets */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white p-5 border border-slate-200 rounded-2xl flex items-center justify-between shadow-sm">
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest block">Auto-Approve Rate</span>
<span className="text-2xl font-black text-slate-800 mt-1">{summary?.auto_approve_rate || 0}%</span>
</div>
<div className="w-10 h-10 bg-emerald-50 rounded-xl flex items-center justify-center text-emerald-600">
<ShieldCheck className="w-5 h-5" />
</div>
</div>
<div className="bg-white p-5 border border-slate-200 rounded-2xl flex items-center justify-between shadow-sm">
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest block">Average Match Score</span>
<span className="text-2xl font-black text-blue-600 mt-1">{summary?.average_match_score || 0}%</span>
</div>
<div className="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center text-blue-600">
<Sparkles className="w-5 h-5" />
</div>
</div>
<div className="bg-white p-5 border border-slate-200 rounded-2xl flex items-center justify-between shadow-sm">
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest block">Flagged Workers</span>
<span className="text-2xl font-black text-amber-600 mt-1">{summary?.flagged_count || 0} Pending</span>
</div>
<div className="w-10 h-10 bg-amber-50 rounded-xl flex items-center justify-center text-amber-600">
<AlertTriangle className="w-5 h-5" />
</div>
</div>
</div>
{/* Verification Queue Datagrid */}
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50/70 border-b border-slate-150 text-[10px] font-black text-slate-500 uppercase tracking-widest">
<th className="py-4 px-6">Worker Name</th>
<th className="py-4 px-6">Passport ID</th>
<th className="py-4 px-6">Confidence Score</th>
<th className="py-4 px-6">Warnings</th>
<th className="py-4 px-6">Processed Date</th>
<th className="py-4 px-6 text-right">Action</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 text-xs font-bold text-slate-700">
{activeList.length > 0 ? (
activeList.map((item) => (
<tr key={item.id} className="hover:bg-slate-50/50 transition-colors">
<td className="py-4 px-6">
<div>
<div className="text-sm font-black text-slate-900">{item.worker_name}</div>
<div className="text-[10px] text-slate-400 font-semibold mt-0.5">{item.nationality} {item.document_type}</div>
</div>
</td>
<td className="py-4 px-6 font-mono font-semibold text-slate-650">{item.passport_number}</td>
<td className="py-4 px-6">
<div className="flex items-center space-x-2">
<div className="w-24 bg-slate-100 rounded-full h-2 overflow-hidden">
<div
className={`h-full rounded-full ${
item.confidence > 90 ? 'bg-emerald-500' :
item.confidence > 70 ? 'bg-blue-500' : 'bg-amber-500'
}`}
style={{ width: `${item.confidence}%` }}
/>
</div>
<span className={`text-[10px] font-black ${
item.confidence > 90 ? 'text-emerald-600' :
item.confidence > 70 ? 'text-blue-600' : 'text-amber-600'
}`}>{item.confidence}%</span>
</div>
</td>
<td className="py-4 px-6">
{item.warnings.length > 0 ? (
<div className="flex flex-wrap gap-1">
{item.warnings.map((w, idx) => (
<span key={idx} className="inline-flex items-center bg-red-50 text-red-700 text-[9px] px-2.5 py-0.5 rounded-full uppercase font-black tracking-wide border border-red-100">
{w}
</span>
))}
</div>
) : (
<span className="text-[10px] font-black text-emerald-600 uppercase">None</span>
)}
</td>
<td className="py-4 px-6 text-slate-400 font-semibold">{item.processed_at}</td>
<td className="py-4 px-6 text-right">
<button
type="button"
onClick={() => openReview(item)}
className={`inline-flex items-center px-4 py-2 border rounded-xl text-[10px] font-black uppercase tracking-wider transition-colors focus:outline-none ${
item.status === 'flagged'
? 'bg-amber-500 text-white border-none shadow-md hover:bg-amber-600'
: 'bg-white hover:bg-slate-50 text-slate-700 border-slate-200'
}`}
>
{item.status === 'flagged' ? 'Verify' : 'Logs'}
</button>
</td>
</tr>
))
) : (
<tr>
<td colSpan="6" className="py-8 text-center text-slate-400 font-medium italic">
No verification records found in this queue.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
{/* Audit logs details grid */}
<div className="bg-white rounded-2xl p-6 border border-slate-200 shadow-sm space-y-4">
<h3 className="text-xs font-black text-slate-800 uppercase tracking-widest flex items-center gap-1.5 border-b border-slate-100 pb-3">
<History className="w-4 h-4 text-slate-600" />
<span>Verification History</span>
</h3>
<div className="space-y-2">
{history.length > 0 ? history.map((log, idx) => (
<div key={idx} className="bg-slate-50 p-3.5 rounded-xl border border-slate-100 flex items-center justify-between text-xs font-bold text-slate-700 shadow-inner">
<div className="flex items-center space-x-3">
<Clock className="w-4 h-4 text-slate-400" />
<span>{log.text}</span>
</div>
<span className="text-[10px] text-slate-400 font-semibold">{log.time} IP: {log.ip}</span>
</div>
)) : (
<div className="text-center py-6 text-slate-400 text-xs font-medium italic">
No verification logs recorded.
</div>
)}
</div>
</div>
</div>
{/* Verification Audit Dialog - Simplified & Width Optimized */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-xl w-full bg-white p-0 overflow-hidden font-sans rounded-[24px] border-none shadow-2xl max-h-[90vh] flex flex-col">
<DialogHeader className="p-6 border-b border-slate-100 bg-[#0f6e56]/5 flex-shrink-0">
<DialogTitle className="text-lg font-black text-slate-800 flex items-center justify-between tracking-tight">
<span className="flex items-center gap-2">
<ShieldCheck className="w-5 h-5 text-[#0F6E56]" />
<span>Verify Worker: <span className="text-[#0F6E56]">{selectedItem?.worker_name}</span></span>
</span>
<div className="flex items-center space-x-2">
<Badge className="border-none bg-[#0F6E56] text-white px-3.5 py-1.5 font-black text-[9px] uppercase tracking-wider rounded-lg shadow-sm">
<span>Score: {selectedItem?.confidence}%</span>
</Badge>
</div>
</DialogTitle>
</DialogHeader>
<div className="p-6 overflow-y-auto flex-1 bg-slate-50/30 space-y-6">
{/* Warnings Box - Rendered Inline */}
{selectedItem?.warnings?.length > 0 && (
<div className="bg-rose-50/50 border border-rose-100 p-5 rounded-2xl space-y-3 shadow-sm">
<span className="text-[10px] font-black text-rose-700 uppercase tracking-widest flex items-center gap-1.5">
<AlertTriangle className="w-4 h-4 text-rose-600 animate-bounce" />
<span>Verification Warnings</span>
</span>
<ul className="space-y-2 text-xs font-bold text-rose-950">
{selectedItem.warnings.map((w, idx) => (
<li key={idx} className="flex items-center gap-2 bg-white px-3 py-2 rounded-xl border border-rose-100 shadow-xs">
<div className="w-1.5 h-1.5 bg-rose-500 rounded-full flex-shrink-0" />
<span>{w}</span>
</li>
))}
</ul>
</div>
)}
{/* OCR Data Extraction Form - Styled Cleanly */}
<div className="bg-white p-5 border border-slate-200 rounded-2xl shadow-sm space-y-6">
<div className="space-y-4">
<div className="flex items-center justify-between border-b border-slate-100 pb-2">
<h3 className="text-xs font-black text-slate-700 uppercase tracking-widest">Extracted Text Vitals</h3>
<button
onClick={() => setIsEditingOcr(!isEditingOcr)}
className="text-xs text-[#0F6E56] hover:text-[#085041] font-black flex items-center gap-1 uppercase tracking-wider text-[10px]"
>
<Edit3 className="w-3.5 h-3.5" />
<span>{isEditingOcr ? 'Cancel' : 'Edit values'}</span>
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{Object.entries(ocrOverrides).map(([key, value]) => (
<div key={key} className="space-y-1.5">
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest block">{key}</label>
{isEditingOcr ? (
<input
type="text"
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-3.5 py-2.5 text-xs font-bold focus:bg-white outline-none focus:ring-2 focus:ring-[#0F6E56]/10 focus:border-[#0F6E56] transition-all"
value={value}
onChange={e => {
const newVal = e.target.value;
setOcrOverrides(prev => ({ ...prev, [key]: newVal }));
}}
/>
) : (
<div className="px-3.5 py-2.5 bg-slate-50 rounded-xl text-xs font-bold text-slate-800 border border-slate-100/50 font-mono tracking-wide">
{value}
</div>
)}
</div>
))}
</div>
</div>
{/* Manual Rejection reason input */}
{showRejectionForm ? (
<div className="bg-rose-50/30 p-4 border border-rose-100 rounded-xl space-y-3">
<label className="text-[10px] font-black text-rose-700 uppercase tracking-widest block">Reason for Rejection</label>
<textarea
rows="2"
className="w-full bg-white border border-rose-200 rounded-xl p-3 text-xs font-bold focus:ring-4 focus:ring-rose-500/10 outline-none transition-all"
placeholder="Explain verification error (e.g. invalid document)..."
value={rejectionReason}
onChange={e => setRejectionReason(e.target.value)}
/>
<div className="flex gap-2">
<button
onClick={() => handleVerifySubmit('reject')}
className="px-4 py-2.5 bg-red-600 hover:bg-red-700 text-white rounded-xl text-[10px] font-black uppercase tracking-widest shadow-md transition-colors"
>
Confirm
</button>
<button
onClick={() => setShowRejectionForm(false)}
className="px-4 py-2.5 bg-slate-200 text-slate-650 hover:bg-slate-350 rounded-xl text-[10px] font-black uppercase tracking-widest transition-colors"
>
Cancel
</button>
</div>
</div>
) : (
<div className="flex gap-3 pt-4 border-t border-slate-100 flex-shrink-0">
<button
onClick={() => handleVerifySubmit('approve')}
className="flex-1 py-3 bg-[#0F6E56] text-white rounded-xl text-[10px] font-black uppercase tracking-widest shadow-lg hover:bg-[#085041] transition-all flex items-center justify-center gap-1"
>
<CheckCircle className="w-4 h-4" />
<span>Approve</span>
</button>
<button
onClick={() => setShowRejectionForm(true)}
className="flex-1 py-3 bg-red-600 text-white rounded-xl text-[10px] font-black uppercase tracking-widest shadow-lg hover:bg-red-700 transition-all flex items-center justify-center gap-1"
>
<XCircle className="w-4 h-4" />
<span>Reject</span>
</button>
</div>
)}
</div>
</div>
</DialogContent>
</Dialog>
</AdminLayout>
);
}