progress track enhnced tested upto eor step work flow service file added for deale onboarding

This commit is contained in:
laxmanhalaki 2026-04-02 01:42:51 +05:30
parent 7fa34dd3d6
commit 574e648618
4 changed files with 312 additions and 230 deletions

View File

@ -51,7 +51,7 @@ import { SocketProvider } from './context/SocketContext';
// Layout Component // Layout Component
const AppLayout = ({ onLogout, title }: { onLogout: () => void, title: string }) => { const AppLayout = ({ onLogout, title }: { onLogout: () => void, title: string }) => {
return ( return (
<div className="flex h-screen bg-slate-50"> <div className="flex h-screen bg-slate-50 overflow-hidden">
<Sidebar onLogout={onLogout} /> <Sidebar onLogout={onLogout} />
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<Header title={title} onRefresh={() => window.location.reload()} /> <Header title={title} onRefresh={() => window.location.reload()} />

View File

@ -322,6 +322,7 @@ export function ApplicationDetails() {
regionId: data.regionId, regionId: data.regionId,
areaId: data.areaId, areaId: data.areaId,
districtId: data.districtId, districtId: data.districtId,
stageApprovals: data.stageApprovals || [],
}; };
setApplication(mappedApp); setApplication(mappedApp);
} catch (error) { } catch (error) {
@ -978,7 +979,7 @@ export function ApplicationDetails() {
{ {
id: 9, id: 9,
name: 'Security Details', name: 'Security Details',
status: getStageStatus('Security Details', () => ['Payment Pending', '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' : 'pending'), 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, date: application.securityDetailsDate,
description: 'Security verification', description: 'Security verification',
documentsUploaded: 3 documentsUploaded: 3
@ -986,7 +987,7 @@ export function ApplicationDetails() {
{ {
id: 10, id: 10,
name: 'LOI Issue', 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' : ['Payment Pending', 'LOI Issued'].includes(application.status) ? 'active' : 'pending'), 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, date: application.loiIssueDate,
description: 'Letter of Intent issued', description: 'Letter of Intent issued',
documentsUploaded: 1 documentsUploaded: 1
@ -1580,7 +1581,9 @@ export function ApplicationDetails() {
const currentStageCode = policyManagedStages[application.status]; const currentStageCode = policyManagedStages[application.status];
const currentUserStageAction = application.stageApprovals?.find( const currentUserStageAction = application.stageApprovals?.find(
(a: any) => a.stageCode === currentStageCode && a.actorUserId === currentUser?.id (a: any) =>
a.stageCode === currentStageCode &&
String(a.actorUserId) === String(currentUser?.id)
); );
const hasMadeStageDecision = !!currentUserStageAction; const hasMadeStageDecision = !!currentUserStageAction;
@ -1591,7 +1594,7 @@ export function ApplicationDetails() {
['Approved', 'Rejected', 'Selected'].includes(currentUserEvaluation?.recommendation || ''); ['Approved', 'Rejected', 'Selected'].includes(currentUserEvaluation?.recommendation || '');
// Final visibility flags // Final visibility flags
const isAdmin = currentUser && ['DD Admin', 'Super Admin', 'NBH', 'DD Lead', 'DD Head'].includes(currentUser.role); const isAdmin = currentUser && ['DD Admin', 'Super Admin', 'NBH', 'DD Lead', 'DD Head', 'Finance'].includes(currentUser.role);
const isAdministrativeStage = [ const isAdministrativeStage = [
'Level 3 Approved', 'FDD Verification', 'Level 3 Approved', 'FDD Verification',
'LOI In Progress', 'LOI Issued', 'Statutory LOI Ack', 'LOI In Progress', 'LOI Issued', 'Statutory LOI Ack',
@ -1816,231 +1819,300 @@ export function ApplicationDetails() {
<Progress value={application.progress} className="h-3 mb-6" /> <Progress value={application.progress} className="h-3 mb-6" />
</div> </div>
<div className="relative"> <div className="relative">
{processStages.map((stage, index) => ( {(() => {
<div key={stage.id}> const getApproverStatus = (stageCode: string | number) => {
<div className="flex gap-4 pb-8"> const stageParticipants = (application.participants || []).filter((p: any) =>
<div className="relative"> p.metadata?.stageCode === stageCode ||
<div className={`w-10 h-10 rounded-full flex items-center justify-center border-2 ${stage.status === 'completed' p.metadata?.allAssignments?.includes(stageCode) ||
? 'bg-green-500 border-green-500' (typeof stageCode === 'number' && (p.metadata?.interviewLevel === stageCode || p.metadata?.allAssignments?.includes(stageCode))) ||
: stage.status === 'active' (typeof stageCode === 'string' && !isNaN(Number(stageCode)) && (p.metadata?.interviewLevel === Number(stageCode) || p.metadata?.allAssignments?.includes(Number(stageCode))))
? 'bg-amber-500 border-amber-500' );
: 'bg-slate-200 border-slate-300'
}`}> return stageParticipants.map((p: any) => {
{stage.isParallel ? ( const saCode = typeof stageCode === 'number' ? `INTERVIEW_LEVEL_${stageCode}` : stageCode;
<GitBranch className="w-5 h-5 text-white" /> const approval = (application.stageApprovals || []).find((sa: any) =>
) : ( sa.stageCode === saCode &&
<> String(sa.actorUserId) === String(p.userId)
{stage.status === 'completed' && ( );
<CheckCircle className="w-5 h-5 text-white" />
)} return {
{stage.status === 'active' && ( name: p.user?.name || 'Unknown',
<Clock className="w-5 h-5 text-white" /> role: p.user?.role || 'Reviewer',
)} status: approval ? (approval.decision === 'Approved' ? 'approved' : 'rejected') : 'pending'
{stage.status === 'pending' && ( };
<div className="w-3 h-3 bg-slate-400 rounded-full"></div> });
)} };
</>
const renderApprovers = (stageName: string) => {
const stageMapping: Record<string, string | number> = {
'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 (
<div className="flex flex-wrap gap-2 mt-3">
{approvers.map((approver, i) => (
<div key={i} className="group relative flex items-center gap-1.5 bg-slate-50 border border-slate-200 rounded-full pl-1 pr-2.5 py-0.5 transition-all hover:bg-white hover:shadow-sm">
<div className={cn(
"w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white",
approver.status === 'approved' ? "bg-green-500" : approver.status === 'rejected' ? "bg-red-500" : "bg-slate-300"
)}>
{approver.name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase()}
</div>
<div className="flex flex-col">
<span className="text-[10px] font-medium text-slate-700 leading-none">{approver.name}</span>
<span className="text-[8px] text-slate-500 leading-none mt-0.5">{approver.role}</span>
</div>
{/* Status Dot Overlay */}
<div className={cn(
"absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full border border-white",
approver.status === 'approved' ? "bg-green-500" : approver.status === 'rejected' ? "bg-red-500" : "bg-amber-400"
)} />
{/* Tooltip */}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-slate-900 text-white text-[10px] rounded opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
{approver.role}: {approver.status.toUpperCase()}
</div>
</div>
))}
</div>
);
};
return processStages.map((stage, index) => (
<div key={stage.id}>
<div className="flex gap-4 pb-8">
<div className="relative">
<div className={`w-10 h-10 rounded-full flex items-center justify-center border-2 z-10 relative ${stage.status === 'completed'
? 'bg-green-500 border-green-500 text-white shadow-sm'
: stage.status === 'active'
? 'bg-amber-500 border-amber-500 text-white animate-pulse-subtle'
: 'bg-white border-slate-300 text-slate-400 shadow-none'
}`}>
{stage.isParallel ? (
<GitBranch className="w-5 h-5" />
) : (
<>
{stage.status === 'completed' ? (
<Check className="w-5 h-5" />
) : stage.status === 'active' ? (
<Clock className="w-5 h-5 text-white" />
) : (
<div className="w-3 h-3 bg-slate-300 rounded-full"></div>
)}
</>
)}
</div>
{index < processStages.length - 1 && !stage.isParallel && (
<div className={`absolute top-10 left-1/2 -translate-x-1/2 w-0.5 h-full z-0 ${stage.status === 'completed' ? 'bg-green-500/30' : 'bg-slate-200'
}`}></div>
)} )}
</div> </div>
{index < processStages.length - 1 && !stage.isParallel && ( <div className="flex-1 pt-1">
<div className={`absolute top-10 left-1/2 -translate-x-1/2 w-0.5 h-full ${stage.status === 'completed' ? 'bg-green-500' : 'bg-slate-300' <p className={cn(
}`}></div> "font-bold transition-colors",
)} stage.status === 'completed' ? "text-green-700" : stage.status === 'active' ? "text-amber-700" : "text-slate-900"
</div> )}>{stage.name}</p>
<div className="flex-1 pt-1"> {stage.description && (
<p className="text-slate-900">{stage.name}</p> <p className="text-slate-600 text-sm mt-0.5 leading-relaxed">{stage.description}</p>
{stage.description && ( )}
<p className="text-slate-600 text-sm mt-0.5">{stage.description}</p>
)} {renderApprovers(stage.name as string)}
{stage.evaluators && stage.evaluators.length > 0 ? (
<p className="text-amber-600 text-sm mt-0.5"> {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(' + ')} <p className="text-amber-600 text-xs mt-1.5 flex items-center gap-1 bg-amber-50 w-fit px-2 py-0.5 rounded border border-amber-100">
</p> <User className="w-3 h-3" />
) : (() => { Evaluators: {stage.evaluators.join(' + ')}
// Determine expected count for this stage </p>
const expectedMap: Record<number, number> = { )}
4: 2, // L1 Interview (ZM + RBM)
5: 2, // L2 Interview (ZBH + DD Lead) {(() => {
6: 2, // L3 Interview (NBH + DD Head) const expectedMap: Record<number, number> = {
8: 3, // LOI Approval (Finance + DD Head + NBH) 4: 2, // L1 Interview (ZM + RBM)
12: 2 // LOA Approval (DD Head + NBH) 5: 2, // L2 Interview (ZBH + DD Lead)
}; 6: 2, // L3 Interview (NBH + DD Head)
const stageId = Number(stage.id); 8: 3, // LOI Approval (Finance + DD Head + NBH)
const expectedCount = expectedMap[stageId]; 12: 2 // LOA Approval (DD Head + NBH)
const actualCount = stage.evaluators?.length || 0; };
const stageId = Number(stage.id);
const expectedCount = expectedMap[stageId];
const actualCount = stage.evaluators?.length || 0;
if (expectedCount && actualCount < expectedCount && application.status !== 'Rejected') {
return (
<div className="mt-2">
<Alert variant="destructive" className="py-2 px-3 border-amber-200 bg-amber-50 text-amber-800">
<AlertCircle className="h-4 w-4 text-amber-600" />
<AlertTitle className="text-xs font-semibold">Missing Evaluators</AlertTitle>
<AlertDescription className="text-xs">
{actualCount === 0
? "Respective role users were not found for this location."
: `Some roles (${actualCount}/${expectedCount}) are missing for this location.`
}
<Button
variant="link"
size="sm"
className="h-auto p-0 ml-1 text-xs text-amber-700 underline"
onClick={handleRetriggerEvaluators}
>
<RefreshCw className="w-3 h-3 mr-1" />
Re-trigger Assignment
</Button>
</AlertDescription>
</Alert>
</div>
);
}
return null;
})()}
{(() => {
const stageDocsCount = documents.filter(doc =>
doc.stage === stage.name ||
(!doc.stage && doc.documentType?.toLowerCase().includes(stage.name.toLowerCase().split(' ')[0]))
).length;
if (expectedCount && actualCount < expectedCount && application.status !== 'Rejected') {
return ( return (
<div className="mt-2"> <div className="flex items-center gap-2 mt-1">
<Alert variant="destructive" className="py-2 px-3 border-amber-200 bg-amber-50 text-amber-800"> <button
<AlertCircle className="h-4 w-4 text-amber-600" /> onClick={() => {
<AlertTitle className="text-xs font-semibold">Missing Evaluators</AlertTitle> setSelectedStage(stage.name);
<AlertDescription className="text-xs"> setShowDocumentsModal(true);
{actualCount === 0 }}
? "Respective role users were not found for this location." 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"
: `Some roles (${actualCount}/${expectedCount}) are missing for this location.` >
} <FileText className="w-3 h-3" />
<Button {stageDocsCount > 0 ? `${stageDocsCount} Documents Uploaded` : 'Upload Document'}
variant="link" </button>
size="sm"
className="h-auto p-0 ml-1 text-xs text-amber-700 underline"
onClick={handleRetriggerEvaluators}
>
<RefreshCw className="w-3 h-3 mr-1" />
Re-trigger Assignment
</Button>
</AlertDescription>
</Alert>
</div> </div>
); );
} })()}
return null;
})()}
{/* Stage Docs Link */}
{(() => {
const stageDocsCount = documents.filter(doc =>
doc.stage === stage.name ||
(!doc.stage && doc.documentType?.toLowerCase().includes(stage.name.toLowerCase().split(' ')[0]))
).length;
return ( <p className="text-slate-500 mt-1 text-xs">
<div className="flex items-center gap-2 mt-1"> {stage.status === 'completed' && stage.date && `Completed: ${new Date(stage.date).toLocaleDateString()}`}
<button {stage.status === 'active' && 'In Progress'}
onClick={() => { {stage.status === 'pending' && 'Pending'}
setSelectedStage(stage.name); </p>
setShowDocumentsModal(true); </div>
}}
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 > 0 ? `${stageDocsCount} Documents Uploaded` : 'Upload Document'}
</button>
</div>
);
})()}
<p className="text-slate-500 mt-1">
{stage.status === 'completed' && stage.date && `Completed: ${new Date(stage.date).toLocaleDateString()}`}
{stage.status === 'active' && 'In Progress'}
{stage.status === 'pending' && 'Pending'}
</p>
</div> </div>
</div>
{/* Parallel Branches */} {stage.isParallel && stage.branches && (
{stage.isParallel && stage.branches && ( <div className="ml-5 mb-8">
<div className="ml-5 mb-8"> {stage.branches.map((branch, branchIndex) => {
{stage.branches.map((branch, branchIndex) => { const branchKey = branch.name.toLowerCase().replace(/\s+/g, '-');
const branchKey = branch.name.toLowerCase().replace(/\s+/g, '-'); const isExpanded = expandedBranches[branchKey];
const isExpanded = expandedBranches[branchKey]; const branchColor = branch.color === 'blue' ? 'blue' : 'green';
const branchColor = branch.color === 'blue' ? 'blue' : 'green';
return ( return (
<div key={branchIndex} className="mb-6 last:mb-0"> <div key={branchIndex} className="mb-6 last:mb-0">
{/* Branch Header - Clickable */} <button
<button onClick={() => setExpandedBranches(prev => ({
onClick={() => setExpandedBranches(prev => ({ ...prev,
...prev, [branchKey]: !prev[branchKey]
[branchKey]: !prev[branchKey] }))}
}))} className={`w-full flex items-center gap-3 p-4 rounded-lg border-2 transition-all hover:shadow-md ${branchColor === 'blue'
className={`w-full flex items-center gap-3 p-4 rounded-lg border-2 transition-all hover:shadow-md ${branchColor === 'blue' ? 'border-blue-300 bg-blue-50 hover:bg-blue-100'
? 'border-blue-300 bg-blue-50 hover:bg-blue-100' : 'border-green-300 bg-green-50 hover:bg-green-100'
: 'border-green-300 bg-green-50 hover:bg-green-100' }`}
}`} >
> {isExpanded ? (
{isExpanded ? ( <ChevronDown className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} />
<ChevronDown className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} /> ) : (
) : ( <ChevronRight className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} />
<ChevronRight className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} /> )}
)} <div className={`w-8 h-8 rounded-full flex items-center justify-center ${branchColor === 'blue' ? 'bg-blue-200' : 'bg-green-200'
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${branchColor === 'blue' ? 'bg-blue-200' : 'bg-green-200' }`}>
}`}> <GitBranch className={`w-4 h-4 ${branchColor === 'blue' ? 'text-blue-700' : 'text-green-700'}`} />
<GitBranch className={`w-4 h-4 ${branchColor === 'blue' ? 'text-blue-700' : 'text-green-700'}`} /> </div>
</div> <div className="flex-1 text-left">
<div className="flex-1 text-left"> <p className={`${branchColor === 'blue' ? 'text-blue-900' : 'text-green-900'} font-semibold`}>
<p className={`${branchColor === 'blue' ? 'text-blue-900' : 'text-green-900'}`}> {branch.name}
{branch.name} </p>
</p> <p className={`text-xs ${branchColor === 'blue' ? 'text-blue-700' : 'text-green-700'}`}>
<p className={`text-sm ${branchColor === 'blue' ? 'text-blue-700' : 'text-green-700'}`}> {branch.stages.length} steps
{branch.stages.length} steps </p>
</p> </div>
</div> </button>
</button>
{/* Branch Content - Expandable */} {isExpanded && (
{isExpanded && ( <div className="mt-4 ml-8 border-l-2 border-slate-200 pl-6 space-y-6">
<div className="mt-4 ml-8 border-l-2 border-slate-200 pl-6 space-y-6"> {branch.stages.map((branchStage) => (
{branch.stages.map((branchStage) => ( <div key={branchStage.id} className="relative">
<div key={branchStage.id} className="relative"> <div className="flex gap-4 text-xs">
<div className="flex gap-4"> <div className="relative">
<div className="relative"> <div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${branchStage.status === 'completed'
<div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${branchStage.status === 'completed' ? `${branchColor === 'blue' ? 'bg-blue-500 border-blue-500' : 'bg-green-500 border-green-500'}`
? `${branchColor === 'blue' ? 'bg-blue-500 border-blue-500' : 'bg-green-500 border-green-500'}` : branchStage.status === 'active'
: branchStage.status === 'active' ? 'bg-amber-500 border-amber-500'
? 'bg-amber-500 border-amber-500' : 'bg-slate-200 border-slate-300'
: 'bg-slate-200 border-slate-300' }`}>
}`}> {branchStage.status === 'completed' ? (
{branchStage.status === 'completed' && ( <Check className="w-4 h-4 text-white" />
<CheckCircle className="w-4 h-4 text-white" /> ) : branchStage.status === 'active' ? (
)} <Clock className="w-4 h-4 text-white" />
{branchStage.status === 'active' && ( ) : (
<Clock className="w-4 h-4 text-white" /> <div className="w-2 h-2 bg-slate-400 rounded-full"></div>
)} )}
{branchStage.status === 'pending' && ( </div>
<div className="w-2 h-2 bg-slate-400 rounded-full"></div> </div>
<div className="flex-1">
<p className="font-semibold text-slate-800">{branchStage.name}</p>
{branchStage.description && (
<p className="text-slate-500 text-xs mt-0.5">{branchStage.description}</p>
)} )}
{(() => {
const branchDocsCount = documents.filter(doc =>
doc.documentType?.toLowerCase().includes(branchStage.name.toLowerCase().split(' ')[0]) ||
doc.stage === branchStage.name
).length;
return (
<div className="flex items-center gap-2 mt-1">
<button
onClick={() => {
setSelectedStage(branchStage.name);
setShowDocumentsModal(true);
}}
className="text-[10px] font-medium text-blue-700 hover:text-blue-800 flex items-center gap-1 bg-blue-50 px-1.5 py-0.5 rounded border border-blue-100"
>
<FileText className="w-2.5 h-2.5" />
{branchDocsCount > 0 ? `${branchDocsCount} Docs` : 'Upload'}
</button>
</div>
);
})()}
<p className="text-slate-400 text-[10px] mt-1">
{branchStage.status === 'completed' && branchStage.date && `Done: ${new Date(branchStage.date).toLocaleDateString()}`}
{branchStage.status === 'active' && 'Evaluating'}
{branchStage.status === 'pending' && 'Pending'}
</p>
</div> </div>
</div> </div>
<div className="flex-1">
<p className="text-slate-900">{branchStage.name}</p>
{branchStage.description && (
<p className="text-slate-600 text-sm mt-0.5">{branchStage.description}</p>
)}
{/* Branch Stage Docs Link */}
{(() => {
const branchDocsCount = documents.filter(doc =>
doc.documentType?.toLowerCase().includes(branchStage.name.toLowerCase().split(' ')[0]) ||
doc.stage === branchStage.name
).length;
return (
<div className="flex items-center gap-2 mt-1">
<button
onClick={() => {
setSelectedStage(branchStage.name);
setShowDocumentsModal(true);
}}
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 > 0 ? `${branchDocsCount} Documents Uploaded` : 'Upload Document'}
</button>
</div>
);
})()}
<p className="text-slate-500 text-sm mt-1">
{branchStage.status === 'completed' && branchStage.date && `Completed: ${new Date(branchStage.date).toLocaleDateString()}`}
{branchStage.status === 'active' && 'In Progress'}
{branchStage.status === 'pending' && 'Pending'}
</p>
</div>
</div> </div>
</div> ))}
))} </div>
</div> )}
)} </div>
</div> );
); })}
})} <div className="h-8 w-0.5 bg-slate-300 ml-5 opacity-50"></div>
</div>
{/* Connecting line to next stage */} )}
<div className="h-8 w-0.5 bg-slate-300 ml-5"></div> </div>
</div> ))
)} })()}
</div>
))}
</div> </div>
</TabsContent> </TabsContent>
@ -2101,7 +2173,6 @@ export function ApplicationDetails() {
</div> </div>
</TabsContent> </TabsContent>
{/* Interviews Tab */}
{/* Interviews Tab */} {/* Interviews Tab */}
<TabsContent value="interviews" className="space-y-6"> <TabsContent value="interviews" className="space-y-6">
<div> <div>
@ -2672,14 +2743,25 @@ export function ApplicationDetails() {
)} )}
{/* Dedicated Onboarding Button - Appears ONLY when everything is ready (last step) */} {/* Dedicated Onboarding Button - Appears ONLY when everything is ready (last step) */}
{isAdmin && ['Inauguration', 'EOR Complete', 'Approved', 'EOR In Progress', 'LOA Pending'].includes(application.status) && !application.dealer && ( {isAdmin && application.status === 'Inauguration' && !application.dealer && (
<Button <div className="space-y-2">
className="w-full bg-green-600 hover:bg-green-700 font-bold shadow-lg shadow-green-100" {eorProgress < 100 && (
onClick={() => setShowOnboardModal(true)} <Alert variant="destructive" className="bg-amber-50 border-amber-200 text-amber-800 py-2">
> <AlertCircle className="h-4 w-4 text-amber-600" />
<CheckCircle className="w-4 h-4 mr-2" /> <AlertDescription className="text-xs">
Onboard as Dealer (Final Step) EOR Checklist must be 100% complete before onboarding. (Current: {eorProgress.toFixed(0)}%)
</Button> </AlertDescription>
</Alert>
)}
<Button
className="w-full bg-green-600 hover:bg-green-700 font-bold shadow-lg shadow-green-100 disabled:bg-slate-300 disabled:text-slate-500"
onClick={() => setShowOnboardModal(true)}
disabled={eorProgress < 100}
>
<CheckCircle className="w-4 h-4 mr-2" />
Onboard as Dealer (Final Step)
</Button>
</div>
)} )}
{/* Dealer Onboarded Status & Link */} {/* Dealer Onboarded Status & Link */}
@ -2799,7 +2881,7 @@ export function ApplicationDetails() {
</div > </div >
{/* Approve Modal */} {/* Approve Modal */}
< Dialog open={showApproveModal} onOpenChange={setShowApproveModal} > <Dialog open={showApproveModal} onOpenChange={setShowApproveModal}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Approve Application</DialogTitle> <DialogTitle>Approve Application</DialogTitle>
@ -3476,10 +3558,10 @@ export function ApplicationDetails() {
</div> </div>
</div> </div>
</DialogContent> </DialogContent>
</Dialog > </Dialog>
{/* Feedback Details Modal */} {/* Feedback Details Modal */}
< Dialog open={showFeedbackDetailsModal} onOpenChange={setShowFeedbackDetailsModal} > <Dialog open={showFeedbackDetailsModal} onOpenChange={setShowFeedbackDetailsModal}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle>Interview Feedback Details</DialogTitle> <DialogTitle>Interview Feedback Details</DialogTitle>

View File

@ -106,7 +106,7 @@ export function Sidebar({ onLogout }: SidebarProps) {
return ( return (
<div <div
className={`bg-slate-900 text-white h-screen flex flex-col transition-all duration-300 ${collapsed ? 'w-20' : 'w-64' className={`bg-slate-900 text-white h-screen flex flex-col transition-all duration-300 overflow-hidden ${collapsed ? 'w-20' : 'w-64'
}`} }`}
> >
{/* Header with Logo */} {/* Header with Logo */}
@ -153,7 +153,7 @@ export function Sidebar({ onLogout }: SidebarProps) {
)} )}
{/* Menu Items */} {/* Menu Items */}
<nav className="flex-1 p-4 space-y-2"> <nav className="flex-1 p-4 space-y-2 overflow-y-auto custom-scrollbar">
{menuItems.map((item) => { {menuItems.map((item) => {
const Icon = item.icon; const Icon = item.icon;
const isActive = activeView === item.id; const isActive = activeView === item.id;

View File

@ -190,7 +190,7 @@ html {
} }
.custom-scrollbar::-webkit-scrollbar { .custom-scrollbar::-webkit-scrollbar {
width: 5px; width: 3px;
} }
.custom-scrollbar::-webkit-scrollbar-track { .custom-scrollbar::-webkit-scrollbar-track {