From 574e648618db1aaacef9abde4e043f8a6e89393f Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Thu, 2 Apr 2026 01:42:51 +0530 Subject: [PATCH] progress track enhnced tested upto eor step work flow service file added for deale onboarding --- src/App.tsx | 2 +- .../applications/ApplicationDetails.tsx | 534 ++++++++++-------- src/components/layout/Sidebar.tsx | 4 +- src/styles/globals.css | 2 +- 4 files changed, 312 insertions(+), 230 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 819117b..36c199c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -51,7 +51,7 @@ import { SocketProvider } from './context/SocketContext'; // Layout Component const AppLayout = ({ onLogout, title }: { onLogout: () => void, title: string }) => { return ( -
+
window.location.reload()} /> diff --git a/src/components/applications/ApplicationDetails.tsx b/src/components/applications/ApplicationDetails.tsx index 9d3d89b..11036aa 100644 --- a/src/components/applications/ApplicationDetails.tsx +++ b/src/components/applications/ApplicationDetails.tsx @@ -322,6 +322,7 @@ export function ApplicationDetails() { regionId: data.regionId, areaId: data.areaId, districtId: data.districtId, + stageApprovals: data.stageApprovals || [], }; setApplication(mappedApp); } catch (error) { @@ -978,7 +979,7 @@ export function ApplicationDetails() { { id: 9, 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, description: 'Security verification', documentsUploaded: 3 @@ -986,7 +987,7 @@ export function ApplicationDetails() { { 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' : ['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, description: 'Letter of Intent issued', documentsUploaded: 1 @@ -1580,7 +1581,9 @@ export function ApplicationDetails() { const currentStageCode = policyManagedStages[application.status]; 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; @@ -1591,7 +1594,7 @@ export function ApplicationDetails() { ['Approved', 'Rejected', 'Selected'].includes(currentUserEvaluation?.recommendation || ''); // 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 = [ 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'LOI Issued', 'Statutory LOI Ack', @@ -1816,231 +1819,300 @@ export function ApplicationDetails() {
-
- {processStages.map((stage, index) => ( -
-
-
-
- {stage.isParallel ? ( - - ) : ( - <> - {stage.status === 'completed' && ( - - )} - {stage.status === 'active' && ( - - )} - {stage.status === 'pending' && ( -
- )} - +
+ {(() => { + 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 => 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.status === 'completed' ? ( + + ) : stage.status === 'active' ? ( + + ) : ( +
+ )} + + )} +
+ {index < processStages.length - 1 && !stage.isParallel && ( +
)}
- {index < processStages.length - 1 && !stage.isParallel && ( -
- )} -
-
-

{stage.name}

- {stage.description && ( -

{stage.description}

- )} - {stage.evaluators && stage.evaluators.length > 0 ? ( -

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

- ) : (() => { - // Determine expected count for this stage - 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; +
+

{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; - 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; - })()} - {/* 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 ( -
- -
- ); - })()} - -

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

+

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

+
-
- {/* Parallel Branches */} - {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'; + {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 ( -
- {/* Branch Header - Clickable */} - + return ( +
+ - {/* Branch Content - Expandable */} - {isExpanded && ( -
- {branch.stages.map((branchStage) => ( -
-
-
-
- {branchStage.status === 'completed' && ( - - )} - {branchStage.status === 'active' && ( - - )} - {branchStage.status === 'pending' && ( -
+ {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: ${new Date(branchStage.date).toLocaleDateString()}`} + {branchStage.status === 'active' && 'Evaluating'} + {branchStage.status === 'pending' && 'Pending'} +

-
-

{branchStage.name}

- {branchStage.description && ( -

{branchStage.description}

- )} - - {/* Branch Stage Docs Link */} - {(() => { - 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 && `Completed: ${new Date(branchStage.date).toLocaleDateString()}`} - {branchStage.status === 'active' && 'In Progress'} - {branchStage.status === 'pending' && 'Pending'} -

-
-
- ))} -
- )} -
- ); - })} - - {/* Connecting line to next stage */} -
-
- )} -
- ))} + ))} +
+ )} +
+ ); + })} +
+
+ )} +
+ )) + })()}
@@ -2101,7 +2173,6 @@ export function ApplicationDetails() {
- {/* Interviews Tab */} {/* Interviews Tab */}
@@ -2672,14 +2743,25 @@ export function ApplicationDetails() { )} {/* 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 && ( +
+ {eorProgress < 100 && ( + + + + EOR Checklist must be 100% complete before onboarding. (Current: {eorProgress.toFixed(0)}%) + + + )} + +
)} {/* Dealer Onboarded Status & Link */} @@ -2799,7 +2881,7 @@ export function ApplicationDetails() {
{/* Approve Modal */} - < Dialog open={showApproveModal} onOpenChange={setShowApproveModal} > + Approve Application @@ -3476,10 +3558,10 @@ export function ApplicationDetails() {
- + {/* Feedback Details Modal */} - < Dialog open={showFeedbackDetailsModal} onOpenChange={setShowFeedbackDetailsModal} > + Interview Feedback Details diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index fe1d47e..ca121d8 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -106,7 +106,7 @@ export function Sidebar({ onLogout }: SidebarProps) { return (
{/* Header with Logo */} @@ -153,7 +153,7 @@ export function Sidebar({ onLogout }: SidebarProps) { )} {/* Menu Items */} -