started progress bar end to end flowe with mockup at backend for external dependecies

This commit is contained in:
laxmanhalaki 2026-03-19 20:21:14 +05:30
parent 2cf919a0dc
commit 9f13f8056e
3 changed files with 423 additions and 93 deletions

View File

@ -4,6 +4,7 @@ import { toast } from 'sonner';
import { mockWorkNotes, 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';
@ -34,7 +35,8 @@ import {
ChevronRight,
GitBranch,
Star,
Zap
Zap,
ShieldCheck
} from 'lucide-react';
import { Progress } from '../ui/progress';
import { Textarea } from '../ui/textarea';
@ -307,6 +309,7 @@ export function ApplicationDetails() {
eorCompleteDate: getStageDate('EOR Complete'),
inaugurationDate: getStageDate('Inauguration'),
participants: data.participants || [],
dealerCode: data.dealerCode,
};
setApplication(mappedApp);
} catch (error) {
@ -321,6 +324,40 @@ export function ApplicationDetails() {
fetchApplication();
}
}, [applicationId]);
const [eorData, setEorData] = useState<any>(null);
const fetchEorData = async () => {
if (!applicationId) return;
try {
const resp = await eorService.getChecklist(applicationId);
if (resp.success && resp.data) {
setEorData(resp.data);
} else {
// Auto-create if not found
await eorService.createChecklist(applicationId);
const retry = await eorService.getChecklist(applicationId);
if (retry.success) setEorData(retry.data);
}
} catch (err) {
console.log('EOR not found, attempting auto-create...');
try {
await eorService.createChecklist(applicationId);
const retry = await eorService.getChecklist(applicationId);
if (retry.success) setEorData(retry.data);
} catch (createErr) {
console.error('Fetch/Create EOR error:', createErr);
}
}
};
useEffect(() => {
if (['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].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<any[]>([]);
const [auditLoading, setAuditLoading] = useState(false);
@ -347,6 +384,7 @@ export function ApplicationDetails() {
const [activeTab, setActiveTab] = useState('questionnaire');
const [showApproveModal, setShowApproveModal] = useState(false);
const [showRejectModal, setShowRejectModal] = useState(false);
const [showWorkNoteModal, setShowWorkNoteModal] = useState(false);
const [showScheduleModal, setShowScheduleModal] = useState(false);
const [showKTMatrixModal, setShowKTMatrixModal] = useState(false);
@ -738,6 +776,9 @@ export function ApplicationDetails() {
// 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);
alert('Failed to upload document');
@ -754,6 +795,10 @@ export function ApplicationDetails() {
return <div>Application not found</div>;
}
const isDocumentUploaded = (docType: string) => {
return (documents || []).some(d => d.documentType === docType);
};
const processStages: ProcessStage[] = [
{
id: 1,
@ -774,7 +819,7 @@ export function ApplicationDetails() {
{
id: 3,
name: 'Shortlist',
status: ['Shortlisted', 'Level 1 Pending', 'Level 1 Approved', 'Level 2 Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Pending', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : 'pending',
status: ['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'].includes(application.status) ? 'completed' : 'pending',
date: '2025-10-04',
description: 'Application shortlisted by DD',
documentsUploaded: 2
@ -782,7 +827,7 @@ export function ApplicationDetails() {
{
id: 4,
name: '1st Level Interview',
status: ['Level 1 Approved', 'Level 2 Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Pending', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 1 Pending' ? 'active' : 'pending',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'Level 1 Interview Pending' ? 'active' : 'pending',
date: application.level1InterviewDate,
description: 'DD-ZM + RBM evaluation',
evaluators: ['DD-ZM', 'RBM'],
@ -791,7 +836,7 @@ export function ApplicationDetails() {
{
id: 5,
name: '2nd Level Interview',
status: ['Level 2 Approved', 'Level 2 Recommended', 'Level 3 Pending', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : ['Level 2 Pending', 'Level 2 Interview Pending'].includes(application.status) ? 'active' : 'pending',
status: ['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'].includes(application.status) ? 'completed' : ['Level 2 Interview Pending'].includes(application.status) ? 'active' : 'pending',
date: application.level2InterviewDate,
description: 'DD Lead + ZBH evaluation',
evaluators: ['DD Lead', 'ZBH'],
@ -800,7 +845,7 @@ export function ApplicationDetails() {
{
id: 6,
name: '3rd Level Interview',
status: ['Level 3 Approved', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : ['Level 3 Pending', 'Level 3 Interview Pending'].includes(application.status) ? 'active' : 'pending',
status: ['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'].includes(application.status) ? 'completed' : ['Level 3 Interview Pending'].includes(application.status) ? 'active' : 'pending',
date: application.level3InterviewDate,
description: 'NBH + DD-Head evaluation',
evaluators: ['NBH', 'DD-Head'],
@ -809,7 +854,7 @@ export function ApplicationDetails() {
{
id: 7,
name: 'FDD',
status: ['Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'FDD Verification' ? 'active' : 'pending',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'FDD Verification' ? 'active' : 'pending',
date: application.fddDate,
description: 'Financial Due Diligence',
documentsUploaded: 5
@ -817,7 +862,7 @@ export function ApplicationDetails() {
{
id: 8,
name: 'LOI Approval',
status: ['Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : 'pending',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'LOI In Progress' ? 'active' : 'pending',
date: application.loiApprovalDate,
description: 'Letter of Intent approval',
documentsUploaded: 1
@ -833,7 +878,7 @@ export function ApplicationDetails() {
{
id: 10,
name: 'LOI Issue',
status: ['Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Payment Pending' ? 'active' : 'pending',
status: ['Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : ['Payment Pending', 'LOI Issued'].includes(application.status) ? 'active' : 'pending',
date: application.loiIssueDate,
description: 'Letter of Intent issued',
documentsUploaded: 1
@ -841,7 +886,7 @@ export function ApplicationDetails() {
{
id: 11,
name: 'Dealer Code Generation',
status: ['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', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Dealer Code Generation' ? 'active' : 'pending',
status: (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'].includes(application.status)) ? 'completed' : application.status === 'Dealer Code Generation' ? 'active' : 'pending',
date: application.dealerCodeDate,
description: 'Dealer code generated and assigned',
documentsUploaded: 0,
@ -854,7 +899,7 @@ export function ApplicationDetails() {
{
id: '11a-1',
name: 'Assigned to Architecture Team',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'Architecture Team Assigned' ? 'active' : 'pending',
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'].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
@ -862,18 +907,18 @@ export function ApplicationDetails() {
{
id: '11a-2',
name: 'Architectural Document Upload',
status: ['Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Architecture Document Upload' ? 'active' : 'pending',
status: isDocumentUploaded('Architecture Blueprint') || isDocumentUploaded('Site Plan') || ['Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].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: 8
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'].includes(application.status) ? 'completed' : application.status === 'Architecture Team Completion' ? 'active' : 'pending',
status: ['LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].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: 4
documentsUploaded: 0
}
]
},
@ -884,79 +929,79 @@ export function ApplicationDetails() {
{
id: '11b-1',
name: 'GST',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'Statutory GST' ? 'active' : 'pending',
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'].includes(application.status) ? 'completed' : application.status === 'Statutory GST' ? 'active' : 'pending',
description: 'GST certificate',
documentsUploaded: 1
documentsUploaded: (documents || []).filter(d => d.documentType === 'GST Certificate').length
},
{
id: '11b-2',
name: 'PAN',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'Statutory PAN' ? 'active' : 'pending',
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'].includes(application.status) ? 'completed' : application.status === 'Statutory PAN' ? 'active' : 'pending',
description: 'PAN card',
documentsUploaded: 1
documentsUploaded: (documents || []).filter(d => d.documentType === 'PAN Card').length
},
{
id: '11b-3',
name: 'Nodal Agreement',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'Statutory Nodal' ? 'active' : 'pending',
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'].includes(application.status) ? 'completed' : application.status === 'Statutory Nodal' ? 'active' : 'pending',
description: 'Nodal agreement document',
documentsUploaded: 1
documentsUploaded: (documents || []).filter(d => d.documentType === 'Nodal Agreement').length
},
{
id: '11b-4',
name: 'Cancelled Check',
status: ['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'].includes(application.status) ? 'completed' : application.status === 'Statutory Check' ? 'active' : 'pending',
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'].includes(application.status) ? 'completed' : application.status === 'Statutory Check' ? 'active' : 'pending',
description: 'Cancelled check copy',
documentsUploaded: 1
documentsUploaded: (documents || []).filter(d => d.documentType === 'Cancelled Check').length
},
{
id: '11b-5',
name: 'Partnership Deed/LLP/MOA/AOA/COI',
status: ['Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory Partnership' ? 'active' : 'pending',
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'].includes(application.status) ? 'completed' : application.status === 'Statutory Partnership' ? 'active' : 'pending',
description: 'Business entity documents',
documentsUploaded: 2
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: ['Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory Firm Reg' ? 'active' : 'pending',
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'].includes(application.status) ? 'completed' : application.status === 'Statutory Firm Reg' ? 'active' : 'pending',
description: 'Firm registration certificate',
documentsUploaded: 1
documentsUploaded: (documents || []).filter(d => d.documentType === 'Firm Registration').length
},
{
id: '11b-7',
name: 'Rental agreement/ Lease agreement / Own/ Land agreement',
status: ['Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory Rental' ? 'active' : 'pending',
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'].includes(application.status) ? 'completed' : application.status === 'Statutory Rental' ? 'active' : 'pending',
description: 'Property agreement document',
documentsUploaded: 1
documentsUploaded: (documents || []).filter(d => ['Rental Agreement', 'Property Documents'].includes(d.documentType)).length
},
{
id: '11b-8',
name: 'Virtual Code',
status: ['Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory Virtual Code' ? 'active' : 'pending',
status: isDocumentUploaded('Virtual Code Confirmation') || ['Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory Virtual Code' ? 'active' : 'pending',
description: 'Virtual code availability',
documentsUploaded: 0
documentsUploaded: (documents || []).filter(d => d.documentType === 'Virtual Code Confirmation').length
},
{
id: '11b-9',
name: 'Domain ID',
status: ['Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory Domain' ? 'active' : 'pending',
status: isDocumentUploaded('Domain ID Setup') || ['Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory Domain' ? 'active' : 'pending',
description: 'Domain ID setup',
documentsUploaded: 0
documentsUploaded: (documents || []).filter(d => d.documentType === 'Domain ID Setup').length
},
{
id: '11b-10',
name: 'MSD Configuration',
status: ['Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory MSD' ? 'active' : 'pending',
status: isDocumentUploaded('MSD Configuration') || ['Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory MSD' ? 'active' : 'pending',
description: 'Microsoft Dynamics configuration',
documentsUploaded: 0
documentsUploaded: (documents || []).filter(d => d.documentType === 'MSD Configuration').length
},
{
id: '11b-11',
name: 'LOI Acknowledgement Copy',
status: ['LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory LOI Ack' ? 'active' : 'pending',
status: isDocumentUploaded('LOI Acknowledgement') || ['LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Statutory LOI Ack' ? 'active' : 'pending',
description: 'LOI acknowledgement copy',
documentsUploaded: 1
documentsUploaded: (documents || []).filter(d => d.documentType === 'LOI Acknowledgement').length
}
]
}
@ -989,21 +1034,38 @@ export function ApplicationDetails() {
];
const eorChecklist = [
{ id: 1, item: 'Sales Standards', completed: true },
{ id: 2, item: 'Service & Spares', completed: true },
{ id: 3, item: 'DMS infra', completed: application.status === 'Approved' },
{ id: 4, item: 'Manpower Training', completed: application.status === 'Approved' },
{ 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: true },
{ 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: true },
{ id: 8, item: 'Virtual code availability', completed: false },
{ id: 9, item: 'Vendor payments', completed: false },
{ id: 10, item: 'Details for website submission', completed: true },
{ 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: application.status === 'Approved' }
{ id: 12, item: 'Auto ordering', completed: false }
];
const eorProgress = (eorChecklist.filter(item => item.completed).length / eorChecklist.length) * 100;
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;
}, []);
@ -1038,9 +1100,9 @@ export function ApplicationDetails() {
// Fallback for document stage if it's a general approval
if (!stageName) {
if (application.status === 'Shortlisted' || application.status === 'Level 1 Pending') stageName = '1st Level Interview';
else if (application.status === 'Level 1 Approved' || application.status === 'Level 2 Pending') stageName = '2nd Level Interview';
else if (application.status === 'Level 2 Approved' || application.status === 'Level 3 Pending') stageName = '3rd Level Interview';
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) {
@ -1085,11 +1147,58 @@ export function ApplicationDetails() {
}
try {
// Application level approval
const newStatus = application.status === 'Inauguration' ? 'Approved' :
application.status === 'EOR Complete' ? 'Inauguration' :
application.status === 'LOA Pending' ? 'EOR Complete' :
application.status === 'Statutory LOI Ack' ? 'LOA Pending' : 'Approved'; // Default fallback
// 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':
newStatus = 'Architecture Team Assigned'; break;
case 'Architecture Team Assigned':
newStatus = 'Architecture Document Upload'; break;
case 'Architecture Document Upload':
newStatus = 'Architecture Team Completion'; break;
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
}
await onboardingService.updateApplicationStatus(applicationId!, {
status: newStatus,
@ -1281,7 +1390,23 @@ export function ApplicationDetails() {
currentUserEvaluation?.decision === 'Selected'; // Maintain compatibility if needed
// Final visibility flags
const shouldShowApproveReject = !hasMadeDecisionForUser && hasSubmittedFeedbackForActive;
const isAdmin = currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role);
const isAdministrativeStage = [
'Shortlisted', '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);
// Show Approve/Reject if:
// 1. It's an interview and feedback is submitted
// 2. OR it's an administrative stage and user is Admin
const shouldShowApproveReject =
(!hasMadeDecisionForUser && hasSubmittedFeedbackForActive) ||
(isAdmin && isAdministrativeStage);
@ -1547,7 +1672,7 @@ export function ApplicationDetails() {
className="text-xs font-medium text-amber-700 hover:text-amber-800 flex items-center gap-1 bg-amber-50 px-2 py-0.5 rounded border border-amber-200"
>
<FileText className="w-3 h-3" />
{stageDocsCount} Documents Uploaded
{stageDocsCount > 0 ? `${stageDocsCount} Documents Uploaded` : 'Upload Document'}
</button>
</div>
);
@ -1648,7 +1773,7 @@ export function ApplicationDetails() {
className="text-xs font-medium text-blue-700 hover:text-blue-800 flex items-center gap-1 bg-blue-50 px-2 py-0.5 rounded border border-blue-200"
>
<FileText className="w-3 h-3" />
{branchDocsCount} Documents Uploaded
{branchDocsCount > 0 ? `${branchDocsCount} Documents Uploaded` : 'Upload Document'}
</button>
</div>
);
@ -1874,7 +1999,7 @@ export function ApplicationDetails() {
)}
</div>
{['Level 2 Approved', 'Level 3 Pending', 'Approved'].includes(application.status) && (
{['Level 2 Approved', 'Level 3 Interview Pending', 'Approved'].includes(application.status) && (
<div>
<h3 className="text-slate-900 mb-4">Level 2 Interview Summary</h3>
<div className="p-4 bg-slate-50 rounded-lg">
@ -1894,18 +2019,136 @@ export function ApplicationDetails() {
<Progress value={eorProgress} className="h-3 mb-6" />
<div className="space-y-3">
{eorChecklist.map((item) => (
<div key={item.id} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
<Checkbox checked={item.completed} />
<span className={item.completed ? 'text-slate-900' : 'text-slate-600'}>
{item.item}
</span>
{item.completed && (
<CheckCircle className="w-4 h-4 text-green-600 ml-auto" />
)}
</div>
))}
{(eorData?.items || eorChecklist).map((item: any) => {
const docType = item.description || item.item;
const hasDocument = !!item.proofDocument;
return (
<div
key={item.id}
className="flex items-center gap-3 p-3 bg-slate-50 rounded-xl transition-all border border-transparent hover:border-slate-200 group"
>
<Checkbox
checked={item.isCompliant || item.completed}
className="pointer-events-none shrink-0"
/>
{/* Clickable Info Area */}
<div
className="flex flex-col flex-1 min-w-0 cursor-pointer"
onClick={() => {
setSelectedStage(`EOR: ${docType}`);
setUploadDocType(docType);
setShowDocumentsModal(true);
if (!hasDocument) setShowUploadForm(true);
else setShowUploadForm(false);
}}
>
<div className="flex items-center gap-2">
<span className={(item.isCompliant || item.completed) ? 'text-slate-900 font-bold' : 'text-slate-600 font-medium'}>
{docType}
</span>
{hasDocument && !item.isCompliant && (
<Badge variant="outline" className="text-[10px] h-4 px-1.5 bg-amber-50 text-amber-600 border-amber-200 uppercase tracking-wider font-bold">
Needs Verification
</Badge>
)}
</div>
{hasDocument && (
<div className="flex items-center gap-1.5 text-xs text-blue-600 font-semibold mt-1">
<FileText className="w-3.5 h-3.5" />
<span className="truncate">{item.proofDocument.fileName}</span>
</div>
)}
{!hasDocument && (
<span className="text-[10px] text-slate-400 mt-1 uppercase tracking-tighter">Click to upload proof</span>
)}
</div>
{/* Separate Action Area (No modal trigger) */}
<div className="flex items-center gap-2 shrink-0">
{hasDocument && !item.isCompliant && isAdmin && (
<div className="flex gap-2">
<Button
size="sm"
className="h-8 px-3 bg-green-600 hover:bg-green-700 text-white font-bold rounded-lg shadow-sm"
onClick={async () => {
await eorService.updateItem(eorData.id, {
...item,
isCompliant: true
});
fetchEorData();
toast.success(`${docType} verified!`);
}}
>
Verify
</Button>
<Button
size="sm"
variant="outline"
className="h-8 px-3 border-red-200 text-red-600 hover:bg-red-50 font-bold rounded-lg"
onClick={async () => {
await eorService.updateItem(eorData.id, {
...item,
isCompliant: false,
proofDocumentId: null
});
fetchEorData();
toast.success(`${docType} rejected.`);
}}
>
Reject
</Button>
</div>
)}
{(item.isCompliant || item.completed) && (
<div className="bg-green-100 p-1.5 rounded-full">
<CheckCircle className="w-4 h-4 text-green-600" />
</div>
)}
{!hasDocument && (
<div className="p-2 text-slate-300 group-hover:text-amber-500 transition-colors">
<Upload className="w-4 h-4" />
</div>
)}
</div>
</div>
);
})}
</div>
{eorProgress === 100 && isAdmin && (application.status === 'EOR In Progress' || application.status === 'LOA Pending') && (
<div className="mt-8 p-6 bg-green-50 rounded-xl border-2 border-green-200 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex flex-col sm:flex-row items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-green-100 flex items-center justify-center shrink-0">
<ShieldCheck className="w-7 h-7 text-green-600" />
</div>
<div className="flex-1 text-center sm:text-left">
<h4 className="text-green-900 font-bold text-lg">EOR Checklist Complete</h4>
<p className="text-green-700 text-sm">All 12 mandatory requirements have been verified. You can now complete the audit and move to final inauguration.</p>
</div>
<Button
className="w-full sm:w-auto bg-green-600 hover:bg-green-700 text-white font-bold h-12 px-8 rounded-xl shadow-lg shadow-green-600/20 transition-all hover:scale-[1.02] active:scale-[0.98]"
onClick={async () => {
try {
await onboardingService.updateApplicationStatus(application.id, {
status: 'EOR Complete',
remarks: 'EOR Checklist verified and audit completed.'
});
toast.success('EOR Audit completed successfully!');
fetchApplication();
} catch (error) {
toast.error('Failed to complete EOR audit');
}
}}
>
Complete Audit & Proceed
</Button>
</div>
</div>
)}
</TabsContent>
{/* Payments Tab */}
@ -2091,22 +2334,26 @@ export function ApplicationDetails() {
{currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) && application.status === 'Dealer Code Generation' && (
<>
<Button
className="w-full bg-blue-600 hover:bg-blue-700"
onClick={handleGenerateDealerCodes}
>
<Zap className="w-4 h-4 mr-2" />
Generate Dealer Codes
</Button>
{!application.dealerCode && (
<Button
className="w-full bg-blue-600 hover:bg-blue-700"
onClick={handleGenerateDealerCodes}
>
<Zap className="w-4 h-4 mr-2" />
Generate Dealer Codes
</Button>
)}
<Button
variant="outline"
className="w-full border-blue-200 hover:bg-blue-50 text-blue-700"
onClick={() => setShowAssignArchitectureModal(true)}
>
<GitBranch className="w-4 h-4 mr-2" />
Assign Architecture Team
</Button>
{application.dealerCode && (
<Button
variant="outline"
className="w-full border-blue-200 hover:bg-blue-50 text-blue-700"
onClick={() => setShowAssignArchitectureModal(true)}
>
<GitBranch className="w-4 h-4 mr-2" />
Assign Architecture Team
</Button>
)}
</>
)}
@ -2161,6 +2408,27 @@ export function ApplicationDetails() {
</>
)}
{/* Dedicated Onboarding Button - Appears when logic is ready to onboard as a dealer */}
{isAdmin && ['Dealer Code Generation', 'Architecture Team Completion', 'LOA Pending', 'EOR Complete', 'Inauguration'].includes(application.status) && (
<Button
className="w-full bg-amber-600 hover:bg-amber-700 font-bold"
onClick={async () => {
if (window.confirm('Do you want to finalize and onboard this dealer?')) {
try {
await onboardingService.createDealer({ applicationId });
toast.success('Dealer record created successfully!');
fetchApplication();
} catch (error) {
toast.error('Failed to create dealer profile');
}
}
}}
>
<User className="w-4 h-4 mr-2" />
Onboard as Dealer
</Button>
)}
{currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) && (
<Dialog open={showAssignModal} onOpenChange={setShowAssignModal}>
<DialogTrigger asChild>
@ -3156,8 +3424,10 @@ export function ApplicationDetails() {
</SelectTrigger>
<SelectContent>
<SelectItem value="null">General / No Stage</SelectItem>
{processStages.map((s) => (
<SelectItem key={s.name} value={s.name}>{s.name}</SelectItem>
{flattenedStages.map((s, idx) => (
<SelectItem key={`${s.name}-${idx}`} value={s.name}>
{s.parentBranch ? `${s.parentBranch}: ${s.name}` : s.name}
</SelectItem>
))}
</SelectContent>
</Select>
@ -3171,11 +3441,35 @@ export function ApplicationDetails() {
<SelectContent>
<SelectItem value="PAN Card">PAN Card</SelectItem>
<SelectItem value="GST Certificate">GST Certificate</SelectItem>
<SelectItem value="Aadhaar Card">Aadhaar Card</SelectItem>
<SelectItem value="Trade License">Trade License</SelectItem>
<SelectItem value="Aadhaar">Aadhaar</SelectItem>
<SelectItem value="Nodal Agreement">Nodal Agreement</SelectItem>
<SelectItem value="Cancelled Check">Cancelled Check</SelectItem>
<SelectItem value="Partnership Deed">Partnership Deed</SelectItem>
<SelectItem value="LLP Agreement">LLP Agreement</SelectItem>
<SelectItem value="Certificate of Incorporation">Certificate of Incorporation</SelectItem>
<SelectItem value="MOA">MOA</SelectItem>
<SelectItem value="AOA">AOA</SelectItem>
<SelectItem value="Board Resolution">Board Resolution</SelectItem>
<SelectItem value="Firm Registration">Firm Registration</SelectItem>
<SelectItem value="Rental Agreement">Rental Agreement</SelectItem>
<SelectItem value="Property Documents">Property Documents</SelectItem>
<SelectItem value="Virtual Code Confirmation">Virtual Code Confirmation</SelectItem>
<SelectItem value="Domain ID Setup">Domain ID Setup</SelectItem>
<SelectItem value="MSD Configuration">MSD Configuration</SelectItem>
<SelectItem value="LOI Acknowledgement">LOI Acknowledgement Copy</SelectItem>
<SelectItem value="Architecture Assignment Document">Architecture Assignment Document</SelectItem>
<SelectItem value="Architecture Blueprint">Architecture Blueprint</SelectItem>
<SelectItem value="Architecture Completion Certificate">Architecture Completion Certificate</SelectItem>
<SelectItem value="Site Plan">Site Plan</SelectItem>
<SelectItem value="Bank Statement">Bank Statement</SelectItem>
<SelectItem value="Property Document">Property Document</SelectItem>
<SelectItem value="Inauguration Photos">Inauguration Photos</SelectItem>
<SelectItem value="Inauguration Report">Inauguration Report</SelectItem>
<SelectItem value="Other">Other</SelectItem>
{(eorData?.items || eorChecklist).map((item: any, idx: number) => (
<SelectItem key={`eor-doc-${idx}`} value={item.description || item.item}>
{item.description || item.item}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

View File

@ -24,14 +24,17 @@ export type ApplicationStatus =
| 'Questionnaire Pending'
| 'Questionnaire Completed'
| 'Shortlisted'
| 'Level 1 Pending'
| 'In Review'
| 'Level 1 Interview Pending'
| 'Level 1 Approved'
| 'Level 2 Pending'
| 'Level 2 Interview Pending'
| 'Level 2 Approved'
| 'Level 2 Recommended'
| 'Level 3 Pending'
| 'Level 3 Interview Pending'
| 'Level 3 Approved'
| 'FDD Verification'
| 'Payment Pending'
| 'LOI In Progress'
| 'LOI Issued'
| 'Dealer Code Generation'
| 'Architecture Team Assigned'
@ -48,9 +51,9 @@ export type ApplicationStatus =
| 'Statutory Domain'
| 'Statutory MSD'
| 'Statutory LOI Ack'
| 'LOA Pending'
| 'EOR In Progress'
| 'EOR Complete'
| 'LOA Pending'
| 'Inauguration'
| 'Approved'
| 'Rejected'
@ -108,6 +111,7 @@ export interface Application {
participants?: Participant[];
architectureAssignedTo?: string;
architectureStatus?: string;
dealerCode?: any;
}
export interface Participant {
@ -248,7 +252,7 @@ export const mockApplications: Application[] = [
state: 'Maharashtra',
ownsBike: true,
pastExperience: '5 years in automobile sales, previously worked with Honda',
status: 'Level 1 Pending',
status: 'Level 1 Interview Pending',
questionnaireMarks: 85,
rank: 1,
totalApplicantsAtLocation: 3,

View File

@ -0,0 +1,32 @@
import client from '../api/client';
export const eorService = {
getChecklist: async (applicationId: string) => {
const response = await client.get(`/eor/${applicationId}`);
return response.data as any;
},
createChecklist: async (applicationId: string) => {
const response = await client.post('/eor', { applicationId });
return response.data as any;
},
updateItem: async (checklistId: string, data: {
itemType: string;
description: string;
isCompliant: boolean;
remarks?: string;
proofDocumentId?: string;
}) => {
const response = await client.post(`/eor/item/${checklistId}`, data);
return response.data as any;
},
submitAudit: async (checklistId: string, data: {
status: string;
overallComments?: string;
}) => {
const response = await client.post(`/eor/audit/${checklistId}`, data);
return response.data as any;
}
};