229 lines
11 KiB
TypeScript
229 lines
11 KiB
TypeScript
import { motion } from 'framer-motion';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { RichTextEditor } from '@/components/ui/rich-text-editor';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { FileText, Zap, Clock } from 'lucide-react';
|
|
import { FormData, RequestTemplate } from '@/hooks/useCreateRequestForm';
|
|
|
|
interface BasicInformationStepProps {
|
|
formData: FormData;
|
|
selectedTemplate: RequestTemplate | null;
|
|
updateFormData: (field: keyof FormData, value: any) => void;
|
|
}
|
|
|
|
/**
|
|
* Component: BasicInformationStep
|
|
*
|
|
* Purpose: Step 2 - Basic information form (title, description, priority)
|
|
*
|
|
* Features:
|
|
* - Request title and description inputs
|
|
* - Priority selection (Express/Standard)
|
|
* - Template-specific additional fields
|
|
* - Test IDs for testing
|
|
*/
|
|
export function BasicInformationStep({
|
|
formData,
|
|
selectedTemplate,
|
|
updateFormData
|
|
}: BasicInformationStepProps) {
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
className="space-y-6"
|
|
data-testid="basic-information-step"
|
|
>
|
|
<div className="text-center mb-8" data-testid="basic-information-header">
|
|
<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" data-testid="basic-information-title">
|
|
Basic Information
|
|
</h2>
|
|
<p className="text-gray-600" data-testid="basic-information-description">
|
|
Provide the essential details for your {selectedTemplate?.name || 'request'}.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="max-w-2xl mx-auto space-y-6" data-testid="basic-information-form">
|
|
<div data-testid="basic-information-title-field">
|
|
<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"
|
|
data-testid="basic-information-title-input"
|
|
/>
|
|
</div>
|
|
|
|
<div data-testid="basic-information-description-field">
|
|
<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.
|
|
<span className="block mt-1 text-xs text-blue-600">
|
|
💡 Tip: You can paste formatted content (lists, tables) and the formatting will be preserved.
|
|
</span>
|
|
</p>
|
|
<RichTextEditor
|
|
value={formData.description || ''}
|
|
onChange={(html) => updateFormData('description', html)}
|
|
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-within:border-blue-500 bg-white shadow-sm"
|
|
minHeight="120px"
|
|
data-testid="basic-information-description-textarea"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6" data-testid="basic-information-priority-section">
|
|
<div data-testid="basic-information-priority-field">
|
|
<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) => updateFormData('priority', value)}
|
|
data-testid="basic-information-priority-radio-group"
|
|
>
|
|
<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')}
|
|
data-testid="basic-information-priority-express-option"
|
|
>
|
|
<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')}
|
|
data-testid="basic-information-priority-standard-option"
|
|
>
|
|
<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>
|
|
|
|
{/* Template-specific fields */}
|
|
{(selectedTemplate?.fields.amount || selectedTemplate?.fields.vendor || selectedTemplate?.fields.timeline || selectedTemplate?.fields.impact) && (
|
|
<div className="border-t pt-6" data-testid="basic-information-additional-details">
|
|
<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" data-testid="basic-information-amount-field">
|
|
<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"
|
|
data-testid="basic-information-amount-input"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label className="text-base font-semibold">Currency</Label>
|
|
<Select
|
|
value={formData.currency}
|
|
onValueChange={(value) => updateFormData('currency', value)}
|
|
data-testid="basic-information-currency-select"
|
|
>
|
|
<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 data-testid="basic-information-vendor-field">
|
|
<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"
|
|
data-testid="basic-information-vendor-input"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div data-testid="basic-information-cost-center-field">
|
|
<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"
|
|
data-testid="basic-information-cost-center-input"
|
|
/>
|
|
</div>
|
|
<div data-testid="basic-information-project-field">
|
|
<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"
|
|
data-testid="basic-information-project-input"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|