/**
* 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'}
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"
/>
>
);
}