import { ArrowLeft, Check, X, RotateCcw, UserPlus, MessageSquare, FileText, Calendar, Send, AlertCircle, Loader2, Upload, Ban, Mail } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Input } from '@/components/ui/input'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Separator } from '@/components/ui/separator'; import { useState, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { User as UserType } from '@/lib/mock-data'; import { toast } from 'sonner'; import { resignationService } from '@/services/resignation.service'; import { SlaBadge } from '@/components/sla/SlaBadge'; import { useSlaBatchStatus } from '@/hooks/useSlaBatchStatus'; import { API } from '@/api/API'; import { DocumentPreviewModal } from '@/components/ui/DocumentPreviewModal'; import { formatDateTime } from '@/components/ui/utils'; import { formatOffboardingStatusLabel, LAST_WORKING_DAY_LABEL } from '@/lib/offboardingDisplay'; import { RESIGNATION_DOCUMENT_TYPES, RESIGNATION_STAGE_OPTIONS } from '@/lib/offboardingDocumentOptions'; import { WIDE_DIALOG_CLASS } from '@/lib/dialogStyles'; interface ResignationDetailsProps { resignationId: string; onBack: () => void; currentUser: UserType | null; } export default ResignationDetails; const STAGE_TO_ROLE_MAP: Record = { 'ASM': 'ASM', 'RBM': 'RBM', 'ZBH': 'ZBH', 'DD Lead': 'DD Lead', 'DD Head': 'DD Head', 'NBH': 'NBH', 'DD Admin': 'DD Admin', 'Legal': 'Legal Admin' }; /** Labels stored as currentStage when a request is closed (revoke/reject/withdraw); not in workflow stage order */ const TERMINAL_STAGE_LABELS = ['REJECTED', 'Rejected', 'REVOKED', 'Revoked', 'WITHDRAWN', 'Withdrawn']; const RESIGNATION_STAGE_ALIASES: Record = { 'Request Submitted': ['Submission', 'Submitted', 'Initiation'], 'ASM': ['ASM', 'ASM Review'], 'RBM': ['RBM', 'RBM Review', 'Regional Review', 'RBM + DD-ZM Review'], 'ZBH': ['ZBH', 'ZBH Review', 'ZM Review'], 'DD Lead': ['DD Lead', 'DD Lead Review', 'DDL Review'], 'DD Head': ['DD Head', 'DD Head Review', 'DDH Review'], 'NBH': ['NBH', 'NBH Approval', 'NBH Review'], 'DD Admin': ['DD Admin', 'DD Admin Review'], 'Awaiting F&F': ['Awaiting F&F', 'Awaiting F&F — manual initiation'], 'Legal': ['Legal', 'Legal - Resignation Letter'], 'F&F Initiated': ['F&F Initiated', 'FNF_INITIATED', 'FNF Initiated'], 'Completed': ['Completed'] }; export function ResignationDetails({ resignationId, onBack, currentUser }: ResignationDetailsProps) { const getDocumentsForStage = (stageName: string, stageKey?: string) => { const allDocs = [ ...(resignationData?.documents || []), ...(resignationData?.uploadedDocuments || []) ]; const baseAliases = [ stageName, stageKey, ...(stageKey ? (RESIGNATION_STAGE_ALIASES[stageKey] || []) : []), ...(RESIGNATION_STAGE_ALIASES[stageName] || []) ] .filter((value): value is string => Boolean(value)) .map((value) => value.trim().toLowerCase()); return allDocs.filter((doc: any) => { if (!doc?.stage) return false; const docStage = String(doc.stage).trim().toLowerCase(); return baseAliases.includes(docStage); }); }; const navigate = useNavigate(); const [actionDialog, setActionDialog] = useState<{ open: boolean, type: 'approve' | 'reject' | 'withdrawal' | 'sendBack' | 'assign' | 'pushfnf' | 'revoke' | 'dispatch' | null }>({ open: false, type: null }); const [remarks, setRemarks] = useState(''); const [assignToUser, setAssignToUser] = useState(''); const [userSearchQuery, setUserSearchQuery] = useState(''); const [selectedSpecificUser, setSelectedSpecificUser] = useState(''); const [availableUsers, setAvailableUsers] = useState([]); const [isLoadingUsers, setIsLoadingUsers] = useState(false); const [forceTriggerFnF, setForceTriggerFnF] = useState(false); const [stageDocumentsDialog, setStageDocumentsDialog] = useState<{ open: boolean; stageName: string; documents: any[] }>({ open: false, stageName: '', documents: [] }); const [resignationData, setResignationData] = useState(null); // Real data from API // URL slug may be the human-readable code (e.g. `RES-...`); SLA expects UUID. // Feed the SLA hook the resolved UUID once the request has loaded. const slaEntityId: string = resignationData?.id || ''; const { get: getSla } = useSlaBatchStatus( slaEntityId ? [{ entityType: 'resignation', entityId: slaEntityId }] : [], Boolean(slaEntityId) ); const [isLoading, setIsLoading] = useState(false); const [auditLogs, setAuditLogs] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [previewDocument, setPreviewDocument] = useState(null); const [showUploadDialog, setShowUploadDialog] = useState(false); const [uploadFile, setUploadFile] = useState(null); const [uploadDocType, setUploadDocType] = useState(RESIGNATION_DOCUMENT_TYPES[0]); const [uploadStage, setUploadStage] = useState(''); const hasUploadedPPT = useMemo(() => { const allDocs = [ ...(resignationData?.documents || []), ...(resignationData?.uploadedDocuments || []) ]; return allDocs.some(doc => (doc.documentType || doc.type) === 'PPT Presentation' ); }, [resignationData]); const fetchResignation = async () => { try { setIsLoading(true); const data = await resignationService.getResignationById(resignationId); setResignationData(data); fetchAuditLogs(); } catch (error) { console.error('Error fetching resignation:', error); } finally { setIsLoading(false); } }; const fetchAuditLogs = async () => { try { const response: any = await API.getAuditLogs('resignation', resignationId); if (response.data && response.data.success) { setAuditLogs(response.data.data || []); } } catch (error) { console.error('Error fetching audit logs:', error); } }; useEffect(() => { fetchResignation(); }, [resignationId]); // Progress stages logic based on live data const progressStages = [ { id: 1, name: 'Request Submitted', key: 'Request Submitted', description: 'Dealer submitted the resignation request' }, { id: 2, name: 'ASM Review', key: 'ASM', description: 'Area Sales Manager review' }, { id: 3, name: 'RBM + DD-ZM Review', key: 'RBM', description: 'Joint approval by Regional Business Manager and DD-ZM' }, { id: 4, name: 'ZBH Review', key: 'ZBH', description: 'Zonal Business Head approval' }, { id: 5, name: 'DD Lead Review', key: 'DD Lead', description: 'DD Lead consolidated review' }, { id: 6, name: 'DD Head Review', key: 'DD Head', description: 'DD Head final dealer development approval' }, { id: 7, name: 'NBH Approval', key: 'NBH', description: 'National Business Head approval' }, { id: 8, name: 'Legal - Resignation Letter', key: 'Legal', description: 'Legal team issues resignation approval letter' }, { id: 9, name: 'DD Admin Review', key: 'DD Admin', description: 'DD Admin verification and final closure' }, { id: 10, name: 'Awaiting F&F', key: 'Awaiting F&F', description: 'Internal review complete — start Full & Final using Push to F&F when ready' }, { id: 11, name: 'F&F Settlement', key: 'F&F Initiated', description: 'Full & Final settlement process' }, { id: 12, name: 'Completed', key: 'Completed', description: 'Resignation process finalized' } ]; const stagesOrdered = ['Request Submitted', 'ASM', 'RBM', 'ZBH', 'DD Lead', 'DD Head', 'NBH', 'Legal', 'DD Admin', 'Awaiting F&F', 'F&F Initiated', 'Completed']; const legalStageApproved = (() => { if (!resignationData) return false; const inOrBeyondFnF = ['F&F Initiated', 'Completed', 'Settled', 'FNF_INITIATED'].includes( String(resignationData.status || resignationData.currentStage || '') ); if (inOrBeyondFnF) return true; const timeline = Array.isArray(resignationData.timeline) ? resignationData.timeline : []; return timeline.some((entry: any) => { const stage = String(entry?.stage || '').trim().toLowerCase(); const targetStage = String(entry?.targetStage || '').trim().toLowerCase(); const action = String(entry?.action || '').trim().toLowerCase(); const atLegal = stage === 'legal' || stage === 'legal - resignation letter'; const legalApprovedTransition = targetStage === 'legal' || targetStage === 'dd admin' || targetStage === 'awaiting f&f' || targetStage === 'f&f initiated' || targetStage === 'fnf_initiated' || action.includes('approved'); return atLegal && legalApprovedTransition; }); })(); const getResignationPermissions = () => { if (!resignationData || !currentUser) { return { canApprove: false, canDispatch: false, dispatchMissed: false, canWithdraw: false, canSendBack: false, canPushToFnF: false, canAssign: false }; } const currentStage = resignationData.currentStage; const status = resignationData.status; const userRole = currentUser.role; const isZmRbmStage = currentStage === 'RBM' || currentStage === 'RBM Review' || currentStage === 'RBM + DD-ZM Review'; const userRoleCode = String(currentUser.roleCode || currentUser.role || '').trim().toUpperCase(); // Check if current user already partially approved this request at this stage const hasAlreadyPartiallyApproved = isZmRbmStage && auditLogs.some(log => log.action === 'PARTIAL_APPROVE' && (log.actor?.id === currentUser.id || log.actorId === currentUser.id || log.actor?.email === currentUser.email || log.userEmail === currentUser.email) && (log.details?.roleCode === userRoleCode || (log.details?.roleCode === 'DD-ZM' && userRoleCode === 'DD ZM')) ); const isFinalState = ['Completed', 'Rejected', 'Withdrawn', 'Revoked'].includes(status); // Check if it's already in the settlement phase const isSettlementPhase = status === 'F&F Initiated' || currentStage === 'F&F Initiated' || status === 'Settled' || status === 'FNF_INITIATED'; const stageIndex = stagesOrdered.indexOf(currentStage); const nbhIndex = stagesOrdered.indexOf('NBH'); const isPastNBH = stageIndex !== -1 && nbhIndex !== -1 && stageIndex >= nbhIndex; const isCurrentlyAssigned = userRoleCode === 'SUPER_ADMIN' || (isZmRbmStage && (userRoleCode === 'RBM' || userRoleCode === 'DD-ZM' || userRoleCode === 'DD ZM')) || userRole === STAGE_TO_ROLE_MAP[currentStage]; const isDDLeadStage = currentStage === 'DD Lead' || currentStage === 'DD Lead Review'; const isDDLead = userRoleCode === 'DD_LEAD' || userRoleCode === 'DD LEAD'; const isLwdReached = (() => { const today = new Date(); today.setHours(0, 0, 0, 0); const lwdString = resignationData.lastOperationalDateServices || resignationData.lastOperationalDateSales; if (!lwdString) return true; const lwd = new Date(lwdString); lwd.setHours(0, 0, 0, 0); return today >= lwd; })(); const isAwaitingFnfGate = currentStage === 'Awaiting F&F'; const resolvedStageKey = (() => { const normalized = String(currentStage || '').trim(); const matched = stagesOrdered.find( (key) => key === normalized || (RESIGNATION_STAGE_ALIASES[key] || []).includes(normalized) ); return matched || normalized; })(); const isNbHApprovalStep = resolvedStageKey === 'NBH'; const isAwaitingFnfStep = resolvedStageKey === 'Awaiting F&F'; /** Legacy rows only: acceptance letter at Legal before DD Admin completed Awaiting F&F gate */ const isLegalLegacyFnfStep = resolvedStageKey === 'Legal'; const fnfPushRoles = ['DD Lead', 'DD Head', 'DD Admin', 'Super Admin']; const fnfPushLegacyRoles = ['DD Lead', 'DD Head', 'DD Admin', 'Super Admin']; // Dispatch action — Legal uploaded the acceptance letter and DD Admin (or // Super Admin) must dispatch a formal copy to the dealer. The button stays // visible from the DD Admin step through Awaiting F&F / F&F Initiated so // an admin who skipped the step earlier can still send the letter // retroactively. Once dispatched it disappears (audit log is the source of // truth) so we don't re-send duplicates. const isDDAdminStage = currentStage === 'DD Admin' || currentStage === 'DD Admin Review'; const isDDAdmin = userRoleCode === 'DD_ADMIN' || userRole === 'DD Admin'; const isSuperAdmin = userRoleCode === 'SUPER_ADMIN' || userRole === 'Super Admin'; const allResignationDocs: any[] = [ ...(resignationData.documents || []), ...(resignationData.uploadedDocuments || []) ]; const hasAcceptanceLetter = allResignationDocs.some((doc: any) => { const docType = String(doc?.documentType || doc?.type || '').toLowerCase(); const docStage = String(doc?.stage || '').toLowerCase(); return docType.includes('acceptance letter') || docStage === 'legal'; }); const hasBeenDispatched = auditLogs.some((log: any) => { const action = String(log?.action || '').toUpperCase(); const desc = String(log?.description || log?.details?.action || '').toLowerCase(); return ( action === 'RESIGNATION_LETTER_DISPATCHED' || desc.includes('resignation letter dispatched') ); }) || (resignationData.timeline || []).some((entry: any) => String(entry?.action || '').toLowerCase().includes('resignation letter dispatched') ); const ddAdminIdx = stagesOrdered.indexOf('DD Admin'); const completedIdx = stagesOrdered.indexOf('Completed'); const currentStageIdx = stagesOrdered.indexOf( stagesOrdered.find( (key) => key === currentStage || (RESIGNATION_STAGE_ALIASES[key] || []).includes(currentStage) ) || currentStage ); const isAtOrAfterDDAdmin = ddAdminIdx !== -1 && currentStageIdx !== -1 && currentStageIdx >= ddAdminIdx && (completedIdx === -1 || currentStageIdx < completedIdx); const canDispatch = isAtOrAfterDDAdmin && (isDDAdmin || isSuperAdmin) && hasAcceptanceLetter && !hasBeenDispatched && !isFinalState; const dispatchMissed = canDispatch && !isDDAdminStage; const canApprove = isCurrentlyAssigned && !isFinalState && !isSettlementPhase && !hasAlreadyPartiallyApproved && !(currentStage === 'Legal' && legalStageApproved) && !(isDDLead && isDDLeadStage && !hasUploadedPPT) && !(currentStage === 'DD Admin' && !isLwdReached) && // Dispatch replaces Approve at the DD Admin stage so the admin must // explicitly send the acceptance letter to the dealer. !isDDAdminStage && !isAwaitingFnfGate; return { canApprove, canDispatch, dispatchMissed, // SRS §7.3.2: Send Back returns to DD Admin for correction. Legal Admin only drafts/uploads // the Resignation Acceptance Letter and cannot send the case back to earlier reviewers. canSendBack: isCurrentlyAssigned && !isFinalState && !isSettlementPhase && stageIndex > 0 && userRole !== 'Legal Admin' && userRoleCode !== 'LEGAL_ADMIN', canWithdraw: userRole === 'Dealer' && !isPastNBH && !isFinalState, canRevoke: (userRoleCode === 'SUPER_ADMIN' || userRole === 'DD Admin') && !isFinalState && !isSettlementPhase, // Push to F&F: after DD Admin gate only — not during NBH (or any earlier) approval. // Roles: DD Lead / DD Head / DD Admin / Super Admin (not NBH). canPushToFnF: fnfPushRoles.includes(userRole) && !isSettlementPhase && !isFinalState && !isNbHApprovalStep && isLwdReached && (isAwaitingFnfStep || (isLegalLegacyFnfStep && fnfPushLegacyRoles.includes(userRole))), canAssign: userRole !== 'Dealer' && !isFinalState }; }; const permissions = getResignationPermissions(); const isNationalLevel = ['Super Admin', 'DD Lead', 'DD Head', 'NBH', 'DD Admin', 'Legal Admin', 'DD-ZM'].includes(currentUser?.role || ''); const stageAliases: Record = { 'ASM': ['ASM', 'ASM Review', 'Request Initiated'], 'RBM': ['RBM', 'RBM Review', 'RBM + DD-ZM Review'], 'ZBH': ['ZBH', 'ZBH Review'], 'DD Lead': ['DD Lead', 'DD Lead Review', 'Lead Review'], 'DD Head': ['DD Head', 'DD Head Review', 'Head Review'], 'NBH': ['NBH', 'NBH Approval', 'NBH Review'], 'DD Admin': ['DD Admin', 'DD Admin Review'], 'Awaiting F&F': ['Awaiting F&F', 'Awaiting F&F — manual initiation'], 'Legal': ['Legal', 'Legal - Resignation Letter', 'Legal Review'], 'F&F Initiated': ['F&F Initiated', 'FNF_INITIATED', 'F&F Settlement', 'Settled'], 'Completed': ['Completed', 'Finalized'] }; const resolveStageKey = (label?: string) => { if (!label) return ''; const normalized = String(label).trim(); const matched = stagesOrdered.find((key) => key === normalized || (stageAliases[key] || []).includes(normalized) ); return matched || normalized; }; const getStageStatus = (stageKey: string) => { if (!resignationData) return 'pending'; const isTerminal = ['Rejected', 'Revoked', 'Withdrawn'].includes(resignationData.status); let currentStageForProgress = resignationData.currentStage; // For terminal states, resolve the last active workflow stage from timeline. // Backend sets currentStage to "Rejected" when status is Revoked — that label is not in stagesOrdered, // so we must recover the pre-terminal stage (e.g. NBH) from the timeline or indexes stay -1 and only ASM shows complete. if (isTerminal && (!currentStageForProgress || TERMINAL_STAGE_LABELS.includes(String(currentStageForProgress)))) { const lastEntry = [...(resignationData.timeline || [])].reverse().find( (e: any) => e?.stage && !TERMINAL_STAGE_LABELS.includes(String(e.stage)) ); if (lastEntry?.stage) currentStageForProgress = lastEntry.stage; } const currentIndex = stagesOrdered.indexOf(resolveStageKey(currentStageForProgress)); const stageIndex = stagesOrdered.indexOf(stageKey); // Final state override: if the whole request is completed, mark current stage as completed too if (resignationData.status === 'Completed' || resignationData.status === 'Settled') { if (stageIndex <= currentIndex) return 'completed'; } const isFailedState = ['Rejected', 'Revoked', 'Withdrawn'].includes(resignationData.status); if (stageKey === 'Legal' && legalStageApproved && !isFailedState) return 'completed'; if (currentIndex === -1) return stageKey === 'ASM' ? 'completed' : 'pending'; if (stageIndex < currentIndex) return 'completed'; if (stageIndex === currentIndex) { return isTerminal ? 'completed' : 'active'; } return 'pending'; }; const handleAction = (type: 'approve' | 'withdrawal' | 'sendBack' | 'assign' | 'pushfnf' | 'revoke' | 'dispatch') => { setActionDialog({ open: true, type }); }; const handleViewStageDocuments = (stageName: string, stageKey?: string) => { const documents = getDocumentsForStage(stageName, stageKey).map((doc: any, index: number) => ({ id: doc.id || `${stageName}-${index}`, name: doc.name || doc.fileName || 'Document', type: doc.type || doc.documentType || 'Document', uploadDate: doc.uploadDate || (doc.createdAt ? formatDateTime(doc.createdAt) : 'N/A'), uploader: typeof doc.uploader === 'string' ? doc.uploader : (doc.uploader?.fullName || doc.uploadedBy || 'System'), filePath: doc.filePath || doc.path })); setStageDocumentsDialog({ open: true, stageName, documents }); }; const handleSubmitAction = async () => { if (!remarks && !['assign', 'pushfnf', 'dispatch'].includes(actionDialog.type || '')) { toast.error('Please provide remarks (min 5 characters)'); return; } // Explicitly enforce min length for standardized offboarding actions if (['sendBack', 'revoke'].includes(actionDialog.type || '') && remarks.trim().length < 5) { toast.error('Remarks are required for this action (min 5 characters).'); return; } if (actionDialog.type === 'assign' && !assignToUser) { toast.error('Please select a designation'); return; } try { setIsSubmitting(true); const actionLabel = actionDialog.type === 'sendBack' ? 'sendBack' : actionDialog.type; const payload = { action: actionLabel, remarks, assignTo: selectedSpecificUser || assignToUser, // Use specific user if selected, otherwise fallback to role for auto-resolution force: forceTriggerFnF }; const response: any = await API.updateResignationStatus(resignationId, payload); if (response.data?.success) { toast.success(response.data?.message || 'Action completed successfully'); setActionDialog({ open: false, type: null }); setRemarks(''); setAssignToUser(''); setSelectedSpecificUser(''); setAvailableUsers([]); setForceTriggerFnF(false); fetchResignation(); } else { const message = response.data?.message || 'Failed to submit action'; toast.error(message); // When Legal approval bumps into LWD gate for F&F initiation, guide user explicitly. if (response.data?.canForce) { toast.info( `${LAST_WORKING_DAY_LABEL} restriction: use "Push to F&F" and enable "Force Initiate F&F Settlement Immediately" if urgent.` ); } } } catch (error: any) { console.error('Error submitting action:', error); toast.error(error.response?.data?.message || 'Failed to submit action'); if (error?.response?.data?.canForce) { toast.info( `${LAST_WORKING_DAY_LABEL} restriction: use "Push to F&F" with the force option if business-approved.` ); } } finally { setIsSubmitting(false); } }; const handleUploadDocument = async () => { if (!uploadFile) { toast.error('Please select a file to upload'); return; } try { setIsSubmitting(true); const formData = new FormData(); formData.append('file', uploadFile); formData.append('documentType', uploadDocType); if (uploadStage) formData.append('stage', uploadStage); await resignationService.uploadDocument(resignationId, formData); toast.success('Document uploaded successfully'); setShowUploadDialog(false); setUploadFile(null); setUploadDocType(RESIGNATION_DOCUMENT_TYPES[0]); setUploadStage(''); fetchResignation(); } catch (error: any) { toast.error(error?.response?.data?.message || 'Failed to upload document'); } finally { setIsSubmitting(false); } }; useEffect(() => { const fetchUsers = async () => { // Fetch users if designation is selected OR search query is typed if (actionDialog.type === 'assign' && (assignToUser || userSearchQuery)) { const timeoutId = setTimeout(async () => { try { setIsLoadingUsers(true); const roleMap: Record = { 'asm': 'ASM', 'rbm': 'RBM', 'zbh': 'ZBH', 'nbh': 'NBH', 'legal': 'Legal Admin' }; const params: any = { limit: 20, search: userSearchQuery }; if (assignToUser) { params.roleCode = roleMap[assignToUser] || assignToUser; } const res: any = await API.getUsers(params); if (res.data?.success) { setAvailableUsers(res.data.data); } } catch (error) { console.error('Error fetching users:', error); } finally { setIsLoadingUsers(false); } }, 300); // 300ms debounce return () => clearTimeout(timeoutId); } }; fetchUsers(); }, [assignToUser, userSearchQuery, actionDialog.type]); if (isLoading && !resignationData) { return (
); } return (
{/* Header */}

{resignationData?.resignationId || resignationId}

{resignationData?.outlet?.name}

{resignationData?.status === 'Settled' ? 'Completed' : formatOffboardingStatusLabel(resignationData?.status || 'Pending')}
Details Progress Documents Audit Trail {isNationalLevel && ( Approval Summary )}
{/* Details Tab */} Resignation Details

{resignationData?.resignationType}

{resignationData?.reason}

{resignationData?.lastOperationalDateSales ? formatDateTime(resignationData.lastOperationalDateSales, 'date') : 'N/A'}

{resignationData?.lastOperationalDateServices ? formatDateTime(resignationData.lastOperationalDateServices, 'date') : 'N/A'}

{resignationData?.additionalInfo || 'No additional info provided'}

{resignationData?.submittedOn ? formatDateTime(resignationData.submittedOn) : 'N/A'}

{resignationData?.currentStage}

Request Information

{resignationData?.dealer?.fullName || resignationData?.outlet?.name}

{resignationData?.dealer?.dealerProfile?.gstNumber || resignationData?.outlet?.gstNumber || 'N/A'}

{resignationData?.dealer?.email || 'N/A'}

{resignationData?.dealer?.dealerProfile?.dealerCode?.salesCode || 'N/A'}

{resignationData?.dealer?.dealerProfile?.dealerCode?.serviceCode || 'N/A'}

{resignationData?.dealer?.dealerProfile?.dealerCode?.gmaCode || 'N/A'}

{resignationData?.dealer?.dealerProfile?.dealerCode?.gearCode || 'N/A'}

{resignationData?.dealer?.dealerProfile?.registeredAddress || resignationData?.outlet?.address}

Operational Details

{resignationData?.dealer?.dealerProfile?.onboardedAt ? formatDateTime(resignationData.dealer.dealerProfile.onboardedAt, 'date') : (resignationData?.outlet?.inaugurationDate ? formatDateTime(resignationData.outlet.inaugurationDate, 'date') : 'N/A')}

{resignationData?.dealer?.dealerProfile?.loaDate ? formatDateTime(resignationData.dealer.dealerProfile.loaDate, 'date') : 'N/A'}

{resignationData?.dealer?.dealerProfile?.loiDate ? formatDateTime(resignationData.dealer.dealerProfile.loiDate, 'date') : 'N/A'}

{resignationData?.dealer?.dealerProfile?.application?.businessType || resignationData?.outlet?.type || 'N/A'}

{resignationData?.outlet?.cityCategory || 'N/A'}

{/* Progress Tab */} Progress Timeline Track the resignation request approval process
{progressStages.map((stage, index) => { const status = getStageStatus(stage.key); const stageDocumentCount = getDocumentsForStage(stage.name, stage.key).length; const stageTimelineEntries = (resignationData?.timeline || []).filter( (t: any) => t.stage === stage.key || t.stage === stage.name ); const timelineEntry = stageTimelineEntries.length > 0 ? stageTimelineEntries[stageTimelineEntries.length - 1] : null; return (
{status === 'completed' ? ( ) : ( {stage.id} )}
{index < progressStages.length - 1 && (
)}

{stage.name}

{stageDocumentCount > 0 && ( )}
{timelineEntry && (
{formatDateTime(timelineEntry.timestamp || timelineEntry.createdAt)}
)}

{stage.description}

{stageTimelineEntries.length > 0 && (
{stageTimelineEntries.map((entry: any, i: number) => (
{entry.user || 'System'} {entry.action} {formatDateTime(entry.timestamp || entry.createdAt)}
{entry.comments || entry.remarks || 'No remarks provided.'}
))}
)}
); })}
{/* Documents Tab */}
Documents View and manage resignation documents
Document Name Type Upload Date Uploader Actions {(() => { const allDocs = [ ...(resignationData?.documents || []), ...(resignationData?.uploadedDocuments || []) ]; // Add clearance documents from legacy JSON field if (resignationData?.departmentalClearances) { Object.entries(resignationData.departmentalClearances).forEach(([dept, data]: [string, any]) => { if (data.supportingDocument) { allDocs.push({ name: `${dept} Clearance Proof`, type: 'Clearance NOC', path: data.supportingDocument, createdAt: data.updatedAt, uploadedBy: data.updatedBy || 'Department Admin' }); } }); } // Add live clearance documents from F&F model if (resignationData?.settlement?.clearances) { resignationData.settlement.clearances.forEach((c: any) => { if (c.supportingDocument) { allDocs.push({ name: `${c.department} Clearance NOC`, type: 'Live NOC', path: c.supportingDocument, createdAt: c.clearedAt || c.updatedAt, uploadedBy: 'Department Admin' }); } }); } if (allDocs.length === 0) return ( No documents found ); return allDocs.map((doc: any, index: number) => (
{doc.name || doc.fileName}
{doc.documentType || doc.type || 'Document'} {doc.createdAt ? formatDateTime(doc.createdAt) : 'N/A'} {doc.uploader?.fullName || doc.uploadedBy || 'Dealer'}
)) })() }
{/* Audit Trail Tab */} Audit Trail Complete history of actions on this resignation request
{auditLogs.length > 0 ? ( auditLogs.map((log: any, index: number) => (
{log.description || log.action} by {log.actor?.name || log.userName || 'System'}
{formatDateTime(log.timestamp || log.createdAt)}
{(log.remarks || log.newData?.remarks || log.details?.remarks) && (
{log.remarks || log.newData?.remarks || log.details?.remarks}
)}
)) ) : (

No activity logs found for this case.

)}
{/* Approval Summary Tab */} {isNationalLevel && (
Approval Summary Comprehensive view of all approvals and remarks
{permissions.canApprove && ( )}
Stage Approver Action Remarks Date {(resignationData?.timeline || []).length > 0 ? ( resignationData.timeline.map((entry: any, index: number) => ( {entry.stage} {entry.user || 'System'} {entry.action} {entry.remarks || entry.comments || '-'} {formatDateTime(entry.timestamp || entry.createdAt)} )) ) : ( No approval records found )}
)}
{/* Sidebar */}
Actions {(() => { const roleNormalized = String(currentUser?.roleCode || currentUser?.role || '').trim().toUpperCase(); const isDDLeadUser = roleNormalized === 'DD LEAD' || roleNormalized === 'DD_LEAD'; const isDDLeadStageCurrent = ['DD Lead', 'DD Lead Review', 'DDL Review'].includes(resignationData?.currentStage); if (isDDLeadUser && isDDLeadStageCurrent) { return ( ); } return null; })()} {permissions.canApprove && ( )} {permissions.canDispatch && ( <> {permissions.dispatchMissed && (
Resignation acceptance letter was not dispatched at the DD Admin step. Please send it to the dealer now.
)} )} {permissions.canSendBack && ( )} {permissions.canWithdraw && ( )} {permissions.canRevoke && ( )} {permissions.canPushToFnF && ( )} {permissions.canAssign && ( )}
{/* Action Dialogs */} setActionDialog({ open, type: null })}> {actionDialog.type === 'approve' && 'Approve Resignation Request'} {actionDialog.type === 'withdrawal' && 'Withdraw Resignation Request'} {actionDialog.type === 'sendBack' && 'Send Back for Clarification'} {actionDialog.type === 'revoke' && 'Revoke Resignation Request'} {actionDialog.type === 'assign' && 'Assign to User'} {actionDialog.type === 'pushfnf' && 'Push to Full & Final Settlement'} {actionDialog.type === 'dispatch' && 'Dispatch Resignation Letter'} {actionDialog.type === 'assign' ? 'Select a user to assign this request to' : actionDialog.type === 'pushfnf' ? 'This will move the resignation request to F&F for dues clearance' : actionDialog.type === 'dispatch' ? 'The Legal-issued acceptance letter will be emailed to the dealer and the request will advance to Awaiting F&F. Remarks are optional.' : 'Please provide remarks for this action' }
{actionDialog.type === 'assign' ? (
setUserSearchQuery(e.target.value)} className="pr-8" /> {isLoadingUsers && }