Dealer_Onboard_Frontend/src/features/onboarding/components/application-details/ApplicationDetailsFddAuditContent.tsx

266 lines
14 KiB
TypeScript

import { AlertCircle, CheckCircle, ClipboardList, Download, Eye, FileText, ShieldAlert, ShieldCheck, Upload } from 'lucide-react';
import { toast } from 'sonner';
import { onboardingService } from '@/services/onboarding.service';
import { cn, formatDateTime } from '@/components/ui/utils';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
interface Props {
application: any;
currentUser: any;
documents: any[];
fddAgencies: any[];
selectedAgencyId: string;
setSelectedAgencyId: (v: string) => void;
isAssigningAgency: boolean;
handleAssignAgency: () => void;
setPreviewDoc: (d: any) => void;
setShowPreviewModal: (v: boolean) => void;
setIsUploading: (v: boolean) => void;
fetchApplication: () => void;
refreshDocuments: () => Promise<void>;
}
export function ApplicationDetailsFddAuditContent({
application,
currentUser,
documents,
fddAgencies,
selectedAgencyId,
setSelectedAgencyId,
isAssigningAgency,
handleAssignAgency,
setPreviewDoc,
setShowPreviewModal,
setIsUploading,
fetchApplication,
refreshDocuments,
}: Props) {
const assignments = application?.fddAssignments || [];
const fddParticipants = application?.participants?.filter((p: any) =>
p.user?.role === 'FDD' || p.user?.roleCode === 'FDD' || p.user?.allRoles?.includes('FDD')
) || [];
const hasAssignment = assignments.length > 0 || fddParticipants.length > 0;
const primaryFddUser = fddParticipants[0]?.user;
const mandatoryFinancialDocs = [
{ type: 'Bank Statement', label: 'Bank Statements' },
{ type: 'Income Tax Returns (ITR)', label: 'ITR (Last 3 Years)' },
{ type: 'CIBIL Report', label: 'CIBIL / Credit Reports' },
{ type: 'Property Documents', label: 'Property Documents' },
{ type: 'Business Valuation Report', label: 'Valuation Reports' },
{ type: 'FDD Final Audit Report', label: 'Final Audit Report' },
];
const getDocByTypeName = (typeName: string) => {
const target = typeName.toLowerCase();
return (documents || []).find((d: any) => {
const docType = (d.documentType || '').toLowerCase();
const fileName = (d.fileName || '').toLowerCase();
if (docType === target) return true;
if (target.includes('itr') && (docType.includes('itr') || fileName.includes('itr'))) return true;
if (target.includes('bank statement') && (docType.includes('bank') || fileName.includes('bank'))) return true;
if (target.includes('cibil') && (docType.includes('cibil') || fileName.includes('cibil') || docType.includes('credit'))) return true;
return false;
});
};
const isFddSupportDoc = (d: any) => {
const type = (d.documentType || '').toLowerCase();
const stage = (d.stage || '').toLowerCase();
return stage === 'fdd' || type.includes('report') || type.includes('itr') || type.includes('bank') || type.includes('cibil') || type.includes('valuation');
};
if (!hasAssignment && !['FDD Verification', 'LOI In Progress', 'Payment Pending'].includes(application.status)) {
return (
<div className="space-y-6" data-testid="onboarding-fdd-no-assignment">
<div className="flex flex-col items-center justify-center p-12 bg-slate-50 rounded-2xl border-2 border-dashed border-slate-200">
<ShieldCheck className="w-12 h-12 text-slate-300 mb-4" />
<h3 className="text-slate-900 font-semibold uppercase tracking-widest text-xs">No FDD Assignment</h3>
<p className="text-slate-500 text-[10px] text-center max-w-xs mt-2 font-medium leading-relaxed uppercase tracking-tight">
The Financial Due Diligence process has not been initiated for this application yet.
</p>
</div>
{(currentUser?.role === 'DD Admin' || currentUser?.role === 'Super Admin') && (
<Card className="border-amber-100 bg-amber-50/30 overflow-hidden rounded-2xl" data-testid="onboarding-fdd-initiate-card">
<CardHeader className="pb-2">
<CardTitle className="text-xs font-black uppercase tracking-widest text-amber-800 flex items-center gap-2">
<ShieldAlert className="w-4 h-4" />
Initiate FDD Audit
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1">
<label className="text-[10px] font-black uppercase tracking-widest text-slate-400 mb-1.5 block">Select FDD Agency <span className="text-red-500">*</span></label>
<select
className="w-full h-11 bg-white border border-slate-200 rounded-xl px-4 text-sm font-medium focus:ring-2 focus:ring-amber-500/20 focus:border-amber-500 outline-none transition-all shadow-sm"
value={selectedAgencyId}
onChange={(e) => setSelectedAgencyId(e.target.value)}
data-testid="onboarding-fdd-agency-select"
>
<option value="">Choose partner agency...</option>
{(fddAgencies || []).map((agency: any) => (
<option key={agency.id} value={agency.id} data-testid={`onboarding-fdd-agency-option-${agency.id}`}>
{agency.fullName || agency.name} ({agency.email})
</option>
))}
</select>
</div>
<div className="flex items-end">
<Button
className="bg-slate-900 text-white hover:bg-slate-800 font-black text-[10px] uppercase tracking-widest px-8 h-11 border-none shadow-lg shadow-slate-900/10 transition-all active:scale-[0.98]"
onClick={handleAssignAgency}
disabled={isAssigningAgency || !selectedAgencyId}
data-testid="onboarding-fdd-assign-button"
>
{isAssigningAgency ? 'Assigning...' : 'Assign & Start Audit'}
</Button>
</div>
</div>
</CardContent>
</Card>
)}
</div>
);
}
return (
<div className="space-y-8" data-testid="onboarding-fdd-audit-content">
{hasAssignment && (
<div className="flex items-center justify-between p-4 bg-slate-50 border border-slate-200 rounded-xl mb-6" data-testid="onboarding-fdd-assignment-banner">
<div className="flex items-center gap-3">
<div className="p-2 bg-amber-100 rounded-lg">
<ShieldCheck className="w-5 h-5 text-amber-600" />
</div>
<div>
<h4 className="text-sm font-bold text-slate-900">FDD Assignment Active</h4>
{primaryFddUser && <p className="text-xs text-slate-500 font-medium" data-testid="onboarding-fdd-assigned-user">Assigned to: {primaryFddUser.name}</p>}
</div>
</div>
</div>
)}
<Card className="border-slate-200 shadow-sm overflow-hidden rounded-2xl" data-testid="onboarding-fdd-checklist-card">
<CardHeader className="bg-slate-50/50 border-b border-slate-100 py-4">
<CardTitle className="text-sm font-black uppercase tracking-widest text-slate-500 flex items-center justify-between">
<div className="flex items-center gap-2"><ClipboardList className="w-4 h-4" /> Financial Artefacts Checklist</div>
<Badge variant="outline" className="text-[10px] bg-white">Verify before sign-off</Badge>
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<div className="divide-y divide-slate-100">
{mandatoryFinancialDocs.map((docType, index) => {
const doc = getDocByTypeName(docType.type);
return (
<div key={docType.type} className="flex items-center justify-between p-4 px-6 hover:bg-slate-50/50 transition-colors" data-testid={`onboarding-fdd-checklist-item-${index}`}>
<div className="flex items-center gap-4">
<div className={cn('w-8 h-8 rounded-lg flex items-center justify-center', doc ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-50 text-slate-300')}>
{doc ? <CheckCircle className="w-5 h-5" /> : <AlertCircle className="w-5 h-5" />}
</div>
<div>
<p className="text-sm font-bold text-slate-800">{docType.label}</p>
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-tighter" data-testid={`onboarding-fdd-checklist-status-${index}`}>
{doc ? `Uploaded: ${formatDateTime(doc.createdAt)}` : 'Missing in Documentation'}
</p>
</div>
</div>
{doc ? (
<Button variant="ghost" size="sm" className="h-8 text-blue-600 font-black text-[10px] uppercase tracking-widest hover:bg-blue-50" onClick={() => { setPreviewDoc(doc); setShowPreviewModal(true); }} data-testid={`onboarding-fdd-checklist-preview-${index}`}>
<Eye className="w-4 h-4 mr-1" /> Preview
</Button>
) : (
<Button
variant="outline"
size="sm"
className="h-8 border-slate-200 text-slate-500 font-black text-[10px] uppercase tracking-widest hover:bg-slate-50 hover:text-blue-600"
data-testid={`onboarding-fdd-checklist-upload-${index}`}
onClick={() => {
const input = document.createElement('input');
input.type = 'file';
input.onchange = async (e: any) => {
const file = e.target.files[0];
if (!file) return;
try {
setIsUploading(true);
const formData = new FormData();
formData.append('file', file);
formData.append('documentType', docType.type);
formData.append('stage', 'FDD');
formData.append('applicationId', application.id);
const res = await onboardingService.uploadDocument(application.id, formData);
if (docType.type === 'FDD Final Audit Report') {
await onboardingService.submitFddReport({
applicationId: application.id,
reportDocumentId: res.data?.id || res.id,
findings: 'Final Audit Report uploaded via checklist.',
recommendation: 'REVIEW_PENDING',
});
fetchApplication();
}
toast.success(`${docType.label} uploaded successfully`);
refreshDocuments();
} catch {
toast.error('Upload failed');
} finally {
setIsUploading(false);
}
};
input.click();
}}
>
<Upload className="w-4 h-4 mr-1 text-slate-300" /> Upload
</Button>
)}
</div>
);
})}
</div>
</CardContent>
</Card>
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-slate-900">Supporting Audit Documents</h3>
<Badge variant="outline" className="bg-slate-50 text-slate-500 border-slate-200" data-testid="onboarding-fdd-support-docs-count">
{(documents || []).filter(isFddSupportDoc).length} Document(s)
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4" data-testid="onboarding-fdd-support-docs-grid">
{(documents || []).filter(isFddSupportDoc).map((doc: any, index: number) => (
<div key={doc.id} className="group bg-white border border-slate-200 rounded-xl p-4 flex items-center justify-between hover:border-amber-400 transition-all hover:shadow-md" data-testid={`onboarding-fdd-support-doc-${index}`}>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-slate-50 flex items-center justify-center"><FileText className="w-5 h-5 text-slate-400" /></div>
<div className="overflow-hidden">
<p className="text-slate-900 font-bold text-sm truncate max-w-[150px]" title={doc.fileName} data-testid={`onboarding-fdd-support-doc-name-${index}`}>{doc.fileName}</p>
<p className="text-slate-500 text-[10px] font-medium uppercase">{doc.documentType}</p>
</div>
</div>
<div className="flex gap-1">
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-amber-600 hover:bg-amber-50" onClick={() => {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000';
window.open(`${baseUrl}/${doc.filePath}`, '_blank');
}} data-testid={`onboarding-fdd-support-doc-download-${index}`}>
<Download className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-amber-600 hover:bg-amber-50" onClick={() => { setPreviewDoc(doc); setShowPreviewModal(true); }} data-testid={`onboarding-fdd-support-doc-preview-${index}`}>
<Eye className="w-4 h-4" />
</Button>
</div>
</div>
))}
{(documents || []).filter(isFddSupportDoc).length === 0 && (
<div className="col-span-full p-8 text-center bg-slate-50 rounded-xl border border-dashed border-slate-200" data-testid="onboarding-fdd-support-docs-empty">
<p className="text-slate-400 text-sm">No supporting audit documents uploaded yet.</p>
</div>
)}
</div>
</div>
</div>
);
}