/** * 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 { Progress } from '@/components/ui/progress'; import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity, AlertTriangle, AlertOctagon } from 'lucide-react'; import { formatDateTime, formatDateDDMMYYYY } from '@/utils/dateFormatter'; import { formatHoursMinutes } from '@/utils/slaTracker'; 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, sendCreditNoteToDealer } 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 // Always fetch from API to ensure fresh data (don't rely on cached request.approvalFlow) // Also watch for changes in totalLevels to detect when approvers are added useEffect(() => { const loadApprovalFlows = async () => { // Always load from real API to get the latest data 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 // Include levelName and levelNumber for proper mapping const flows = approvals .map((level: any) => ({ step: level.levelNumber || level.level_number || 0, levelNumber: level.levelNumber || level.level_number || 0, levelName: level.levelName || level.level_name, 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, })) // Sort by levelNumber to ensure correct order (critical for proper display) .sort((a: any, b: any) => (a.levelNumber || 0) - (b.levelNumber || 0)); // Only update if the data actually changed (avoid unnecessary re-renders) setApprovalFlow(prevFlows => { // Check if flows are different if (prevFlows.length !== flows.length) { return flows; } // Check if any levelNumber or levelName changed const hasChanges = prevFlows.some((prev: any, idx: number) => { const curr = flows[idx]; return !curr || prev.levelNumber !== curr.levelNumber || prev.levelName !== curr.levelName || prev.approverEmail !== curr.approverEmail; }); return hasChanges ? flows : prevFlows; }); } else { // If no approvals found, clear the flow setApprovalFlow([]); } } catch (error) { console.warn('Failed to load approval flows from API:', error); // On error, try to use request.approvalFlow as fallback if (request?.approvalFlow && request.approvalFlow.length > 0) { setApprovalFlow(request.approvalFlow); } } } else if (request?.approvalFlow && request.approvalFlow.length > 0) { // Fallback: use request.approvalFlow only if no requestId available setApprovalFlow(request.approvalFlow); } }; loadApprovalFlows(); }, [request?.id, request?.requestId, request?.totalLevels, refreshTrigger]); // Also reload when request.currentStep or totalLevels changes (to catch step transitions and new approvers) 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, levelNumber: level.levelNumber || level.level_number || 0, levelName: level.levelName || level.level_name, 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, })) // Sort by levelNumber to ensure correct order .sort((a: any, b: any) => (a.levelNumber || 0) - (b.levelNumber || 0)); // Update state with new flows setApprovalFlow(flows); } } catch (error) { console.warn('Failed to load approval flows from API:', error); } }; loadApprovalFlows(); } }, [request?.currentStep, request?.totalLevels]); // Enhanced refresh handler that also reloads approval flows const handleRefresh = () => { setRefreshTrigger(prev => prev + 1); onRefresh?.(); }; // Step title and description mapping based on actual step number (not array index) // This handles cases where approvers are added between steps const getStepTitle = (stepNumber: number, levelName?: string, approverName?: string): string => { // Use levelName from backend if available (most accurate) // Check if it's an "Additional Approver" - this indicates a dynamically added approver if (levelName && levelName.trim()) { // If it starts with "Additional Approver", use it as-is (it's already formatted) if (levelName.toLowerCase().includes('additional approver')) { return levelName; } // Otherwise use the levelName from backend (preserved from original step) return levelName; } // Fallback to mapping based on step number const stepTitleMap: Record = { 1: 'Dealer - Proposal Submission', 2: 'Requestor Evaluation & Confirmation', 3: 'Department Lead Approval', 4: 'Activity Creation', 5: 'Dealer - Completion Documents', 6: 'Requestor - Claim Approval', 7: 'E-Invoice Generation', 8: 'Credit Note from SAP', }; // If step number exists in map, use it if (stepTitleMap[stepNumber]) { return stepTitleMap[stepNumber]; } // For dynamically added steps, create a title from approver name or generic if (approverName && approverName !== 'Unknown' && approverName !== 'System') { return `Additional Approver - ${approverName}`; } return `Additional Approver - Step ${stepNumber}`; }; const getStepDescription = (stepNumber: number, levelName?: string, approverName?: string): string => { // Check if this is an "Additional Approver" (dynamically added) const isAdditionalApprover = levelName && levelName.toLowerCase().includes('additional approver'); // If this is an additional approver, use generic description if (isAdditionalApprover) { if (approverName && approverName !== 'Unknown' && approverName !== 'System') { return `${approverName} will review and approve this request as an additional approver.`; } return `Additional approver will review and approve this request.`; } // Use levelName to determine description (handles shifted steps correctly) // This ensures descriptions shift with their steps when approvers are added if (levelName && levelName.trim()) { const levelNameLower = levelName.toLowerCase(); // Map level names to descriptions (works even after shifting) if (levelNameLower.includes('dealer') && levelNameLower.includes('proposal')) { return 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests'; } if (levelNameLower.includes('requestor') && (levelNameLower.includes('evaluation') || levelNameLower.includes('confirmation'))) { return 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)'; } if (levelNameLower.includes('department lead')) { return 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)'; } if (levelNameLower.includes('activity creation')) { return 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.'; } if (levelNameLower.includes('dealer') && (levelNameLower.includes('completion') || levelNameLower.includes('documents'))) { return 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description'; } if (levelNameLower.includes('requestor') && (levelNameLower.includes('claim') || levelNameLower.includes('approval'))) { return 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.'; } if (levelNameLower.includes('e-invoice') || levelNameLower.includes('invoice generation')) { return 'E-invoice will be generated through DMS.'; } if (levelNameLower.includes('credit note') || levelNameLower.includes('sap')) { return 'Got credit note from SAP. Review and send to dealer to complete the claim management process.'; } } // Fallback to step number mapping (for backwards compatibility) const stepDescriptionMap: Record = { 1: 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests', 2: 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)', 3: 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)', 4: 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.', 5: 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description', 6: 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.', 7: 'E-invoice will be generated through DMS.', 8: 'Got credit note from SAP. Review and send to dealer to complete the claim management process.', }; if (stepDescriptionMap[stepNumber]) { return stepDescriptionMap[stepNumber]; } // Final fallback if (approverName && approverName !== 'Unknown' && approverName !== 'System') { return `${approverName} will review and approve this request.`; } return `Step ${stepNumber} approval required.`; }; // Transform approval flow to dealer claim workflow steps const workflowSteps: WorkflowStep[] = approvalFlow.map((step: any, index: number) => { // Get actual step number from levelNumber or step field const actualStepNumber = step.levelNumber || step.level_number || step.step || index + 1; // Get levelName from the approval level if available const levelName = step.levelName || step.level_name; // Find approval data for this step const approval = request?.approvals?.find((a: any) => a.levelId === step.levelId); // Extract IO details from internalOrder table (Department Lead step - check by levelName) let ioDetails = undefined; const isDeptLeadStep = levelName && levelName.toLowerCase().includes('department lead'); if (isDeptLeadStep || actualStepNumber === 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 (actualStepNumber === 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'; } // Business logic: Only show elapsed time for active or completed steps // Waiting steps (future steps) should have elapsedHours = 0 // This ensures that when in step 1, only step 1 shows elapsed time, others show 0 const isWaiting = normalizedStatus === 'waiting'; // Only calculate/show elapsed hours for active or completed steps // For waiting steps, elapsedHours should be 0 (they haven't started yet) const elapsedHours = isWaiting ? 0 : (step.elapsedHours || 0); const approverName = step.approver || step.approverName || 'Unknown'; return { step: actualStepNumber, title: getStepTitle(actualStepNumber, levelName, approverName), approver: approverName, description: getStepDescription(actualStepNumber, levelName, approverName) || step.description || '', tatHours: step.tatHours || 24, status: normalizedStatus as any, comment: step.comment || approval?.comment, approvedAt: step.approvedAt || approval?.timestamp, elapsedHours, // Only non-zero for active/completed steps ioDetails, dmsDetails, einvoiceUrl: actualStepNumber === 7 ? (approval as any)?.einvoiceUrl : undefined, emailTemplateUrl: (approval as any)?.emailTemplateUrl || undefined, }; }); const totalSteps = request?.totalSteps || 8; // Calculate currentStep from approval flow - find the first pending or in_progress step // IMPORTANT: Use the workflow's currentLevel from backend (most accurate) // Fallback to finding first pending step if currentLevel not available // Note: Status normalization already handled in workflowSteps mapping above const backendCurrentLevel = request?.currentLevel || request?.current_level || request?.currentStep; // Find the step that matches backend's currentLevel const activeStepFromBackend = workflowSteps.find(s => s.step === backendCurrentLevel); // If backend currentLevel exists and step is pending/in_progress, use it // Otherwise, find first pending/in_progress step const activeStep = activeStepFromBackend && (activeStepFromBackend.status === 'pending' || activeStepFromBackend.status === 'in_progress') ? activeStepFromBackend : 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 : (backendCurrentLevel || 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; // Find the initiator's step dynamically (Requestor Evaluation step) // This handles cases where approvers are added between steps, causing step numbers to shift const initiatorEmail = ( (request as any)?.initiator?.email?.toLowerCase() || (request as any)?.initiatorEmail?.toLowerCase() || '' ); // Find the step where the initiator is the approver // Check by: 1) approverEmail matches initiatorEmail, OR 2) levelName contains "Requestor Evaluation" const initiatorStepLevel = approvalFlow.find((l: any) => { const levelApproverEmail = (l.approverEmail || '').toLowerCase(); const levelName = (l.levelName || '').toLowerCase(); return (initiatorEmail && levelApproverEmail === initiatorEmail) || levelName.includes('requestor evaluation') || levelName.includes('requestor') && levelName.includes('confirmation'); }); const initiatorStepNumber = initiatorStepLevel ? (initiatorStepLevel.step || initiatorStepLevel.levelNumber || initiatorStepLevel.level_number || 2) : 2; // Fallback to 2 if not found // Check if user is approver for the initiator's step (requestor evaluation) const step2Level = initiatorStepLevel || 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; // Find Department Lead step dynamically (handles step shifts) const deptLeadStepLevel = approvalFlow.find((l: any) => { const levelName = (l.levelName || '').toLowerCase(); return levelName.includes('department lead'); }); const step3ApproverEmail = (deptLeadStepLevel?.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 the initiator's step levelId dynamically const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; // Find the initiator's step by checking approverEmail or levelName const initiatorEmail = ( (request as any)?.initiator?.email?.toLowerCase() || (request as any)?.initiatorEmail?.toLowerCase() || '' ); const step2Level = approvals.find((level: any) => { const levelApproverEmail = (level.approverEmail || level.approver_email || '').toLowerCase(); const levelName = (level.levelName || level.level_name || '').toLowerCase(); const levelNumber = level.levelNumber || level.level_number; // Check if this is the initiator's step return (initiatorEmail && levelApproverEmail === initiatorEmail) || levelName.includes('requestor evaluation') || (levelName.includes('requestor') && levelName.includes('confirmation')) || // Fallback: if initiatorStepNumber was found earlier, use it (levelNumber === initiatorStepNumber); }) || approvals.find((level: any) => (level.levelNumber || level.level_number) === 2 ); // Final fallback to level 2 if (!step2Level?.levelId && !step2Level?.level_id) { throw new Error('Initiator approval level not found'); } const levelId = step2Level.levelId || step2Level.level_id; // Approve the initiator's step 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 the initiator's step levelId dynamically const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; // Find the initiator's step by checking approverEmail or levelName const initiatorEmail = ( (request as any)?.initiator?.email?.toLowerCase() || (request as any)?.initiatorEmail?.toLowerCase() || '' ); const step2Level = approvals.find((level: any) => { const levelApproverEmail = (level.approverEmail || level.approver_email || '').toLowerCase(); const levelName = (level.levelName || level.level_name || '').toLowerCase(); const levelNumber = level.levelNumber || level.level_number; // Check if this is the initiator's step return (initiatorEmail && levelApproverEmail === initiatorEmail) || levelName.includes('requestor evaluation') || (levelName.includes('requestor') && levelName.includes('confirmation')) || // Fallback: if initiatorStepNumber was found earlier, use it (levelNumber === initiatorStepNumber); }) || approvals.find((level: any) => (level.levelNumber || level.level_number) === 2 ); // Final fallback to level 2 if (!step2Level?.levelId && !step2Level?.level_id) { throw new Error('Initiator approval level not found'); } const levelId = step2Level.levelId || step2Level.level_id; // Reject the initiator's step 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 (Department Lead step - found dynamically) 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 Department Lead step levelId dynamically const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; // Find Department Lead step by levelName (handles step shifts) const step3Level = approvals.find((level: any) => { const levelName = (level.levelName || level.level_name || '').toLowerCase(); return levelName.includes('department lead'); }) || approvals.find((level: any) => (level.levelNumber || level.level_number) === 3 ); // Fallback to level 3 if (!step3Level?.levelId && !step3Level?.level_id) { throw new Error('Department Lead approval level not found'); } const levelId = step3Level.levelId || step3Level.level_id; // First, update IO details using dealer claim API // Only pass ioNumber and ioRemark - don't override existing balance values // Balance values should already be stored when amount was blocked earlier await updateIODetails(requestId, { ioNumber: data.ioNumber, ioRemark: data.ioRemark, // Don't pass balance fields - let backend preserve existing values }); // 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 Department Lead step levelId dynamically const details = await getWorkflowDetails(requestId); const approvals = details?.approvalLevels || details?.approvals || []; // Find Department Lead step by levelName (handles step shifts) const step3Level = approvals.find((level: any) => { const levelName = (level.levelName || level.level_name || '').toLowerCase(); return levelName.includes('department lead'); }) || approvals.find((level: any) => (level.levelNumber || level.level_number) === 3 ); // Fallback to level 3 if (!step3Level?.levelId && !step3Level?.level_id) { throw new Error('Department Lead approval level not found'); } const levelId = step3Level.levelId || step3Level.level_id; // Reject Department Lead step 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: // 1. It's pending or in_progress // 2. AND it matches currentStep (from backend or calculated) // 3. AND it's the actual current step (not a future step that happens to be pending) const stepStatus = step.status?.toLowerCase() || ''; const isPendingOrInProgress = stepStatus === 'pending' || stepStatus === 'in_progress'; const matchesCurrentStep = step.step === currentStep; // Step is active only if it matches the current step AND is pending/in_progress const isActive = isPendingOrInProgress && matchesCurrentStep; const isCompleted = step.status === 'approved'; // Find approval data for this step to get SLA information // First find the corresponding level in approvalFlow to get levelId const stepLevel = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === step.step); const approval = stepLevel?.levelId ? request?.approvals?.find((a: any) => a.levelId === stepLevel.levelId || a.level_id === stepLevel.levelId) : null; // Check if step is paused const isPaused = approval?.status === 'PAUSED' || (request?.pauseInfo?.isPaused && (request?.pauseInfo?.levelId === approval?.levelId || request?.pauseInfo?.level_id === approval?.levelId)); return (
{/* Step Icon */}
{getStepIcon(step.status)}
{/* Step Content */}

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

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

