import { useState, useRef, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { RichTextEditor } from '@/components/ui/rich-text-editor'; import { FormattedDescription } from '@/components/common/FormattedDescription'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { Calendar } from '@/components/ui/calendar'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { motion, AnimatePresence } from 'framer-motion'; import { ArrowLeft, ArrowRight, Calendar as CalendarIcon, Check, Receipt, Building, MapPin, Clock, CheckCircle, Info, FileText, Users, XCircle, Loader2, } from 'lucide-react'; import { format } from 'date-fns'; import { toast } from 'sonner'; import { getAllDealers as fetchDealersFromAPI, verifyDealerLogin, type DealerInfo } from '@/services/dealerApi'; import { ClaimApproverSelectionStep } from './ClaimApproverSelectionStep'; import { useAuth } from '@/contexts/AuthContext'; // CLAIM_STEPS definition (same as in ClaimApproverSelectionStep) const CLAIM_STEPS = [ { level: 1, name: 'Dealer Proposal Submission', description: 'Dealer submits proposal documents', defaultTat: 72, isAuto: false, approverType: 'dealer' }, { level: 2, name: 'Requestor Evaluation', description: 'Initiator evaluates dealer proposal', defaultTat: 48, isAuto: false, approverType: 'initiator' }, { level: 3, name: 'Department Lead Approval', description: 'Department lead approves and blocks IO budget', defaultTat: 72, isAuto: false, approverType: 'manual' }, { level: 4, name: 'Activity Creation', description: 'System auto-processes activity creation', defaultTat: 1, isAuto: true, approverType: 'system' }, { level: 5, name: 'Dealer Completion Documents', description: 'Dealer submits completion documents', defaultTat: 120, isAuto: false, approverType: 'dealer' }, { level: 6, name: 'Requestor Claim Approval', description: 'Initiator approves completion', defaultTat: 48, isAuto: false, approverType: 'initiator' }, { level: 7, name: 'E-Invoice Generation', description: 'System generates e-invoice via DMS', defaultTat: 1, isAuto: true, approverType: 'system' }, { level: 8, name: 'Credit Note Confirmation', description: 'System/Finance processes credit note confirmation', defaultTat: 48, isAuto: true, approverType: 'system' }, ]; interface ClaimManagementWizardProps { onBack?: () => void; onSubmit?: (claimData: any) => void; } const CLAIM_TYPES = [ 'Riders Mania Claims', 'Marketing Cost – Bike to Vendor', 'Media Bike Service', 'ARAI Motorcycle Liquidation', 'ARAI Certification – STA Approval CNR', 'Procurement of Spares/Apparel/GMA for Events', 'Fuel for Media Bike Used for Event', 'Motorcycle Buyback and Goodwill Support', 'Liquidation of Used Motorcycle', 'Motorcycle Registration CNR (Owned or Gifted by RE)', 'Legal Claims Reimbursement', 'Service Camp Claims', 'Corporate Claims – Institutional Sales PDI' ]; const STEP_NAMES = [ 'Claim Details', 'Approver Selection', 'Review & Submit' ]; export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizardProps) { const { user } = useAuth(); const [currentStep, setCurrentStep] = useState(1); const [verifyingDealer, setVerifyingDealer] = useState(false); const [dealerSearchResults, setDealerSearchResults] = useState([]); const [dealerSearchLoading, setDealerSearchLoading] = useState(false); const [dealerSearchInput, setDealerSearchInput] = useState(''); const dealerSearchTimer = useRef(null); const [isSubmitting, setIsSubmitting] = useState(false); const submitTimeoutRef = useRef(null); // Cleanup timeout on unmount useEffect(() => { return () => { if (submitTimeoutRef.current) { clearTimeout(submitTimeoutRef.current); } }; }, []); const [formData, setFormData] = useState({ activityName: '', activityType: '', dealerCode: '', dealerName: '', dealerEmail: '', dealerPhone: '', dealerAddress: '', activityDate: undefined as Date | undefined, location: '', requestDescription: '', periodStartDate: undefined as Date | undefined, periodEndDate: undefined as Date | undefined, estimatedBudget: '', // Approvers array for all 8 steps approvers: [] as Array<{ email: string; name?: string; userId?: string; level: number; tat?: number | string; tatType?: 'hours' | 'days'; isAdditional?: boolean; insertAfterLevel?: number; stepName?: string; originalStepLevel?: number; }> }); const totalSteps = STEP_NAMES.length; // Handle dealer search input with debouncing const handleDealerSearchInputChange = (value: string) => { setDealerSearchInput(value); // Clear previous timer if (dealerSearchTimer.current) { clearTimeout(dealerSearchTimer.current); } // If input is empty, clear results if (!value || value.trim().length < 2) { setDealerSearchResults([]); setDealerSearchLoading(false); return; } // Set loading state setDealerSearchLoading(true); // Debounce search dealerSearchTimer.current = setTimeout(async () => { try { const results = await fetchDealersFromAPI(value, 10); // Limit to 10 results setDealerSearchResults(results); } catch (error) { console.error('Error searching dealers:', error); setDealerSearchResults([]); } finally { setDealerSearchLoading(false); } }, 300); }; const updateFormData = (field: string, value: any) => { setFormData(prev => { const updated = { ...prev, [field]: value }; // Validate period dates if (field === 'periodStartDate') { // If start date is selected and end date exists, validate end date if (value && updated.periodEndDate && value > updated.periodEndDate) { // Clear end date if it's before the new start date updated.periodEndDate = undefined; toast.error('End date must be on or after the start date. End date has been cleared.'); } } else if (field === 'periodEndDate') { // If end date is selected and start date exists, validate end date if (value && updated.periodStartDate && value < updated.periodStartDate) { toast.error('End date must be on or after the start date.'); // Don't update the end date if it's invalid return prev; } } return updated; }); }; const isStepValid = () => { switch (currentStep) { case 1: return formData.activityName && formData.activityType && formData.dealerCode && formData.dealerName && formData.activityDate && formData.location && formData.requestDescription; case 2: // Validate that all required approvers are assigned (Step 3 only, Step 8 is now system/Finance) const approvers = formData.approvers || []; // Find step 3 approver by originalStepLevel first, then fallback to level const step3Approver = approvers.find((a: any) => a.originalStepLevel === 3 || (!a.originalStepLevel && a.level === 3 && !a.isAdditional) ); // Step 8 is now a system step, no validation needed return step3Approver?.email && step3Approver?.userId && step3Approver?.tat; case 3: return true; default: return false; } }; const nextStep = () => { if (currentStep < totalSteps) { if (!isStepValid()) { // Show specific error messages for step 2 (approver selection) if (currentStep === 2) { const approvers = formData.approvers || []; // Find step 3 approver by originalStepLevel first, then fallback to level const step3Approver = approvers.find((a: any) => a.originalStepLevel === 3 || (!a.originalStepLevel && a.level === 3 && !a.isAdditional) ); const missingSteps: string[] = []; if (!step3Approver?.email || !step3Approver?.userId || !step3Approver?.tat) { missingSteps.push('Department Lead Approval'); } if (missingSteps.length > 0) { toast.error(`Please add missing approvers: ${missingSteps.join(', ')}`); } else { toast.error('Please complete all required approver selections (email, user verification, and TAT) before proceeding.'); } } else { toast.error('Please complete all required fields before proceeding.'); } return; } setCurrentStep(currentStep + 1); } }; const prevStep = () => { if (currentStep > 1) { setCurrentStep(currentStep - 1); } }; const handleDealerSelect = async (selectedDealer: DealerInfo) => { // Verify dealer is logged in setVerifyingDealer(true); try { const verifiedDealer = await verifyDealerLogin(selectedDealer.dealerCode); if (!verifiedDealer.isLoggedIn) { toast.error( `Dealer "${verifiedDealer.dealerName || verifiedDealer.displayName}" (${verifiedDealer.dealerCode}) has not logged in to the system. Please ask them to log in first.`, { duration: 5000 } ); // Clear the selection setDealerSearchInput(''); setDealerSearchResults([]); updateFormData('dealerCode', ''); updateFormData('dealerName', ''); updateFormData('dealerEmail', ''); updateFormData('dealerPhone', ''); updateFormData('dealerAddress', ''); setVerifyingDealer(false); return; } // Dealer is logged in, update form data updateFormData('dealerCode', verifiedDealer.dealerCode); updateFormData('dealerName', verifiedDealer.dealerName || verifiedDealer.displayName); updateFormData('dealerEmail', verifiedDealer.email || ''); updateFormData('dealerPhone', verifiedDealer.phone || ''); updateFormData('dealerAddress', ''); // Address not available in API response // Clear search input and results setDealerSearchInput(verifiedDealer.dealerName || verifiedDealer.displayName); setDealerSearchResults([]); toast.success(`Dealer "${verifiedDealer.dealerName || verifiedDealer.displayName}" verified and logged in`); } catch (error: any) { const errorMessage = error.message || 'Failed to verify dealer login'; toast.error(errorMessage, { duration: 5000 }); // Clear the selection setDealerSearchInput(''); setDealerSearchResults([]); updateFormData('dealerCode', ''); updateFormData('dealerName', ''); updateFormData('dealerEmail', ''); updateFormData('dealerPhone', ''); updateFormData('dealerAddress', ''); } finally { setVerifyingDealer(false); } }; const handleSubmit = () => { // Prevent multiple submissions if (isSubmitting) { return; } // Approvers are already using integer levels with proper shifting // Just sort them and prepare for submission const approvers = formData.approvers || []; const sortedApprovers = [...approvers].sort((a, b) => a.level - b.level); // Check for duplicate levels (should not happen, but safeguard) const levelMap = new Map(); const duplicates: number[] = []; sortedApprovers.forEach((approver) => { if (levelMap.has(approver.level)) { duplicates.push(approver.level); } else { levelMap.set(approver.level, approver); } }); if (duplicates.length > 0) { toast.error(`Duplicate approver levels detected: ${duplicates.join(', ')}. Please refresh and try again.`); console.error('Duplicate levels found:', duplicates, sortedApprovers); return; } // Prepare final approvers array - preserve stepName for additional approvers // The backend will use stepName to set the levelName for approval levels // Also preserve originalStepLevel so backend can identify which step each approver belongs to const finalApprovers = sortedApprovers.map((approver) => { const result: any = { email: approver.email, name: approver.name, userId: approver.userId, level: approver.level, tat: approver.tat, tatType: approver.tatType, }; // Preserve stepName for additional approvers if (approver.isAdditional && approver.stepName) { result.stepName = approver.stepName; result.isAdditional = true; } // Preserve originalStepLevel for fixed steps (so backend can identify which step this is) if (approver.originalStepLevel) { result.originalStepLevel = approver.originalStepLevel; } return result; }); const claimData = { ...formData, templateType: 'claim-management', submittedAt: new Date().toISOString(), status: 'pending', currentStep: 'initiator-review', // Pass normalized approvers array to backend approvers: finalApprovers }; // Set submitting state to prevent multiple clicks setIsSubmitting(true); // Clear any existing timeout if (submitTimeoutRef.current) { clearTimeout(submitTimeoutRef.current); } // Set a timeout as a fallback to reset loading state (30 seconds) // In most cases, the parent component will navigate away on success, // but this prevents the button from being stuck in loading state if there's an error submitTimeoutRef.current = setTimeout(() => { setIsSubmitting(false); submitTimeoutRef.current = null; }, 30000); // Don't show toast here - let the parent component handle success/error after API call if (onSubmit) { try { onSubmit(claimData); // Note: On success, the component will unmount when parent navigates away (timeout cleared in useEffect) // On error, the timeout will reset the state after 30 seconds } catch (error) { // If onSubmit throws synchronously, reset state immediately if (submitTimeoutRef.current) { clearTimeout(submitTimeoutRef.current); submitTimeoutRef.current = null; } setIsSubmitting(false); console.error('Error submitting claim:', error); } } else { // If no onSubmit handler, reset immediately if (submitTimeoutRef.current) { clearTimeout(submitTimeoutRef.current); submitTimeoutRef.current = null; } setIsSubmitting(false); } }; const renderStepContent = () => { switch (currentStep) { case 1: return (

Claim Details

Provide comprehensive information about your claim request

{/* Activity Name and Type */}
updateFormData('activityName', e.target.value)} className="mt-2 h-12" />
{/* Dealer Selection */}
{ if (formData.dealerCode) { // If dealer is already selected, clear selection first updateFormData('dealerCode', ''); updateFormData('dealerName', ''); updateFormData('dealerEmail', ''); updateFormData('dealerPhone', ''); updateFormData('dealerAddress', ''); setDealerSearchInput(e.target.value); } else { handleDealerSearchInputChange(e.target.value); } }} onFocus={() => { // When input is focused, show search results if input has value if (dealerSearchInput && dealerSearchInput.length >= 2) { handleDealerSearchInputChange(dealerSearchInput); } }} className="h-12 border-2 border-gray-300 focus:border-blue-500" disabled={verifyingDealer} /> {formData.dealerCode && (
Verified
)} {/* Search suggestions dropdown */} {(dealerSearchLoading || dealerSearchResults.length > 0) && !formData.dealerCode && (
{dealerSearchLoading ? (
Searching...
) : (
    {dealerSearchResults.map((dealer) => (
  • handleDealerSelect(dealer)} >
    {dealer.dealerName || dealer.displayName}
    {dealer.dealerCode} {dealer.email && ( <> {dealer.email} )}
    {dealer.city && dealer.state && (
    {dealer.city}, {dealer.state}
    )}
    {dealer.isLoggedIn ? ( ) : ( )}
  • ))}
)}
)}
Status:
Logged in
Not logged in
{formData.dealerCode && (

Selected: {formData.dealerName} ({formData.dealerCode})

{formData.dealerEmail && (

Email: {formData.dealerEmail}

)}
)}
{/* Date and Location */}
updateFormData('activityDate', date)} initialFocus />
updateFormData('location', e.target.value)} className="mt-2 h-12" />
{/* Request Detail */}

