diff --git a/src/App.tsx b/src/App.tsx index f6c19fa..e75783f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,7 +9,7 @@ import { SharedSummaries } from '@/pages/SharedSummaries/SharedSummaries'; import { SharedSummaryDetail } from '@/pages/SharedSummaries/SharedSummaryDetail'; import { WorkNotes } from '@/pages/WorkNotes'; import { CreateRequest } from '@/pages/CreateRequest'; -import { ClaimManagementWizard } from '@/components/workflow/ClaimManagementWizard'; +import { ClaimManagementWizard } from '@/dealer-claim/components/request-creation/ClaimManagementWizard'; import { MyRequests } from '@/pages/MyRequests'; import { Requests } from '@/pages/Requests/Requests'; import { UserAllRequests } from '@/pages/Requests/UserAllRequests'; @@ -297,25 +297,36 @@ function AppRoutes({ onLogout }: AppProps) { // Call API to create claim request const response = await createClaimRequest(payload); + + // Validate response - ensure request was actually created successfully + if (!response || !response.request) { + throw new Error('Invalid response from server: Request object not found'); + } + const createdRequest = response.request; + // Validate that we have at least one identifier (requestNumber or requestId) + if (!createdRequest.requestNumber && !createdRequest.requestId) { + throw new Error('Invalid response from server: Request identifier not found'); + } + // Close manager modal if open setManagerModalOpen(false); setManagerModalData(null); + // Only show success toast if request was actually created successfully toast.success('Claim Request Submitted', { description: 'Your claim management request has been created successfully.', }); - // Navigate to the created request detail page - if (createdRequest?.requestId) { - const { navigateToRequest } = await import('@/utils/requestNavigation'); - navigateToRequest({ - requestId: createdRequest.requestId, - request: createdRequest, - navigate, - }); + // Navigate to the created request detail page using requestNumber + if (createdRequest.requestNumber) { + navigate(`/request/${createdRequest.requestNumber}`); + } else if (createdRequest.requestId) { + // Fallback to requestId if requestNumber is not available + navigate(`/request/${createdRequest.requestId}`); } else { + // This should not happen due to validation above, but just in case navigate('/my-requests'); } } catch (error: any) { diff --git a/src/components/modals/ManagerSelectionModal.tsx b/src/components/modals/ManagerSelectionModal.tsx index 2407729..40b554f 100644 --- a/src/components/modals/ManagerSelectionModal.tsx +++ b/src/components/modals/ManagerSelectionModal.tsx @@ -7,7 +7,6 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { AlertCircle, CheckCircle2, User, Mail, Building2 } from 'lucide-react'; -import { Badge } from '@/components/ui/badge'; interface Manager { userId: string; diff --git a/src/components/workflow/ClaimManagementDetail/ClaimManagementDetail.tsx b/src/components/workflow/ClaimManagementDetail/ClaimManagementDetail.tsx deleted file mode 100644 index 8a6d110..0000000 --- a/src/components/workflow/ClaimManagementDetail/ClaimManagementDetail.tsx +++ /dev/null @@ -1,794 +0,0 @@ -import { useState, useMemo } 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 { toast } from 'sonner'; -import { DealerDocumentModal } from '@/components/modals/DealerDocumentModal'; -import { InitiatorVerificationModal } from '@/components/modals/InitiatorVerificationModal'; -import { Progress } from '@/components/ui/progress'; -import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase'; -import { formatDateDDMMYYYY } from '@/utils/dateFormatter'; -import { - ArrowLeft, - Clock, - FileText, - MessageSquare, - CheckCircle, - XCircle, - Download, - Eye, - Flame, - Target, - TrendingUp, - RefreshCw, - Activity, - MapPin, - Mail, - Phone, - Building, - Receipt, - Upload, - UserPlus, - ClipboardList, - DollarSign, - Calendar -} from 'lucide-react'; - -interface ClaimManagementDetailProps { - requestId: string; - onBack?: () => void; - onOpenModal?: (modal: string) => void; - dynamicRequests?: any[]; -} - -// Utility functions -const getPriorityConfig = (priority: string) => { - switch (priority) { - case 'express': - case 'urgent': - return { - color: 'bg-red-100 text-red-800 border-red-200', - icon: Flame - }; - case 'standard': - return { - color: 'bg-blue-100 text-blue-800 border-blue-200', - icon: Target - }; - default: - return { - color: 'bg-gray-100 text-gray-800 border-gray-200', - icon: Target - }; - } -}; - -const getStatusConfig = (status: string) => { - switch (status) { - case 'pending': - return { - color: 'bg-yellow-100 text-yellow-800 border-yellow-200', - icon: Clock - }; - case 'in-review': - return { - color: 'bg-blue-100 text-blue-800 border-blue-200', - icon: Eye - }; - case 'approved': - return { - color: 'bg-green-100 text-green-800 border-green-200', - icon: CheckCircle - }; - case 'rejected': - return { - color: 'bg-red-100 text-red-800 border-red-200', - icon: XCircle - }; - default: - return { - color: 'bg-gray-100 text-gray-800 border-gray-200', - icon: Clock - }; - } -}; - -const getSLAConfig = (progress: number) => { - if (progress >= 80) { - return { - bg: 'bg-red-50', - color: 'bg-red-500', - textColor: 'text-red-700' - }; - } else if (progress >= 60) { - return { - bg: 'bg-orange-50', - color: 'bg-orange-500', - textColor: 'text-orange-700' - }; - } else { - return { - bg: 'bg-green-50', - color: 'bg-green-500', - textColor: 'text-green-700' - }; - } -}; - -const getStepIcon = (status: string) => { - switch (status) { - case 'approved': - return ; - case 'rejected': - return ; - case 'pending': - case 'in-review': - return ; - default: - return ; - } -}; - -const getActionTypeIcon = (type: string) => { - switch (type) { - case 'approval': - case 'approved': - return ; - case 'rejection': - case 'rejected': - return ; - case 'comment': - return ; - case 'status_change': - return ; - case 'assignment': - return ; - case 'created': - return ; - default: - return ; - } -}; - -export function ClaimManagementDetail({ - requestId, - onBack, - onOpenModal, - dynamicRequests = [] -}: ClaimManagementDetailProps) { - const [activeTab, setActiveTab] = useState('overview'); - const [dealerDocModal, setDealerDocModal] = useState(false); - const [initiatorVerificationModal, setInitiatorVerificationModal] = useState(false); - - // Get claim from database or dynamic requests - const claim = useMemo(() => { - // First check static database - const staticClaim = CLAIM_MANAGEMENT_DATABASE[requestId]; - if (staticClaim) return staticClaim; - - // Then check dynamic requests - const dynamicClaim = dynamicRequests.find((req: any) => req.id === requestId); - if (dynamicClaim) return dynamicClaim; - - return null; - }, [requestId, dynamicRequests]); - - if (!claim) { - return ( -
-
-

Claim Not Found

-

The claim request you're looking for doesn't exist.

- -
-
- ); - } - - const priorityConfig = getPriorityConfig(claim.priority); - const statusConfig = getStatusConfig(claim.status); - const slaConfig = getSLAConfig(claim.slaProgress); - - return ( -
-
- {/* Header Section */} -
-
-
- - -
-
- -
- -
-
-

{claim.id}

- - {claim.priority} priority - - - - {claim.status} - - - - Claim Management - -
-

{claim.title}

-
-
-
- -
- {claim.amount && claim.amount !== 'TBD' && ( -
-

Claim Amount

-

{claim.amount}

-
- )} - -
-
- - {/* SLA Progress */} -
-
-
- - SLA Progress -
- - {claim.slaRemaining} - -
- -

- Due: {formatDateDDMMYYYY(claim.slaEndDate, true)} • {claim.slaProgress}% elapsed -

-
-
- - {/* Tabs */} - - - - - Overview - - - - Workflow (8-Steps) - - - - Documents - - - - Activity - - - - {/* Overview Tab */} - -
- {/* Left Column - Main Content (2/3 width) */} -
- {/* Activity Information */} - - - - - Activity Information - - - -
-
- -

{claim.claimDetails?.activityName || 'N/A'}

-
-
- -

{claim.claimDetails?.activityType || 'N/A'}

-
-
- -

- - {claim.claimDetails?.location || 'N/A'} -

-
-
- -

{claim.claimDetails?.activityDate || 'N/A'}

-
-
- -

- - {claim.claimDetails?.estimatedBudget || 'N/A'} -

-
-
- -

- {claim.claimDetails?.periodStart || 'N/A'} - {claim.claimDetails?.periodEnd || 'N/A'} -

-
-
- -
- -

- {claim.claimDetails?.requestDescription || claim.description || 'N/A'} -

-
-
-
- - {/* Dealer Information */} - - - - - Dealer Information - - - -
-
- -

{claim.claimDetails?.dealerCode || 'N/A'}

-
-
- -

{claim.claimDetails?.dealerName || 'N/A'}

-
-
- -
- -
-
- - {claim.claimDetails?.dealerEmail || 'N/A'} -
-
- - {claim.claimDetails?.dealerPhone || 'N/A'} -
- {claim.claimDetails?.dealerAddress && ( -
- - {claim.claimDetails.dealerAddress} -
- )} -
-
-
-
- - {/* Initiator Information */} - - - Request Initiator - - -
- - - {claim.initiator?.avatar || 'U'} - - -
-

{claim.initiator?.name || 'N/A'}

-

{claim.initiator?.role || 'N/A'}

-

{claim.initiator?.department || 'N/A'}

- -
-
- - {claim.initiator?.email || 'N/A'} -
-
- - {claim.initiator?.phone || 'N/A'} -
-
-
-
-
-
-
- - {/* Right Column - Quick Actions Sidebar (1/3 width) */} -
- {/* Quick Actions */} - - - Quick Actions - - - - - -
- - -
-
-
- - {/* Spectators */} - {claim.spectators && claim.spectators.length > 0 && ( - - - Spectators - - - {claim.spectators.map((spectator: any, index: number) => ( -
- - - {spectator.avatar} - - -
-

{spectator.name}

-

{spectator.role}

-
-
- ))} -
-
- )} -
-
-
- - {/* Workflow Tab - 8 Step Process */} - - - -
-
- - - Claim Management Workflow - - - 8-Step approval process for dealer claim management - -
- - Step {claim.currentStep} of {claim.totalSteps} - -
-
- - {claim.approvalFlow && claim.approvalFlow.length > 0 ? ( -
- {claim.approvalFlow.map((step: any, index: number) => { - const isActive = step.status === 'pending' || step.status === 'in-review'; - const isCompleted = step.status === 'approved'; - const isRejected = step.status === 'rejected'; - - return ( -
-
-
- {getStepIcon(step.status)} -
- -
-
-
-
-

Step {step.step}: {step.role}

- - {step.status} - -
-

{step.approver}

- {step.description && ( -

{step.description}

- )} -
-
- {step.tatHours && ( -

TAT: {step.tatHours}h

- )} - {step.elapsedHours !== undefined && step.elapsedHours > 0 && ( -

Elapsed: {step.elapsedHours}h

- )} -
-
- - {step.comment && ( -
-

{step.comment}

-
- )} - - {step.timestamp && ( -

- {isCompleted ? 'Approved' : isRejected ? 'Rejected' : 'Actioned'} on {step.timestamp} -

- )} - - {/* Workflow-specific Action Buttons */} - {isActive && ( -
- {step.step === 1 && step.role === 'Dealer - Document Upload' && ( - - )} - {step.step === 2 && step.role === 'Initiator Evaluation' && ( - - )} - {step.step === 4 && step.role === 'Department Lead Approval' && ( - - )} - {step.step === 5 && step.role === 'Dealer - Completion Documents' && ( - - )} - {step.step === 6 && step.role === 'Initiator Verification' && ( - - )} - {step.step === 8 && step.role.includes('Credit Note') && ( - - )} -
- )} -
-
-
- ); - })} -
- ) : ( -

No workflow steps defined

- )} -
-
-
- - {/* Documents Tab */} - - - -
- - - Claim Documents - - -
-
- - {claim.documents && claim.documents.length > 0 ? ( -
- {claim.documents.map((doc: any, index: number) => ( -
-
-
- -
-
-

{doc.name}

-

- {doc.size} • Uploaded by {doc.uploadedBy} on {doc.uploadedAt} -

-
-
-
- - -
-
- ))} -
- ) : ( -

No documents uploaded yet

- )} -
-
-
- - {/* Activity Tab - Audit Trail */} - - - - - - Claim Activity Timeline - - - Complete audit trail of all claim management activities - - - -
- {claim.auditTrail && claim.auditTrail.length > 0 ? claim.auditTrail.map((entry: any, index: number) => ( -
-
- {getActionTypeIcon(entry.type)} -
-
-
-
-

{entry.action}

-

{entry.details}

-

by {entry.user}

-
- {entry.timestamp} -
-
-
- )) : ( -
- -

No activity recorded yet

-

Actions and updates will appear here

-
- )} -
-
-
-
-
-
- - {/* Claim Management Modals */} - {dealerDocModal && ( - setDealerDocModal(false)} - onSubmit={async (_documents) => { - toast.success('Documents Uploaded', { - description: 'Your documents have been submitted for review.', - }); - setDealerDocModal(false); - }} - dealerName={claim.claimDetails?.dealerName || 'Dealer'} - activityName={claim.claimDetails?.activityName || claim.title} - /> - )} - - {initiatorVerificationModal && ( - setInitiatorVerificationModal(false)} - onSubmit={async (data) => { - toast.success('Verification Complete', { - description: `Amount set to ${data.approvedAmount}. E-invoice will be generated.`, - }); - setInitiatorVerificationModal(false); - }} - activityName={claim.claimDetails?.activityName || claim.title} - requestedAmount={claim.claimDetails?.estimatedBudget || claim.amount || 'TBD'} - documents={claim.documents || []} - /> - )} -
- ); -} diff --git a/src/components/workflow/ClaimManagementDetail/index.ts b/src/components/workflow/ClaimManagementDetail/index.ts deleted file mode 100644 index 4e7a576..0000000 --- a/src/components/workflow/ClaimManagementDetail/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ClaimManagementDetail } from './ClaimManagementDetail'; diff --git a/src/components/workflow/ClaimManagementWizard/ClaimManagementWizard.tsx b/src/components/workflow/ClaimManagementWizard/ClaimManagementWizard.tsx deleted file mode 100644 index d505fdf..0000000 --- a/src/components/workflow/ClaimManagementWizard/ClaimManagementWizard.tsx +++ /dev/null @@ -1,684 +0,0 @@ -import { useState, 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 { Textarea } from '@/components/ui/textarea'; -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, -} from 'lucide-react'; -import { format } from 'date-fns'; -import { toast } from 'sonner'; -import { getAllDealers as fetchDealersFromAPI, getDealerByCode, type DealerInfo } from '@/services/dealerApi'; - -interface ClaimManagementWizardProps { - onBack?: () => void; - onSubmit?: (claimData: any) => void; -} - -const CLAIM_TYPES = [ - 'Marketing Activity', - 'Promotional Event', - 'Dealer Training', - 'Infrastructure Development', - 'Customer Experience Initiative', - 'Service Campaign' -]; - -const STEP_NAMES = [ - 'Claim Details', - 'Review & Submit' -]; - -export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizardProps) { - const [currentStep, setCurrentStep] = useState(1); - const [dealers, setDealers] = useState([]); - const [loadingDealers, setLoadingDealers] = useState(true); - - 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: '' - }); - - const totalSteps = STEP_NAMES.length; - - // Fetch dealers from API on component mount - useEffect(() => { - const fetchDealers = async () => { - setLoadingDealers(true); - try { - const fetchedDealers = await fetchDealersFromAPI(); - setDealers(fetchedDealers); - } catch (error) { - toast.error('Failed to load dealer list.'); - console.error('Error fetching dealers:', error); - } finally { - setLoadingDealers(false); - } - }; - fetchDealers(); - }, []); - - const updateFormData = (field: string, value: any) => { - setFormData(prev => ({ ...prev, [field]: value })); - }; - - const isStepValid = () => { - switch (currentStep) { - case 1: - return formData.activityName && - formData.activityType && - formData.dealerCode && - formData.dealerName && - formData.activityDate && - formData.location && - formData.requestDescription; - case 2: - return true; - default: - return false; - } - }; - - const nextStep = () => { - if (currentStep < totalSteps && isStepValid()) { - setCurrentStep(currentStep + 1); - } - }; - - const prevStep = () => { - if (currentStep > 1) { - setCurrentStep(currentStep - 1); - } - }; - - const handleDealerChange = async (dealerCode: string) => { - const selectedDealer = dealers.find(d => d.dealerCode === dealerCode); - if (selectedDealer) { - updateFormData('dealerCode', dealerCode); - updateFormData('dealerName', selectedDealer.dealerName); - updateFormData('dealerEmail', selectedDealer.email || ''); - updateFormData('dealerPhone', selectedDealer.phone || ''); - updateFormData('dealerAddress', ''); // Address not available in API response - - // Try to fetch full dealer info from API if available - try { - const fullDealerInfo = await getDealerByCode(dealerCode); - if (fullDealerInfo) { - updateFormData('dealerEmail', fullDealerInfo.email || selectedDealer.email || ''); - updateFormData('dealerPhone', fullDealerInfo.phone || selectedDealer.phone || ''); - } - } catch (error) { - // Ignore error, use basic info from list - console.debug('Could not fetch full dealer info:', error); - } - } - }; - - const handleSubmit = () => { - const claimData = { - ...formData, - templateType: 'claim-management', - submittedAt: new Date().toISOString(), - status: 'pending', - currentStep: 'initiator-review', - workflowSteps: [ - { - step: 1, - name: 'Initiator Evaluation', - status: 'pending', - approver: 'Current User (Initiator)', - description: 'Review and confirm all claim details and documents' - }, - { - step: 2, - name: 'IO Confirmation', - status: 'waiting', - approver: 'System', - description: 'Automatic IO generation upon initiator approval' - }, - { - step: 3, - name: 'Department Lead Approval', - status: 'waiting', - approver: 'Department Lead', - description: 'Budget blocking and final approval' - }, - { - step: 4, - name: 'Document Submission', - status: 'waiting', - approver: 'Dealer', - description: 'Dealer submits completion documents' - }, - { - step: 5, - name: 'Document Verification', - status: 'waiting', - approver: 'Initiator', - description: 'Verify completion documents' - }, - { - step: 6, - name: 'E-Invoice Generation', - status: 'waiting', - approver: 'System', - description: 'Auto-generate e-invoice based on approved amount' - }, - { - step: 7, - name: 'Credit Note Issuance', - status: 'waiting', - approver: 'Finance', - description: 'Issue credit note to dealer' - } - ] - }; - - toast.success('Claim Request Created', { - description: 'Your claim management request has been submitted successfully.' - }); - - if (onSubmit) { - onSubmit(claimData); - } - }; - - 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 */} -
- - - {formData.dealerCode && ( -

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

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