{step.approver}

{step.description}

TAT: {formatHoursMinutes(step.tatHours)}

{/* Only show elapsed time for active or completed steps, not for waiting steps */} {step.elapsedHours && (isActive || isCompleted) && (

Elapsed: {formatHoursMinutes(step.elapsedHours)}

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

{step.comment}

)} {/* Active Approver - SLA Time Tracking (Only show for current active step) */} {isActive && approval?.sla && (
Due by: {approval.sla.deadline ? formatDateDDMMYYYY(approval.sla.deadline, true) : 'Not set'}
{/* Current Approver - Time Tracking */}
= 100 ? 'bg-red-50 border-red-200' : (approval.sla.percentageUsed || 0) >= 75 ? 'bg-orange-50 border-orange-200' : (approval.sla.percentageUsed || 0) >= 50 ? 'bg-amber-50 border-amber-200' : 'bg-green-50 border-green-200' }`}>

Current Approver - Time Tracking {isPaused && '(Paused)'}

Time elapsed since assigned: {approval.sla.elapsedText || '0 hours'}
Time used: {approval.sla.elapsedText || '0 hours'} / {formatHoursMinutes(step.tatHours)} allocated
{/* Progress Bar */}
{(() => { const percentUsed = approval.sla.percentageUsed || 0; const getActiveIndicatorColor = () => { if (isPaused) return 'bg-gray-500'; if (percentUsed >= 100) return 'bg-red-600'; if (percentUsed >= 75) return 'bg-orange-500'; if (percentUsed >= 50) return 'bg-amber-500'; return 'bg-green-600'; }; const getActiveTextColor = () => { if (isPaused) return 'text-gray-600'; if (percentUsed >= 100) return 'text-red-600'; if (percentUsed >= 75) return 'text-orange-600'; if (percentUsed >= 50) return 'text-amber-600'; return 'text-green-600'; }; return ( <>
Progress: {Math.min(100, percentUsed)}% of TAT used {approval.sla.remainingText || '0 hours'} remaining
); })()} {approval.sla.status === 'breached' && (

Deadline Breached

)} {approval.sla.status === 'critical' && (

Approaching Deadline

)}
)} {/* 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 */} {/* Only show action buttons if: 1. Step is active (pending/in_progress and matches currentStep) 2. AND current user is the approver for this step (or is dealer for dealer steps) */} {(() => { // Find the step level from approvalFlow to verify user is the approver const stepLevel = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === step.step); const stepApproverEmail = (stepLevel?.approverEmail || '').toLowerCase(); const isUserApproverForThisStep = stepApproverEmail && userEmail === stepApproverEmail; // For dealer steps (1 and 5), also check if user is dealer const isDealerStep = step.step === 1 || (stepLevel?.levelName && stepLevel.levelName.toLowerCase().includes('dealer')); const isUserAuthorized = isUserApproverForThisStep || (isDealerStep && isDealer); // Step must be active AND user must be authorized return isActive && isUserAuthorized; })() && (
{/* Step 1: Submit Proposal Button - Only for dealer or step 1 approver */} {step.step === 1 && (isDealer || isStep1Approver) && ( )} {/* Step 2 (or shifted step): Review Request - Only for initiator or step approver */} {/* Use initiatorStepNumber to handle cases where approvers are added between steps */} {step.step === initiatorStepNumber && (isInitiator || isStep2Approver) && ( )} {/* Department Lead Step: Approve and Organise IO - Find dynamically by levelName */} {(() => { // Find Department Lead step dynamically (handles step shifts) const deptLeadStepLevel = approvalFlow.find((l: any) => { const levelName = (l.levelName || '').toLowerCase(); return levelName.includes('department lead'); }); // Check if this is the Department Lead step const isDeptLeadStep = deptLeadStepLevel && (step.step === (deptLeadStepLevel.step || deptLeadStepLevel.levelNumber || deptLeadStepLevel.level_number)); if (!isDeptLeadStep) return null; // Check if user is the Department Lead approver const deptLeadApproverEmail = (deptLeadStepLevel?.approverEmail || '').toLowerCase(); const isDeptLeadApprover = deptLeadApproverEmail && userEmail === deptLeadApproverEmail; if (!(isDeptLeadApprover || isStep3Approver || isCurrentApprover)) return null; // Check if IO number is available (same way as IO tab and modal) const internalOrder = request?.internalOrder || request?.internal_order; const ioNumber = internalOrder?.ioNumber || internalOrder?.io_number || request?.ioNumber || ''; const hasIONumber = ioNumber && ioNumber.trim() !== ''; return (
{!hasIONumber && (

IO Number Not Available

Please add an IO number in the IO tab before approving this step.

)}
); })()} {/* Step 5 (or shifted step): Upload Completion Documents - Only for dealer */} {/* Check if dealer is the approver for this step (handles step shifts) */} {(() => { // Find the step level from approvalFlow to verify dealer is the approver const stepLevel = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === step.step); const stepApproverEmail = (stepLevel?.approverEmail || '').toLowerCase(); // Check if dealer is the approver for this step const isDealerForThisStep = isDealer && stepApproverEmail === dealerEmail; // Check if this is the Dealer Completion Documents step // by checking if the levelName contains "Dealer Completion" or "Completion Documents" const levelName = (stepLevel?.levelName || step.title || '').toLowerCase(); const isDealerCompletionStep = levelName.includes('dealer completion') || levelName.includes('completion documents'); return isDealerForThisStep && isDealerCompletionStep; })() && ( )} {/* 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} preFilledIORemark={request?.internalOrder?.ioRemark || request?.internalOrder?.io_remark || request?.internal_order?.ioRemark || request?.internal_order?.io_remark || undefined} preFilledBlockedAmount={request?.internalOrder?.ioBlockedAmount || request?.internalOrder?.io_blocked_amount || request?.internal_order?.ioBlockedAmount || request?.internal_order?.io_blocked_amount || undefined} preFilledRemainingBalance={request?.internalOrder?.ioRemainingBalance || request?.internalOrder?.io_remaining_balance || request?.internal_order?.ioRemainingBalance || request?.internal_order?.io_remaining_balance || 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 () => { try { const requestId = request?.requestId || request?.id; if (!requestId) { toast.error('Request ID not found'); return; } await sendCreditNoteToDealer(requestId); toast.success('Credit note sent to dealer successfully. Step 8 has been approved.'); // Refresh the request details to show updated status if (onRefresh) { onRefresh(); } } catch (error: any) { console.error('Failed to send credit note to dealer:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to send credit note to dealer'; toast.error(errorMessage); } }} 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: (request as any)?.creditNote?.status || (request as any)?.claimDetails?.creditNote?.status || ((request as any)?.creditNote?.creditNoteNumber ? 'CONFIRMED' : 'PENDING'), }} 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" /> ); }