2026-05-15 17:40:21 +05:30

306 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { Head, router } from '@inertiajs/react';
import AdminLayout from '@/Layouts/AdminLayout';
import {
Search,
FileText,
Eye,
CheckCircle,
XCircle,
Clock,
Edit2,
ZoomIn,
X,
Loader2
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from '@/components/ui/pagination';
export default function Verifications({ verifications, status }) {
const [selectedItem, setSelectedItem] = useState(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [lightboxImage, setLightboxImage] = useState(null);
// Dialog state
const [ocrOverrides, setOcrOverrides] = useState({});
const [editingField, setEditingField] = useState(null);
const [rejectionReason, setRejectionReason] = useState('');
const [showRejectionInput, setShowRejectionInput] = useState(false);
const [submitting, setSubmitting] = useState(false);
const tabs = [
{ label: 'All', value: 'all' },
{ label: 'Auto-Approved', value: 'approved' },
{ label: 'Flagged/Manual', value: 'rejected' },
];
const handleTabClick = (tabValue) => {
router.get('/admin/workers/verifications', { status: tabValue }, { preserveState: true });
};
const openReview = (item) => {
setSelectedItem(item);
setOcrOverrides(item.ocr_data || {});
setEditingField(null);
setShowRejectionInput(false);
setRejectionReason('');
setIsDialogOpen(true);
};
return (
<AdminLayout title="Worker Identity Verifications">
<Head title="Worker Verifications - Admin Portal" />
<div className="font-sans max-w-7xl mx-auto space-y-6">
{/* Header / Sub-title */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-xl font-bold text-gray-900 tracking-tight">Identity Audit Logs</h1>
<p className="text-xs text-gray-500 mt-0.5">Automated document verification system logs and OCR extractions.</p>
</div>
{/* Filter Tabs */}
<div className="flex bg-slate-200/60 p-1 rounded-xl w-fit border border-slate-200">
{tabs.map((tab) => (
<button
key={tab.value}
type="button"
onClick={() => handleTabClick(tab.value)}
className={`px-4 py-1.5 rounded-lg text-xs font-semibold transition-all ${
status === tab.value
? 'bg-white text-[#0F6E56] shadow-sm'
: 'text-slate-600 hover:text-slate-900'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
{/* Desktop Table / Mobile Card List */}
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
<div className="hidden sm:block overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50/70 border-b border-gray-100 text-xs font-semibold text-slate-500">
<th className="py-3.5 px-5">Worker Name</th>
<th className="py-3.5 px-5">Nationality</th>
<th className="py-3.5 px-5">Passport No.</th>
<th className="py-3.5 px-5">Processed</th>
<th className="py-3.5 px-5">Status</th>
<th className="py-3.5 px-5 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100 text-sm">
{verifications?.data?.map((item) => (
<tr key={item.id} className="hover:bg-slate-50/50 transition-colors">
<td className="py-4 px-5 font-medium text-gray-900">{item.worker_name}</td>
<td className="py-4 px-5 text-gray-600">{item.nationality}</td>
<td className="py-4 px-5 font-mono text-xs text-gray-600">{item.passport_number}</td>
<td className="py-4 px-5 text-xs text-gray-500">{item.processed_at}</td>
<td className="py-4 px-5">
<Badge variant="outline" className="bg-emerald-50 text-emerald-700 border-emerald-200 px-2.5 py-0.5 rounded-full font-medium text-xs">
<CheckCircle className="w-3 h-3 mr-1 inline" /> Auto-Verified
</Badge>
</td>
<td className="py-4 px-5 text-right">
<button
type="button"
onClick={() => openReview(item)}
className="inline-flex items-center px-3 py-1.5 border border-slate-200 hover:border-[#0F6E56] text-xs font-semibold text-slate-700 hover:text-[#0F6E56] bg-white hover:bg-teal-50/30 rounded-lg transition-colors focus:outline-none"
>
<Eye className="w-3.5 h-3.5 mr-1.5" /> View Log
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Mobile Card List */}
<div className="block sm:hidden divide-y divide-gray-100">
{verifications?.data?.map((item) => (
<div key={item.id} className="p-4 space-y-3">
<div className="flex items-center justify-between">
<span className="font-semibold text-gray-900">{item.worker_name}</span>
<Badge className="bg-emerald-50 text-emerald-700 border-emerald-200">Auto-Verified</Badge>
</div>
<div className="grid grid-cols-2 text-xs text-gray-600 gap-1">
<div><span className="text-gray-400">Nationality:</span> {item.nationality}</div>
<div><span className="text-gray-400">Passport:</span> {item.passport_number}</div>
<div className="col-span-2 text-gray-400 text-[10px] mt-1">Processed: {item.processed_at}</div>
</div>
<button
type="button"
onClick={() => openReview(item)}
className="w-full inline-flex items-center justify-center px-3 py-2 border border-slate-200 text-xs font-semibold text-slate-700 hover:text-[#0F6E56] bg-slate-50 hover:bg-teal-50 rounded-lg transition-colors"
>
<Eye className="w-3.5 h-3.5 mr-1.5" /> View Audit Details
</button>
</div>
))}
</div>
{verifications?.data?.length === 0 && (
<div className="p-12 text-center text-gray-500 text-sm font-medium">
No verification logs found for the selected filter.
</div>
)}
</div>
{/* Pagination Component */}
{verifications?.links && verifications.links.length > 3 && (
<div className="flex justify-center pt-4">
<Pagination>
<PaginationContent>
{verifications.links.map((link, idx) => {
const isPrev = link.label.includes('Previous');
const isNext = link.label.includes('Next');
const label = isPrev ? '' : isNext ? '' : link.label;
if (!link.url) {
return (
<PaginationItem key={idx}>
<span className="px-3 py-2 text-xs text-slate-300 cursor-not-allowed select-none">
{label}
</span>
</PaginationItem>
);
}
return (
<PaginationItem key={idx}>
<PaginationLink
href={link.url}
isActive={link.active}
className={`text-xs px-3 py-2 rounded-lg transition-colors ${
link.active ? 'bg-[#0F6E56] text-white hover:bg-[#085041] hover:text-white font-bold' : 'text-slate-600 hover:bg-slate-100'
}`}
>
{label}
</PaginationLink>
</PaginationItem>
);
})}
</PaginationContent>
</Pagination>
</div>
)}
</div>
{/* Review Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="max-w-4xl bg-white p-0 overflow-hidden font-sans rounded-2xl">
<DialogHeader className="p-6 pb-4 border-b border-slate-100 bg-slate-50/50">
<DialogTitle className="text-lg font-semibold text-gray-800 flex items-center justify-between tracking-tight">
<span>Verification Log: <span className="text-[#0F6E56]">{selectedItem?.worker_name}</span></span>
<Badge className="bg-emerald-100 text-emerald-800 border-none px-3 py-1">System Verified</Badge>
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 p-6 max-h-[80vh] overflow-y-auto">
{/* Left: Document Images */}
<div className="space-y-4">
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider flex items-center">
<FileText className="w-3.5 h-3.5 mr-1.5" /> Scanned Proof Documents ({selectedItem?.document_images?.length || 0})
</h3>
<div className="grid grid-cols-1 gap-4">
{selectedItem?.document_images?.map((imgUrl, i) => (
<div
key={i}
onClick={() => setLightboxImage(imgUrl)}
className="relative group rounded-xl overflow-hidden border border-slate-200 bg-slate-100 cursor-pointer shadow-sm hover:ring-2 hover:ring-[#0F6E56]/40 transition-all aspect-[4/3]"
>
<img
src={imgUrl}
alt="Document scan"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div className="absolute inset-0 bg-black/30 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-white text-xs font-semibold backdrop-blur-[1px]">
<ZoomIn className="w-5 h-5 mr-1.5" /> Click to View
</div>
</div>
))}
</div>
</div>
{/* Right: OCR Data & Status */}
<div className="space-y-6 flex flex-col justify-between">
<div className="space-y-4">
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider flex items-center justify-between">
<span>System Extracted Data</span>
<span className="text-[10px] text-emerald-600 bg-emerald-50 px-2 py-0.5 rounded-full font-medium">Confidence: 98%</span>
</h3>
<div className="space-y-2.5">
{Object.entries(ocrOverrides).map(([key, value]) => (
<div key={key} className="flex items-center justify-between p-3 bg-slate-50 rounded-xl text-sm border border-slate-100 hover:border-slate-200 transition-colors">
<span className="font-medium text-slate-600 text-xs">{key}:</span>
<span className="text-gray-900 font-semibold text-xs font-mono">{value}</span>
</div>
))}
</div>
</div>
{/* Status Info */}
<div className="space-y-4 pt-4 border-t border-slate-100">
<div className="bg-emerald-50 p-4 rounded-xl border border-emerald-100 flex items-start">
<CheckCircle className="w-5 h-5 text-emerald-600 mr-3 mt-0.5" />
<div>
<p className="text-xs font-bold text-emerald-800">Verified Automatically</p>
<p className="text-[11px] text-emerald-700 mt-1">
This profile was verified by the system on {selectedItem?.processed_at}. No further action is required from administrators.
</p>
</div>
</div>
<button
type="button"
onClick={() => setIsDialogOpen(false)}
className="w-full bg-slate-900 hover:bg-slate-800 text-white py-3 px-4 rounded-xl font-semibold text-xs shadow-sm flex items-center justify-center transition-colors"
>
Close Audit Log
</button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
{/* Lightbox Zoom */}
{lightboxImage && (
<div
onClick={() => setLightboxImage(null)}
className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 animate-in fade-in duration-200"
>
<button
type="button"
className="absolute top-6 right-6 text-white/80 hover:text-white p-2 bg-white/10 hover:bg-white/20 rounded-full transition-all focus:outline-none"
onClick={() => setLightboxImage(null)}
>
<X className="w-6 h-6" />
</button>
<img
src={lightboxImage}
alt="Zoomed document"
className="max-w-full max-h-[90vh] object-contain rounded-xl shadow-2xl border border-white/20 animate-in zoom-in-95 duration-200"
/>
</div>
)}
</AdminLayout>
);
}