Re_Figma_Code/src/components/modals/NewRequestModal.tsx

597 lines
22 KiB
TypeScript

import { useState } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/dialog';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { Label } from '../ui/label';
import { Textarea } from '../ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { Badge } from '../ui/badge';
import { Avatar, AvatarFallback } from '../ui/avatar';
import { Progress } from '../ui/progress';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Switch } from '../ui/switch';
import { Calendar } from '../ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import {
ArrowLeft,
ArrowRight,
Calendar as CalendarIcon,
Upload,
X,
FileText,
Check,
Users
} from 'lucide-react';
import { format } from 'date-fns';
interface NewRequestModalProps {
open: boolean;
onClose: () => void;
onSubmit?: (requestData: any) => void;
}
export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProps) {
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState({
title: '',
description: '',
priority: '',
slaEndDate: undefined as Date | undefined,
approvers: [] as any[],
workflowType: 'sequential',
spectators: [] as any[],
documents: [] as File[]
});
const totalSteps = 5;
// Mock users for selection
const availableUsers = [
{ id: '1', name: 'Mike Johnson', role: 'Team Lead', avatar: 'MJ' },
{ id: '2', name: 'Lisa Wong', role: 'Finance Manager', avatar: 'LW' },
{ id: '3', name: 'David Kumar', role: 'Department Head', avatar: 'DK' },
{ id: '4', name: 'Anna Smith', role: 'Marketing Coordinator', avatar: 'AS' },
{ id: '5', name: 'John Doe', role: 'Budget Analyst', avatar: 'JD' }
];
const updateFormData = (field: string, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const addApprover = (user: any) => {
if (!formData.approvers.find(a => a.id === user.id)) {
updateFormData('approvers', [...formData.approvers, user]);
}
};
const removeApprover = (userId: string) => {
updateFormData('approvers', formData.approvers.filter(a => a.id !== userId));
};
const addSpectator = (user: any) => {
if (!formData.spectators.find(s => s.id === user.id)) {
updateFormData('spectators', [...formData.spectators, user]);
}
};
const removeSpectator = (userId: string) => {
updateFormData('spectators', formData.spectators.filter(s => s.id !== userId));
};
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
updateFormData('documents', [...formData.documents, ...files]);
};
const removeDocument = (index: number) => {
const newDocs = formData.documents.filter((_, i) => i !== index);
updateFormData('documents', newDocs);
};
const isStepValid = () => {
switch (currentStep) {
case 1:
return formData.title && formData.description && formData.priority && formData.slaEndDate;
case 2:
return formData.approvers.length > 0;
case 3:
return true; // Spectators are optional
case 4:
return true; // Documents are optional
case 5:
return true; // Review step
default:
return false;
}
};
const nextStep = () => {
if (currentStep < totalSteps && isStepValid()) {
setCurrentStep(currentStep + 1);
}
};
const prevStep = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
const handleSubmit = () => {
if (isStepValid()) {
onSubmit?.(formData);
onClose();
// Reset form
setCurrentStep(1);
setFormData({
title: '',
description: '',
priority: '',
slaEndDate: undefined,
approvers: [],
workflowType: 'sequential',
spectators: [],
documents: []
});
}
};
const renderStepContent = () => {
switch (currentStep) {
case 1:
return (
<div className="space-y-4">
<div>
<Label htmlFor="title">Request Title *</Label>
<Input
id="title"
placeholder="Enter a descriptive title for your request"
value={formData.title}
onChange={(e) => updateFormData('title', e.target.value)}
/>
</div>
<div>
<Label htmlFor="description">Description *</Label>
<Textarea
id="description"
placeholder="Provide detailed information about your request"
className="min-h-[120px]"
value={formData.description}
onChange={(e) => updateFormData('description', e.target.value)}
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<Label>Priority *</Label>
<Select value={formData.priority} onValueChange={(value) => updateFormData('priority', value)}>
<SelectTrigger>
<SelectValue placeholder="Select priority" />
</SelectTrigger>
<SelectContent>
<SelectItem value="high">High Priority</SelectItem>
<SelectItem value="medium">Medium Priority</SelectItem>
<SelectItem value="low">Low Priority</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>SLA End Date *</Label>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-full justify-start text-left">
<CalendarIcon className="mr-2 h-4 w-4" />
{formData.slaEndDate ? format(formData.slaEndDate, 'PPP') : 'Pick a date'}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={formData.slaEndDate}
onSelect={(date) => updateFormData('slaEndDate', date)}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
</div>
</div>
);
case 2:
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<Label>Workflow Type</Label>
<div className="flex items-center space-x-2">
<Label htmlFor="workflow-sequential" className="text-sm">Sequential</Label>
<Switch
id="workflow-sequential"
checked={formData.workflowType === 'parallel'}
onCheckedChange={(checked) => updateFormData('workflowType', checked ? 'parallel' : 'sequential')}
/>
<Label htmlFor="workflow-sequential" className="text-sm">Parallel</Label>
</div>
</div>
<div className="p-3 bg-muted/50 rounded-lg text-sm text-muted-foreground">
{formData.workflowType === 'sequential'
? 'Approvers will review the request one after another in the order you specify.'
: 'All approvers will review the request simultaneously.'
}
</div>
<div>
<Label>Add Approvers *</Label>
<Select onValueChange={(userId) => {
const user = availableUsers.find(u => u.id === userId);
if (user) addApprover(user);
}}>
<SelectTrigger>
<SelectValue placeholder="Select users to add as approvers" />
</SelectTrigger>
<SelectContent>
{availableUsers
.filter(user => !formData.approvers.find(a => a.id === user.id))
.map(user => (
<SelectItem key={user.id} value={user.id}>
<div className="flex items-center gap-2">
<Avatar className="h-6 w-6">
<AvatarFallback className="bg-re-green text-white text-xs">
{user.avatar}
</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-muted-foreground">{user.role}</p>
</div>
</div>
</SelectItem>
))
}
</SelectContent>
</Select>
</div>
{formData.approvers.length > 0 && (
<div className="space-y-2">
<Label>Selected Approvers ({formData.approvers.length})</Label>
<div className="space-y-2">
{formData.approvers.map((approver, index) => (
<div key={approver.id} className="flex items-center justify-between p-2 bg-muted/30 rounded-lg">
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
{formData.workflowType === 'sequential' && (
<Badge variant="outline" className="text-xs">
{index + 1}
</Badge>
)}
<Avatar className="h-8 w-8">
<AvatarFallback className="bg-re-green text-white text-xs">
{approver.avatar}
</AvatarFallback>
</Avatar>
</div>
<div>
<p className="font-medium text-sm">{approver.name}</p>
<p className="text-xs text-muted-foreground">{approver.role}</p>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeApprover(approver.id)}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
)}
</div>
);
case 3:
return (
<div className="space-y-4">
<div>
<Label>Add Spectators (Optional)</Label>
<p className="text-sm text-muted-foreground mb-2">
Spectators can view the request and participate in work notes but cannot approve or edit.
</p>
<Select onValueChange={(userId) => {
const user = availableUsers.find(u => u.id === userId);
if (user) addSpectator(user);
}}>
<SelectTrigger>
<SelectValue placeholder="Select users to add as spectators" />
</SelectTrigger>
<SelectContent>
{availableUsers
.filter(user =>
!formData.spectators.find(s => s.id === user.id) &&
!formData.approvers.find(a => a.id === user.id)
)
.map(user => (
<SelectItem key={user.id} value={user.id}>
<div className="flex items-center gap-2">
<Avatar className="h-6 w-6">
<AvatarFallback className="bg-re-light-green text-white text-xs">
{user.avatar}
</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-muted-foreground">{user.role}</p>
</div>
</div>
</SelectItem>
))
}
</SelectContent>
</Select>
</div>
{formData.spectators.length > 0 && (
<div className="space-y-2">
<Label>Selected Spectators ({formData.spectators.length})</Label>
<div className="space-y-2">
{formData.spectators.map((spectator) => (
<div key={spectator.id} className="flex items-center justify-between p-2 bg-muted/30 rounded-lg">
<div className="flex items-center gap-3">
<Avatar className="h-8 w-8">
<AvatarFallback className="bg-re-light-green text-white text-xs">
{spectator.avatar}
</AvatarFallback>
</Avatar>
<div>
<p className="font-medium text-sm">{spectator.name}</p>
<p className="text-xs text-muted-foreground">{spectator.role}</p>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeSpectator(spectator.id)}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
)}
</div>
);
case 4:
return (
<div className="space-y-4">
<div>
<Label>Upload Documents (Optional)</Label>
<p className="text-sm text-muted-foreground mb-2">
Attach supporting documents for your request. Maximum 10MB per file.
</p>
<div className="border-2 border-dashed border-border rounded-lg p-6 text-center">
<Upload className="h-8 w-8 mx-auto mb-2 text-muted-foreground" />
<p className="text-sm text-muted-foreground mb-2">
Drag and drop files here, or click to browse
</p>
<input
type="file"
multiple
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
onChange={handleFileUpload}
className="hidden"
id="file-upload"
/>
<Label htmlFor="file-upload" className="cursor-pointer">
<Button variant="outline" size="sm" type="button">
Browse Files
</Button>
</Label>
</div>
</div>
{formData.documents.length > 0 && (
<div className="space-y-2">
<Label>Uploaded Documents ({formData.documents.length})</Label>
<div className="space-y-2">
{formData.documents.map((file, index) => (
<div key={index} className="flex items-center justify-between p-2 bg-muted/30 rounded-lg">
<div className="flex items-center gap-3">
<FileText className="h-6 w-6 text-muted-foreground" />
<div>
<p className="font-medium text-sm">{file.name}</p>
<p className="text-xs text-muted-foreground">
{(file.size / (1024 * 1024)).toFixed(2)} MB
</p>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeDocument(index)}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
)}
</div>
);
case 5:
return (
<div className="space-y-6">
<div>
<h3 className="font-semibold mb-2">Review Your Request</h3>
<p className="text-sm text-muted-foreground">
Please review all details before submitting your request.
</p>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
Basic Information
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div>
<Label>Title</Label>
<p className="text-sm">{formData.title}</p>
</div>
<div>
<Label>Description</Label>
<p className="text-sm text-muted-foreground">{formData.description}</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Priority</Label>
<Badge className="mt-1">
{formData.priority}
</Badge>
</div>
<div>
<Label>SLA End Date</Label>
<p className="text-sm">
{formData.slaEndDate ? format(formData.slaEndDate, 'PPP') : 'Not set'}
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5" />
Workflow & Participants
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div>
<Label>Workflow Type</Label>
<Badge variant="outline" className="mt-1">
{formData.workflowType}
</Badge>
</div>
<div>
<Label>Approvers ({formData.approvers.length})</Label>
<div className="flex flex-wrap gap-2 mt-1">
{formData.approvers.map((approver, index) => (
<Badge key={approver.id} variant="secondary">
{formData.workflowType === 'sequential' && `${index + 1}. `}
{approver.name}
</Badge>
))}
</div>
</div>
{formData.spectators.length > 0 && (
<div>
<Label>Spectators ({formData.spectators.length})</Label>
<div className="flex flex-wrap gap-2 mt-1">
{formData.spectators.map((spectator) => (
<Badge key={spectator.id} variant="outline">
{spectator.name}
</Badge>
))}
</div>
</div>
)}
{formData.documents.length > 0 && (
<div>
<Label>Documents ({formData.documents.length})</Label>
<div className="flex flex-wrap gap-2 mt-1">
{formData.documents.map((doc, index) => (
<Badge key={index} variant="outline">
{doc.name}
</Badge>
))}
</div>
</div>
)}
</CardContent>
</Card>
</div>
);
default:
return null;
}
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Create New Request</DialogTitle>
<DialogDescription>
Step {currentStep} of {totalSteps}
</DialogDescription>
</DialogHeader>
{/* Progress Bar */}
<div className="space-y-2">
<Progress value={(currentStep / totalSteps) * 100} className="h-2" />
<div className="flex justify-between text-xs text-muted-foreground">
<span>Basics</span>
<span>Workflow</span>
<span>Participants</span>
<span>Documents</span>
<span>Review</span>
</div>
</div>
{/* Step Content */}
<div className="py-4">
{renderStepContent()}
</div>
{/* Navigation Buttons */}
<div className="flex justify-between">
<Button
variant="outline"
onClick={prevStep}
disabled={currentStep === 1}
>
<ArrowLeft className="h-4 w-4 mr-2" />
Back
</Button>
<div className="flex gap-2">
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
{currentStep === totalSteps ? (
<Button
onClick={handleSubmit}
disabled={!isStepValid()}
className="bg-re-green hover:bg-re-green/90"
>
<Check className="h-4 w-4 mr-2" />
Submit Request
</Button>
) : (
<Button
onClick={nextStep}
disabled={!isStepValid()}
className="bg-re-green hover:bg-re-green/90"
>
Next
<ArrowRight className="h-4 w-4 ml-2" />
</Button>
)}
</div>
</div>
</DialogContent>
</Dialog>
);
}