2026-06-01 14:50:42 +05:30

413 lines
24 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 }) {
const [selectedItem, setSelectedItem] = useState(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
// Vetting form overrides
const [ocrOverrides, setOcrOverrides] = useState({});
const [isEditingOcr, setIsEditingOcr] = useState(false);
const [rejectionReason, setRejectionReason] = useState('');
const [showRejectionForm, setShowRejectionForm] = useState(false);
// Local queue of workers to verify
const localQueue = [
{
id: 101,
worker_name: 'Fatima Zahra',
nationality: 'Morocco',
passport_number: 'MA9823471',
processed_at: '2026-05-23 14:15',
status: 'approved',
confidence: 98,
verification_method: 'Auto',
document_type: 'Passport',
warnings: [],
ocr_data: {
'Name': 'Fatima Zahra',
'DOB': '1992-08-14',
'Nationality': 'Morocco',
'Passport No.': 'MA9823471',
'Expiry': '2030-11-20',
}
},
{
id: 103,
worker_name: 'Amina Diop',
nationality: 'Senegal',
passport_number: 'SN8765432',
processed_at: '2026-05-23 11:30',
status: 'flagged',
confidence: 58,
verification_method: 'Auto',
document_type: 'Passport',
warnings: ['Blurry text region', 'Signature discrepancy flagged'],
ocr_data: {
'Name': 'Amina Diop',
'DOB': '1994-12-01',
'Nationality': 'Senegal',
'Passport No.': 'SN8765432',
'Expiry': '2031-08-10',
}
},
{
id: 104,
worker_name: 'Siti Aminah',
nationality: 'Indonesia',
passport_number: 'B76543210',
processed_at: '2026-05-22 16:40',
status: 'flagged',
confidence: 62,
verification_method: 'Auto',
document_type: 'Visa scan details',
warnings: ['Document expiration alert (Expires in 5 days)'],
ocr_data: {
'Name': 'Siti Aminah',
'DOB': '1988-03-25',
'Nationality': 'Indonesia',
'Passport No.': 'B76543210',
'Expiry': '2026-05-28',
}
}
];
const activeList = localQueue.filter(item => {
if (status === 'approved') return item.status === 'approved';
if (status === 'rejected') return item.status === 'flagged';
return true;
});
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">92.4%</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">91.8%</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">2 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.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>
))}
</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">
{[
{ time: '2026-05-23 15:20', text: 'Admin manual approval completed for Leila Bekri passport verification', ip: '192.168.1.5' },
{ time: '2026-05-23 14:15', text: 'Auto passport verification completed successfully for Fatima Zahra', ip: 'Auto-System' },
{ time: '2026-05-23 13:42', text: 'Auto verification flagged passport scan of Grace Omondi due to blurred signature scan', ip: 'Auto-System' },
].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>
</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>
);
}