import { useState, useEffect } from 'react'; import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { toast } from 'sonner'; import { Application, ApplicationStatus } from '../../lib/mock-data'; import { onboardingService } from '../../services/onboarding.service'; import { auditService } from '../../services/audit.service'; import { eorService } from '../../services/eor.service'; import QuestionnaireResponseView from './QuestionnaireResponseView'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; import { cn, formatDateTime } from '@/components/ui/utils'; import { DocumentPreviewModal } from '../ui/DocumentPreviewModal'; import { Button } from '../ui/button'; import { Badge } from '../ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { ArrowLeft, CheckCircle, XCircle, MessageSquare, Calendar, Clock, Upload, Download, FileText, User, MapPin, Mail, Phone, GraduationCap, Bike, Award, ClipboardList, ChevronDown, ChevronRight, GitBranch, Star, Zap, ShieldCheck, Eye, Lock, } from 'lucide-react'; import { Progress } from '../ui/progress'; import { Textarea } from '../ui/textarea'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Alert, AlertDescription, AlertTitle } from '../ui/alert'; import { AlertCircle, RefreshCw, Check, Loader2 } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from '../ui/dialog'; import { ScrollArea } from '../ui/scroll-area'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '../ui/table'; import { Checkbox } from '../ui/checkbox'; import { Separator } from '../ui/separator'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../ui/select'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../ui/dropdown-menu'; interface ProcessStage { id: number | string; name: string; status: 'completed' | 'active' | 'pending'; date?: string; description?: string; evaluators?: string[]; documentsUploaded?: number; isParallel?: boolean; isLocked?: boolean; lockMessage?: string; branches?: { name: string; color: string; stages: ProcessStage[]; }[]; } const KT_MATRIX_CRITERIA = [ { name: "Age", weight: 5, maxScore: 10, options: [ { label: "20 to 40 years old", value: "20-40", score: 10 }, { label: "40 to 50 years old", value: "40-50", score: 5 }, { label: "Above 50 years old", value: "above-50", score: 0 } ] }, { name: "Qualification", weight: 5, maxScore: 10, options: [ { label: "Post Graduate", value: "post-graduate", score: 10 }, { label: "Graduate", value: "graduate", score: 5 }, { label: "SSLC", value: "sslc", score: 0 } ] }, { name: "Local Knowledge and Influence", weight: 5, maxScore: 10, options: [ { label: "Excellent PR", value: "excellent", score: 10 }, { label: "Good PR", value: "good", score: 5 }, { label: "Poor PR", value: "poor", score: 0 } ] }, { name: "Base Location vs Applied Location", weight: 10, maxScore: 10, options: [ { label: "Native of the Applied location", value: "native", score: 10 }, { label: "Willing to relocate", value: "relocate", score: 5 }, { label: "Will manage remotely with occasional visits", value: "remote", score: 0 } ] }, { name: "Why Interested in Royal Enfield Business?", weight: 10, maxScore: 10, options: [ { label: "Passion", value: "passion", score: 10 }, { label: "Business expansion / Status symbol", value: "business", score: 5 } ] }, { name: "Passion for Royal Enfield", weight: 10, maxScore: 10, options: [ { label: "Currently owns a Royal Enfield", value: "owns", score: 10 }, { label: "Owned by Immediate Relative", value: "relative", score: 5 }, { label: "Does not own Royal Enfield", value: "none", score: 0 } ] }, { name: "Passion For Rides", weight: 10, maxScore: 10, options: [ { label: "Goes for long rides regularly", value: "regular", score: 10 }, { label: "Goes for long rides rarely", value: "rarely", score: 5 }, { label: "Doesn't go for rides", value: "never", score: 0 } ] }, { name: "With Whom Partnering?", weight: 5, maxScore: 10, options: [ { label: "Within family", value: "family", score: 10 }, { label: "Outside family", value: "outside", score: 0 } ] }, { name: "Who Will Manage the Firm?", weight: 10, maxScore: 10, options: [ { label: "Owner managed", value: "owner", score: 10 }, { label: "Partly owner / partly manager model", value: "partly", score: 5 }, { label: "Fully manager model", value: "manager", score: 0 } ] }, { name: "Business Acumen", weight: 5, maxScore: 10, options: [ { label: "Has similar automobile experience", value: "automobile", score: 10 }, { label: "Has successful business but not automobile", value: "other-business", score: 5 }, { label: "No business experience", value: "no-experience", score: 0 } ] }, { name: "Time Availability", weight: 5, maxScore: 10, options: [ { label: "Full Time Availability for RE Business", value: "full-time", score: 10 }, { label: "Part Time Availability for RE Business", value: "part-time", score: 5 }, { label: "Not Available personally, Manager will handle", value: "manager", score: 0 } ] }, { name: "Property Ownership", weight: 5, maxScore: 10, options: [ { label: "Has own property in proposed location", value: "own", score: 10 }, { label: "Will rent / lease", value: "rent", score: 0 } ] }, { name: "Investment in the Business", weight: 5, maxScore: 10, options: [ { label: "Full own funds", value: "own-funds", score: 10 }, { label: "Partially from the bank", value: "partial-bank", score: 5 }, { label: "Completely bank funded", value: "full-bank", score: 0 } ] }, { name: "Will Expand to Other 2W/4W OEMs?", weight: 5, maxScore: 10, options: [ { label: "No", value: "no", score: 10 }, { label: "Yes", value: "yes", score: 0 } ] }, { name: "Plans of Expansion with RE", weight: 5, maxScore: 10, options: [ { label: "Immediate blood relation will join & expand", value: "blood-relation", score: 10 }, { label: "Wants to expand by himself into more clusters", value: "self-expand", score: 5 }, { label: "No plans for expansion", value: "no-plans", score: 0 } ] } ]; export function ApplicationDetails() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { user: currentUser } = useSelector((state: RootState) => state.auth); const applicationId = id || ''; const onBack = () => navigate(-1); // const application = mockApplications.find(app => app.id === applicationId); const [application, setApplication] = useState(null); const [loading, setLoading] = useState(true); const fetchApplication = async () => { try { setLoading(true); const data = await onboardingService.getApplicationById(applicationId!); // Helper to find stage date const getStageDate = (stageName: string) => { const stage = data.progressTracking?.find((p: any) => p.stageName === stageName); return stage?.stageCompletedAt ? new Date(stage.stageCompletedAt).toISOString() : stage?.stageStartedAt ? new Date(stage.stageStartedAt).toISOString() : undefined; }; // Map backend data to frontend Application interface const mappedApp: Application = { id: data.id, registrationNumber: data.applicationId || 'N/A', name: data.applicantName, email: data.email, phone: data.phone, age: data.age, education: data.education, residentialAddress: data.address || data.city || '', businessAddress: data.address || '', preferredLocation: data.preferredLocation, state: data.state, ownsBike: data.ownRoyalEnfield === 'yes', pastExperience: data.experienceYears ? `${data.experienceYears} years` : (data.description || ''), status: data.overallStatus as ApplicationStatus, questionnaireMarks: data.score || data.questionnaireMarks || 0, // Read from score or correct field questionnaireResponses: data.questionnaireResponses || [], // Map responses rank: 0, totalApplicantsAtLocation: 0, assignedUsers: [], progress: data.progressPercentage || 0, isShortlisted: data.isShortlisted || true, // Default to true for now // Add other fields to match interface companyName: data.companyName, source: data.source, existingDealer: data.existingDealer, royalEnfieldModel: data.royalEnfieldModel, description: data.description, pincode: data.pincode, locationType: data.locationType, ownRoyalEnfield: data.ownRoyalEnfield, address: data.address, // Map timeline dates from progressTracking submissionDate: data.createdAt ? new Date(data.createdAt).toISOString() : '', questionnaireDate: getStageDate('Questionnaire'), shortlistDate: getStageDate('Shortlist'), level1InterviewDate: getStageDate('1st Level Interview'), level2InterviewDate: getStageDate('2nd Level Interview'), level3InterviewDate: getStageDate('3rd Level Interview'), fddDate: getStageDate('FDD'), loiApprovalDate: getStageDate('LOI Approval'), securityDetailsDate: getStageDate('Security Details'), loiIssueDate: getStageDate('LOI Issue'), dealerCodeDate: getStageDate('Dealer Code Generation'), architectureAssignedDate: getStageDate('Architecture Team Assigned'), architectureDocumentDate: getStageDate('Architecture Document Upload'), architectureCompletionDate: getStageDate('Architecture Team Completion'), loaDate: getStageDate('LOA'), eorCompleteDate: getStageDate('EOR Complete'), inaugurationDate: getStageDate('Inauguration'), onboardedDate: data.overallStatus === 'Onboarded' ? (data.updatedAt ? new Date(data.updatedAt).toISOString() : new Date().toISOString()) : undefined, progressTracking: data.progressTracking || [], participants: data.participants || [], dealerCode: data.dealerCode, zoneId: data.zoneId, regionId: data.regionId, areaId: data.areaId, districtId: data.districtId, stageApprovals: data.stageApprovals || [], fddAssignments: data.fddAssignments || [], }; setApplication(mappedApp); } catch (error) { console.error('Failed to fetch application details', error); } finally { setLoading(false); } }; useEffect(() => { if (applicationId) { fetchApplication(); } }, [applicationId]); const [eorData, setEorData] = useState(null); const fetchEorData = async () => { if (!applicationId) return; try { const resp = await eorService.getChecklist(applicationId); if (resp.success && resp.data) { setEorData(resp.data); } } catch (err) { console.log('EOR checklist not found or not yet initiated.'); setEorData(null); } }; useEffect(() => { if (['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application?.status || '')) { fetchEorData(); } }, [applicationId, application?.status]); const eorProgress = eorData?.items ? (eorData.items.filter((item: any) => item.isCompliant).length / eorData.items.length) * 100 : 0; // Audit Trail State const [auditLogs, setAuditLogs] = useState([]); const [auditLoading, setAuditLoading] = useState(false); // Fetch audit logs when application loads useEffect(() => { if (applicationId) { const fetchAuditLogs = async () => { setAuditLoading(true); try { const logs = await auditService.getAuditLogs('application', applicationId); setAuditLogs(Array.isArray(logs) ? logs : []); } catch (error) { console.error('Failed to fetch audit logs', error); setAuditLogs([]); } finally { setAuditLoading(false); } }; fetchAuditLogs(); } }, [applicationId]); const routerLocation = useLocation(); const [activeTab, setActiveTab] = useState(routerLocation.state?.activeTab || 'questionnaire'); const [showApproveModal, setShowApproveModal] = useState(false); const [showOnboardModal, setShowOnboardModal] = useState(false); const [isOnboarding, setIsOnboarding] = useState(false); const [showRejectModal, setShowRejectModal] = useState(false); const [rejectionReason, setRejectionReason] = useState(''); const [scheduledInterviewParticipants, setScheduledInterviewParticipants] = useState([]); const [showScheduleModal, setShowScheduleModal] = useState(false); const [showKTMatrixModal, setShowKTMatrixModal] = useState(false); const [showLevel2FeedbackModal, setShowLevel2FeedbackModal] = useState(false); const [showLevel3FeedbackModal, setShowLevel3FeedbackModal] = useState(false); const [showDocumentsModal, setShowDocumentsModal] = useState(false); const [showAssignModal, setShowAssignModal] = useState(false); const [selectedStage, setSelectedStage] = useState(null); const [interviewMode, setInterviewMode] = useState('virtual'); const [approvalRemark, setApprovalRemark] = useState(''); const [expandedBranches, setExpandedBranches] = useState<{ [key: string]: boolean }>({ 'architectural-work': true, 'statutory-documents': true }); const [users, setUsers] = useState([]); const [selectedUser, setSelectedUser] = useState(''); const [participantType, setParticipantType] = useState('contributor'); const [interviewDate, setInterviewDate] = useState(''); const [interviewType, setInterviewType] = useState('level1'); const [meetingLink, setMeetingLink] = useState(''); const [location, setLocation] = useState(''); const [documents, setDocuments] = useState([]); const [showUploadForm, setShowUploadForm] = useState(false); // Toggle for upload view const [uploadFile, setUploadFile] = useState(null); const [uploadDocType, setUploadDocType] = useState(''); const [approvalFile, setApprovalFile] = useState(null); // State for approval modal file const [isUploading, setIsUploading] = useState(false); const [previewDoc, setPreviewDoc] = useState(null); const [showPreviewModal, setShowPreviewModal] = useState(false); const [selectedInterviewerId, setSelectedInterviewerId] = useState(''); const [interviews, setInterviews] = useState([]); const [isScheduling, setIsScheduling] = useState(false); const [showAssignArchitectureModal, setShowAssignArchitectureModal] = useState(false); const [architectureLeadId, setArchitectureLeadId] = useState(''); const [isAssigningArchitecture, setIsAssigningArchitecture] = useState(false); const [showArchitectureStatusModal, setShowArchitectureStatusModal] = useState(false); const [architectureStatus, setArchitectureStatus] = useState('COMPLETED'); const [architectureRemarks, setArchitectureRemarks] = useState(''); const [isUpdatingArchitecture, setIsUpdatingArchitecture] = useState(false); const [isAssigningParticipant, setIsAssigningParticipant] = useState(false); const [isApproving, setIsApproving] = useState(false); const [isRejecting, setIsRejecting] = useState(false); // KT Matrix State const [ktMatrixScores, setKtMatrixScores] = useState>({}); const [ktMatrixRemarks, setKtMatrixRemarks] = useState(''); const [isSubmittingKT, setIsSubmittingKT] = useState(false); const [selectedInterviewForFeedback, setSelectedInterviewForFeedback] = useState(null); // Payment Details State const [deposits, setDeposits] = useState([]); const [paymentConfigs, setPaymentConfigs] = useState({}); useEffect(() => { if (applicationId) { const fetchPaymentData = async () => { try { const [depositData, configData] = await Promise.all([ onboardingService.getSecurityDeposit(applicationId), onboardingService.getSystemConfigs({ category: 'SECURITY_DEPOSIT', format: 'map' }) ]); setDeposits(Array.isArray(depositData) ? depositData : [depositData].filter(Boolean)); setPaymentConfigs(configData || {}); } catch (error) { console.error('Failed to fetch payment data', error); } }; fetchPaymentData(); } }, [applicationId]); const getDeposit = (type: string) => deposits.find(d => d.depositType === type); const handleKTMatrixChange = (criterionName: string, score: number) => { setKtMatrixScores(prev => ({ ...prev, [criterionName]: score })); }; const calculateKTScore = () => { let totalWeightedScore = 0; KT_MATRIX_CRITERIA.forEach(criterion => { const score = ktMatrixScores[criterion.name] || 0; const weightedScore = (score / criterion.maxScore) * criterion.weight; totalWeightedScore += weightedScore; }); return totalWeightedScore.toFixed(2); }; const handleSubmitKTMatrix = async () => { if (Object.keys(ktMatrixScores).length < KT_MATRIX_CRITERIA.length) { toast.warning('Please fill all fields in the KT Matrix'); return; } // Use the selected interview ID or fallback (though UI now forces selection) const interviewId = selectedInterviewForFeedback?.id || interviews.find(i => i.status !== 'Completed')?.id || interviews[0]?.id; if (!interviewId) { toast.error('No active interview found to link this KT Matrix to.'); return; } try { setIsSubmittingKT(true); const criteriaScores = KT_MATRIX_CRITERIA.map(c => ({ criterionName: c.name, score: ktMatrixScores[c.name] || 0, maxScore: c.maxScore, weightage: c.weight })); await onboardingService.submitKTMatrix({ interviewId, criteriaScores, feedback: ktMatrixRemarks, recommendation: null // No auto-decision }); toast.success('KT Matrix submitted successfully'); setShowKTMatrixModal(false); // Reset form setKtMatrixScores({}); setKtMatrixRemarks(''); await fetchInterviews(); await fetchApplication(); // Refresh application status and progress } catch (error) { toast.error('Failed to submit KT Matrix'); } finally { setIsSubmittingKT(false); } }; // Level 2 Feedback State const [level2Feedback, setLevel2Feedback] = useState({ strategicVision: '', managementCapabilities: '', operationalUnderstanding: '', keyStrengths: '', areasOfConcern: '', additionalComments: '', overallScore: '', interviewerName: currentUser?.name || '', interviewDate: new Date().toISOString().split('T')[0] }); const [isSubmittingLevel2, setIsSubmittingLevel2] = useState(false); const handleLevel2Change = (field: string, value: string) => { setLevel2Feedback(prev => ({ ...prev, [field]: value })); }; const handleSubmitLevel2Feedback = async () => { if (!level2Feedback.overallScore) { toast.warning('Please provide an overall score.'); return; } const interviewId = selectedInterviewForFeedback?.id || interviews.find(i => i.status !== 'Completed' && i.level === 2)?.id; if (!interviewId) { toast.error('No active Level 2 interview found to link this feedback to.'); return; } try { setIsSubmittingLevel2(true); const feedbackItems = [ { type: 'Strategic Vision', comments: level2Feedback.strategicVision }, { type: 'Management Capabilities', comments: level2Feedback.managementCapabilities }, { type: 'Operational Understanding', comments: level2Feedback.operationalUnderstanding }, { type: 'Key Strengths', comments: level2Feedback.keyStrengths }, { type: 'Areas of Concern', comments: level2Feedback.areasOfConcern }, { type: 'Additional Comments', comments: level2Feedback.additionalComments } ].filter(item => item.comments.trim() !== ''); await onboardingService.submitLevel2Feedback({ interviewId, overallScore: Number(level2Feedback.overallScore), feedbackItems }); toast.success('Level 2 Feedback submitted successfully'); setShowLevel2FeedbackModal(false); // Reset form setLevel2Feedback({ strategicVision: '', managementCapabilities: '', operationalUnderstanding: '', keyStrengths: '', areasOfConcern: '', additionalComments: '', overallScore: '', interviewerName: currentUser?.name || '', interviewDate: new Date().toISOString().split('T')[0] }); fetchInterviews(); // Refresh to show feedback fetchApplication(); // Refresh application status } catch (error) { toast.error('Failed to submit Level 2 Feedback'); } finally { setIsSubmittingLevel2(false); } }; // Level 3 Feedback State const [level3Feedback, setLevel3Feedback] = useState({ strategicVision: '', managementCapabilities: '', operationalUnderstanding: '', brandAlignment: '', executiveSummary: '', keyStrengths: '', areasOfConcern: '', additionalComments: '', overallScore: '', interviewerName: currentUser?.name || '', interviewDate: new Date().toISOString().split('T')[0] }); const [isSubmittingLevel3, setIsSubmittingLevel3] = useState(false); const handleLevel3Change = (field: string, value: string) => { setLevel3Feedback(prev => ({ ...prev, [field]: value })); }; const handleSubmitLevel3Feedback = async () => { if (!level3Feedback.overallScore) { toast.warning('Please provide an overall score.'); return; } const interviewId = selectedInterviewForFeedback?.id || interviews.find(i => i.status !== 'Completed' && i.level === 3)?.id; if (!interviewId) { toast.error('No active Level 3 interview found to link this feedback to.'); return; } try { setIsSubmittingLevel3(true); // Level 3 might have slightly different fields or same structure. Assuming same for now. const feedbackItems = [ { type: 'Business Vision & Strategy', comments: level3Feedback.strategicVision }, { type: 'Leadership & Decision Making', comments: level3Feedback.managementCapabilities }, { type: 'Operational & Financial Readiness', comments: level3Feedback.operationalUnderstanding }, { type: 'Brand Alignment', comments: level3Feedback.brandAlignment }, { type: 'Key Strengths', comments: level3Feedback.keyStrengths }, { type: 'Areas of Concern', comments: level3Feedback.areasOfConcern }, { type: 'Executive Summary', comments: level3Feedback.executiveSummary }, { type: 'Additional Comments', comments: level3Feedback.additionalComments } ].filter(item => item.comments.trim() !== ''); // Reusing submitLevel2Feedback endpoint as it maps to InterviewFeedback table generic enough for Level 3 too // Or we can create specific one if needed, but logic is identical so reusing service method await onboardingService.submitLevel2Feedback({ interviewId, overallScore: Number(level3Feedback.overallScore), feedbackItems }); toast.success('Level 3 Feedback submitted successfully'); setShowLevel3FeedbackModal(false); // Reset form setLevel3Feedback({ strategicVision: '', managementCapabilities: '', operationalUnderstanding: '', brandAlignment: '', executiveSummary: '', keyStrengths: '', areasOfConcern: '', additionalComments: '', overallScore: '', interviewerName: currentUser?.name || '', interviewDate: new Date().toISOString().split('T')[0] }); fetchInterviews(); fetchApplication(); } catch (error) { toast.error('Failed to submit Level 3 Feedback'); } finally { setIsSubmittingLevel3(false); } }; // Feedback Details Modal State const [selectedEvaluationForView, setSelectedEvaluationForView] = useState(null); const [showFeedbackDetailsModal, setShowFeedbackDetailsModal] = useState(false); const fetchInterviews = async () => { if (applicationId) { try { const data = await onboardingService.getInterviews(applicationId); setInterviews(data || []); } catch (error) { console.error('Failed to fetch interviews', error); } } }; useEffect(() => { fetchInterviews(); }, [applicationId]); const handleAddInterviewer = () => { if (!selectedInterviewerId) return; const usersList = Array.isArray(users) ? users : []; const userToAdd = usersList.find(u => u.id === selectedInterviewerId); if (userToAdd && !scheduledInterviewParticipants.find(p => p.id === userToAdd.id)) { setScheduledInterviewParticipants([...scheduledInterviewParticipants, userToAdd]); setSelectedInterviewerId(''); } }; const handleRemoveInterviewer = (userId: string) => { setScheduledInterviewParticipants(scheduledInterviewParticipants.filter(p => p.id !== userId)); }; useEffect(() => { if ((activeTab === 'documents' || activeTab === 'progress') && applicationId) { const fetchDocuments = async () => { try { const docs = await onboardingService.getDocuments(applicationId); setDocuments(docs || []); } catch (error) { console.error('Failed to fetch documents', error); } }; fetchDocuments(); } }, [activeTab, applicationId]); const fetchUsers = async (type?: string) => { // Only fetch users if user has admin/DD/NBH roles to avoid 403s if (!currentUser || !['DD Admin', 'Super Admin', 'DD Lead', 'DD Head', 'NBH'].includes(currentUser.role)) { return; } try { const params: any = {}; if (type) { const roleMapping: any = { 'level1': ['DD-ZM', 'RBM'], 'level2': ['DD Lead', 'ZBH'], 'level3': ['NBH', 'DD Head'] }; params.roleCode = roleMapping[type]; // Include location from the application if (application) { params.locationId = application.districtId || application.areaId || application.regionId || application.zoneId; } } const response = await onboardingService.getUsers(params); if (Array.isArray(response)) { setUsers(response); } else if (response && Array.isArray(response.data)) { setUsers(response.data); } else if (response && Array.isArray(response.users)) { setUsers(response.users); } else { console.warn('Unexpected users response:', response); setUsers([]); } } catch (error) { console.error('Failed to fetch users', error); setUsers([]); } }; useEffect(() => { if (showScheduleModal && application) { fetchUsers(interviewType); // Auto-fill participants based on pre-assigned evaluators for this level const levelNum = parseInt(interviewType.replace('level', '')) || 1; const preAssigned = (application?.participants || []) .filter((p: any) => p.metadata?.interviewLevel === levelNum || p.metadata?.interviewLevel === String(levelNum) || p.metadata?.allAssignments?.includes(levelNum) || p.metadata?.allAssignments?.includes(String(levelNum)) ) .map((p: any) => p.user) .filter(Boolean); if (preAssigned.length > 0) { // Ensure uniqueness by user ID const uniquePreassigned: any[] = []; const seenIds = new Set(); preAssigned.forEach((u: any) => { if (u.id && !seenIds.has(u.id)) { seenIds.add(u.id); uniquePreassigned.push(u); } }); setScheduledInterviewParticipants(uniquePreassigned); } else { setScheduledInterviewParticipants([]); } } else if ((showAssignArchitectureModal || showAssignModal) && application) { fetchUsers(); // Default fetch for other modals like Assign } }, [showScheduleModal, showAssignArchitectureModal, showAssignModal, interviewType, application?.participants]); const handleScheduleInterview = async () => { if (!interviewDate) { toast.warning('Please select date and time'); return; } try { setIsScheduling(true); const payload = { applicationId: application?.id, level: interviewType, scheduledAt: interviewDate, type: interviewMode === 'virtual' ? 'Virtual Interview' : 'Physical Interview', location: interviewMode === 'virtual' ? meetingLink : location, participants: scheduledInterviewParticipants.map(p => p.id) }; await onboardingService.scheduleInterview(payload); toast.success('Interview scheduled successfully'); setShowScheduleModal(false); // Refresh interviews await fetchInterviews(); await fetchApplication(); // Refresh application status } catch (error) { toast.error('Failed to schedule interview'); console.error(error); } finally { setIsScheduling(false); } }; const handleCancelInterview = async (interviewId: string) => { if (!window.confirm('Are you sure you want to cancel this interview?')) return; try { await onboardingService.updateInterview(interviewId, { status: 'Cancelled' }); toast.success('Interview cancelled successfully'); fetchInterviews(); } catch (error) { toast.error('Failed to cancel interview'); console.error(error); } }; const handleUpload = async () => { if (!uploadFile || !uploadDocType) { toast.warning('Please select a file and document type'); return; } try { setIsUploading(true); const formData = new FormData(); formData.append('file', uploadFile); formData.append('documentType', uploadDocType); if (selectedStage) { formData.append('stage', selectedStage); } await onboardingService.uploadDocument(applicationId!, formData); toast.success('Document uploaded successfully'); setShowUploadForm(false); setUploadFile(null); setUploadDocType(''); // Refresh documents const docs = await onboardingService.getDocuments(applicationId); setDocuments(docs || []); // Refresh EOR Data in case an EOR document was uploaded fetchEorData(); } catch (error) { console.error('Upload failed', error); toast.error('Failed to upload document'); } finally { setIsUploading(false); } }; if (loading) { return
Loading application details...
; } if (!application) { return
Application not found
; } const isDocumentUploaded = (docType: string) => { return (documents || []).some(d => d.documentType === docType); }; const getStageStatus = (stageName: string, fallbackLogic: () => ProcessStage['status']): ProcessStage['status'] => { const backendStage = (application.progressTracking || []).find((ps: any) => ps.stageName === stageName); if (backendStage && (backendStage.status === 'completed' || backendStage.status === 'active')) { return backendStage.status as any; } return fallbackLogic(); }; const processStages: ProcessStage[] = [ { id: 1, name: 'Submitted', status: 'completed', date: application.submissionDate, description: 'Application submitted', documentsUploaded: 3 }, { id: 2, name: 'Questionnaire', status: getStageStatus('Questionnaire', () => application.questionnaireMarks ? 'completed' : 'pending'), date: application.questionnaireDate, description: 'Questionnaire completed', documentsUploaded: 0 }, { id: 3, name: 'Shortlist', status: getStageStatus('Shortlist', () => ['Shortlisted', 'Level 1 Interview Pending', 'Level 1 Approved', 'Level 2 Interview Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Rejected', 'Onboarded'].includes(application.status) ? 'completed' : 'pending'), date: application.shortlistDate, description: 'Application shortlisted by DD', documentsUploaded: 2 }, { id: 4, name: '1st Level Interview', status: getStageStatus('1st Level Interview', () => ['Level 1 Approved', 'Level 2 Interview Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Level 1 Interview Pending' ? 'active' : application.status === 'Rejected' && application.progress >= 40 ? 'completed' : 'pending'), date: application.level1InterviewDate, description: 'DD-ZM + RBM evaluation', evaluators: Array.from(new Set((application.participants || []) .filter((p: any) => p.metadata?.interviewLevel === 1 || p.metadata?.interviewLevel === '1' || p.metadata?.allAssignments?.includes(1) || p.metadata?.allAssignments?.includes('1') ) .map((p: any) => `${p.user?.name} (${p.user?.role})`) )), documentsUploaded: 1 }, { id: 5, name: '2nd Level Interview', status: getStageStatus('2nd Level Interview', () => ['Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Level 2 Interview Pending' ? 'active' : application.status === 'Rejected' && application.progress >= 55 ? 'completed' : 'pending'), date: application.level2InterviewDate, description: 'DD Lead + ZBH evaluation', evaluators: Array.from(new Set((application.participants || []) .filter((p: any) => p.metadata?.interviewLevel === 2 || p.metadata?.interviewLevel === '2' || p.metadata?.allAssignments?.includes(2) || p.metadata?.allAssignments?.includes('2') ) .map((p: any) => `${p.user?.name} (${p.user?.role})`) )), documentsUploaded: 1 }, { id: 6, name: '3rd Level Interview', status: getStageStatus('3rd Level Interview', () => ['Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Level 3 Interview Pending' ? 'active' : application.status === 'Rejected' && application.progress >= 70 ? 'completed' : 'pending'), date: application.level3InterviewDate, description: 'NBH + DD Head evaluation', evaluators: Array.from(new Set((application.participants || []) .filter((p: any) => p.metadata?.interviewLevel === 3 || p.metadata?.interviewLevel === '3' || p.metadata?.allAssignments?.includes(3) || p.metadata?.allAssignments?.includes('3') ) .map((p: any) => `${p.user?.name} (${p.user?.role})`) )), documentsUploaded: 2 }, { id: 7, name: 'FDD', status: getStageStatus('FDD', () => ['LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'FDD Verification' ? 'active' : 'pending'), date: application.fddDate, description: 'Financial Due Diligence', documentsUploaded: 5 }, { id: 8, name: 'LOI Approval', status: getStageStatus('LOI Approval', () => ['Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'LOI In Progress' ? 'active' : 'pending'), date: application.loiApprovalDate, description: 'Letter of Intent approval', evaluators: Array.from(new Set((application.participants || []) .filter((p: any) => p.metadata?.stageCode === 'LOI_APPROVAL' || p.metadata?.allAssignments?.includes('LOI_APPROVAL') ) .map((p: any) => `${p.user?.name} (${p.user?.role})`) )), documentsUploaded: 1 }, { id: 9, name: 'Security Details', status: getStageStatus('Security Details', () => ['LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Payment Pending' ? 'active' : 'pending'), date: application.securityDetailsDate, description: 'Security verification', documentsUploaded: 3 }, { id: 10, name: 'LOI Issue', status: getStageStatus('LOI Issue', () => ['Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'LOI Issued' ? 'active' : 'pending'), date: application.loiIssueDate, description: 'Letter of Intent issued', documentsUploaded: 1 }, { id: 11, name: 'Dealer Code Generation', status: getStageStatus('Dealer Code Generation', () => (application.dealerCode || ['Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'Statutory GST', 'Statutory PAN', 'Statutory NODAL', 'Statutory NODAL', 'Statutory Check', 'Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status)) ? 'completed' : application.status === 'Dealer Code Generation' ? 'active' : 'pending'), date: application.dealerCodeDate, description: 'Dealer code generated and assigned', documentsUploaded: 0, isParallel: true, branches: [ { name: 'Architectural Work', color: 'blue', stages: [ { id: '11a-1', name: 'Assigned to Architecture Team', status: application.architectureAssignedTo || ['Architecture Document Upload', 'Architecture Team Completion', 'Statutory GST', 'Statutory PAN', 'Statutory Nodal', 'Statutory Check', 'Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) || application.architectureStatus === 'COMPLETED' || isDocumentUploaded('Architecture Assignment Document') ? 'completed' : application.status === 'Architecture Team Assigned' ? 'active' : 'pending', date: application.architectureAssignedDate, description: 'Assigned to architecture team for site planning', documentsUploaded: 0 }, { id: '11a-2', name: 'Architectural Document Upload', status: isDocumentUploaded('Architecture Blueprint') || isDocumentUploaded('Site Plan') || ['Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) || application.architectureStatus === 'COMPLETED' ? 'completed' : (application.architectureAssignedTo || application.status === 'Architecture Document Upload' || application.architectureStatus === 'IN_PROGRESS') ? 'active' : 'pending', date: application.architectureDocumentDate, description: 'Architectural documents and blueprints uploaded', documentsUploaded: (documents || []).filter(d => ['Architecture Blueprint', 'Site Plan'].includes(d.documentType)).length }, { id: '11a-3', name: 'Architecture Team Completion', status: ['LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) || application.architectureStatus === 'COMPLETED' || isDocumentUploaded('Architecture Completion Certificate') ? 'completed' : application.status === 'Architecture Team Completion' ? 'active' : 'pending', date: application.architectureCompletionDate, description: 'Architecture team work completed', documentsUploaded: 0 } ] }, { name: 'Statutory Documents', color: 'green', stages: [ { id: '11b-1', name: 'GST', status: isDocumentUploaded('GST Certificate') || ['Statutory PAN', 'Statutory Nodal', 'Statutory Check', 'Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory GST' ? 'active' : 'pending', description: 'GST certificate', documentsUploaded: (documents || []).filter(d => d.documentType === 'GST Certificate').length }, { id: '11b-2', name: 'PAN', status: isDocumentUploaded('PAN Card') || ['Statutory Nodal', 'Statutory Check', 'Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory PAN' ? 'active' : 'pending', description: 'PAN card', documentsUploaded: (documents || []).filter(d => d.documentType === 'PAN Card').length }, { id: '11b-3', name: 'Nodal Agreement', status: isDocumentUploaded('Nodal Agreement') || ['Statutory Check', 'Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory Nodal' ? 'active' : 'pending', description: 'Nodal agreement document', documentsUploaded: (documents || []).filter(d => d.documentType === 'Nodal Agreement').length }, { id: '11b-4', name: 'Cancelled Check', status: isDocumentUploaded('Cancelled Check') || ['Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory Check' ? 'active' : 'pending', description: 'Cancelled check copy', documentsUploaded: (documents || []).filter(d => d.documentType === 'Cancelled Check').length }, { id: '11b-5', name: 'Partnership Deed/LLP/MOA/AOA/COI', status: isDocumentUploaded('Partnership Deed') || isDocumentUploaded('LLP Agreement') || isDocumentUploaded('Certificate of Incorporation') || isDocumentUploaded('MOA') || isDocumentUploaded('AOA') || ['Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory Partnership' ? 'active' : 'pending', description: 'Business entity documents', documentsUploaded: (documents || []).filter(d => ['Partnership Deed', 'LLP Agreement', 'Certificate of Incorporation', 'MOA', 'AOA'].includes(d.documentType)).length }, { id: '11b-6', name: 'Firm Registration Certificate', status: isDocumentUploaded('Firm Registration') || ['Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory Firm Reg' ? 'active' : 'pending', description: 'Firm registration certificate', documentsUploaded: (documents || []).filter(d => d.documentType === 'Firm Registration').length }, { id: '11b-7', name: 'Rental agreement/ Lease agreement / Own/ Land agreement', status: isDocumentUploaded('Rental Agreement') || isDocumentUploaded('Property Documents') || ['Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory Rental' ? 'active' : 'pending', description: 'Property agreement document', documentsUploaded: (documents || []).filter(d => ['Rental Agreement', 'Property Documents'].includes(d.documentType)).length }, { id: '11b-8', name: 'Virtual Code', status: isDocumentUploaded('Virtual Code Confirmation') || ['Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory Virtual Code' ? 'active' : 'pending', description: 'Virtual code availability', documentsUploaded: (documents || []).filter(d => d.documentType === 'Virtual Code Confirmation').length }, { id: '11b-9', name: 'Domain ID', status: isDocumentUploaded('Domain ID Setup') || ['Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory Domain' ? 'active' : 'pending', description: 'Domain ID setup', documentsUploaded: (documents || []).filter(d => d.documentType === 'Domain ID Setup').length }, { id: '11b-10', name: 'MSD Configuration', status: isDocumentUploaded('MSD Configuration') || ['Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory MSD' ? 'active' : 'pending', description: 'Microsoft Dynamics configuration', documentsUploaded: (documents || []).filter(d => d.documentType === 'MSD Configuration').length }, { id: '11b-11', name: 'LOI Acknowledgement Copy', status: isDocumentUploaded('LOI Acknowledgement') || ['LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Statutory LOI Ack' ? 'active' : 'pending', description: 'LOI acknowledgement copy', documentsUploaded: (documents || []).filter(d => d.documentType === 'LOI Acknowledgement').length } ] } ] }, { id: 12, name: 'LOA', status: getStageStatus('LOA', () => ['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'LOA Pending' ? 'active' : 'pending'), isLocked: application.status === 'LOA Pending' && getDeposit('FINAL')?.status !== 'Verified' && !documents.some(d => (d.documentType?.toLowerCase().includes('final') && d.documentType?.toLowerCase().includes('deposit')) && d.status === 'Approved'), lockMessage: 'Final Security Deposit (₹15L) must be verified by Finance before LOA Approval.', date: application.loaDate, description: 'Letter of Authorization', evaluators: Array.from(new Set((application.participants || []) .filter((p: any) => p.metadata?.stageCode === 'LOA_APPROVAL' || p.metadata?.allAssignments?.includes('LOA_APPROVAL') ) .map((p: any) => `${p.user?.name} (${p.user?.role})`) )), documentsUploaded: 1 }, { id: 13, name: 'EOR Complete', status: getStageStatus('EOR Complete', () => ['Inauguration', 'Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'EOR Complete' ? 'active' : 'pending'), date: application.eorCompleteDate, description: 'Essential Operating Requirements completed', documentsUploaded: 6 }, { id: 14, name: 'Inauguration', status: getStageStatus('Inauguration', () => ['Approved', 'Onboarded'].includes(application.status) ? 'completed' : application.status === 'Inauguration' ? 'active' : 'pending'), date: application.inaugurationDate, description: 'Dealership inauguration ceremony', documentsUploaded: 2 }, { id: 15, name: 'Dealership Active', status: getStageStatus('Onboarded', () => application.status === 'Onboarded' ? 'completed' : 'pending'), date: application.onboardedDate, description: 'Dealer profile and login created' } ]; const eorChecklist = [ { id: 1, item: 'Sales Standards', completed: false }, { id: 2, item: 'Service & Spares', completed: false }, { id: 3, item: 'DMS infra', completed: false }, { id: 4, item: 'Manpower Training', completed: false }, { id: 5, item: 'Trade certificate with test ride bikes registration', completed: false }, { id: 6, item: 'GST certificate including Accessories & Apparels billing', completed: false }, { id: 7, item: 'Inventory Funding', completed: false }, { id: 8, item: 'Virtual code availability', completed: false }, { id: 9, item: 'Vendor payments', completed: false }, { id: 10, item: 'Details for website submission', completed: false }, { id: 11, item: 'Infra Insurance both Showroom and Service center', completed: false }, { id: 12, item: 'Auto ordering', completed: false } ]; const flattenedStages: any[] = processStages.reduce((acc: any[], stage) => { acc.push({ name: stage.name }); if (stage.branches) { stage.branches.forEach((branch: any) => { branch.stages.forEach((subStage: any) => { acc.push({ name: subStage.name, parentBranch: branch.name }); }); }); } if (stage.name === 'EOR In Progress' || stage.name === 'EOR Complete') { (eorData?.items || eorChecklist).forEach((item: any) => { acc.push({ name: `EOR: ${item.description || item.item}`, parentBranch: 'EOR' }); }); } return acc; }, []); const getDocumentsForStage = (stageName: string) => { return documents.filter(doc => doc.stage === stageName || (!doc.stage && doc.documentType?.toLowerCase().includes(stageName.toLowerCase().split(' ')[0])) ); }; const handleApprove = async () => { try { setIsApproving(true); // Check if user has an active interview to approve const activeInterview = interviews.find(i => i.status !== 'Completed' && i.status !== 'Cancelled' && i.participants?.some((p: any) => p.userId === currentUser?.id) ); // Handle File Upload if exists if (approvalFile && applicationId) { try { const formData = new FormData(); formData.append('file', approvalFile); formData.append('documentType', 'Approval Attachment'); // Determine stage based on active interview let stageName = null; if (activeInterview) { if (activeInterview.level === 1 || activeInterview.level === '1') stageName = '1st Level Interview'; else if (activeInterview.level === 2 || activeInterview.level === '2') stageName = '2nd Level Interview'; else if (activeInterview.level === 3 || activeInterview.level === '3') stageName = '3rd Level Interview'; } // Fallback for document stage if it's a general approval if (!stageName) { if (application.status === 'Shortlisted' || application.status === 'Level 1 Interview Pending') stageName = '1st Level Interview'; else if (application.status === 'Level 1 Approved' || application.status === 'Level 2 Interview Pending') stageName = '2nd Level Interview'; else if (application.status === 'Level 2 Approved' || application.status === 'Level 3 Interview Pending') stageName = '3rd Level Interview'; } if (stageName) { formData.append('stage', stageName); } await onboardingService.uploadDocument(applicationId, formData); toast.success('Document uploaded with approval'); } catch (error) { console.error('Failed to upload approval document', error); toast.error('Failed to upload document'); } } if (activeInterview) { try { await onboardingService.updateInterviewDecision({ interviewId: activeInterview.id, decision: 'Approved', remarks: approvalRemark }); toast.success('Interview approved successfully'); setShowApproveModal(false); setApprovalRemark(''); setApprovalFile(null); // Reset file fetchInterviews(); // Refresh application to check if status updated fetchApplication(); return; } catch (error) { toast.error('Failed to approve interview'); return; } } if (!approvalRemark.trim()) { toast.warning('Please enter a remark'); return; } // Application level approval - Robust State Machine let newStatus = application.status; switch (application.status) { case 'Shortlisted': case 'Level 1 Interview Pending': newStatus = 'Level 1 Approved'; break; case 'Level 1 Approved': case 'Level 2 Interview Pending': newStatus = 'Level 2 Approved'; break; case 'Level 2 Approved': case 'Level 3 Interview Pending': newStatus = 'Level 3 Approved'; break; case 'Level 3 Approved': newStatus = 'FDD Verification'; break; case 'FDD Verification': newStatus = 'LOI In Progress'; break; case 'LOI In Progress': newStatus = 'LOI Issued'; break; case 'LOI Issued': newStatus = 'Dealer Code Generation'; break; case 'Dealer Code Generation': case 'Architecture Team Assigned': case 'Architecture Document Upload': case 'Architecture Team Completion': newStatus = 'Statutory GST'; break; case 'Statutory GST': case 'Statutory PAN': case 'Statutory Nodal': case 'Statutory Check': case 'Statutory Partnership': case 'Statutory Firm Reg': case 'Statutory Rental': case 'Statutory Virtual Code': case 'Statutory Domain': case 'Statutory MSD': case 'Statutory LOI Ack': newStatus = 'LOA Pending'; break; case 'LOA Pending': newStatus = 'EOR In Progress'; break; case 'EOR In Progress': newStatus = 'EOR Complete'; break; case 'EOR Complete': newStatus = 'Inauguration'; break; case 'Inauguration': newStatus = 'Approved'; break; default: newStatus = 'Approved'; // Final fallback } const policyManagedStages: { [key: string]: string } = { 'Level 1 Interview Pending': 'INTERVIEW_LEVEL_1', 'Level 2 Interview Pending': 'INTERVIEW_LEVEL_2', 'Level 2 Recommended': 'INTERVIEW_LEVEL_2', 'Level 3 Interview Pending': 'INTERVIEW_LEVEL_3', 'LOI In Progress': 'LOI_APPROVAL', 'LOA Pending': 'LOA_APPROVAL' }; const stageCodeForPolicy = policyManagedStages[application.status]; if (stageCodeForPolicy) { const response = await onboardingService.submitStageDecision({ applicationId: application.id, stageCode: stageCodeForPolicy, decision: 'Approved', remarks: approvalRemark, nextStatus: newStatus }); if (response.data?.statusUpdated) { toast.success(response.message || 'Stage completed and moved to next step'); } else { toast.info(response.message || 'Approval recorded. Waiting for other mandatory approvers.'); } } else { await onboardingService.updateApplicationStatus(applicationId!, { status: newStatus, remarks: approvalRemark }); toast.success(`Application moved to ${newStatus}`); } // Special case: If final approval, create Dealer record if (newStatus === 'Approved') { // In a real scenario, we'd have the dealerCodeId from the application's associated DealerCode record await onboardingService.createDealer({ applicationId: applicationId, // dealerCodeId is handled by backend if not provided, or we can fetch it }); toast.success('Application approved and Dealer profile created!'); } else { toast.success(`Application moved to ${newStatus}`); } setShowApproveModal(false); setApprovalRemark(''); setApprovalFile(null); fetchApplication(); } catch (error) { console.error('Approval error:', error); toast.error('Failed to process approval'); } finally { setIsApproving(false); } }; const handleReject = async () => { try { setIsRejecting(true); // Check if user has an active interview to reject const activeInterview = interviews.find(i => i.status !== 'Completed' && i.status !== 'Cancelled' && i.participants?.some((p: any) => p.userId === currentUser?.id) ); if (activeInterview) { try { await onboardingService.updateInterviewDecision({ interviewId: activeInterview.id, decision: 'Rejected', remarks: rejectionReason }); toast.success('Interview rejected'); setShowRejectModal(false); setRejectionReason(''); fetchInterviews(); fetchApplication(); return; } catch (error) { toast.error('Failed to reject interview'); return; } } if (!rejectionReason.trim()) { toast.warning('Please enter a reason for rejection'); return; } const policyManagedStages: { [key: string]: string } = { 'Level 1 Interview Pending': 'INTERVIEW_LEVEL_1', 'Level 2 Interview Pending': 'INTERVIEW_LEVEL_2', 'Level 2 Recommended': 'INTERVIEW_LEVEL_2', 'Level 3 Interview Pending': 'INTERVIEW_LEVEL_3', 'LOI In Progress': 'LOI_APPROVAL', 'LOA Pending': 'LOA_APPROVAL' }; const stageCodeForPolicy = policyManagedStages[application.status]; if (stageCodeForPolicy) { await onboardingService.submitStageDecision({ applicationId: application.id, stageCode: stageCodeForPolicy, decision: 'Rejected', remarks: rejectionReason, interviewId: activeInterview?.id }); } else { await onboardingService.updateApplicationStatus(applicationId!, { status: 'Rejected', remarks: rejectionReason }); } toast.success('Application rejected'); setShowRejectModal(false); setRejectionReason(''); fetchApplication(); } catch (error) { console.error('Rejection error:', error); toast.error('Failed to process rejection'); } finally { setIsRejecting(false); } }; const handleGenerateDealerCodes = async () => { try { await onboardingService.generateDealerCodes(applicationId!); toast.success('Dealer codes generated successfully'); fetchApplication(); } catch (error) { console.error('Generate codes error:', error); toast.error('Failed to generate dealer codes'); } }; const handleAssignArchitecture = async () => { if (!architectureLeadId) { toast.warning('Please select an architecture lead'); return; } try { setIsAssigningArchitecture(true); await onboardingService.assignArchitectureTeam(applicationId!, architectureLeadId); toast.success('Architecture team assigned successfully'); setShowAssignArchitectureModal(false); fetchApplication(); // Refresh to update status } catch (error) { toast.error('Failed to assign architecture team'); } finally { setIsAssigningArchitecture(false); } }; const handleUpdateArchitectureStatus = async () => { try { setIsUpdatingArchitecture(true); await onboardingService.updateArchitectureStatus(applicationId!, architectureStatus, architectureRemarks); toast.success('Architecture status updated successfully'); setShowArchitectureStatusModal(false); fetchApplication(); } catch (error) { toast.error('Failed to update architecture status'); } finally { setIsUpdatingArchitecture(false); } }; const handleAddParticipant = async () => { if (!selectedUser) { toast.warning('Please select a user'); return; } try { setIsAssigningParticipant(true); await onboardingService.addParticipant({ requestId: applicationId, requestType: 'application', userId: selectedUser, participantType: participantType || 'contributor' }); toast.success('User assigned successfully!'); // Refresh application data fetchApplication(); setSelectedUser(''); setShowAssignModal(false); } catch (error) { toast.error('Failed to assign user'); } finally { setIsAssigningParticipant(false); } }; const handleRetriggerEvaluators = async () => { try { setLoading(true); await onboardingService.retriggerEvaluators(applicationId!); toast.success('Evaluators re-assigned successfully'); await fetchApplication(); } catch (error) { toast.error('Failed to re-assign evaluators'); } finally { setLoading(false); } }; if (loading) { return
Loading...
; } if (!application) { return
Application not found
; } // Determine if current user has an active interview and if they have submitted feedback const interviewsList = Array.isArray(interviews) ? interviews : []; // For action buttons, we only care about pending interviews const activeInterviewForUser = interviewsList.find(i => ['Scheduled', 'Rescheduled', 'Pending', 'In Progress'].includes(i.status) && i.participants?.some((p: any) => p.userId === currentUser?.id) ); // For checking if a decision was ALREADY made, we look at ANY interview the user participated in for the current level const lastInterviewForUser = [...interviewsList].reverse().find(i => i.participants?.some((p: any) => p.userId === currentUser?.id) ); const currentUserEvaluation = (activeInterviewForUser || lastInterviewForUser)?.evaluations?.find( (e: any) => e.evaluatorId === currentUser?.id ); // Helper to check interview level completion const isInterviewCompleted = (level: number) => { return interviewsList.some(i => (Number(i.level) === level) && i.status === 'Completed'); }; const isInterviewActive = (level: number) => { return interviewsList.some(i => (Number(i.level) === level) && i.status === 'Scheduled'); }; // Robust checks for feedback and decision // 1. If there's an active interview, feedback is required before Approve/Reject // 2. hasMadeDecision should check if the evaluation has a recommendation const hasSubmittedFeedback = !!currentUserEvaluation; // Specific to the current active interview context const hasSubmittedFeedbackForActive = activeInterviewForUser && hasSubmittedFeedback; const policyManagedStages: { [key: string]: string } = { 'Level 1 Interview Pending': 'INTERVIEW_LEVEL_1', 'Level 2 Interview Pending': 'INTERVIEW_LEVEL_2', 'Level 2 Recommended': 'INTERVIEW_LEVEL_2', 'Level 3 Interview Pending': 'INTERVIEW_LEVEL_3', 'LOI In Progress': 'LOI_APPROVAL', 'LOA Pending': 'LOA_APPROVAL' }; const currentStageCode = policyManagedStages[application.status]; const currentUserStageAction = application.stageApprovals?.find( (a: any) => a.stageCode === currentStageCode && String(a.actorUserId) === String(currentUser?.id) ); const hasMadeStageDecision = !!currentUserStageAction; const hasMadeDecisionForUser = !!currentUserStageAction || currentUserEvaluation?.decision === 'Approved' || currentUserEvaluation?.decision === 'Rejected' || ['Approved', 'Rejected', 'Selected'].includes(currentUserEvaluation?.recommendation || ''); // Final visibility flags const isAdmin = currentUser && ['DD Admin', 'Super Admin', 'NBH', 'DD Lead', 'DD Head', 'Finance'].includes(currentUser.role); const isAdministrativeStage = [ 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'LOI Issued', 'Statutory LOI Ack', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'Statutory GST', 'Statutory PAN', 'Statutory Nodal', 'Statutory Check', 'Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'LOA Pending', 'EOR Complete', 'Inauguration' ].includes(application.status); const finalDepositVerified = getDeposit('FINAL')?.status === 'Verified'; const isLoaLocked = application.status === 'LOA Pending' && !finalDepositVerified; // Show Approve/Reject if: // 1. It's an interview and feedback is submitted AND no decision made yet // 2. OR it's an administrative stage and user is Admin AND hasn't made a decision yet const shouldShowApproveReject = !isLoaLocked && ( (!hasMadeDecisionForUser && hasSubmittedFeedbackForActive) || (isAdmin && isAdministrativeStage && !hasMadeStageDecision) ); const shouldShowDecisionMessage = hasMadeDecisionForUser && (!isAdministrativeStage || hasMadeStageDecision); const renderFddAuditContent = () => { const assignments = application?.fddAssignments || []; if (assignments.length === 0) { return (

No FDD Assignment

The Financial Due Diligence process has not been initiated for this application yet.

); } return (

Financial Due Diligence Reports

{assignments.length} Assignment(s)
{assignments.map((assignment: any) => (

FDD Agency Audit

Agency ID: {assignment.assignedToAgency || 'Assigned'} • Status: {assignment.status}

{assignment.status}
{(!assignment.reports || assignment.reports.length === 0) ? (

Waiting for internal or external agency to submit the final audit report...

) : (
{assignment.reports.map((report: any) => (
{report.recommendation?.toUpperCase()}
"{report.findings || 'No detail findings provided by the auditor.'}"
{report.reportDocument ? (

{report.reportDocument.fileName}

SUBMITTED {new Date(report.createdAt).toLocaleDateString()}

) : (
No audit report file attached
)}
{report.verifiedAt ? (
Audit Verified by Finance
) : (
Pending Finance Review
)}
))}
)} ))}
); }; return (
{/* Header */}

{application.name}

{application.registrationNumber}

{/* Main Content */}
{/* Core Details Card */} Applicant Information

Full Name

{application.name}

Email

{application.email}

Phone

{application.phone}

Age

{application.age ? `${application.age} years` : 'N/A'}

Education

{application.education || 'N/A'}

Preferred Location

{application.preferredLocation || 'N/A'}

Location Type

{application.locationType || 'N/A'}

Owns Bike

{application.ownRoyalEnfield === 'yes' ? 'Yes' : 'No'}

{application.ownRoyalEnfield === 'yes' && (

Bike Model

{application.royalEnfieldModel || 'N/A'}

)}

Existing Dealer

{application.existingDealer === 'yes' ? 'Yes' : 'No'}

{application.existingDealer === 'yes' && (

Company Name

{application.companyName || 'N/A'}

)}

Source

{application.source || 'N/A'}

{application.questionnaireMarks !== undefined && (

Questionnaire Score

{application.questionnaireMarks}/100

)}

Address

{application.address || 'N/A'}

Pincode

{application.pincode || 'N/A'}

Description

{application.description || 'N/A'}

Past Experience

{application.pastExperience || 'N/A'}

{/* Tabs Section */} {/* Only show tabs for shortlisted applications (opportunity requests and regular dealership requests) */} {/* Hide tabs for non-opportunity requests (lead generation) */} {application.isShortlisted !== false && (
Questionnaire Progress Documents Interviews FDD Audit EOR Checklist Payments Audit Trail
{/* Questionnaire Response Tab */} {/* Progress Tab */}

Application Journey

{application.progress}% Complete
{(() => { const getApproverStatus = (stageCode: string | number) => { const stageParticipants = (application.participants || []).filter((p: any) => p.metadata?.stageCode === stageCode || p.metadata?.allAssignments?.includes(stageCode) || (typeof stageCode === 'number' && (p.metadata?.interviewLevel === stageCode || p.metadata?.allAssignments?.includes(stageCode))) || (typeof stageCode === 'string' && !isNaN(Number(stageCode)) && (p.metadata?.interviewLevel === Number(stageCode) || p.metadata?.allAssignments?.includes(Number(stageCode)))) ); return stageParticipants.map((p: any) => { const saCode = typeof stageCode === 'number' ? `INTERVIEW_LEVEL_${stageCode}` : stageCode; const approval = (application.stageApprovals || []).find((sa: any) => sa.stageCode === saCode && String(sa.actorUserId) === String(p.userId) ); return { name: p.user?.name || 'Unknown', role: p.user?.role || 'Reviewer', status: approval ? (approval.decision === 'Approved' ? 'approved' : 'rejected') : 'pending' }; }); }; const renderApprovers = (stageName: string) => { const stageMapping: Record = { '1st Level Interview': 1, '2nd Level Interview': 2, '3rd Level Interview': 3, 'LOI Approval': 'LOI_APPROVAL', 'LOA': 'LOA_APPROVAL' }; const stageCode = stageMapping[stageName]; if (!stageCode) return null; const approvers = getApproverStatus(stageCode); if (approvers.length === 0) return null; return (
{approvers.map((approver, i) => (
{approver.name.split(' ').map((n: string) => n[0]).join('').substring(0, 2).toUpperCase()}
{approver.name} {approver.role}
{/* Status Dot Overlay */}
{/* Tooltip */}
{approver.role}: {approver.status.toUpperCase()}
))}
); }; return processStages.map((stage, index) => (
{stage.isParallel ? ( ) : stage.isLocked ? (
Stage Locked {stage.lockMessage}
) : ( <> {stage.status === 'completed' ? ( ) : stage.status === 'active' ? ( ) : (
)} )}
{index < processStages.length - 1 && !stage.isParallel && (
)}

{stage.name}

{stage.description && (

{stage.description}

)} {renderApprovers(stage.name as string)} {stage.evaluators && stage.evaluators.length > 0 && !['LOI Approval', 'LOA', '1st Level Interview', '2nd Level Interview', '3rd Level Interview'].includes(stage.name as string) && (

Evaluators: {stage.evaluators.join(' + ')}

)} {(() => { const expectedMap: Record = { 4: 2, // L1 Interview (ZM + RBM) 5: 2, // L2 Interview (ZBH + DD Lead) 6: 2, // L3 Interview (NBH + DD Head) 8: 3, // LOI Approval (Finance + DD Head + NBH) 12: 2 // LOA Approval (DD Head + NBH) }; const stageId = Number(stage.id); const expectedCount = expectedMap[stageId]; const actualCount = stage.evaluators?.length || 0; if (expectedCount && actualCount < expectedCount && application.status !== 'Rejected') { return (
Missing Evaluators {actualCount === 0 ? "Respective role users were not found for this location." : `Some roles (${actualCount}/${expectedCount}) are missing for this location.` }
); } return null; })()} {(() => { const stageDocsCount = documents.filter(doc => doc.stage === stage.name || (!doc.stage && doc.documentType?.toLowerCase().includes(stage.name.toLowerCase().split(' ')[0])) ).length; return (
); })()}

{stage.status === 'completed' && stage.date && `Completed: ${formatDateTime(stage.date)}`} {stage.status === 'active' && 'In Progress'} {stage.status === 'pending' && 'Pending'}

{stage.isParallel && stage.branches && (
{stage.branches.map((branch, branchIndex) => { const branchKey = branch.name.toLowerCase().replace(/\s+/g, '-'); const isExpanded = expandedBranches[branchKey]; const branchColor = branch.color === 'blue' ? 'blue' : 'green'; return (
{isExpanded && (
{branch.stages.map((branchStage) => (
{branchStage.status === 'completed' ? ( ) : branchStage.status === 'active' ? ( ) : (
)}

{branchStage.name}

{branchStage.description && (

{branchStage.description}

)} {(() => { const branchDocsCount = documents.filter(doc => doc.documentType?.toLowerCase().includes(branchStage.name.toLowerCase().split(' ')[0]) || doc.stage === branchStage.name ).length; return (
); })()}

{branchStage.status === 'completed' && branchStage.date && `Done: ${formatDateTime(branchStage.date)}`} {branchStage.status === 'active' && 'Evaluating'} {branchStage.status === 'pending' && 'Pending'}

))}
)}
); })}
)}
)) })()}
{/* Documents Tab */}

Uploaded Documents

File Name Type Upload Date Uploader Actions {documents.length === 0 ? ( No documents uploaded yet ) : ( documents.map((doc) => ( {doc.fileName} {doc.documentType} {new Date(doc.createdAt).toLocaleDateString()} {doc.uploader?.fullName || (doc.uploadedBy ? 'Unknown User' : 'Applicant')}
)))}
{/* Interviews Tab */}

Scheduled Interviews

Level Date & Time Type Location/Link Status Scheduled By Actions {(!interviews || interviews.length === 0) ? ( No interviews scheduled yet ) : ( (Array.isArray(interviews) ? interviews : []).map((interview) => ( Level {interview.level} {interview.scheduleDate ? new Date(interview.scheduleDate).toLocaleString() : 'N/A'} {interview.interviewType} {interview.interviewType?.toLowerCase().includes('virtual') ? ( Join Meeting ) : ( interview.linkOrLocation )} {interview.status} {interview.scheduler?.fullName || interview.scheduledBy || 'N/A'} {(interview.status === 'Scheduled' || interview.status === 'scheduled') && ( )} )) )}

Interview Feedback

{(!interviews || interviews.length === 0) ? (

No interviews scheduled.

) : ( (Array.isArray(interviews) ? interviews : []).map((interview) => (

Level {interview.level} Interview ({new Date(interview.scheduleDate).toLocaleDateString()} - {interview.interviewType})

{interview.evaluations && interview.evaluations.length > 0 ? ( Interviewer Role {interview.level === 1 ? 'Score (KT Matrix)' : 'Overall Score'} Remarks Recommendation {interview.evaluations.map((evalItem: any) => ( {evalItem.evaluator?.fullName} {evalItem.evaluator?.role?.roleName || 'N/A'} {evalItem.ktMatrixScore ? ( = 50 ? 'outline' : 'destructive') : (Number(evalItem.ktMatrixScore) >= 5 ? 'outline' : 'destructive') }> {evalItem.ktMatrixScore}/{interview.level === 1 ? '100' : '10'} ) : 'N/A'} {evalItem.remarks ? (
{evalItem.remarks} {evalItem.feedbackDetails && evalItem.feedbackDetails.length > 0 && ( )}
) : evalItem.feedbackDetails && evalItem.feedbackDetails.length > 0 ? ( ) : ( evalItem.qualitativeFeedback || '-' )}
{evalItem.recommendation || '-'}
))}
) : (

No feedback recorded yet.

)}
)) )}
{['Level 2 Approved', 'Level 3 Interview Pending', 'Approved', 'Onboarded'].includes(application.status) && (

Level 2 Interview Summary

Decision: Approved by both ZBH and DD Lead

Overall Assessment: Strong candidate with excellent business plan

)}
{renderFddAuditContent()} {/* EOR Checklist Tab */}

Essential Operating Requirements

{Math.round(eorProgress)}% Complete
{(eorData?.items || eorChecklist).map((item: any) => { const docType = item.description || item.item; const hasDocument = !!item.proofDocument; return (
{/* Clickable Info Area */}
{ setSelectedStage(`EOR: ${docType}`); setUploadDocType(docType); setShowDocumentsModal(true); if (!hasDocument) setShowUploadForm(true); else setShowUploadForm(false); }} >
{docType} {hasDocument && !item.isCompliant && ( Needs Verification )}
{hasDocument && (
{item.proofDocument.fileName}
)} {!hasDocument && ( Click to upload proof )}
{/* Separate Action Area (No modal trigger) */}
{hasDocument && !item.isCompliant && isAdmin && (
)} {(item.isCompliant || item.completed) && (
)} {!hasDocument && (
)}
); })}
{eorProgress === 100 && isAdmin && (application.status === 'EOR In Progress' || application.status === 'LOA Pending') && (

EOR Checklist Complete

All 12 mandatory requirements have been verified. You can now complete the audit and move to final inauguration.

)}
{/* Payments Tab */}

Security Deposits

{deposits.length} Payment Record(s)
{/* Initial Security Deposit */} {(() => { const deposit = getDeposit('INITIAL'); const config = paymentConfigs.INITIAL_SECURITY_DEPOSIT; const expectedAmount = config?.amount || 500000; return (
Advance Payment
{deposit?.status || 'Awaiting'}
Amount Received ₹{Number(deposit?.amount || 0).toLocaleString()}
Expected Total ₹{expectedAmount.toLocaleString()}
{deposit?.paymentReference && (
Ref: {deposit.paymentReference} {deposit.verifiedAt && {new Date(deposit.verifiedAt).toLocaleDateString()}}
)} {deposit?.remarks && (
"{deposit.remarks}"
)}
); })()} {/* Final Security Deposit */} {(() => { const deposit = getDeposit('FINAL'); const config = paymentConfigs.FINAL_SECURITY_DEPOSIT; const expectedAmount = config?.amount || 1500000; return (
Final Security Deposit
{deposit?.status || 'Awaiting'}
Amount Received ₹{Number(deposit?.amount || 0).toLocaleString()}
Expected Total ₹{expectedAmount.toLocaleString()}
{deposit?.paymentReference && (
Ref: {deposit.paymentReference} {deposit.verifiedAt && {new Date(deposit.verifiedAt).toLocaleDateString()}}
)} {deposit?.remarks && (
"{deposit.remarks}"
)}
); })()}
{/* Payment Proof Documents */} Verification Documents {documents.filter((d: any) => d.documentType?.toLowerCase().includes('deposit')).length > 0 ? (
{documents.filter((d: any) => d.documentType?.toLowerCase().includes('deposit')).map((doc: any, index: number) => (

{doc.fileName || doc.name}

{doc.documentType}

))}
) : (

No payment proofs uploaded yet.

)}
{/* Audit Trail Tab */}
{auditLoading ? (
Loading audit trail...
) : auditLogs.length === 0 ? (
No audit logs recorded yet for this application.
) : ( auditLogs.map((log: any) => (

{log.description || log.action}

{new Date(log.timestamp).toLocaleString()}

by {log.userName || 'System'}

{log.changes && log.changes.length > 0 && (
{log.changes.map((change: string, idx: number) => (

{change}

))}
)}
)) )}
)}
{/* Right Sidebar - Summary and Actions */}
{/* Summary Card */} Summary

Registration ID

{application.registrationNumber}

Current Status

{application.status}
{application.rank && (

Rank

{application.rank} of {application.totalApplicantsAtLocation} in {application.preferredLocation}

)}

Progress

{application.progress}%
{application.deadline && (

Questionnaire Deadline

{new Date(application.deadline).toLocaleDateString()}

)}
{/* Actions Card */} {/* Only show Actions card for shortlisted applications (opportunity requests and regular dealership requests) */} {/* Hide Actions for non-opportunity requests (lead generation) - these are read-only records */} {application.isShortlisted !== false && ( Actions {/* Show Approve/Reject block */} {isLoaLocked && ( Stage Locked Final Security Deposit (₹15L) must be verified by Finance before LOA Approval can proceed. )} {shouldShowApproveReject && ( <> )} {(shouldShowDecisionMessage) && (
You have {(currentUserStageAction?.decision === 'Approved' || currentUserEvaluation?.decision === 'Approved' || currentUserEvaluation?.recommendation === 'Approved' || currentUserEvaluation?.decision === 'Selected') ? 'Approved' : 'Rejected'}
)} {currentUser && ['DD Admin', 'Super Admin', 'DD AM', 'ASM'].includes(currentUser.role) && !([1, 2, 3].every(level => interviews.some(i => i.level === level))) && ( )} {currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) && application.status === 'Dealer Code Generation' && ( <> {!application.dealerCode && ( )} {application.dealerCode && ( )} )} {((currentUser && currentUser.id === application.architectureAssignedTo) || (currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role))) && application.architectureStatus === 'IN_PROGRESS' && ( )} {/* Show Interview Feedback only if active interview exists AND feedback NOT submitted */} {activeInterviewForUser && !hasSubmittedFeedback && ( { setSelectedInterviewForFeedback(activeInterviewForUser); if (activeInterviewForUser.level === 1) setShowKTMatrixModal(true); else if (activeInterviewForUser.level === 2) setShowLevel2FeedbackModal(true); else setShowLevel3FeedbackModal(true); }} > Level {activeInterviewForUser.level} - {activeInterviewForUser.interviewType} )} {application.status === 'Questionnaire Pending' && ( <> )} {/* Dedicated Onboarding Button - Appears ONLY when everything is ready (last step) */} {isAdmin && application.status === 'Inauguration' && !application.dealer && (
{eorProgress < 100 && ( EOR Checklist must be 100% complete before onboarding. (Current: {eorProgress.toFixed(0)}%) )}
)} {/* Dealer Onboarded Status & Link */} {application.dealer && (
Dealer Profile Active
This application has been successfully onboarded as a dealer. A user account has been created for the dealer.
{application.dealerCode && (
Dealer Code: {application.dealerCode.code}
)}
)} {currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) && ( Assign User to Application Select a user and their role for this application.
)}
)}
{/* Approve Modal */} Approve Application Provide approval remarks and optionally attach supporting documents.