Explain what you need approval for, why it's needed, and any relevant background information. 💡 Tip: You can paste formatted content (lists, tables) and the formatting will be preserved.

updateFormData('requestDescription', html)} placeholder="Provide comprehensive details about your claim requirement including scope, objectives, expected outcomes, and any supporting context that will help approvers make an informed decision." className="min-h-[120px] text-base border-2 border-gray-300 focus-within:border-blue-500 bg-white shadow-sm" minHeight="120px" />
{/* Period (Optional) */}
Optional
updateFormData('periodStartDate', date)} initialFocus // Maximum date is the end date (if selected) toDate={formData.periodEndDate || undefined} />
updateFormData('periodEndDate', date)} initialFocus // Minimum date is the start date (if selected) fromDate={formData.periodStartDate || undefined} /> {!formData.periodStartDate && (

Please select start date first

)}
{(formData.periodStartDate || formData.periodEndDate) && (
{formData.periodStartDate && formData.periodEndDate ? (

Period: {format(formData.periodStartDate, 'MMM dd, yyyy')} - {format(formData.periodEndDate, 'MMM dd, yyyy')}

) : (

{formData.periodStartDate ? 'Please select end date for the period' : 'Please select start date first'}

)}
)}
); case 2: return ( ); case 3: return (

Review & Submit

Review your claim details before submission

{/* Activity Information */} Activity Information

{formData.activityName}

{formData.activityType}
{/* Dealer Information */} Dealer Information

{formData.dealerCode}

{formData.dealerName}

{formData.dealerEmail}

{formData.dealerPhone}

{formData.dealerAddress && (

{formData.dealerAddress}

)}
{/* Approver Information */} Selected Approvers
{(() => { // Sort approvers by level and filter out system approvers const sortedApprovers = [...(formData.approvers || [])] .filter((a: any) => !a.email?.includes('system@') && !a.email?.includes('finance@')) .sort((a: any, b: any) => a.level - b.level); return sortedApprovers.map((approver: any) => { const tat = Number(approver.tat || 0); const tatType = approver.tatType || 'hours'; const hours = tatType === 'days' ? tat * 24 : tat; // Find step name - handle additional approvers and shifted levels let stepName = 'Unknown'; let stepLabel = ''; if (approver.isAdditional) { // Additional approver - use stepName if available stepName = approver.stepName || 'Additional Approver'; const afterStep = CLAIM_STEPS.find(s => s.level === approver.insertAfterLevel); stepLabel = approver.stepName || `Additional Approver (after "${afterStep?.name || 'Unknown'}")`; } else { // Fixed step - find by originalStepLevel first, then fallback to level const step = approver.originalStepLevel ? CLAIM_STEPS.find(s => s.level === approver.originalStepLevel) : CLAIM_STEPS.find(s => s.level === approver.level && !s.isAuto); stepName = step?.name || 'Unknown'; stepLabel = stepName; } return (
{approver.isAdditional && ( ADDITIONAL )}

{approver.name || approver.email || 'Not selected'}

{approver.email && (

{approver.email}

)}

{hours} hours

TAT

); }); })()}
{/* Date & Location */} Date & Location

{formData.activityDate ? format(formData.activityDate, 'PPP') : 'N/A'}

{formData.location}

{formData.estimatedBudget && (

{formData.estimatedBudget}

)}
{/* Request Details */} Request Details
{/* Period (if provided) */} {(formData.periodStartDate || formData.periodEndDate) && ( Period

{formData.periodStartDate ? format(formData.periodStartDate, 'PPP') : 'Not specified'}

{formData.periodEndDate ? format(formData.periodEndDate, 'PPP') : 'Not specified'}

)} {/* Confirmation Message */}

Ready to Submit

Please review all the information above. Once submitted, your claim request will enter the approval workflow.

); default: return null; } }; return (
{/* Header */}
Claim Management Template

New Claim Request

Step {currentStep} of {totalSteps}: {STEP_NAMES[currentStep - 1]}

{/* Progress Bar */}
{STEP_NAMES.map((_name, index) => ( {index + 1} ))}
{/* Step Content */} {renderStepContent()} {/* Navigation */}
{currentStep < totalSteps ? ( ) : ( )}
); }