266 lines
14 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
|
|
|