669 lines
45 KiB
TypeScript
669 lines
45 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import { API } from '@/api/API';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { useSelector } from 'react-redux';
|
|
import { RootState } from '@/store';
|
|
import {
|
|
ArrowLeft,
|
|
FileText,
|
|
Upload,
|
|
Loader2,
|
|
Eye,
|
|
CheckCircle2,
|
|
Clock
|
|
} from 'lucide-react';
|
|
import { WorkNotesPage } from './WorkNotesPage';
|
|
import { toast } from 'sonner';
|
|
import { DocumentPreviewModal } from '@/components/ui/DocumentPreviewModal';
|
|
import { formatDateTime } from '@/components/ui/utils';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
DialogFooter
|
|
} from '@/components/ui/dialog';
|
|
import { Button } from '@/components/ui/button';
|
|
import { AlertTriangle, Info, ShieldCheck } from 'lucide-react';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
|
|
|
|
export function FDDApplicationDetails() {
|
|
const { id } = useParams<{ id: string }>();
|
|
const navigate = useNavigate();
|
|
const [application, setApplication] = useState<any>(null);
|
|
const [assignment, setAssignment] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [uploading, setUploading] = useState(false);
|
|
const [selectedDocType, setSelectedDocType] = useState('');
|
|
const [activeTab, setActiveTab] = useState<'details' | 'worknotes'>('details');
|
|
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
|
const [selectedPreviewDoc, setSelectedPreviewDoc] = useState<any>(null);
|
|
const [showFinalizeModal, setShowFinalizeModal] = useState(false);
|
|
const [showFlagModal, setShowFlagModal] = useState(false);
|
|
const [fddAuditFindings, setFddAuditFindings] = useState<string>('');
|
|
const user = useSelector((state: RootState) => state.auth.user);
|
|
const isFddRole = user?.role === 'FDD';
|
|
|
|
useEffect(() => {
|
|
if (id) fetchApplication();
|
|
}, [id]);
|
|
|
|
const fetchApplication = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const [appRes, assRes]: any = await Promise.all([
|
|
API.getApplicationById(id!),
|
|
API.getFddAssignment(id!)
|
|
]);
|
|
|
|
if (appRes.data?.success) {
|
|
setApplication(appRes.data.data);
|
|
}
|
|
if (assRes.data?.success) {
|
|
setAssignment(assRes.data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching application:', error);
|
|
const errorMsg = (error as any).response?.data?.message || 'Access Denied: Not authorized for FDD access';
|
|
toast.error(errorMsg);
|
|
navigate('/fdd-dashboard');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
if (!file || !selectedDocType) {
|
|
if (!selectedDocType) toast.error('Please select a document type first');
|
|
return;
|
|
}
|
|
|
|
setUploading(true);
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
formData.append('documentType', selectedDocType);
|
|
formData.append('stage', 'FDD');
|
|
formData.append('applicationId', id!);
|
|
formData.append('requestType', 'application');
|
|
|
|
try {
|
|
const response: any = await API.uploadDocument(id!, formData);
|
|
if (response.data?.success) {
|
|
// Automatically link if it's the final report category
|
|
if (selectedDocType === 'FDD Final Audit Report') {
|
|
const docId = response.data.data?.id || response.data.id;
|
|
await API.submitFddReport({
|
|
assignmentId: assignment?.id,
|
|
applicationId: id,
|
|
reportDocumentId: docId,
|
|
findings: 'Final Audit Report submitted.',
|
|
recommendation: 'REVIEW_PENDING'
|
|
});
|
|
}
|
|
toast.success(`${selectedDocType} uploaded successfully`);
|
|
fetchApplication();
|
|
setSelectedDocType('');
|
|
}
|
|
} catch (error) {
|
|
toast.error('Failed to upload document');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const handlePreview = (doc: any) => {
|
|
if (!doc || !doc.filePath) {
|
|
toast.error('Document source file not found');
|
|
return;
|
|
}
|
|
|
|
setSelectedPreviewDoc({
|
|
fileName: doc.originalName || doc.fileName || 'Document',
|
|
filePath: doc.filePath,
|
|
documentType: doc.documentType,
|
|
createdAt: doc.createdAt,
|
|
mimeType: doc.mimeType
|
|
});
|
|
setIsPreviewOpen(true);
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center h-[70vh] bg-slate-50/50 rounded-2xl border border-slate-200 border-dashed">
|
|
<Loader2 className="w-12 h-12 animate-spin text-blue-600 mb-4" />
|
|
<p className="text-slate-500 font-medium">Authenticating and loading secure data...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!application) return null;
|
|
|
|
const isFDDStageActive = application.currentStage === 'FDD_VERIFICATION' || application.currentStage === 'FDD';
|
|
|
|
// Check if report is already submitted in assignment
|
|
const isReportSubmitted = assignment?.status === 'Report Submitted';
|
|
|
|
// Check if the application has already passed the FDD stage
|
|
const isCompleted = !isFDDStageActive && (application.overallStatus !== 'Active' || application.currentProgress >= 75) || isReportSubmitted;
|
|
|
|
// Check if the application has not yet arrived at the FDD stage
|
|
const isNotReachedYet = !isFDDStageActive && application.currentProgress < 70 && !isReportSubmitted;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 max-w-7xl mx-auto mb-10">
|
|
{application?.statutoryStatus === 'Flagged' && (
|
|
<div className="bg-red-50 border border-red-200 p-4 rounded-xl flex items-center gap-4 animate-in fade-in slide-in-from-top-4 duration-500" data-testid="onboarding-fdd-details-flag-banner">
|
|
<div className="bg-red-100 p-2 rounded-lg">
|
|
<AlertTriangle className="w-5 h-5 text-red-600" />
|
|
</div>
|
|
<div>
|
|
<h4 className="text-sm font-bold text-red-900 leading-none">APPLICATION FLAGGED BY YOU</h4>
|
|
<p className="text-red-700 text-[10px] font-bold uppercase tracking-wider mt-1 opacity-80">Marked as non-responsive for follow-up by DD Team</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{/* Action Bar */}
|
|
<div className="flex items-center justify-between">
|
|
<button
|
|
onClick={() => navigate('/fdd-dashboard')}
|
|
className="flex items-center gap-2 text-slate-600 hover:text-slate-900 font-medium transition-all group"
|
|
data-testid="onboarding-fdd-details-back-btn"
|
|
>
|
|
<div className="p-2 rounded-full group-hover:bg-slate-100 transition-colors">
|
|
<ArrowLeft className="w-5 h-5" />
|
|
</div>
|
|
Back to Dashboard
|
|
</button>
|
|
<div className="flex items-center gap-3">
|
|
{isNotReachedYet ? (
|
|
<div className="flex items-center gap-2 px-4 py-2 bg-slate-100 border border-slate-200 text-slate-500 font-bold text-[10px] uppercase tracking-[0.1em] rounded-lg" data-testid="onboarding-fdd-details-awaiting-badge">
|
|
<Clock className="w-4 h-4" />
|
|
Awaiting Previous Stages
|
|
</div>
|
|
) : !isCompleted ? (
|
|
<>
|
|
{isFddRole && (
|
|
<button
|
|
disabled={uploading}
|
|
onClick={() => setShowFlagModal(true)}
|
|
className="px-4 py-2 bg-red-50 text-red-600 font-bold text-xs uppercase tracking-wider hover:bg-red-100 rounded-lg transition-all flex items-center gap-2 border border-red-100 shadow-sm"
|
|
data-testid="onboarding-fdd-details-flag-btn"
|
|
>
|
|
<AlertTriangle className="w-4 h-4" />
|
|
Flag Non-Responsive
|
|
</button>
|
|
)}
|
|
</>
|
|
) : (
|
|
<div className="flex items-center gap-2 px-4 py-2 bg-green-50 border border-green-200 text-green-700 font-bold text-[10px] uppercase tracking-[0.1em] rounded-lg shadow-inner" data-testid="onboarding-fdd-details-submitted-badge">
|
|
<CheckCircle2 className="w-4 h-4" />
|
|
Final Audit Report Submitted
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Header Card */}
|
|
<Card className="border border-slate-200 shadow-sm bg-white" data-testid="onboarding-fdd-details-header">
|
|
<CardContent className="p-6">
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-14 h-14 bg-slate-900 text-white rounded-lg flex items-center justify-center font-bold text-xl" data-testid="onboarding-fdd-details-avatar">
|
|
{application.applicantName.charAt(0)}
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-0.5">
|
|
<h1 className="text-2xl font-bold text-slate-900 tracking-tight" data-testid="onboarding-fdd-details-name">{application.applicantName}</h1>
|
|
<Badge variant="outline" className="text-slate-500 font-medium px-2 py-0" data-testid="onboarding-fdd-details-id-badge">
|
|
{application.applicationId}
|
|
</Badge>
|
|
</div>
|
|
<div className="flex items-center gap-3 text-sm text-slate-500" data-testid="onboarding-fdd-details-meta">
|
|
<span>{application.city}, {application.state}</span>
|
|
<span className="text-slate-300">•</span>
|
|
<span>{application.businessType || 'Dealership'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<div className="text-right hidden md:block">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Status</p>
|
|
<p className="text-sm font-bold text-slate-700">Financial Due Diligence</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Navigation Tabs */}
|
|
<div className="flex items-center gap-8 border-b border-slate-200" data-testid="onboarding-fdd-details-tabs">
|
|
<button
|
|
onClick={() => setActiveTab('details')}
|
|
className={`pb-3 text-sm font-semibold transition-all relative ${
|
|
activeTab === 'details' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
data-testid="onboarding-fdd-details-tab-workspace"
|
|
>
|
|
Workspace
|
|
{activeTab === 'details' && <div className="absolute bottom-[-1px] left-0 right-0 h-0.5 bg-blue-600" />}
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('worknotes')}
|
|
className={`pb-3 text-sm font-semibold transition-all relative ${
|
|
activeTab === 'worknotes' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
data-testid="onboarding-fdd-details-tab-worknotes"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
Work Notes
|
|
</div>
|
|
{activeTab === 'worknotes' && <div className="absolute bottom-[-1px] left-0 right-0 h-0.5 bg-blue-600" />}
|
|
</button>
|
|
</div>
|
|
|
|
{activeTab === 'details' ? (
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Left Column: Financial Data & Uploads */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
<Card className="border border-slate-200 shadow-sm bg-white" data-testid="onboarding-fdd-details-workspace-card">
|
|
<CardHeader className="border-b border-slate-100 px-6 py-4">
|
|
<CardTitle className="text-base font-bold flex items-center gap-2 text-slate-800">
|
|
<Upload className="w-4 h-4 text-slate-500" />
|
|
{isCompleted ? 'Finalized Financial Reports' : isNotReachedYet ? 'Audit Workspace' : 'Financial Report Submission'}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-6">
|
|
{isNotReachedYet && (
|
|
<div className="mb-6 p-8 bg-slate-50 border border-dashed border-slate-200 rounded-xl flex flex-col items-center justify-center text-center" data-testid="onboarding-fdd-details-not-active">
|
|
<div className="w-16 h-16 bg-white rounded-full flex items-center justify-center text-slate-300 mb-4 shadow-sm">
|
|
<Clock className="w-8 h-8" />
|
|
</div>
|
|
<h4 className="text-lg font-bold text-slate-900 mb-2">Stage Not Yet Active</h4>
|
|
<p className="text-sm text-slate-500 max-w-sm mb-6">This application is still being processed in previous documentation or interview stages. The FDD workspace will activate once the previous stages are approved.</p>
|
|
<div className="flex items-center gap-2 text-[10px] font-bold text-slate-400 uppercase tracking-widest px-4 py-1.5 bg-white rounded-full border border-slate-200">
|
|
Status: {application.status || 'Pending Review'}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{isCompleted && (
|
|
<div className="mb-6 p-4 bg-green-50/50 border border-green-100 rounded-xl flex items-center gap-4" data-testid="onboarding-fdd-details-completed-alert">
|
|
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center text-green-600 shrink-0">
|
|
<CheckCircle2 className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-green-800">Verification Stage Completed</p>
|
|
<p className="text-[11px] text-green-600 font-medium">The FDD report has been submitted and the case is now locked for further audits.</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{!isCompleted && !isNotReachedYet && (
|
|
<div className="p-10 border-2 border-dashed border-slate-200 rounded-lg flex flex-col items-center justify-center text-center" data-testid="onboarding-fdd-details-upload-section">
|
|
<div className="w-12 h-12 bg-slate-50 text-slate-400 rounded-full flex items-center justify-center mb-4">
|
|
<FileText className="w-6 h-6" />
|
|
</div>
|
|
<p className="text-slate-600 font-medium mb-1" data-testid="onboarding-fdd-details-upload-title">
|
|
{isFddRole ? 'Select and upload the due diligence report' : 'View Authorized Documents'}
|
|
</p>
|
|
<p className="text-slate-400 text-xs mb-6" data-testid="onboarding-fdd-details-upload-hint">
|
|
{isFddRole ? 'PDF or JPG formats accepted (Max 10MB)' : 'You are in View-Only mode for this Audit'}
|
|
</p>
|
|
|
|
{isFddRole && (
|
|
<div className="w-full max-w-sm space-y-4">
|
|
<select
|
|
value={selectedDocType}
|
|
onChange={(e) => setSelectedDocType(e.target.value)}
|
|
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded text-sm font-medium text-slate-700 outline-none focus:ring-1 focus:ring-blue-500 transition-all"
|
|
data-testid="onboarding-fdd-details-doc-type-select"
|
|
>
|
|
<option value="">Select Document Category...</option>
|
|
<option value="FDD Final Audit Report">FDD Final Audit Report</option>
|
|
<option value="Bank Statement">Bank Statement</option>
|
|
<option value="Income Tax Returns (ITR)">Income Tax Returns (ITR)</option>
|
|
<option value="CIBIL Report">CIBIL Report</option>
|
|
<option value="Business Valuation Report">Business Valuation Report</option>
|
|
<option value="Property Documents">Property Documents</option>
|
|
</select>
|
|
|
|
<div className="relative">
|
|
{uploading ? (
|
|
<div className="w-full py-2.5 bg-slate-100 rounded flex items-center justify-center gap-2" data-testid="onboarding-fdd-details-uploading-state">
|
|
<Loader2 className="w-4 h-4 animate-spin text-slate-400" />
|
|
<span className="text-slate-500 text-xs font-bold uppercase tracking-wider">Uploading...</span>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<input
|
|
type="file"
|
|
className="absolute inset-0 opacity-0 cursor-pointer"
|
|
onChange={handleFileUpload}
|
|
disabled={!selectedDocType}
|
|
data-testid="onboarding-fdd-details-file-input"
|
|
/>
|
|
<div className={`w-full py-2.5 text-center font-bold uppercase tracking-wider text-xs rounded transition-all ${
|
|
!selectedDocType ? 'bg-slate-100 text-slate-300' : 'bg-slate-900 text-white hover:bg-slate-800'
|
|
}`} data-testid="onboarding-fdd-details-browse-btn">
|
|
Browse & Upload
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* List of Uploaded Documents */}
|
|
<div className="mt-8 border-t border-slate-100 pt-8" data-testid="onboarding-fdd-details-documents-section">
|
|
<h3 className="text-sm font-bold text-slate-800 mb-4 px-1">Submitted Documentation</h3>
|
|
<div className="space-y-6">
|
|
{/* SECTION 1: APPLICANT DOCUMENTS */}
|
|
<div data-testid="onboarding-fdd-details-applicant-docs">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2 flex items-center gap-2">
|
|
<div className="w-1.5 h-1.5 rounded-full bg-blue-500"></div>
|
|
Applicant's KYC & Financials
|
|
</p>
|
|
<div className="space-y-2">
|
|
{application.uploadedDocuments?.filter((d: any) => !d.uploader || d.uploader.roleCode !== 'FDD').map((doc: any, i: number) => (
|
|
<div key={i} className="p-3 border border-slate-100 rounded flex items-center justify-between hover:bg-slate-50 transition-all group" data-testid={`onboarding-fdd-details-applicant-doc-row-${i}`}>
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded bg-slate-100 flex items-center justify-center text-slate-400 group-hover:bg-white transition-colors">
|
|
<FileText className="w-4 h-4" />
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<p className="text-xs font-bold text-slate-900" data-testid={`onboarding-fdd-details-applicant-doc-name-${i}`}>{doc.originalName || doc.fileName}</p>
|
|
<span className="text-[8px] bg-slate-100 text-slate-500 px-1 py-0.5 rounded uppercase font-bold tracking-tighter">APPLICANT</span>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 font-medium" data-testid={`onboarding-fdd-details-applicant-doc-meta-${i}`}>
|
|
{doc.documentType} • {formatDateTime(doc.createdAt)}
|
|
{doc.uploader?.fullName && ` • by ${doc.uploader.fullName}`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => handlePreview(doc)}
|
|
className="p-1.5 hover:bg-white rounded text-slate-400 hover:text-blue-600 transition-all"
|
|
data-testid={`onboarding-fdd-details-applicant-doc-preview-${i}`}
|
|
>
|
|
<Eye className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{application.uploadedDocuments?.filter((d: any) => !d.uploader || d.uploader.roleCode !== 'FDD').length === 0 && (
|
|
<p className="text-[10px] text-slate-400 italic px-1" data-testid="onboarding-fdd-details-applicant-docs-empty">No documents from applicant yet.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* SECTION 2: MY SUBMISSIONS */}
|
|
<div data-testid="onboarding-fdd-details-my-submissions">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2 flex items-center gap-2">
|
|
<div className="w-1.5 h-1.5 rounded-full bg-amber-500"></div>
|
|
My Uploaded Reports
|
|
</p>
|
|
<div className="space-y-2">
|
|
{application.uploadedDocuments?.filter((d: any) => d.uploader?.roleCode === 'FDD').map((doc: any, i: number) => (
|
|
<div key={i} className="p-3 border border-amber-100 bg-amber-50/30 rounded flex items-center justify-between hover:bg-amber-50 transition-all group" data-testid={`onboarding-fdd-details-my-report-row-${i}`}>
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded bg-amber-100 flex items-center justify-center text-amber-500">
|
|
<FileText className="w-4 h-4" />
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<p className="text-xs font-bold text-slate-900" data-testid={`onboarding-fdd-details-my-report-name-${i}`}>{doc.originalName || doc.fileName}</p>
|
|
<span className="text-[8px] bg-amber-500 text-white px-1 py-0.5 rounded uppercase font-bold tracking-tighter">YOUR AUDIT REPORT</span>
|
|
</div>
|
|
<p className="text-[10px] text-slate-400 font-medium" data-testid={`onboarding-fdd-details-my-report-meta-${i}`}>
|
|
{doc.documentType} • {formatDateTime(doc.createdAt)}
|
|
{doc.uploader?.fullName && ` • by ${doc.uploader.fullName}`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => handlePreview(doc)}
|
|
className="p-1.5 hover:bg-white rounded text-slate-400 hover:text-amber-600 transition-all"
|
|
data-testid={`onboarding-fdd-details-my-report-preview-${i}`}
|
|
>
|
|
<Eye className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{application.uploadedDocuments?.filter((d: any) => d.uploader?.roleCode === 'FDD').length === 0 && (
|
|
<div className="text-center py-4 bg-slate-50 border border-dashed border-slate-200 rounded-lg" data-testid="onboarding-fdd-details-my-submissions-empty">
|
|
<p className="text-slate-400 text-[10px]">No audit reports uploaded yet.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Right Column: Applicant Meta & Guidelines */}
|
|
<div className="space-y-6">
|
|
<Card className="border border-slate-200 shadow-sm bg-white" data-testid="onboarding-fdd-details-profile-card">
|
|
<CardHeader className="border-b border-slate-100 px-6 py-4">
|
|
<CardTitle className="text-xs font-bold uppercase tracking-wider text-slate-500">Applicant Profile</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-6 space-y-4">
|
|
<div className="space-y-1 pb-4 border-b border-slate-50" data-testid="onboarding-fdd-details-target-loc">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Target Location</p>
|
|
<p className="text-sm font-extrabold text-slate-900">{application.city}, {application.state}</p>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4 text-xs" data-testid="onboarding-fdd-details-profile-meta">
|
|
<div className="space-y-1">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Education</p>
|
|
<p className="font-bold text-slate-800">{application.education || 'N/A'}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Experience</p>
|
|
<p className="font-bold text-slate-800">{application.experienceYears || '0'} Years</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Investment Cap</p>
|
|
<p className="font-bold text-slate-800">{application.investmentCapacity || 'N/A'}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Age</p>
|
|
<p className="font-bold text-slate-800">{application.age || 'N/A'}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1 pt-4 border-t border-slate-50 text-xs" data-testid="onboarding-fdd-details-communication">
|
|
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Communication</p>
|
|
<p className="font-bold text-slate-800">{application.email}</p>
|
|
<p className="text-slate-500 font-medium">{application.phone}</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="p-6 bg-slate-900 rounded-lg text-white font-medium" data-testid="onboarding-fdd-details-instructions">
|
|
<h4 className="text-sm font-bold mb-2">Instructions</h4>
|
|
<ul className="text-xs text-slate-300 space-y-2 list-disc pl-4">
|
|
<li>Bank statements must cover 12 months.</li>
|
|
<li>GST discrepancies must be noted.</li>
|
|
<li>Verify property papers with originals.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="bg-white rounded-lg border border-slate-200 min-h-[600px] overflow-hidden">
|
|
<WorkNotesPage onBack={() => setActiveTab('details')} requestId={id} requestType="application" />
|
|
</div>
|
|
)}
|
|
|
|
<DocumentPreviewModal
|
|
isOpen={isPreviewOpen}
|
|
onClose={() => setIsPreviewOpen(false)}
|
|
document={selectedPreviewDoc}
|
|
/>
|
|
|
|
{/* Finalize Confirmation Modal */}
|
|
<Dialog open={showFinalizeModal} onOpenChange={setShowFinalizeModal}>
|
|
<DialogContent className="max-w-md p-0 overflow-hidden border-none shadow-2xl" data-testid="onboarding-fdd-details-finalize-modal">
|
|
<div className="bg-slate-950 p-6 flex items-center justify-center relative overflow-hidden">
|
|
<div className="absolute inset-0 bg-gradient-to-br from-amber-600/20 to-transparent" />
|
|
<div className="w-16 h-16 bg-amber-600/20 rounded-full flex items-center justify-center animate-pulse relative z-10">
|
|
<ShieldCheck className="w-8 h-8 text-amber-500" />
|
|
</div>
|
|
</div>
|
|
<div className="p-8 space-y-4">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-2xl font-bold text-slate-900 text-center" data-testid="onboarding-fdd-details-finalize-title">Submit Audit Report</DialogTitle>
|
|
<DialogDescription className="text-slate-500 text-center pt-2 leading-relaxed text-base" data-testid="onboarding-fdd-details-finalize-desc">
|
|
You are about to submit your final findings. This action will <span className="font-bold text-slate-800 underline decoration-amber-500 decoration-2">notify the Admin</span> for review and approval.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="bg-amber-50 p-4 rounded-xl flex gap-3 border border-amber-100 italic" data-testid="onboarding-fdd-details-finalize-info">
|
|
<Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
|
<p className="text-xs text-amber-800 leading-normal">
|
|
Once submitted, you cannot edit the findings. Ensure all documents are uploaded.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4 pt-2">
|
|
|
|
<div className="space-y-2">
|
|
<Label className="text-[10px] font-black uppercase tracking-widest text-slate-400">Detailed Audit Findings & Remarks</Label>
|
|
<Textarea
|
|
placeholder="Enter detailed financial observations..."
|
|
className="min-h-[120px] bg-slate-50 border-slate-200 rounded-xl focus:ring-amber-500 text-sm resize-none"
|
|
value={fddAuditFindings}
|
|
onChange={(e) => setFddAuditFindings(e.target.value)}
|
|
data-testid="onboarding-fdd-details-finalize-remarks"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="flex flex-col sm:flex-row gap-3 pt-4 sm:pt-6">
|
|
<Button
|
|
variant="outline"
|
|
className="w-full sm:flex-1 h-12 rounded-xl font-bold text-slate-600 hover:bg-slate-50 border-slate-200"
|
|
onClick={() => setShowFinalizeModal(false)}
|
|
disabled={uploading}
|
|
data-testid="onboarding-fdd-details-finalize-cancel"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
className="w-full sm:flex-1 h-12 rounded-xl font-bold bg-slate-950 hover:bg-slate-900 text-white shadow-lg shadow-slate-200 transition-all active:scale-95 border-b-2 border-amber-600"
|
|
data-testid="onboarding-fdd-details-finalize-confirm"
|
|
onClick={async () => {
|
|
try {
|
|
if (!fddAuditFindings.trim()) {
|
|
toast.error('Please provide findings.');
|
|
return;
|
|
}
|
|
setUploading(true);
|
|
const latestReport = assignment?.reports?.[0];
|
|
const res: any = await API.submitFddReport({
|
|
assignmentId: assignment?.id,
|
|
applicationId: id,
|
|
reportDocumentId: latestReport?.reportDocumentId,
|
|
findings: fddAuditFindings,
|
|
recommendation: null
|
|
});
|
|
|
|
if (res.data?.success) {
|
|
toast.success('FDD Report submitted successfully.');
|
|
setShowFinalizeModal(false);
|
|
fetchApplication();
|
|
}
|
|
} catch (e) {
|
|
toast.error('Failed to submit report');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
}}
|
|
disabled={uploading}
|
|
>
|
|
{uploading ? <Loader2 className="w-5 h-5 animate-spin" /> : 'Confirm & Submit'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Flag Non-Responsive Confirmation Modal */}
|
|
<Dialog open={showFlagModal} onOpenChange={setShowFlagModal}>
|
|
<DialogContent className="max-w-md p-0 overflow-hidden border-none shadow-2xl" data-testid="onboarding-fdd-details-flag-modal">
|
|
<div className="bg-slate-950 p-6 flex items-center justify-center relative overflow-hidden">
|
|
<div className="absolute inset-0 bg-gradient-to-br from-red-600/20 to-transparent" />
|
|
<div className="w-16 h-16 bg-red-600/20 rounded-full flex items-center justify-center relative z-10">
|
|
<AlertTriangle className="w-8 h-8 text-red-500" />
|
|
</div>
|
|
</div>
|
|
<div className="p-8 space-y-4">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-2xl font-bold text-slate-900 text-center" data-testid="onboarding-fdd-details-flag-modal-title">Flag Applicant</DialogTitle>
|
|
<DialogDescription className="text-slate-500 text-center pt-2 leading-relaxed text-base" data-testid="onboarding-fdd-details-flag-modal-desc">
|
|
Are you sure you want to flag this applicant as <span className="font-bold text-red-600">Non-Responsive</span>?
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="bg-red-50 p-4 rounded-xl flex gap-3 border border-red-100 italic">
|
|
<p className="text-xs text-red-800 leading-normal text-center w-full" data-testid="onboarding-fdd-details-flag-modal-text">
|
|
"Applicant is non-responsive to FDD queries."
|
|
</p>
|
|
</div>
|
|
|
|
<DialogFooter className="flex flex-col sm:flex-row gap-3 pt-4 sm:pt-6">
|
|
<Button
|
|
variant="outline"
|
|
className="w-full sm:flex-1 h-12 rounded-xl font-bold text-slate-600 hover:bg-slate-50 border-slate-200"
|
|
onClick={() => setShowFlagModal(false)}
|
|
disabled={uploading}
|
|
data-testid="onboarding-fdd-details-flag-modal-cancel"
|
|
>
|
|
Go Back
|
|
</Button>
|
|
<Button
|
|
className="w-full sm:flex-1 h-12 rounded-xl font-bold bg-slate-950 hover:bg-slate-900 text-white shadow-lg shadow-slate-200 transition-all active:scale-95 border-b-2 border-red-600"
|
|
data-testid="onboarding-fdd-details-flag-modal-confirm"
|
|
onClick={async () => {
|
|
try {
|
|
setUploading(true);
|
|
// Use dedicated API that updates model AND creates a specific Audit Log entry
|
|
await API.flagNonResponsive({
|
|
applicationId: id,
|
|
remarks: 'Applicant is non-responsive to FDD queries.'
|
|
});
|
|
toast.error('Application flagged for non-responsiveness.');
|
|
setShowFlagModal(false);
|
|
fetchApplication();
|
|
} catch (e) {
|
|
toast.error('Action failed');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
}}
|
|
disabled={uploading}
|
|
>
|
|
{uploading ? <Loader2 className="w-5 h-5 animate-spin" /> : 'Flag Applicant'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|
|
|