Re_Figma_Code/src/pages/CreateRequest/CreateRequest.tsx

1805 lines
83 KiB
TypeScript

import { useState } from 'react';
import { Card, CardContent, CardDescription, 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 { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Separator } from '@/components/ui/separator';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { motion, AnimatePresence } from 'framer-motion';
import { TemplateSelectionModal } from '@/components/modals/TemplateSelectionModal';
import {
ArrowLeft,
ArrowRight,
Upload,
X,
User,
Clock,
FileText,
Check,
Users,
Zap,
Shield,
Target,
Flame,
TrendingUp,
DollarSign,
AlertCircle,
CheckCircle,
Info,
Rocket,
Plus,
Minus,
Eye,
Lightbulb,
Settings
} from 'lucide-react';
import { format } from 'date-fns';
interface CreateRequestProps {
onBack?: () => void;
onSubmit?: (requestData: any) => void;
}
interface RequestTemplate {
id: string;
name: string;
description: string;
category: string;
icon: React.ComponentType<any>;
estimatedTime: string;
commonApprovers: string[];
suggestedSLA: number;
priority: 'high' | 'medium' | 'low';
fields: {
amount?: boolean;
vendor?: boolean;
timeline?: boolean;
impact?: boolean;
};
}
const REQUEST_TEMPLATES: RequestTemplate[] = [
{
id: 'custom',
name: 'Custom Request',
description: 'Create a custom request for unique business needs with full flexibility to define your own workflow and requirements',
category: 'General',
icon: Lightbulb,
estimatedTime: 'Variable',
commonApprovers: [],
suggestedSLA: 3,
priority: 'medium',
fields: {}
},
{
id: 'existing-template',
name: 'Existing Template',
description: 'Use a pre-configured template with predefined approval workflows, timelines, and requirements for faster processing',
category: 'Templates',
icon: FileText,
estimatedTime: '1-2 days',
commonApprovers: ['Department Head', 'Manager'],
suggestedSLA: 2,
priority: 'medium',
fields: { timeline: true }
}
];
const MOCK_USERS = [
{ id: '1', name: 'Mike Johnson', role: 'Team Lead', avatar: 'MJ', department: 'Operations', email: 'mike.johnson@royalenfield.com', level: 2, canClose: false },
{ id: '2', name: 'Lisa Wong', role: 'Finance Manager', avatar: 'LW', department: 'Finance', email: 'lisa.wong@royalenfield.com', level: 3, canClose: false },
{ id: '3', name: 'David Kumar', role: 'Department Head', avatar: 'DK', department: 'IT', email: 'david.kumar@royalenfield.com', level: 4, canClose: true },
{ id: '4', name: 'Anna Smith', role: 'Marketing Coordinator', avatar: 'AS', department: 'Marketing', email: 'anna.smith@royalenfield.com', level: 1, canClose: false },
{ id: '5', name: 'John Doe', role: 'Budget Analyst', avatar: 'JD', department: 'Finance', email: 'john.doe@royalenfield.com', level: 2, canClose: false },
{ id: '6', name: 'Sarah Chen', role: 'Marketing Manager', avatar: 'SC', department: 'Marketing', email: 'sarah.chen@royalenfield.com', level: 3, canClose: false },
{ id: '7', name: 'Emily Davis', role: 'Creative Director', avatar: 'ED', department: 'Marketing', email: 'emily.davis@royalenfield.com', level: 3, canClose: false },
{ id: '8', name: 'Robert Kim', role: 'Legal Counsel', avatar: 'RK', department: 'Legal', email: 'robert.kim@royalenfield.com', level: 4, canClose: true },
{ id: '9', name: 'Jennifer Lee', role: 'CEO', avatar: 'JL', department: 'Executive', email: 'jennifer.lee@royalenfield.com', level: 5, canClose: true },
{ id: '10', name: 'Michael Brown', role: 'CFO', avatar: 'MB', department: 'Finance', email: 'michael.brown@royalenfield.com', level: 5, canClose: true }
];
// User levels - keeping for future use
// const USER_LEVELS = [
// { level: 1, name: 'Junior Level', description: 'Junior staff and coordinators', color: 'bg-gray-100 text-gray-800' },
// { level: 2, name: 'Mid Level', description: 'Team leads and supervisors', color: 'bg-blue-100 text-blue-800' },
// { level: 3, name: 'Senior Level', description: 'Managers and senior staff', color: 'bg-green-100 text-green-800' },
// { level: 4, name: 'Executive Level', description: 'Department heads and directors', color: 'bg-orange-100 text-orange-800' },
// { level: 5, name: 'C-Suite Level', description: 'Executive leadership', color: 'bg-purple-100 text-purple-800' }
// ];
// SLA Templates - keeping for future use
// const SLA_TEMPLATES = [
// { id: 'urgent', name: 'Urgent', hours: 4, description: 'Critical business impact', color: 'bg-red-100 text-red-800' },
// { id: 'high', name: 'High Priority', hours: 24, description: 'High business impact', color: 'bg-orange-100 text-orange-800' },
// { id: 'medium', name: 'Medium Priority', hours: 72, description: 'Moderate business impact', color: 'bg-yellow-100 text-yellow-800' },
// { id: 'low', name: 'Low Priority', hours: 120, description: 'Low business impact', color: 'bg-green-100 text-green-800' },
// { id: 'custom', name: 'Custom SLA', hours: 0, description: 'Define your own timeline', color: 'bg-blue-100 text-blue-800' }
// ];
const STEP_NAMES = [
'Template Selection',
'Basic Information',
'Approval Workflow',
'Participants & Access',
'Documents & Attachments',
'Review & Submit'
];
export function CreateRequest({ onBack, onSubmit }: CreateRequestProps) {
const [currentStep, setCurrentStep] = useState(1);
const [selectedTemplate, setSelectedTemplate] = useState<RequestTemplate | null>(null);
const [emailInput, setEmailInput] = useState('');
const [showTemplateModal, setShowTemplateModal] = useState(false);
const [newUserData, setNewUserData] = useState({
name: '',
email: '',
role: '',
department: '',
level: 1
});
const [formData, setFormData] = useState({
// Template and basic info
template: '',
title: '',
description: '',
category: '',
// Details
priority: '',
urgency: '',
businessImpact: '',
amount: '',
currency: 'USD',
vendor: '',
timeline: '',
// SLA and dates
slaTemplate: '',
slaHours: 0,
customSlaHours: 0,
slaEndDate: undefined as Date | undefined,
expectedCompletionDate: undefined as Date | undefined,
breachEscalation: true,
reminderSchedule: '50' as '25' | '50' | '75',
// Workflow
workflowType: 'sequential' as 'sequential' | 'parallel',
requiresAllApprovals: true,
escalationEnabled: true,
reminderEnabled: true,
minimumLevel: 1,
maxLevel: 1,
// Participants
approvers: [] as any[],
approverCount: 1,
spectators: [] as any[],
ccList: [] as any[],
invitedUsers: [] as any[],
// Access settings
allowComments: true,
allowDocumentUpload: true,
// Documents
documents: [] as File[],
// Additional metadata
tags: [] as string[],
relatedRequests: [] as string[],
costCenter: '',
project: ''
});
const totalSteps = STEP_NAMES.length;
const updateFormData = (field: string, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const validateEmail = (email: string) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
const createUserFromEmail = (email: string, name: string = '', role: string = '', department: string = '', level: number = 1) => {
const avatar = name ? name.split(' ').map(n => (n?.[0] || '')).join('').toUpperCase() : email.split('@')[0]?.substring(0, 2).toUpperCase() || 'XX';
return {
id: `temp-${Date.now()}-${Math.random()}`,
name: name || email.split('@')[0],
email,
role: role || 'External User',
department: department || 'External',
avatar,
level,
canClose: level >= 4,
isInvited: true
};
};
const getPriorityIcon = (priority: string) => {
switch (priority) {
case 'high': return <Flame className="w-4 h-4 text-red-600" />;
case 'medium': return <Target className="w-4 h-4 text-orange-600" />;
case 'low': return <TrendingUp className="w-4 h-4 text-green-600" />;
default: return <Target className="w-4 h-4 text-gray-600" />;
}
};
const isStepValid = () => {
switch (currentStep) {
case 1: return selectedTemplate !== null;
case 2: return formData.title.trim() !== '' && formData.description.trim() !== '' && formData.priority !== '';
case 3: return (formData.approverCount || 1) > 0 &&
formData.approvers.length === (formData.approverCount || 1) &&
formData.approvers.every(approver => {
if (!approver || !approver.email) return false;
// Check TAT validation based on type
const tatType = approver.tatType || 'hours';
if (tatType === 'hours') {
return approver.tat && approver.tat > 0 && approver.tat <= 720;
} else if (tatType === 'days') {
return approver.tat && approver.tat > 0 && approver.tat <= 30;
}
return false;
});
case 4: return true; // Participants are optional except approvers
case 5: return true; // Documents are optional
case 6: return true; // Review & Submit
default: return false;
}
};
const nextStep = () => {
if (currentStep < totalSteps && isStepValid()) {
setCurrentStep(currentStep + 1);
}
};
const prevStep = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
const selectTemplate = (template: RequestTemplate) => {
setSelectedTemplate(template);
updateFormData('template', template.id);
updateFormData('category', template.category);
updateFormData('priority', template.priority);
// Auto-suggest SLA
const suggestedDate = new Date();
suggestedDate.setDate(suggestedDate.getDate() + template.suggestedSLA);
updateFormData('slaEndDate', suggestedDate);
// If user selected "Existing Template", show the template selection modal
if (template.id === 'existing-template') {
setShowTemplateModal(true);
}
};
const handleTemplateSelection = (templateId: string) => {
// This will be handled by routing to the specific template wizard in App.tsx
if (onSubmit) {
onSubmit({ templateType: templateId });
}
};
const addUser = (user: any, type: 'approvers' | 'spectators' | 'ccList' | 'invitedUsers') => {
const currentList = formData[type];
if (!currentList.find((u: any) => u.id === user.id)) {
const updatedList = [...currentList, user];
updateFormData(type, updatedList);
// Update max level if adding approver
if (type === 'approvers') {
const maxApproverLevel = Math.max(...updatedList.map((a: any) => a.level), 0);
updateFormData('maxLevel', maxApproverLevel);
}
}
};
const removeUser = (userId: string, type: 'approvers' | 'spectators' | 'ccList' | 'invitedUsers') => {
const currentList = formData[type];
const updatedList = currentList.filter((u: any) => u.id !== userId);
updateFormData(type, updatedList);
// Update max level if removing approver
if (type === 'approvers') {
const maxApproverLevel = Math.max(...updatedList.map((a: any) => a.level), 0);
updateFormData('maxLevel', maxApproverLevel);
}
};
const addUserByEmail = () => {
if (!validateEmail(emailInput)) return;
// Check if user already exists
const existingUser = [...MOCK_USERS, ...formData.invitedUsers].find(u => u.email === emailInput);
if (existingUser) {
setEmailInput('');
return existingUser;
}
// Create new user
const newUser = createUserFromEmail(
emailInput,
newUserData.name,
newUserData.role,
newUserData.department,
newUserData.level
);
addUser(newUser, 'invitedUsers');
setEmailInput('');
setNewUserData({ name: '', email: '', role: '', department: '', level: 1 });
return newUser;
};
const inviteAndAddUser = (type: 'approvers' | 'spectators' | 'ccList') => {
const user = addUserByEmail();
if (user) {
addUser(user, type);
}
};
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
updateFormData('documents', [...formData.documents, ...files]);
};
const handleSubmit = () => {
if (isStepValid()) {
const requestData = {
...formData,
template: selectedTemplate,
id: `RE-REQ-${Date.now()}`,
createdAt: new Date().toISOString(),
status: 'pending'
};
onSubmit?.(requestData);
}
};
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="min-h-full flex flex-col items-center justify-center py-8"
>
{/* Header Section */}
<div className="text-center mb-12 max-w-3xl">
<h1 className="text-4xl lg:text-5xl font-bold text-gray-900 mb-4">
Choose Your Request Type
</h1>
<p className="text-lg text-gray-600">
Start with a pre-built template for faster approvals, or create a custom request tailored to your needs.
</p>
</div>
{/* Template Cards Grid */}
<div className="w-full max-w-6xl grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
{REQUEST_TEMPLATES.map((template) => (
<motion.div
key={template.id}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
>
<Card
className={`cursor-pointer h-full transition-all duration-300 border-2 ${
selectedTemplate?.id === template.id
? 'border-blue-500 shadow-xl bg-blue-50/50 ring-2 ring-blue-200'
: 'border-gray-200 hover:border-blue-300 hover:shadow-lg'
}`}
onClick={() => selectTemplate(template)}
>
<CardHeader className="space-y-4 pb-4">
<div className="flex items-start justify-between">
<div className={`w-14 h-14 rounded-xl flex items-center justify-center ${
selectedTemplate?.id === template.id
? 'bg-blue-100'
: 'bg-gray-100'
}`}>
<template.icon className={`w-7 h-7 ${
selectedTemplate?.id === template.id
? 'text-blue-600'
: 'text-gray-600'
}`} />
</div>
{selectedTemplate?.id === template.id && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", stiffness: 500, damping: 15 }}
>
<div className="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center">
<Check className="w-5 h-5 text-white" />
</div>
</motion.div>
)}
</div>
<div className="text-left">
<CardTitle className="text-xl mb-2">{template.name}</CardTitle>
<div className="flex items-center gap-2">
<Badge variant="secondary" className="text-xs">
{template.category}
</Badge>
{getPriorityIcon(template.priority)}
</div>
</div>
</CardHeader>
<CardContent className="pt-0 space-y-4">
<p className="text-sm text-gray-600 leading-relaxed line-clamp-2">
{template.description}
</p>
<Separator />
<div className="grid grid-cols-2 gap-3 text-xs text-gray-500">
<div className="flex items-center gap-1.5">
<Clock className="w-3.5 h-3.5" />
<span>{template.estimatedTime}</span>
</div>
<div className="flex items-center gap-1.5">
<Users className="w-3.5 h-3.5" />
<span>{template.commonApprovers.length} approvers</span>
</div>
</div>
</CardContent>
</Card>
</motion.div>
))}
</div>
{/* Template Details Card - Only show when template is selected */}
<AnimatePresence>
{selectedTemplate && (
<motion.div
initial={{ opacity: 0, y: 20, height: 0 }}
animate={{ opacity: 1, y: 0, height: 'auto' }}
exit={{ opacity: 0, y: -20, height: 0 }}
transition={{ duration: 0.3 }}
className="w-full max-w-6xl"
>
<Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-2 border-blue-200">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-blue-900">
<Info className="w-5 h-5" />
{selectedTemplate.name} - Template Details
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-white/60 p-3 rounded-lg">
<Label className="text-blue-900 font-semibold">Suggested SLA</Label>
<p className="text-blue-700 mt-1">{selectedTemplate.suggestedSLA} days</p>
</div>
<div className="bg-white/60 p-3 rounded-lg">
<Label className="text-blue-900 font-semibold">Priority Level</Label>
<div className="flex items-center gap-1 mt-1">
{getPriorityIcon(selectedTemplate.priority)}
<span className="text-blue-700 capitalize">{selectedTemplate.priority}</span>
</div>
</div>
<div className="bg-white/60 p-3 rounded-lg">
<Label className="text-blue-900 font-semibold">Estimated Duration</Label>
<p className="text-blue-700 mt-1">{selectedTemplate.estimatedTime}</p>
</div>
</div>
<div className="bg-white/60 p-3 rounded-lg">
<Label className="text-blue-900 font-semibold">Common Approvers</Label>
<div className="flex flex-wrap gap-2 mt-2">
{selectedTemplate.commonApprovers.map((approver, index) => (
<Badge key={`${selectedTemplate.id}-approver-${index}-${approver}`} variant="outline" className="border-blue-300 text-blue-700 bg-white">
{approver}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
case 2:
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-blue-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
<FileText className="w-8 h-8 text-white" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Basic Information</h2>
<p className="text-gray-600">
Provide the essential details for your {selectedTemplate?.name || 'request'}.
</p>
</div>
<div className="max-w-2xl mx-auto space-y-6">
<div>
<Label htmlFor="title" className="text-base font-semibold">Request Title *</Label>
<p className="text-sm text-gray-600 mb-3">
Be specific and descriptive. This will be visible to all participants.
</p>
<Input
id="title"
placeholder="e.g., Approval on new office location"
value={formData.title}
onChange={(e) => updateFormData('title', e.target.value)}
className="text-base h-12 border-2 border-gray-300 focus:border-blue-500 bg-white shadow-sm"
/>
</div>
<div>
<Label htmlFor="description" className="text-base font-semibold">Detailed Description *</Label>
<p className="text-sm text-gray-600 mb-3">
Explain what you need approval for, why it's needed, and any relevant background information.
</p>
<Textarea
id="description"
placeholder="Provide comprehensive details about your request 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:border-blue-500 bg-white shadow-sm resize-none"
value={formData.description}
onChange={(e) => updateFormData('description', e.target.value)}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<Label className="text-base font-semibold">Priority Level *</Label>
<p className="text-sm text-gray-600 mb-2">
select priority for your request
</p>
<RadioGroup
value={formData.priority || ''}
onValueChange={(value) => {
console.log('Priority changed to:', value);
updateFormData('priority', value);
}}
>
<div
className={`flex items-center space-x-3 p-3 rounded-lg border-2 cursor-pointer transition-all ${
formData.priority === 'express'
? 'border-red-500 bg-red-100'
: 'border-red-200 bg-red-50 hover:bg-red-100'
}`}
onClick={() => updateFormData('priority', 'express')}
>
<RadioGroupItem value="express" id="express" />
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<Zap className="w-4 h-4 text-red-600" />
<Label htmlFor="express" className="font-medium text-red-900 cursor-pointer">Express</Label>
<Badge variant="destructive" className="text-xs">URGENT</Badge>
</div>
<p className="text-xs text-red-700">
Includes calendar days in TAT - faster processing timeline
</p>
</div>
</div>
<div
className={`flex items-center space-x-3 p-3 rounded-lg border cursor-pointer transition-all ${
formData.priority === 'standard'
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:bg-gray-50'
}`}
onClick={() => updateFormData('priority', 'standard')}
>
<RadioGroupItem value="standard" id="standard" />
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<Clock className="w-4 h-4 text-blue-600" />
<Label htmlFor="standard" className="font-medium text-blue-900 cursor-pointer">Standard</Label>
<Badge variant="secondary" className="text-xs">DEFAULT</Badge>
</div>
<p className="text-xs text-gray-600">
Includes working days in TAT - regular processing timeline
</p>
</div>
</div>
</RadioGroup>
</div>
</div>
{/* SLA Configuration */}
<div className="space-y-4">
</div>
{/* Template-specific fields */}
{(selectedTemplate?.fields.amount || selectedTemplate?.fields.vendor || selectedTemplate?.fields.timeline || selectedTemplate?.fields.impact) && (
<div className="border-t pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Additional Details</h3>
<div className="space-y-6">
{selectedTemplate?.fields.amount && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="md:col-span-2">
<Label htmlFor="amount" className="text-base font-semibold">Budget Amount</Label>
<Input
id="amount"
placeholder="Enter amount"
value={formData.amount}
onChange={(e) => updateFormData('amount', e.target.value)}
className="text-base h-12 border-2 border-gray-300 focus:border-blue-500 bg-white shadow-sm"
/>
</div>
<div>
<Label className="text-base font-semibold">Currency</Label>
<Select value={formData.currency} onValueChange={(value) => updateFormData('currency', value)}>
<SelectTrigger className="h-12 border-2 border-gray-300 focus:border-blue-500 bg-white shadow-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="USD">USD ($)</SelectItem>
<SelectItem value="EUR">EUR (€)</SelectItem>
<SelectItem value="GBP">GBP (£)</SelectItem>
<SelectItem value="INR">INR (₹)</SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
{selectedTemplate?.fields.vendor && (
<div>
<Label htmlFor="vendor" className="text-base font-semibold">Vendor/Supplier</Label>
<Input
id="vendor"
placeholder="Enter vendor or supplier name"
value={formData.vendor}
onChange={(e) => updateFormData('vendor', e.target.value)}
className="text-base h-12 border-2 border-gray-300 focus:border-blue-500 bg-white shadow-sm"
/>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<Label htmlFor="costCenter" className="text-base font-semibold">Cost Center</Label>
<Input
id="costCenter"
placeholder="e.g., Marketing, IT, Operations"
value={formData.costCenter}
onChange={(e) => updateFormData('costCenter', e.target.value)}
className="text-base h-12 border-2 border-gray-300 focus:border-blue-500 bg-white shadow-sm"
/>
</div>
<div>
<Label htmlFor="project" className="text-base font-semibold">Related Project</Label>
<Input
id="project"
placeholder="Associated project name or code"
value={formData.project}
onChange={(e) => updateFormData('project', e.target.value)}
className="text-base h-12 border-2 border-gray-300 focus:border-blue-500 bg-white shadow-sm"
/>
</div>
</div>
</div>
</div>
)}
</div>
</motion.div>
);
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-orange-500 to-red-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Users className="w-8 h-8 text-white" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Approval Workflow</h2>
<p className="text-gray-600">
Define the approval hierarchy and assign approvers by email ID.
</p>
</div>
<div className="max-w-4xl mx-auto space-y-8">
{/* Number of Approvers */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="w-5 h-5" />
Approval Configuration
</CardTitle>
<CardDescription>
Configure how many approvers you need and define the approval sequence.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div>
<Label className="text-base font-semibold mb-4 block">Number of Approvers *</Label>
<div className="flex items-center gap-4">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
const currentCount = formData.approverCount || 1;
const newCount = Math.max(1, currentCount - 1);
updateFormData('approverCount', newCount);
// Trim approvers array if reducing count
if (formData.approvers.length > newCount) {
updateFormData('approvers', formData.approvers.slice(0, newCount));
}
}}
disabled={(formData.approverCount || 1) <= 1}
>
<Minus className="w-4 h-4" />
</Button>
<span className="text-2xl font-semibold w-12 text-center">{formData.approverCount || 1}</span>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
const currentCount = formData.approverCount || 1;
const newCount = Math.min(10, currentCount + 1);
updateFormData('approverCount', newCount);
}}
disabled={(formData.approverCount || 1) >= 10}
>
<Plus className="w-4 h-4" />
</Button>
</div>
<p className="text-sm text-gray-600 mt-2">
Maximum 10 approvers allowed. Each approver will review sequentially.
</p>
</div>
</CardContent>
</Card>
{/* Approval Hierarchy */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="w-5 h-5" />
Approval Hierarchy *
</CardTitle>
<CardDescription>
Define the approval sequence. Each approver will review the request in order from Level 1 to Level {formData.approverCount || 1}.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Initiator Card */}
<div className="p-4 rounded-lg border-2 border-blue-200 bg-blue-50">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-600 rounded-full flex items-center justify-center">
<User className="w-5 h-5 text-white" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-semibold text-blue-900">Request Initiator</span>
<Badge variant="secondary" className="text-xs">YOU</Badge>
</div>
<p className="text-sm text-blue-700">Creates and submits the request</p>
</div>
</div>
</div>
{/* Dynamic Approver Cards */}
{Array.from({ length: formData.approverCount || 1 }, (_, index) => {
const level = index + 1;
const isLast = level === (formData.approverCount || 1);
// Ensure approvers array has enough items
if (!formData.approvers[index]) {
const newApprovers = [...formData.approvers];
newApprovers[index] = { email: '', name: '', level: level, tat: '' as any };
updateFormData('approvers', newApprovers);
}
return (
<div key={level} className="space-y-3">
{/* Connection Line */}
<div className="flex justify-center">
<div className="w-px h-6 bg-gray-300"></div>
</div>
{/* Approver Card */}
<div className={`p-4 rounded-lg border-2 transition-all ${
formData.approvers[index]?.email
? 'border-green-200 bg-green-50'
: 'border-gray-200 bg-gray-50'
}`}>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${
formData.approvers[index]?.email
? 'bg-green-600'
: 'bg-gray-400'
}`}>
<span className="text-white font-semibold">{level}</span>
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-3">
<span className="font-semibold text-gray-900">
Approver Level {level}
</span>
{isLast && (
<Badge variant="destructive" className="text-xs">FINAL APPROVER</Badge>
)}
</div>
<div className="space-y-4">
<div>
<Label htmlFor={`approver-${level}`} className="text-sm font-medium">
Email Address *
</Label>
<Input
id={`approver-${level}`}
type="email"
placeholder="approver@royalenfield.com"
value={formData.approvers[index]?.email || ''}
onChange={(e) => {
const newApprovers = [...formData.approvers];
newApprovers[index] = {
...newApprovers[index],
email: e.target.value,
level: level
};
updateFormData('approvers', newApprovers);
}}
className="h-10 border-2 border-gray-300 focus:border-blue-500 mt-1"
/>
<p className="text-xs text-gray-500 mt-1 flex items-center gap-1">
<span className="font-medium">@</span>
Use @ sign to tag a user
</p>
</div>
{/* Peer Approver Section */}
<div className="border-t border-gray-200 pt-3">
{(formData.approvers[index]?.showPeerInput || formData.approvers[index]?.peerEmail) && (
<div className="space-y-2">
<Input
type="email"
placeholder="peer.approver@royalenfield.com"
value={formData.approvers[index]?.peerEmail || ''}
onChange={(e) => {
const newApprovers = [...formData.approvers];
newApprovers[index] = {
...newApprovers[index],
peerEmail: e.target.value,
level: level
};
updateFormData('approvers', newApprovers);
}}
className="h-9 border-2 border-gray-300 focus:border-blue-500 text-sm"
/>
<p className="text-xs text-gray-500">
This user can approve the request if the main approver is unavailable.
</p>
</div>
)}
{formData.approvers[index]?.peerEmail && (
<div className="mt-2 p-2 bg-blue-50 rounded-lg border border-blue-200">
<div className="flex items-center gap-2">
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center">
<Users className="w-3 h-3 text-white" />
</div>
<div className="flex-1">
<p className="text-sm font-medium text-blue-900">
{formData.approvers[index]?.peerEmail}
</p>
<p className="text-xs text-blue-700">Peer Approver</p>
</div>
</div>
</div>
)}
</div>
<div>
<Label htmlFor={`tat-${level}`} className="text-sm font-medium">
TAT (Turn Around Time) *
</Label>
<div className="flex items-center gap-2 mt-1">
<Input
id={`tat-${level}`}
type="number"
placeholder={formData.approvers[index]?.tatType === 'days' ? '7' : '24'}
min="1"
max={formData.approvers[index]?.tatType === 'days' ? '30' : '720'}
value={formData.approvers[index]?.tat || ''}
onChange={(e) => {
const newApprovers = [...formData.approvers];
newApprovers[index] = {
...newApprovers[index],
tat: parseInt(e.target.value) || '',
level: level,
tatType: formData.approvers[index]?.tatType || 'hours'
};
updateFormData('approvers', newApprovers);
}}
className="h-10 border-2 border-gray-300 focus:border-blue-500 flex-1"
/>
<Select
value={formData.approvers[index]?.tatType || 'hours'}
onValueChange={(value) => {
const newApprovers = [...formData.approvers];
newApprovers[index] = {
...newApprovers[index],
tatType: value as 'hours' | 'days',
level: level,
// Clear the value when switching to set appropriate defaults
tat: ''
};
updateFormData('approvers', newApprovers);
}}
>
<SelectTrigger className="w-20 h-10 border-2 border-gray-300">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="hours">Hours</SelectItem>
<SelectItem value="days">Days</SelectItem>
</SelectContent>
</Select>
</div>
<p className="text-xs text-gray-600 mt-1">
{formData.approvers[index]?.tatType === 'days'
? 'Maximum days for this approver to review and respond (1-30 days)'
: 'Maximum time for this approver to review and respond (1-720 hours)'
}
</p>
</div>
<p className="text-xs text-gray-600">
{isLast
? 'This approver can close the request upon approval'
: 'Request will proceed to next level upon approval'
}
</p>
</div>
</div>
</div>
</div>
</div>
);
})}
{/* Enhanced Summary with TAT */}
<div className="mt-6 space-y-4">
{/* Approval Flow Summary */}
<div className="p-4 bg-blue-50 rounded-lg border border-blue-200">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
<div>
<h4 className="font-semibold text-blue-900 mb-1">Approval Flow Summary</h4>
<p className="text-sm text-blue-700">
Your request will follow this sequence: <strong>You (Initiator)</strong> →
{Array.from({ length: formData.approverCount || 1 }, (_, i) =>
` Level ${i + 1} Approver`
).join(' ')}.
The final approver can close the request.
</p>
</div>
</div>
</div>
{/* TAT Summary */}
{(() => {
// Calculate total TAT
let totalHours = 0;
let tatBreakdown = [];
let hasIncompleteData = false;
for (let i = 0; i < (formData.approverCount || 1); i++) {
const approver = formData.approvers[i];
if (approver?.tat && approver?.tatType) {
const tatValue = parseInt(approver.tat);
const hours = approver.tatType === 'days' ? tatValue * 24 : tatValue;
totalHours += hours;
tatBreakdown.push({
level: i + 1,
tat: approver.tat,
tatType: approver.tatType,
hours: hours
});
} else {
hasIncompleteData = true;
}
}
const totalDays = Math.ceil(totalHours / 24);
const workingDays = Math.ceil(totalHours / 8); // Assuming 8-hour working days
return (
<div className="p-4 bg-gradient-to-r from-emerald-50 to-teal-50 rounded-lg border border-emerald-200">
<div className="flex items-start gap-3">
<Clock className="w-5 h-5 text-emerald-600 mt-0.5" />
<div className="flex-1">
<div className="flex items-center justify-between mb-2">
<h4 className="font-semibold text-emerald-900">TAT Summary</h4>
{!hasIncompleteData && (
<div className="text-right">
<div className="text-lg font-bold text-emerald-800">{totalDays} {totalDays === 1 ? 'Day' : 'Days'}</div>
<div className="text-xs text-emerald-600">Total Duration</div>
</div>
)}
</div>
{hasIncompleteData ? (
<p className="text-sm text-amber-700 bg-amber-50 p-2 rounded border border-amber-200">
<AlertCircle className="w-4 h-4 inline mr-1" />
Complete all approver TAT information to see total duration summary
</p>
) : (
<div className="space-y-3">
{/* TAT Breakdown */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{tatBreakdown.map((item, index) => (
<div key={index} className="bg-white/60 p-2 rounded border border-emerald-100">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-emerald-900">Level {item.level}</span>
<span className="text-sm text-emerald-700">{item.tat} {item.tatType}</span>
</div>
</div>
))}
</div>
{/* Total Summary */}
<div className="bg-white/80 p-3 rounded border border-emerald-200">
<div className="grid grid-cols-2 gap-4 text-center">
<div>
<div className="text-lg font-bold text-emerald-800">{totalHours}h</div>
<div className="text-xs text-emerald-600">Total Hours</div>
</div>
<div>
<div className="text-lg font-bold text-emerald-800">{workingDays}</div>
<div className="text-xs text-emerald-600">Working Days*</div>
</div>
</div>
<p className="text-xs text-emerald-600 mt-2 text-center">
*Based on 8-hour working days
</p>
</div>
</div>
)}
</div>
</div>
</div>
);
})()}
</div>
</CardContent>
</Card>
</div>
</motion.div>
);
case 4:
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-teal-500 to-green-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Eye className="w-8 h-8 text-white" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Participants & Access</h2>
<p className="text-gray-600">
Configure additional participants and visibility settings for your request.
</p>
</div>
<div className="max-w-3xl mx-auto space-y-8">
{/* Additional Participants */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Spectators */}
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between text-base">
<div className="flex items-center gap-2">
<Eye className="w-4 h-4" />
Spectators
</div>
<Badge variant="outline" className="text-xs">
{formData.spectators.length}
</Badge>
</CardTitle>
<CardDescription>
Users who can view and comment but cannot approve
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<div className="flex items-center gap-2">
<Input
placeholder="Use @ sign to add a user"
value={emailInput}
onChange={(e) => setEmailInput(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter' && validateEmail(emailInput)) {
inviteAndAddUser('spectators');
}
}}
className="text-sm"
/>
<Button
size="sm"
onClick={() => inviteAndAddUser('spectators')}
disabled={!validateEmail(emailInput)}
>
Add
</Button>
</div>
</div>
<div className="space-y-2 max-h-40 overflow-y-auto">
{formData.spectators.map((spectator) => (
<div key={spectator.id} className="flex items-center justify-between p-2 bg-teal-50 rounded-lg">
<div className="flex items-center gap-2">
<Avatar className="h-6 w-6">
<AvatarFallback className="bg-teal-600 text-white text-xs">
{spectator.avatar}
</AvatarFallback>
</Avatar>
<span className="text-sm font-medium">{spectator.name}</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeUser(spectator.id, 'spectators')}
>
<X className="h-3 w-3" />
</Button>
</div>
))}
</div>
</CardContent>
</Card>
{/* CC List */}
</div>
</div>
</motion.div>
);
case 5:
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-indigo-500 to-purple-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Upload className="w-8 h-8 text-white" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">Documents & Attachments</h2>
<p className="text-gray-600">
Upload supporting documents, files, and any additional materials for your request.
</p>
</div>
<div className="max-w-2xl mx-auto space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="w-5 h-5" />
File Upload
</CardTitle>
<CardDescription>
Attach supporting documents (PDF, Word, Excel, Images). Max 10MB per file.
</CardDescription>
</CardHeader>
<CardContent>
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
<Upload className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<h3 className="text-lg font-semibold text-gray-900 mb-2">Upload Files</h3>
<p className="text-gray-600 mb-4">
Drag and drop files here, or click to browse
</p>
<input
type="file"
multiple
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.ppt,.pptx"
onChange={handleFileUpload}
className="hidden"
id="file-upload"
/>
<Label htmlFor="file-upload" className="cursor-pointer">
<Button variant="outline" size="lg" type="button">
<Plus className="w-4 h-4 mr-2" />
Browse Files
</Button>
</Label>
<p className="text-xs text-gray-500 mt-2">
Supported formats: PDF, DOC, DOCX, XLS, XLSX, JPG, PNG, PPT, PPTX
</p>
</div>
</CardContent>
</Card>
{formData.documents.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Uploaded Files</span>
<Badge variant="secondary">
{formData.documents.length} file{formData.documents.length !== 1 ? 's' : ''}
</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{formData.documents.map((file, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg border">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<FileText className="h-5 w-5 text-blue-600" />
</div>
<div>
<p className="font-medium text-gray-900">{file.name}</p>
<div className="flex items-center gap-3 text-sm text-gray-600">
<span>{(file.size / (1024 * 1024)).toFixed(2)} MB</span>
<span>•</span>
<span>{file.type || 'Unknown type'}</span>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm">
<Eye className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
const newDocs = formData.documents.filter((_, i) => i !== index);
updateFormData('documents', newDocs);
}}
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
</motion.div>
);
case 6:
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-teal-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">
Please review all details before submitting your request for approval.
</p>
</div>
<div className="max-w-5xl mx-auto space-y-8">
{/* Request Overview */}
<Card className="border-2 border-green-200 bg-green-50/50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-green-900">
<Rocket className="w-5 h-5" />
Request Overview
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div>
<Label className="text-green-900 font-semibold">Request Type</Label>
<p className="text-green-800 mt-1">{selectedTemplate?.name}</p>
<Badge variant="outline" className="mt-2 text-xs border-green-300 text-green-700">
{selectedTemplate?.category}
</Badge>
</div>
<div>
<Label className="text-green-900 font-semibold">Priority</Label>
<div className="flex items-center gap-2 mt-1">
{getPriorityIcon(formData.priority)}
<span className="text-green-800 capitalize">{formData.priority}</span>
</div>
</div>
<div>
<Label className="text-green-900 font-semibold">Workflow Type</Label>
<p className="text-green-800 mt-1 capitalize">{formData.workflowType}</p>
<p className="text-sm text-green-700">{formData.approverCount || 1} Level{(formData.approverCount || 1) > 1 ? 's' : ''}</p>
</div>
</div>
<div>
<Label className="text-green-900 font-semibold">Request Title</Label>
<p className="text-green-800 font-medium mt-1 text-lg">{formData.title}</p>
</div>
</CardContent>
</Card>
{/* Basic Information */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="w-5 h-5" />
Basic Information
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label className="font-semibold">Description</Label>
<p className="text-sm text-gray-700 mt-1 p-3 bg-gray-50 rounded-lg border">
{formData.description}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label className="font-semibold">Priority Level</Label>
<div className="flex items-center gap-2 mt-1">
{getPriorityIcon(formData.priority)}
<span className="capitalize font-medium">{formData.priority}</span>
</div>
</div>
{formData.urgency && (
<div>
<Label className="font-semibold">Urgency</Label>
<p className="text-sm text-gray-700 mt-1 capitalize">{formData.urgency}</p>
</div>
)}
</div>
{formData.businessImpact && (
<div>
<Label className="font-semibold">Business Impact</Label>
<p className="text-sm text-gray-700 mt-1 p-3 bg-gray-50 rounded-lg border">
{formData.businessImpact}
</p>
</div>
)}
{/* Financial Information */}
{formData.amount && (
<div className="p-3 bg-blue-50 rounded-lg border border-blue-200">
<div className="flex items-center gap-2 mb-3">
<DollarSign className="w-4 h-4 text-blue-600" />
<Label className="font-semibold text-blue-900">Financial Details</Label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<span className="text-sm text-blue-700">Amount</span>
<p className="font-semibold text-blue-900">{formData.amount} {formData.currency}</p>
</div>
{formData.costCenter && (
<div>
<span className="text-sm text-blue-700">Cost Center</span>
<p className="font-medium text-blue-900">{formData.costCenter}</p>
</div>
)}
</div>
</div>
)}
{/* Additional Fields */}
{(formData.vendor || formData.timeline || formData.project) && (
<div className="space-y-3">
<Label className="font-semibold">Additional Information</Label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{formData.vendor && (
<div>
<span className="text-sm text-gray-600">Vendor</span>
<p className="text-sm font-medium">{formData.vendor}</p>
</div>
)}
{formData.timeline && (
<div>
<span className="text-sm text-gray-600">Timeline</span>
<p className="text-sm font-medium">{formData.timeline}</p>
</div>
)}
{formData.project && (
<div>
<span className="text-sm text-gray-600">Related Project</span>
<p className="text-sm font-medium">{formData.project}</p>
</div>
)}
{formData.expectedCompletionDate && (
<div>
<span className="text-sm text-gray-600">Expected Completion</span>
<p className="text-sm font-medium">{format(formData.expectedCompletionDate, 'PPP')}</p>
</div>
)}
</div>
</div>
)}
</CardContent>
</Card>
{/* Approval Workflow */}
<Card className="border-2 border-orange-200 bg-orange-50/50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-orange-900">
<Users className="w-5 h-5" />
Approval Workflow
</CardTitle>
<CardDescription className="text-orange-700">
Sequential approval hierarchy with TAT (Turn Around Time) for each level
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 p-3 bg-orange-100 rounded-lg">
<div>
<Label className="text-orange-900 font-semibold">Workflow Type</Label>
<p className="text-orange-800 mt-1 capitalize">{formData.workflowType}</p>
</div>
<div>
<Label className="text-orange-900 font-semibold">Approval Levels</Label>
<p className="text-orange-800 mt-1">{formData.approverCount || 1} Level{(formData.approverCount || 1) > 1 ? 's' : ''}</p>
</div>
<div>
<Label className="text-orange-900 font-semibold">Escalation</Label>
<p className="text-orange-800 mt-1">{formData.escalationEnabled ? 'Enabled' : 'Disabled'}</p>
</div>
</div>
{/* Approver Details */}
<div className="space-y-4">
{Array.from({ length: formData.approverCount || 1 }, (_, index) => {
const level = index + 1;
const isLast = level === (formData.approverCount || 1);
const approver = formData.approvers[index];
return (
<div key={level} className="p-4 bg-white rounded-lg border border-orange-200">
<div className="flex items-center gap-4">
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${
approver?.email ? 'bg-green-600' : 'bg-gray-400'
}`}>
<span className="text-white font-semibold">{level}</span>
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className="font-semibold text-gray-900">
Approver Level {level}
</span>
{isLast && (
<Badge variant="destructive" className="text-xs">FINAL APPROVER</Badge>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<span className="text-sm text-gray-600">Email Address</span>
<p className="text-sm font-medium text-gray-900">
{approver?.email || 'Not assigned'}
</p>
</div>
<div>
<span className="text-sm text-gray-600">TAT (Turn Around Time)</span>
<p className="text-sm font-medium text-gray-900">
{approver?.tat ?
`${approver.tat} ${approver.tatType === 'days' ? 'day' : 'hour'}${approver.tat !== 1 ? 's' : ''}`
: 'Not set'
}
</p>
</div>
</div>
</div>
<div className="flex flex-col items-end gap-1">
{approver?.email && (
<Badge variant="outline" className="text-xs border-green-300 text-green-700">
✓ Assigned
</Badge>
)}
{isLast && (
<Badge className="text-xs bg-red-100 text-red-800 border border-red-200">
Can Close
</Badge>
)}
</div>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
{/* Participants & Access */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Eye className="w-5 h-5" />
Participants & Access Control
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 p-3 bg-gray-50 rounded-lg">
<div>
<Label className="font-semibold">Comments</Label>
<p className="text-sm text-gray-700 mt-1">{formData.allowComments ? 'Allowed' : 'Disabled'}</p>
</div>
<div>
<Label className="font-semibold">Document Upload</Label>
<p className="text-sm text-gray-700 mt-1">{formData.allowDocumentUpload ? 'Allowed' : 'Disabled'}</p>
</div>
</div>
{/* Spectators */}
{formData.spectators.length > 0 && (
<div>
<Label className="font-semibold text-sm">Spectators ({formData.spectators.length})</Label>
<div className="flex flex-wrap gap-2 mt-2">
{formData.spectators.map((spectator) => (
<Badge key={spectator.id} variant="outline" className="text-xs">
{spectator.name} ({spectator.email})
</Badge>
))}
</div>
<p className="text-xs text-gray-600 mt-2">View-only access with commenting privileges</p>
</div>
)}
{/* CC List */}
{formData.ccList.length > 0 && (
<div>
<Label className="font-semibold text-sm">CC List ({formData.ccList.length})</Label>
<div className="flex flex-wrap gap-2 mt-2">
{formData.ccList.map((cc) => (
<Badge key={cc.id} variant="secondary" className="text-xs">
{cc.name} ({cc.email})
</Badge>
))}
</div>
<p className="text-xs text-gray-600 mt-2">Will receive email notifications about request updates</p>
</div>
)}
</CardContent>
</Card>
{/* Documents & Attachments */}
{formData.documents.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Upload className="w-5 h-5" />
Documents & Attachments
</CardTitle>
<CardDescription>
{formData.documents.length} document{formData.documents.length !== 1 ? 's' : ''} attached to this request
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{formData.documents.map((doc, index) => (
<div key={index} className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg border">
<FileText className="w-5 h-5 text-gray-500 flex-shrink-0" />
<div className="flex-1 min-w-0">
<p className="font-medium text-sm truncate">{doc.name}</p>
<div className="flex items-center gap-3 text-xs text-gray-500 mt-1">
<span>{(doc.size / (1024 * 1024)).toFixed(2)} MB</span>
<span>•</span>
<span>{doc.type || 'Unknown type'}</span>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Final Confirmation */}
<Card className="border-2 border-blue-200 bg-blue-50/50">
<CardContent className="pt-6">
<div className="flex items-start gap-4">
<CheckCircle className="w-6 h-6 text-blue-600 mt-1 flex-shrink-0" />
<div className="flex-1">
<h3 className="font-semibold text-blue-900 mb-2">Ready to Submit Request</h3>
<p className="text-sm text-blue-700 mb-4">
Once submitted, your request will enter the approval workflow and notifications will be sent to all relevant participants.
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
<div>
<span className="text-blue-700">Request Type:</span>
<p className="font-medium text-blue-900">{selectedTemplate?.name}</p>
</div>
<div>
<span className="text-blue-700">Approval Levels:</span>
<p className="font-medium text-blue-900">{formData.approverCount || 1}</p>
</div>
<div>
<span className="text-blue-700">Documents:</span>
<p className="font-medium text-blue-900">{formData.documents.length} attached</p>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</motion.div>
);
default:
return null;
}
};
return (
<div className="h-full flex flex-col bg-gradient-to-br from-gray-50 to-white">
{/* Header */}
<div className="bg-white border-b border-gray-200 px-6 py-4 flex-shrink-0">
<div className="flex items-center justify-between max-w-6xl mx-auto">
<div className="flex items-center gap-4">
<Button variant="ghost" size="icon" onClick={onBack} className="shrink-0">
<ArrowLeft className="h-5 w-5" />
</Button>
<div>
<h1 className="text-2xl font-bold text-gray-900">Create New Request</h1>
<p className="text-gray-600">
Step {currentStep} of {totalSteps}: {STEP_NAMES[currentStep - 1]}
</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className="text-sm font-medium text-gray-900">{Math.round((currentStep / totalSteps) * 100)}% Complete</p>
<p className="text-xs text-gray-600">{totalSteps - currentStep} steps remaining</p>
</div>
</div>
</div>
</div>
{/* Progress Bar */}
<div className="bg-white border-b border-gray-200 px-6 py-3 flex-shrink-0">
<div className="max-w-6xl mx-auto">
<div className="flex items-center justify-between mb-2">
{STEP_NAMES.map((_, index) => (
<div key={index} className="flex items-center">
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold ${
index + 1 < currentStep
? 'bg-green-600 text-white'
: index + 1 === currentStep
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-600'
}`}>
{index + 1 < currentStep ? (
<Check className="w-4 h-4" />
) : (
index + 1
)}
</div>
{index < STEP_NAMES.length - 1 && (
<div className={`w-12 lg:w-16 h-1 mx-2 ${
index + 1 < currentStep ? 'bg-green-600' : 'bg-gray-200'
}`} />
)}
</div>
))}
</div>
<div className="hidden lg:flex justify-between text-xs text-gray-600 mt-2">
{STEP_NAMES.map((step, index) => (
<span key={index} className={`${
index + 1 === currentStep ? 'font-semibold text-blue-600' : ''
}`}>
{step}
</span>
))}
</div>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto">
<div className="max-w-6xl mx-auto p-6">
<AnimatePresence mode="wait">
{renderStepContent()}
</AnimatePresence>
</div>
</div>
{/* Footer */}
<div className="bg-white border-t border-gray-200 px-6 py-4 flex-shrink-0">
<div className="flex justify-between items-center max-w-6xl mx-auto">
<Button
variant="outline"
onClick={prevStep}
disabled={currentStep === 1}
size="lg"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Previous
</Button>
<div className="flex gap-3">
<Button variant="outline" onClick={onBack} size="lg">
Save Draft
</Button>
{currentStep === totalSteps ? (
<Button
onClick={handleSubmit}
disabled={!isStepValid()}
size="lg"
className="bg-green-600 hover:bg-green-700 px-8"
>
<Rocket className="h-4 w-4 mr-2" />
Submit Request
</Button>
) : (
<Button
onClick={() => {
console.log('Next button clicked!');
console.log('Current step:', currentStep);
console.log('Is step valid:', isStepValid());
console.log('Form data:', formData);
nextStep();
}}
disabled={!isStepValid()}
size="lg"
className="px-8"
>
Next Step
<ArrowRight className="h-4 w-4 ml-2" />
</Button>
)}
</div>
</div>
</div>
{/* Template Selection Modal */}
<TemplateSelectionModal
open={showTemplateModal}
onClose={() => setShowTemplateModal(false)}
onSelectTemplate={handleTemplateSelection}
/>
</div>
);
}