/** * Dealer Claim Request Workflow Tab * * This component is specific to Dealer Claim requests. * Located in: src/dealer-claim/components/request-detail/ */ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity } from 'lucide-react'; import { formatDateTime } from '@/utils/dateFormatter'; import { DealerProposalSubmissionModal } from './modals'; import { InitiatorProposalApprovalModal } from './modals'; import { DeptLeadIOApprovalModal } from './modals'; import { DealerCompletionDocumentsModal } from './modals'; import { CreditNoteSAPModal } from './modals'; import { EmailNotificationTemplateModal } from './modals'; import { DMSPushModal } from './modals'; import { toast } from 'sonner'; import { submitProposal, updateIODetails, submitCompletion, updateEInvoice } from '@/services/dealerClaimApi'; import { getWorkflowDetails, approveLevel, rejectLevel } from '@/services/workflowApi'; import { uploadDocument } from '@/services/documentApi'; interface DealerClaimWorkflowTabProps { request: any; user: any; isInitiator: boolean; onSkipApprover?: (data: any) => void; onRefresh?: () => void; } interface WorkflowStep { step: number; title: string; approver: string; description: string; tatHours: number; status: 'pending' | 'approved' | 'waiting' | 'rejected' | 'in_progress'; comment?: string; approvedAt?: string; elapsedHours?: number; // Special fields for dealer claims ioDetails?: { ioNumber: string; ioRemark: string; organizedBy: string; organizedAt: string; blockedAmount?: number; availableBalance?: number; remainingBalance?: number; }; dmsDetails?: { dmsNumber: string; dmsRemarks: string; pushedBy: string; pushedAt: string; }; einvoiceUrl?: string; emailTemplateUrl?: string; } /** * Safe date formatter with fallback */ const formatDateSafe = (dateString: string | undefined | null): string => { if (!dateString) return ''; try { return formatDateTime(dateString); } catch (error) { // Fallback to simple date format try { return new Date(dateString).toLocaleString('en-IN', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true, }); } catch { return dateString; } } }; /** * Get step icon based on status */ const getStepIcon = (status: string) => { switch (status) { case 'approved': return ; case 'pending': return ; case 'rejected': return ; default: return ; } }; /** * Get step badge variant */ const getStepBadgeVariant = (status: string) => { switch (status) { case 'approved': return 'bg-green-100 text-green-800 border-green-200'; case 'pending': return 'bg-purple-100 text-purple-800 border-purple-200'; case 'rejected': return 'bg-red-100 text-red-800 border-red-200'; default: return 'bg-gray-100 text-gray-800 border-gray-200'; } }; /** * Get step card styling */ const getStepCardStyle = (status: string, isActive: boolean) => { if (isActive && (status === 'pending' || status === 'in_progress')) { return 'border-purple-500 bg-purple-50 shadow-md'; } if (status === 'approved') { return 'border-green-500 bg-green-50'; } if (status === 'rejected') { return 'border-red-500 bg-red-50'; } return 'border-gray-200 bg-white'; }; /** * Get step icon background */ const getStepIconBg = (status: string) => { switch (status) { case 'approved': return 'bg-green-100'; case 'pending': return 'bg-purple-100'; case 'rejected': return 'bg-red-100'; default: return 'bg-gray-100'; } }; export function DealerClaimWorkflowTab({ request, user, isInitiator, onSkipApprover: _onSkipApprover, onRefresh }: DealerClaimWorkflowTabProps) { const [showProposalModal, setShowProposalModal] = useState(false); const [showApprovalModal, setShowApprovalModal] = useState(false); const [showIOApprovalModal, setShowIOApprovalModal] = useState(false); const [showCompletionModal, setShowCompletionModal] = useState(false); const [showDMSPushModal, setShowDMSPushModal] = useState(false); const [showCreditNoteModal, setShowCreditNoteModal] = useState(false); const [showEmailTemplateModal, setShowEmailTemplateModal] = useState(false); const [selectedStepForEmail, setSelectedStepForEmail] = useState<{ stepNumber: number; stepName: string } | null>(null); // Load approval flows from real API const [approvalFlow, setApprovalFlow] = useState([]); const [refreshTrigger, setRefreshTrigger] = useState(0); // Reload approval flows whenever request changes or after refresh useEffect(() => { const loadApprovalFlows = async () => { // First check if request has approvalFlow if (request?.approvalFlow && request.approvalFlow.length > 0) { setApprovalFlow(request.approvalFlow); return; } // Load from real API if (request?.id || request?.requestId) { const requestId = request.id || request.requestId; try { const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; if (approvals && approvals.length > 0) { // Transform approval levels to match expected format const flows = approvals.map((level: any) => ({ step: level.levelNumber || level.level_number || 0, approver: level.approverName || level.approver_name || '', approverEmail: (level.approverEmail || level.approver_email || '').toLowerCase(), status: level.status?.toLowerCase() || 'waiting', tatHours: level.tatHours || level.tat_hours || 24, elapsedHours: level.elapsedHours || level.elapsed_hours, approvedAt: level.actionDate || level.action_date, comment: level.comments || level.comment, levelId: level.levelId || level.level_id, })); setApprovalFlow(flows); } } catch (error) { console.warn('Failed to load approval flows from API:', error); } } }; loadApprovalFlows(); }, [request, refreshTrigger]); // Also reload when request.currentStep changes useEffect(() => { if (request?.id || request?.requestId) { const requestId = request.id || request.requestId; const loadApprovalFlows = async () => { try { const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; if (approvals && approvals.length > 0) { const flows = approvals.map((level: any) => ({ step: level.levelNumber || level.level_number || 0, approver: level.approverName || level.approver_name || '', approverEmail: (level.approverEmail || level.approver_email || '').toLowerCase(), status: level.status?.toLowerCase() || 'waiting', tatHours: level.tatHours || level.tat_hours || 24, elapsedHours: level.elapsedHours || level.elapsed_hours, approvedAt: level.actionDate || level.action_date, comment: level.comments || level.comment, levelId: level.levelId || level.level_id, })); setApprovalFlow(flows); } } catch (error) { console.warn('Failed to load approval flows from API:', error); } }; loadApprovalFlows(); } }, [request?.currentStep]); // Enhanced refresh handler that also reloads approval flows const handleRefresh = () => { setRefreshTrigger(prev => prev + 1); onRefresh?.(); }; // Transform approval flow to dealer claim workflow steps const workflowSteps: WorkflowStep[] = approvalFlow.map((step: any, index: number) => { const stepTitles = [ 'Dealer - Proposal Submission', 'Requestor Evaluation & Confirmation', 'Dept Lead Approval', 'Activity Creation', 'Dealer - Completion Documents', 'Requestor - Claim Approval', 'E-Invoice Generation', 'Credit Note from SAP', ]; const stepDescriptions = [ 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests', 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)', 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)', 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.', 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description', 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.', 'E-invoice will be generated through DMS.', 'Got credit note from SAP. Review and send to dealer to complete the claim management process.', ]; // Find approval data for this step const approval = request?.approvals?.find((a: any) => a.levelId === step.levelId); // Extract IO details from internalOrder table (Step 3) let ioDetails = undefined; if (step.step === 3) { // Get IO details from dedicated internalOrder table const internalOrder = request?.internalOrder || request?.internal_order; if (internalOrder?.ioNumber || internalOrder?.io_number) { // Try multiple field name variations for ioRemark const ioRemarkValue = internalOrder.ioRemark || internalOrder.io_remark || internalOrder.IORemark || internalOrder.IO_Remark || (internalOrder as any)?.ioRemark || (internalOrder as any)?.io_remark || ''; ioDetails = { ioNumber: internalOrder.ioNumber || internalOrder.io_number || '', ioRemark: (ioRemarkValue && typeof ioRemarkValue === 'string' && ioRemarkValue.trim()) ? ioRemarkValue.trim() : 'N/A', blockedAmount: internalOrder.ioBlockedAmount || internalOrder.io_blocked_amount || 0, availableBalance: internalOrder.ioAvailableBalance || internalOrder.io_available_balance || 0, remainingBalance: internalOrder.ioRemainingBalance || internalOrder.io_remaining_balance || 0, organizedBy: internalOrder.organizer?.displayName || internalOrder.organizer?.name || internalOrder.organizedBy || step.approver || 'N/A', organizedAt: internalOrder.organizedAt || internalOrder.organized_at || step.approvedAt || request?.updatedAt || '', }; } } // Extract DMS details from approval data (Step 6) let dmsDetails = undefined; if (step.step === 6) { if (approval?.dmsDetails) { dmsDetails = { dmsNumber: approval.dmsDetails.dmsNumber || '', dmsRemarks: approval.dmsDetails.dmsRemarks || '', pushedBy: approval.dmsDetails.pushedBy || step.approver, pushedAt: approval.dmsDetails.pushedAt || step.approvedAt || '', }; } else if (request?.dmsNumber) { // Fallback to request-level DMS data dmsDetails = { dmsNumber: request.dmsNumber || '', dmsRemarks: request.dmsRemarks || request.dmsDetails?.dmsRemarks || '', pushedBy: step.approver, pushedAt: step.approvedAt || request.updatedAt || '', }; } } // Normalize status - handle "in-review" and other variations let normalizedStatus = (step.status || 'waiting').toLowerCase(); if (normalizedStatus === 'in-review' || normalizedStatus === 'in_review' || normalizedStatus === 'in review') { normalizedStatus = 'in_progress'; } return { step: step.step || index + 1, title: stepTitles[index] || `Step ${step.step || index + 1}`, approver: step.approver || 'Unknown', description: stepDescriptions[index] || step.description || '', tatHours: step.tatHours || 24, status: normalizedStatus as any, comment: step.comment || approval?.comment, approvedAt: step.approvedAt || approval?.timestamp, elapsedHours: step.elapsedHours, ioDetails, dmsDetails, einvoiceUrl: step.step === 7 ? (approval as any)?.einvoiceUrl : undefined, emailTemplateUrl: step.step === 4 ? (approval as any)?.emailTemplateUrl : undefined, }; }); const totalSteps = request?.totalSteps || 8; // Calculate currentStep from approval flow - find the first pending or in_progress step // If no pending/in_progress step, use the request's currentStep // Note: Status normalization already handled in workflowSteps mapping above const activeStep = workflowSteps.find(s => { const status = s.status?.toLowerCase() || ''; return status === 'pending' || status === 'in_progress' || status === 'in-review' || status === 'in_review'; }); const currentStep = activeStep ? activeStep.step : (request?.currentStep || 1); // Check if current user is the dealer (for steps 1 and 5) const userEmail = (user as any)?.email?.toLowerCase() || ''; const dealerEmail = ( (request as any)?.dealerEmail?.toLowerCase() || (request as any)?.dealer?.email?.toLowerCase() || (request as any)?.claimDetails?.dealerEmail?.toLowerCase() || (request as any)?.claimDetails?.dealer_email?.toLowerCase() || '' ); const isDealer = dealerEmail && userEmail === dealerEmail; // Check if current user is the approver for the current step const currentApprovalLevel = approvalFlow.find((level: any) => (level.step || level.levelNumber || level.level_number) === currentStep ); const approverEmail = (currentApprovalLevel?.approverEmail || '').toLowerCase(); const isCurrentApprover = approverEmail && userEmail === approverEmail; // Check if user is approver for step 2 (requestor evaluation) - match by email const step2Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 2); const step2ApproverEmail = (step2Level?.approverEmail || '').toLowerCase(); const isStep2Approver = step2ApproverEmail && userEmail === step2ApproverEmail; // Check if user is approver for step 1 (dealer proposal submission) - match by email const step1Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 1); const step1ApproverEmail = (step1Level?.approverEmail || '').toLowerCase(); const isStep1Approver = step1ApproverEmail && userEmail === step1ApproverEmail; // Check if user is approver for step 3 (department lead approval) - match by email const step3Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 3); const step3ApproverEmail = (step3Level?.approverEmail || '').toLowerCase(); const isStep3Approver = step3ApproverEmail && userEmail === step3ApproverEmail; // Handle proposal submission const handleProposalSubmit = async (data: { proposalDocument: File | null; costBreakup: Array<{ id: string; description: string; amount: number }>; expectedCompletionDate: string; otherDocuments: File[]; dealerComments: string; }) => { try { if (!request?.id && !request?.requestId) { throw new Error('Request ID not found'); } const requestId = request.id || request.requestId; // Upload proposal document if provided if (data.proposalDocument) { await uploadDocument(data.proposalDocument, requestId, 'APPROVAL'); } // Upload other supporting documents for (const file of data.otherDocuments) { await uploadDocument(file, requestId, 'SUPPORTING'); } // Submit proposal using dealer claim API const totalBudget = data.costBreakup.reduce((sum, item) => sum + item.amount, 0); await submitProposal(requestId, { proposalDocument: data.proposalDocument || undefined, costBreakup: data.costBreakup.map(item => ({ description: item.description, amount: item.amount, })), totalEstimatedBudget: totalBudget, expectedCompletionDate: data.expectedCompletionDate, dealerComments: data.dealerComments, }); // Activity is logged by backend service - no need to create work note toast.success('Proposal submitted successfully'); handleRefresh(); } catch (error: any) { console.error('Failed to submit proposal:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to submit proposal. Please try again.'; toast.error(errorMessage); throw error; } }; // Handle proposal approval const handleProposalApprove = async (comments: string) => { try { if (!request?.id && !request?.requestId) { throw new Error('Request ID not found'); } const requestId = request.id || request.requestId; // Get approval levels to find Step 2 levelId const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; const step2Level = approvals.find((level: any) => (level.levelNumber || level.level_number) === 2 ); if (!step2Level?.levelId && !step2Level?.level_id) { throw new Error('Step 2 approval level not found'); } const levelId = step2Level.levelId || step2Level.level_id; // Approve Step 2 using real API await approveLevel(requestId, levelId, comments); // Activity is logged by backend approval service - no need to create work note toast.success('Proposal approved successfully'); handleRefresh(); } catch (error: any) { console.error('Failed to approve proposal:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to approve proposal. Please try again.'; toast.error(errorMessage); throw error; } }; // Handle proposal rejection const handleProposalReject = async (comments: string) => { try { if (!request?.id && !request?.requestId) { throw new Error('Request ID not found'); } const requestId = request.id || request.requestId; // Get approval levels to find Step 2 levelId const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; const step2Level = approvals.find((level: any) => (level.levelNumber || level.level_number) === 2 ); if (!step2Level?.levelId && !step2Level?.level_id) { throw new Error('Step 2 approval level not found'); } const levelId = step2Level.levelId || step2Level.level_id; // Reject Step 2 using real API await rejectLevel(requestId, levelId, 'Proposal rejected by requestor', comments); // Activity is logged by backend approval service - no need to create work note toast.success('Proposal rejected. Request has been cancelled.'); handleRefresh(); } catch (error: any) { console.error('Failed to reject proposal:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to reject proposal. Please try again.'; toast.error(errorMessage); throw error; } }; // Handle IO approval (Step 3) const handleIOApproval = async (data: { ioNumber: string; ioRemark: string; comments: string; }) => { try { if (!request?.id && !request?.requestId) { throw new Error('Request ID not found'); } const requestId = request.id || request.requestId; // Get approval levels to find Step 3 levelId const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; const step3Level = approvals.find((level: any) => (level.levelNumber || level.level_number) === 3 ); if (!step3Level?.levelId && !step3Level?.level_id) { throw new Error('Step 3 approval level not found'); } const levelId = step3Level.levelId || step3Level.level_id; // First, update IO details using dealer claim API // Note: We need to get IO balance from SAP integration, but for now we'll use placeholder values // The backend should handle SAP integration await updateIODetails(requestId, { ioNumber: data.ioNumber, ioRemark: data.ioRemark, ioAvailableBalance: 0, // Should come from SAP integration ioBlockedAmount: 0, // Should come from SAP integration ioRemainingBalance: 0, // Should come from SAP integration }); // Approve Step 3 using real API // IO remark is stored in claimDetails, so we just pass the comments await approveLevel(requestId, levelId, data.comments); // Activity is logged by backend approval service - no need to create work note toast.success('Request approved and IO organized successfully'); handleRefresh(); } catch (error: any) { console.error('Failed to approve and organize IO:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to approve request. Please try again.'; toast.error(errorMessage); throw error; } }; // Handle completion documents submission (Step 5) const handleCompletionSubmit = async (data: { activityCompletionDate: string; numberOfParticipants?: number; closedExpenses: Array<{ id: string; description: string; amount: number }>; totalClosedExpenses: number; completionDocuments: File[]; activityPhotos: File[]; invoicesReceipts?: File[]; attendanceSheet?: File; completionDescription: string; }) => { try { if (!request?.id && !request?.requestId) { throw new Error('Request ID not found'); } const requestId = request.id || request.requestId; // Transform expense items to match API format const closedExpenses = data.closedExpenses.map(item => ({ description: item.description, amount: item.amount, })); // Submit completion documents using dealer claim API await submitCompletion(requestId, { activityCompletionDate: data.activityCompletionDate, numberOfParticipants: data.numberOfParticipants, closedExpenses, totalClosedExpenses: data.totalClosedExpenses, completionDocuments: data.completionDocuments, activityPhotos: data.activityPhotos, }); // Upload supporting documents if provided if (data.invoicesReceipts && data.invoicesReceipts.length > 0) { for (const file of data.invoicesReceipts) { await uploadDocument(file, requestId, 'SUPPORTING'); } } if (data.attendanceSheet) { await uploadDocument(data.attendanceSheet, requestId, 'SUPPORTING'); } // Activity is logged by backend service - no need to create work note toast.success('Completion documents submitted successfully'); handleRefresh(); } catch (error: any) { console.error('Failed to submit completion documents:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to submit completion documents. Please try again.'; toast.error(errorMessage); throw error; } }; // Handle DMS push (Step 6) const handleDMSPush = async (_comments: string) => { try { if (!request?.id && !request?.requestId) { throw new Error('Request ID not found'); } const requestId = request.id || request.requestId; // Call API to push to DMS (this will auto-generate e-invoice) // eInvoiceDate is required, so we pass current date const today = new Date().toISOString().slice(0, 10); await updateEInvoice(requestId as string, { eInvoiceDate: today, }); // Activity is logged by backend service - no need to create work note toast.success('Pushed to DMS successfully. E-invoice will be generated automatically.'); handleRefresh(); } catch (error: any) { console.error('[DealerClaimWorkflowTab] Error pushing to DMS:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to push to DMS. Please try again.'; toast.error(errorMessage); throw error; } }; // Handle IO rejection (Step 3) const handleIORejection = async (comments: string) => { try { if (!request?.id && !request?.requestId) { throw new Error('Request ID not found'); } const requestId = request.id || request.requestId; // Get approval levels to find Step 3 levelId const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; const step3Level = approvals.find((level: any) => (level.levelNumber || level.level_number) === 3 ); if (!step3Level?.levelId && !step3Level?.level_id) { throw new Error('Step 3 approval level not found'); } const levelId = step3Level.levelId || step3Level.level_id; // Reject Step 3 using real API await rejectLevel(requestId, levelId, 'Dept Lead rejected - More clarification required', comments); // Activity is logged by backend approval service - no need to create work note toast.success('Request rejected. Request has been cancelled.'); handleRefresh(); } catch (error: any) { console.error('Failed to reject request:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to reject request. Please try again.'; toast.error(errorMessage); throw error; } }; // Extract proposal data from request const [proposalData, setProposalData] = useState(null); useEffect(() => { if (!request) { setProposalData(null); return; } const loadProposalData = async () => { try { const requestId = request.id || request.requestId; if (!requestId) { setProposalData(null); return; } // Get workflow details which includes documents and proposal details const details = await getWorkflowDetails(requestId); const documents = details?.documents || []; const proposalDetails = request.proposalDetails || details?.proposalDetails || {}; // Find proposal document (category APPROVAL or type proposal) const proposalDoc = documents.find((d: any) => d.category === 'APPROVAL' || d.type === 'proposal' || d.documentCategory === 'APPROVAL' ); // Find supporting documents const otherDocs = documents.filter((d: any) => d.category === 'SUPPORTING' || d.type === 'supporting' || d.documentCategory === 'SUPPORTING' ); // Ensure costBreakup is an array let costBreakup = proposalDetails.costBreakup || []; if (typeof costBreakup === 'string') { try { costBreakup = JSON.parse(costBreakup); } catch (e) { console.warn('Failed to parse costBreakup JSON:', e); costBreakup = []; } } if (!Array.isArray(costBreakup)) { costBreakup = []; } setProposalData({ proposalDocument: proposalDoc ? { name: proposalDoc.fileName || proposalDoc.file_name || proposalDoc.name, id: proposalDoc.documentId || proposalDoc.document_id || proposalDoc.id, } : undefined, costBreakup: costBreakup, expectedCompletionDate: proposalDetails.expectedCompletionDate || '', otherDocuments: otherDocs.map((d: any) => ({ name: d.fileName || d.file_name || d.name, id: d.documentId || d.document_id || d.id, })), dealerComments: proposalDetails.dealerComments || '', submittedAt: proposalDetails.submittedAt, }); } catch (error) { console.warn('Failed to load proposal data:', error); // Fallback to request data only const proposalDetails = request.proposalDetails || {}; // Ensure costBreakup is an array let costBreakup = proposalDetails.costBreakup || []; if (typeof costBreakup === 'string') { try { costBreakup = JSON.parse(costBreakup); } catch (e) { console.warn('Failed to parse costBreakup JSON:', e); costBreakup = []; } } if (!Array.isArray(costBreakup)) { costBreakup = []; } setProposalData({ proposalDocument: undefined, costBreakup: costBreakup, expectedCompletionDate: proposalDetails.expectedCompletionDate || '', otherDocuments: [], dealerComments: proposalDetails.dealerComments || '', submittedAt: proposalDetails.submittedAt, }); } }; loadProposalData(); }, [request]); // Get dealer and activity info const dealerName = request?.claimDetails?.dealerName || request?.dealerInfo?.name || 'Dealer'; const activityName = request?.claimDetails?.activityName || request?.activityInfo?.activityName || request?.title || 'Activity'; return ( <>
Claim Management Workflow 8-Step approval process for dealer claim management
Step {currentStep} of {totalSteps}
{workflowSteps.map((step, index) => { // Step is active if it's pending or in_progress and matches currentStep const isActive = (step.status === 'pending' || step.status === 'in_progress') && step.step === currentStep; const isCompleted = step.status === 'approved'; return (
{/* Step Icon */}
{getStepIcon(step.status)}
{/* Step Content */}

Step {step.step}: {step.title}

{step.status.toLowerCase()} {/* Email Template Button (Step 4) - Show when approved */} {step.step === 4 && step.status === 'approved' && ( )} {/* E-Invoice Download Button (Step 7) */} {step.step === 7 && step.einvoiceUrl && isCompleted && ( )}

{step.approver}

{step.description}

TAT: {step.tatHours}h

{step.elapsedHours && (

Elapsed: {step.elapsedHours}h

)}
{/* Comment Section */} {step.comment && (

{step.comment}

)} {/* IO Organization Details (Step 3) - Show when step is approved and has IO details */} {step.step === 3 && step.status === 'approved' && step.ioDetails && step.ioDetails.ioNumber && (

IO Organisation Details

IO Number: {step.ioDetails.ioNumber}
{step.ioDetails.blockedAmount !== undefined && step.ioDetails.blockedAmount > 0 && (
Blocked Amount: ₹{step.ioDetails.blockedAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
)}

IO Remark:

{step.ioDetails.ioRemark || 'N/A'}

Organised by {step.ioDetails.organizedBy || step.approver || 'N/A'} on{' '} {step.ioDetails.organizedAt ? formatDateSafe(step.ioDetails.organizedAt) : (step.approvedAt ? formatDateSafe(step.approvedAt) : 'N/A') }
)} {/* DMS Processing Details (Step 6) */} {step.step === 6 && step.dmsDetails && step.dmsDetails.dmsNumber && (

DMS Processing Details

DMS Number: {step.dmsDetails.dmsNumber}
{step.dmsDetails.dmsRemarks && (

DMS Remarks:

{step.dmsDetails.dmsRemarks}

)} {step.dmsDetails.pushedAt && (
Pushed by {step.dmsDetails.pushedBy} on{' '} {formatDateSafe(step.dmsDetails.pushedAt)}
)}
)} {/* Action Buttons */} {isActive && (
{/* Step 1: Submit Proposal Button - Only for dealer or step 1 approver */} {step.step === 1 && (isDealer || isStep1Approver) && ( )} {/* Step 2: Confirm Request - Only for initiator or step 2 approver */} {step.step === 2 && (isInitiator || isStep2Approver) && ( )} {/* Step 3: Approve and Organise IO - Only for department lead (step 3 approver) */} {step.step === 3 && (() => { // Find step 3 from approvalFlow to get approverEmail const step3Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 3); const step3ApproverEmail = (step3Level?.approverEmail || '').toLowerCase(); const isStep3ApproverByEmail = step3ApproverEmail && userEmail === step3ApproverEmail; return isStep3ApproverByEmail || isStep3Approver || isCurrentApprover; })() && ( )} {/* Step 5: Upload Completion Documents - Only for dealer */} {step.step === 5 && isDealer && ( )} {/* Step 6: Push to DMS - Only for initiator or step 6 approver */} {step.step === 6 && (isInitiator || (() => { const step6Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 6); const step6ApproverEmail = (step6Level?.approverEmail || '').toLowerCase(); return step6ApproverEmail && userEmail === step6ApproverEmail; })()) && ( )} {/* Step 8: View & Send Credit Note - Only for finance approver or step 8 approver */} {step.step === 8 && (() => { const step8Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 8); const step8ApproverEmail = (step8Level?.approverEmail || '').toLowerCase(); const isStep8Approver = step8ApproverEmail && userEmail === step8ApproverEmail; // Also check if user has finance role const userRole = (user as any)?.role?.toUpperCase() || ''; const isFinanceUser = userRole === 'FINANCE' || userRole === 'ADMIN'; return isStep8Approver || isFinanceUser; })() && ( )}
)} {/* Approved Date */} {step.approvedAt && (

Approved on {formatDateSafe(step.approvedAt)}

)}
); })}
{/* Dealer Proposal Submission Modal */} setShowProposalModal(false)} onSubmit={handleProposalSubmit} dealerName={dealerName} activityName={activityName} requestId={request?.id || request?.requestId} /> {/* Initiator Proposal Approval Modal */} { setShowApprovalModal(false); }} onApprove={handleProposalApprove} onReject={handleProposalReject} proposalData={proposalData} dealerName={dealerName} activityName={activityName} requestId={request?.id || request?.requestId} /> {/* Dept Lead IO Approval Modal */} setShowIOApprovalModal(false)} onApprove={handleIOApproval} onReject={handleIORejection} requestTitle={request?.title} requestId={request?.id || request?.requestId} preFilledIONumber={request?.internalOrder?.ioNumber || request?.internalOrder?.io_number || request?.internal_order?.ioNumber || request?.internal_order?.io_number || undefined} preFilledBlockedAmount={request?.internalOrder?.ioBlockedAmount || request?.internalOrder?.io_blocked_amount || request?.internal_order?.ioBlockedAmount || request?.internal_order?.io_blocked_amount || undefined} /> {/* Dealer Completion Documents Modal */} setShowCompletionModal(false)} onSubmit={handleCompletionSubmit} dealerName={dealerName} activityName={activityName} requestId={request?.id || request?.requestId} /> {/* DMS Push Modal */} setShowDMSPushModal(false)} onPush={handleDMSPush} completionDetails={{ activityCompletionDate: request?.completionDetails?.activityCompletionDate || request?.completionDetails?.activity_completion_date, numberOfParticipants: request?.completionDetails?.numberOfParticipants || request?.completionDetails?.number_of_participants, closedExpenses: request?.completionExpenses || request?.completion_expenses || request?.completionDetails?.closedExpenses || request?.completionDetails?.closed_expenses, totalClosedExpenses: request?.budgetTracking?.closedExpenses || request?.budgetTracking?.closed_expenses || request?.completionDetails?.totalClosedExpenses || request?.completionDetails?.total_closed_expenses, completionDescription: request?.completionDetails?.completionDescription || request?.completionDetails?.completion_description, }} ioDetails={{ ioNumber: request?.internalOrder?.ioNumber || request?.internalOrder?.io_number || request?.internal_order?.ioNumber || request?.internal_order?.io_number, blockedAmount: request?.internalOrder?.ioBlockedAmount || request?.internalOrder?.io_blocked_amount || request?.internal_order?.ioBlockedAmount || request?.internal_order?.io_blocked_amount, availableBalance: request?.internalOrder?.ioAvailableBalance || request?.internalOrder?.io_available_balance || request?.internal_order?.ioAvailableBalance || request?.internal_order?.io_available_balance, remainingBalance: request?.internalOrder?.ioRemainingBalance || request?.internalOrder?.io_remaining_balance || request?.internal_order?.ioRemainingBalance || request?.internal_order?.io_remaining_balance, }} requestTitle={request?.title} requestNumber={request?.requestNumber || request?.request_number || request?.id} /> {/* Credit Note from SAP Modal (Step 8) */} setShowCreditNoteModal(false)} onDownload={async () => { // TODO: Implement download functionality toast.info('Download functionality will be implemented'); }} onSendToDealer={async () => { // TODO: Implement send to dealer functionality toast.info('Send to dealer functionality will be implemented'); }} creditNoteData={{ creditNoteNumber: (request as any)?.creditNote?.creditNoteNumber || (request as any)?.creditNote?.credit_note_number || (request as any)?.claimDetails?.creditNote?.creditNoteNumber || (request as any)?.claimDetails?.creditNoteNumber || (request as any)?.claimDetails?.credit_note_number, creditNoteDate: (request as any)?.creditNote?.creditNoteDate || (request as any)?.creditNote?.credit_note_date || (request as any)?.claimDetails?.creditNote?.creditNoteDate || (request as any)?.claimDetails?.creditNoteDate || (request as any)?.claimDetails?.credit_note_date, creditNoteAmount: (request as any)?.creditNote?.creditNoteAmount ? Number((request as any)?.creditNote?.creditNoteAmount) : ((request as any)?.creditNote?.credit_note_amount ? Number((request as any)?.creditNote?.credit_note_amount) : ((request as any)?.claimDetails?.creditNote?.creditNoteAmount ? Number((request as any)?.claimDetails?.creditNote?.creditNoteAmount) : ((request as any)?.claimDetails?.creditNoteAmount ? Number((request as any)?.claimDetails?.creditNoteAmount) : ((request as any)?.claimDetails?.credit_note_amount ? Number((request as any)?.claimDetails?.credit_note_amount) : undefined)))), status: 'APPROVED', }} dealerInfo={{ dealerName: (request as any)?.claimDetails?.dealerName || (request as any)?.claimDetails?.dealer_name, dealerCode: (request as any)?.claimDetails?.dealerCode || (request as any)?.claimDetails?.dealer_code, dealerEmail: (request as any)?.claimDetails?.dealerEmail || (request as any)?.claimDetails?.dealer_email, }} activityName={(request as any)?.claimDetails?.activityName || (request as any)?.claimDetails?.activity_name} requestNumber={request?.requestNumber || request?.id} requestId={request?.requestId || request?.id} dueDate={request?.dueDate} /> {/* Email Notification Template Modal */} { setShowEmailTemplateModal(false); setSelectedStepForEmail(null); }} stepNumber={selectedStepForEmail?.stepNumber || 4} stepName={selectedStepForEmail?.stepName || 'Activity Creation'} requestNumber={request?.requestNumber || request?.id || request?.request_number} recipientEmail="system@royalenfield.com" /> ); }