785 lines
33 KiB
TypeScript
785 lines
33 KiB
TypeScript
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,
|
||
Users,
|
||
} from 'lucide-react';
|
||
import { format } from 'date-fns';
|
||
import { toast } from 'sonner';
|
||
import { getAllDealers as fetchDealersFromAPI, getDealerByCode, type DealerInfo } from '@/services/dealerApi';
|
||
import { ClaimApproverSelectionStep } from './ClaimApproverSelectionStep';
|
||
import { useAuth } from '@/contexts/AuthContext';
|
||
|
||
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 [dealers, setDealers] = useState<DealerInfo[]>([]);
|
||
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: '',
|
||
// Approvers array for all 8 steps
|
||
approvers: [] as Array<{
|
||
email: string;
|
||
name?: string;
|
||
userId?: string;
|
||
level: number;
|
||
tat?: number | string;
|
||
tatType?: 'hours' | 'days';
|
||
}>
|
||
});
|
||
|
||
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 => {
|
||
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 || [];
|
||
const step3Approver = approvers.find((a: any) => a.level === 3);
|
||
// 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 || [];
|
||
const step3Approver = approvers.find((a: any) => a.level === 3);
|
||
const missingSteps: string[] = [];
|
||
|
||
if (!step3Approver?.email || !step3Approver?.userId || !step3Approver?.tat) {
|
||
missingSteps.push('Step 3: 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 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
|
||
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',
|
||
// Pass approvers array to backend
|
||
approvers: formData.approvers || []
|
||
};
|
||
|
||
// Don't show toast here - let the parent component handle success/error after API call
|
||
if (onSubmit) {
|
||
onSubmit(claimData);
|
||
}
|
||
};
|
||
|
||
const renderStepContent = () => {
|
||
switch (currentStep) {
|
||
case 1:
|
||
return (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -20 }}
|
||
className="space-y-6"
|
||
>
|
||
<div className="text-center mb-8">
|
||
<div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||
<Receipt className="w-8 h-8 text-white" />
|
||
</div>
|
||
<h2 className="text-2xl font-bold text-gray-900 mb-2">Claim Details</h2>
|
||
<p className="text-gray-600">
|
||
Provide comprehensive information about your claim request
|
||
</p>
|
||
</div>
|
||
|
||
<div className="max-w-3xl mx-auto space-y-6">
|
||
{/* Activity Name and Type */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<Label htmlFor="activityName" className="text-base font-semibold">Activity Name *</Label>
|
||
<Input
|
||
id="activityName"
|
||
placeholder="e.g., Himalayan Adventure Fest 2024"
|
||
value={formData.activityName}
|
||
onChange={(e) => updateFormData('activityName', e.target.value)}
|
||
className="mt-2 h-12"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="activityType" className="text-base font-semibold">Activity Type *</Label>
|
||
<Select value={formData.activityType} onValueChange={(value) => updateFormData('activityType', value)}>
|
||
<SelectTrigger className="mt-2 !h-12 data-[size=default]:!h-12" id="activityType">
|
||
<SelectValue placeholder="Select activity type" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{CLAIM_TYPES.map((type) => (
|
||
<SelectItem key={type} value={type}>{type}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Dealer Selection */}
|
||
<div>
|
||
<Label className="text-base font-semibold">Dealer Code / Dealer Name *</Label>
|
||
<Select value={formData.dealerCode} onValueChange={handleDealerChange} disabled={loadingDealers}>
|
||
<SelectTrigger className="mt-2 !h-12 data-[size=default]:!h-12" id="dealer-select">
|
||
<SelectValue placeholder={loadingDealers ? "Loading dealers..." : "Select dealer"}>
|
||
{formData.dealerCode && (
|
||
<div className="flex items-center gap-2">
|
||
<span className="font-mono text-sm">{formData.dealerCode}</span>
|
||
<span className="text-gray-400">•</span>
|
||
<span>{formData.dealerName}</span>
|
||
</div>
|
||
)}
|
||
</SelectValue>
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{dealers.length === 0 && !loadingDealers ? (
|
||
<div className="p-2 text-sm text-gray-500">No dealers available</div>
|
||
) : (
|
||
dealers.map((dealer) => (
|
||
<SelectItem key={dealer.userId} value={dealer.dealerCode}>
|
||
<div className="flex items-center gap-2">
|
||
<span className="font-mono text-sm font-semibold">{dealer.dealerCode}</span>
|
||
<span className="text-gray-400">•</span>
|
||
<span>{dealer.dealerName}</span>
|
||
</div>
|
||
</SelectItem>
|
||
))
|
||
)}
|
||
</SelectContent>
|
||
</Select>
|
||
{formData.dealerCode && (
|
||
<p className="text-sm text-gray-600 mt-2">
|
||
Selected: <span className="font-semibold">{formData.dealerName}</span> ({formData.dealerCode})
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Date and Location */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<Label className="text-base font-semibold">Date *</Label>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-full justify-start text-left mt-2 h-12"
|
||
>
|
||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||
{formData.activityDate ? format(formData.activityDate, 'PPP') : 'Select date'}
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-auto p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={formData.activityDate}
|
||
onSelect={(date) => updateFormData('activityDate', date)}
|
||
initialFocus
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="location" className="text-base font-semibold">Location *</Label>
|
||
<Input
|
||
id="location"
|
||
placeholder="e.g., Mumbai, Maharashtra"
|
||
value={formData.location}
|
||
onChange={(e) => updateFormData('location', e.target.value)}
|
||
className="mt-2 h-12"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Request Detail */}
|
||
<div>
|
||
<Label htmlFor="requestDescription" className="text-base font-semibold">Request in Detail - Brief Requirement *</Label>
|
||
<Textarea
|
||
id="requestDescription"
|
||
placeholder="Provide a detailed description of your claim requirement..."
|
||
value={formData.requestDescription}
|
||
onChange={(e) => updateFormData('requestDescription', e.target.value)}
|
||
className="mt-2 min-h-[120px]"
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
Include key details about the claim, objectives, and expected outcomes
|
||
</p>
|
||
</div>
|
||
|
||
{/* Period (Optional) */}
|
||
<div>
|
||
<div className="flex items-center gap-2 mb-3">
|
||
<Label className="text-base font-semibold">Period (If Any)</Label>
|
||
<Badge variant="secondary" className="text-xs">Optional</Badge>
|
||
</div>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<Label className="text-sm text-gray-600">Start Date</Label>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-full justify-start text-left mt-2 h-12"
|
||
>
|
||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||
{formData.periodStartDate ? format(formData.periodStartDate, 'PPP') : 'Start date'}
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-auto p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={formData.periodStartDate}
|
||
onSelect={(date) => updateFormData('periodStartDate', date)}
|
||
initialFocus
|
||
// Maximum date is the end date (if selected)
|
||
toDate={formData.periodEndDate || undefined}
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
|
||
<div>
|
||
<Label className="text-sm text-gray-600">End Date</Label>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-full justify-start text-left mt-2 h-12"
|
||
disabled={!formData.periodStartDate}
|
||
>
|
||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||
{formData.periodEndDate ? format(formData.periodEndDate, 'PPP') : 'End date'}
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-auto p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={formData.periodEndDate}
|
||
onSelect={(date) => updateFormData('periodEndDate', date)}
|
||
initialFocus
|
||
// Minimum date is the start date (if selected)
|
||
fromDate={formData.periodStartDate || undefined}
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
{!formData.periodStartDate && (
|
||
<p className="text-xs text-gray-500 mt-1">Please select start date first</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
{(formData.periodStartDate || formData.periodEndDate) && (
|
||
<div className="mt-2">
|
||
{formData.periodStartDate && formData.periodEndDate ? (
|
||
<p className="text-xs text-gray-600">
|
||
Period: {format(formData.periodStartDate, 'MMM dd, yyyy')} - {format(formData.periodEndDate, 'MMM dd, yyyy')}
|
||
</p>
|
||
) : (
|
||
<p className="text-xs text-gray-500">
|
||
{formData.periodStartDate
|
||
? 'Please select end date for the period'
|
||
: 'Please select start date first'}
|
||
</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
|
||
case 2:
|
||
return (
|
||
<ClaimApproverSelectionStep
|
||
formData={formData}
|
||
updateFormData={updateFormData}
|
||
currentUserEmail={(user as any)?.email || ''}
|
||
currentUserId={(user as any)?.userId || ''}
|
||
currentUserName={
|
||
(user as any)?.displayName ||
|
||
(user as any)?.name ||
|
||
((user as any)?.firstName && (user as any)?.lastName
|
||
? `${(user as any).firstName} ${(user as any).lastName}`.trim()
|
||
: (user as any)?.email || 'User')
|
||
}
|
||
/>
|
||
);
|
||
|
||
case 3:
|
||
return (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -20 }}
|
||
className="space-y-6"
|
||
>
|
||
<div className="text-center mb-8">
|
||
<div className="w-16 h-16 bg-gradient-to-br from-green-500 to-emerald-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||
<CheckCircle className="w-8 h-8 text-white" />
|
||
</div>
|
||
<h2 className="text-2xl font-bold text-gray-900 mb-2">Review & Submit</h2>
|
||
<p className="text-gray-600">
|
||
Review your claim details before submission
|
||
</p>
|
||
</div>
|
||
|
||
<div className="max-w-3xl mx-auto space-y-6">
|
||
{/* Activity Information */}
|
||
<Card className="border-2">
|
||
<CardHeader className="bg-gradient-to-br from-blue-50 to-indigo-50">
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Receipt className="w-5 h-5 text-blue-600" />
|
||
Activity Information
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="pt-6 space-y-4">
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Activity Name</Label>
|
||
<p className="font-semibold text-gray-900 mt-1">{formData.activityName}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Activity Type</Label>
|
||
<Badge variant="secondary" className="mt-1">{formData.activityType}</Badge>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Dealer Information */}
|
||
<Card className="border-2">
|
||
<CardHeader className="bg-gradient-to-br from-green-50 to-emerald-50">
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Building className="w-5 h-5 text-green-600" />
|
||
Dealer Information
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="pt-6 space-y-4">
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Dealer Code</Label>
|
||
<p className="font-semibold text-gray-900 mt-1 font-mono">{formData.dealerCode}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Dealer Name</Label>
|
||
<p className="font-semibold text-gray-900 mt-1">{formData.dealerName}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Email</Label>
|
||
<p className="text-gray-900 mt-1">{formData.dealerEmail}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Phone</Label>
|
||
<p className="text-gray-900 mt-1">{formData.dealerPhone}</p>
|
||
</div>
|
||
{formData.dealerAddress && (
|
||
<div className="col-span-2">
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Address</Label>
|
||
<p className="text-gray-900 mt-1">{formData.dealerAddress}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Approver Information */}
|
||
<Card className="border-2">
|
||
<CardHeader className="bg-gradient-to-br from-purple-50 to-indigo-50">
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Users className="w-5 h-5 text-purple-600" />
|
||
Selected Approvers
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="pt-6 space-y-4">
|
||
<div className="space-y-3">
|
||
{(formData.approvers || []).filter((a: any) => !a.email?.includes('system@')).map((approver: any) => {
|
||
const stepNames: Record<number, string> = {
|
||
1: 'Dealer Proposal Submission',
|
||
2: 'Requestor Evaluation',
|
||
3: 'Department Lead Approval',
|
||
4: 'Activity Creation',
|
||
5: 'Dealer Completion Documents',
|
||
6: 'Requestor Claim Approval',
|
||
7: 'E-Invoice Generation',
|
||
8: 'Credit Note Confirmation',
|
||
};
|
||
const tat = Number(approver.tat || 0);
|
||
const tatType = approver.tatType || 'hours';
|
||
const hours = tatType === 'days' ? tat * 24 : tat;
|
||
|
||
return (
|
||
<div key={approver.level} className="p-3 bg-gray-50 rounded-lg border">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">
|
||
Step {approver.level}: {stepNames[approver.level]}
|
||
</Label>
|
||
<p className="font-semibold text-gray-900 mt-1">{approver.name || approver.email || 'Not selected'}</p>
|
||
{approver.email && (
|
||
<p className="text-xs text-gray-500 mt-1">{approver.email}</p>
|
||
)}
|
||
</div>
|
||
<div className="text-right">
|
||
<p className="text-sm font-semibold text-gray-900">{hours} hours</p>
|
||
<p className="text-xs text-gray-500">TAT</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Date & Location */}
|
||
<Card className="border-2">
|
||
<CardHeader className="bg-gradient-to-br from-purple-50 to-pink-50">
|
||
<CardTitle className="flex items-center gap-2">
|
||
<CalendarIcon className="w-5 h-5 text-purple-600" />
|
||
Date & Location
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="pt-6 space-y-4">
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Date</Label>
|
||
<p className="font-semibold text-gray-900 mt-1">
|
||
{formData.activityDate ? format(formData.activityDate, 'PPP') : 'N/A'}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Location</Label>
|
||
<div className="flex items-center gap-2 mt-1">
|
||
<MapPin className="w-4 h-4 text-gray-500" />
|
||
<p className="font-semibold text-gray-900">{formData.location}</p>
|
||
</div>
|
||
</div>
|
||
{formData.estimatedBudget && (
|
||
<div className="col-span-2">
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Estimated Budget</Label>
|
||
<p className="text-xl font-bold text-blue-900 mt-1">{formData.estimatedBudget}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Request Details */}
|
||
<Card className="border-2">
|
||
<CardHeader className="bg-gradient-to-br from-orange-50 to-amber-50">
|
||
<CardTitle className="flex items-center gap-2">
|
||
<FileText className="w-5 h-5 text-orange-600" />
|
||
Request Details
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="pt-6 space-y-4">
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Brief Requirement</Label>
|
||
<div className="mt-2 p-4 bg-gray-50 rounded-lg border">
|
||
<p className="text-gray-900 whitespace-pre-wrap">{formData.requestDescription}</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Period (if provided) */}
|
||
{(formData.periodStartDate || formData.periodEndDate) && (
|
||
<Card className="border-2">
|
||
<CardHeader className="bg-gradient-to-br from-cyan-50 to-blue-50">
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Clock className="w-5 h-5 text-cyan-600" />
|
||
Period
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="pt-6 space-y-4">
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">Start Date</Label>
|
||
<p className="font-semibold text-gray-900 mt-1">
|
||
{formData.periodStartDate ? format(formData.periodStartDate, 'PPP') : 'Not specified'}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs text-gray-600 uppercase tracking-wider">End Date</Label>
|
||
<p className="font-semibold text-gray-900 mt-1">
|
||
{formData.periodEndDate ? format(formData.periodEndDate, 'PPP') : 'Not specified'}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Confirmation Message */}
|
||
<div className="bg-blue-50 border-2 border-blue-200 rounded-lg p-6">
|
||
<div className="flex items-start gap-3">
|
||
<Info className="w-6 h-6 text-blue-600 flex-shrink-0 mt-0.5" />
|
||
<div>
|
||
<p className="font-semibold text-blue-900 mb-1">Ready to Submit</p>
|
||
<p className="text-sm text-blue-800">
|
||
Please review all the information above. Once submitted, your claim request will enter the approval workflow.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
|
||
default:
|
||
return null;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="w-full bg-gradient-to-br from-gray-50 to-gray-100 py-4 sm:py-6 lg:py-8 px-3 sm:px-4 lg:px-6 overflow-y-auto">
|
||
<div className="max-w-6xl mx-auto pb-8">
|
||
{/* Header */}
|
||
<div className="mb-6 sm:mb-8">
|
||
<Button
|
||
variant="ghost"
|
||
onClick={onBack}
|
||
className="mb-3 sm:mb-4 gap-2 text-sm sm:text-base"
|
||
>
|
||
<ArrowLeft className="w-3 h-3 sm:w-4 sm:h-4" />
|
||
<span className="hidden sm:inline">Back to Templates</span>
|
||
<span className="sm:hidden">Back</span>
|
||
</Button>
|
||
|
||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-0">
|
||
<div>
|
||
<Badge variant="secondary" className="mb-2 text-xs">Claim Management Template</Badge>
|
||
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold text-gray-900">New Claim Request</h1>
|
||
<p className="text-sm sm:text-base text-gray-600 mt-1">
|
||
Step {currentStep} of {totalSteps}: <span className="hidden sm:inline">{STEP_NAMES[currentStep - 1]}</span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Progress Bar */}
|
||
<div className="mt-4 sm:mt-6">
|
||
<Progress value={(currentStep / totalSteps) * 100} className="h-2" />
|
||
<div className="flex justify-between mt-2 px-1">
|
||
{STEP_NAMES.map((_name, index) => (
|
||
<span
|
||
key={index}
|
||
className={`text-xs sm:text-sm ${
|
||
index + 1 <= currentStep ? 'text-blue-600 font-medium' : 'text-gray-400'
|
||
}`}
|
||
>
|
||
{index + 1}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Step Content */}
|
||
<Card className="mb-6 sm:mb-8">
|
||
<CardContent className="p-4 sm:p-6 lg:p-8">
|
||
<AnimatePresence mode="wait">
|
||
{renderStepContent()}
|
||
</AnimatePresence>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Navigation */}
|
||
<div className="flex flex-col sm:flex-row justify-between gap-3 sm:gap-0 pb-4 sm:pb-0">
|
||
<Button
|
||
variant="outline"
|
||
onClick={prevStep}
|
||
disabled={currentStep === 1}
|
||
className="gap-2 w-full sm:w-auto order-2 sm:order-1"
|
||
>
|
||
<ArrowLeft className="w-4 h-4" />
|
||
Previous
|
||
</Button>
|
||
|
||
{currentStep < totalSteps ? (
|
||
<Button
|
||
onClick={nextStep}
|
||
className={`gap-2 w-full sm:w-auto order-1 sm:order-2 ${
|
||
!isStepValid()
|
||
? 'opacity-50 cursor-pointer hover:opacity-60'
|
||
: ''
|
||
}`}
|
||
>
|
||
Next
|
||
<ArrowRight className="w-4 h-4" />
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
onClick={handleSubmit}
|
||
disabled={!isStepValid()}
|
||
className="gap-2 bg-green-600 hover:bg-green-700 w-full sm:w-auto order-1 sm:order-2"
|
||
>
|
||
<Check className="w-4 h-4" />
|
||
Submit Claim Request
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|