|
import React, { useState } from 'react'; |
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; |
|
import { Input } from './ui/input'; |
|
import { Button } from './ui/button'; |
|
import { |
|
Select, |
|
SelectContent, |
|
SelectItem, |
|
SelectTrigger, |
|
SelectValue, |
|
} from './ui/select'; |
|
import { Textarea } from './ui/textarea'; |
|
import { AlertCircle } from 'lucide-react'; |
|
import { useAuth } from '../services/AuthContext'; |
|
|
|
const OpportunityForm = () => { |
|
|
|
const generateId = () => `opp-${crypto.randomUUID()}`; |
|
|
|
|
|
const initialFormState = { |
|
opportunityId: generateId(), |
|
customerName: '', |
|
opportunityName: '', |
|
opportunityState: '', |
|
opportunityDescription: '', |
|
opportunityValue: '', |
|
closeDate: '', |
|
customerContact: '', |
|
customerContactRole: '', |
|
activity: '', |
|
nextSteps: '' |
|
}; |
|
|
|
const [formData, setFormData] = useState(initialFormState); |
|
const [isSubmitting, setIsSubmitting] = useState(false); |
|
const [errors, setErrors] = useState({}); |
|
const { token } = useAuth(); |
|
|
|
const handleChange = (e) => { |
|
const { name, value } = e.target; |
|
setFormData(prev => ({ |
|
...prev, |
|
[name]: value |
|
})); |
|
|
|
if (errors[name]) { |
|
setErrors(prev => ({ ...prev, [name]: '' })); |
|
} |
|
}; |
|
|
|
const handleSelectChange = (value) => { |
|
setFormData(prev => ({ |
|
...prev, |
|
opportunityState: value |
|
})); |
|
if (errors.opportunityState) { |
|
setErrors(prev => ({ ...prev, opportunityState: '' })); |
|
} |
|
}; |
|
|
|
const validateForm = () => { |
|
const newErrors = {}; |
|
if (!formData.customerName.trim()) newErrors.customerName = 'Customer name is required'; |
|
if (!formData.opportunityName.trim()) newErrors.opportunityName = 'Opportunity name is required'; |
|
if (!formData.opportunityState) newErrors.opportunityState = 'Opportunity state is required'; |
|
if (!formData.opportunityDescription.trim()) newErrors.opportunityDescription = 'Description is required'; |
|
if (!formData.opportunityValue) newErrors.opportunityValue = 'Value is required'; |
|
if (!formData.closeDate) newErrors.closeDate = 'Close date is required'; |
|
if (!formData.customerContact.trim()) newErrors.customerContact = 'Customer contact is required'; |
|
if (!formData.customerContactRole.trim()) newErrors.customerContactRole = 'Contact role is required'; |
|
if (!formData.activity.trim()) newErrors.activity = 'Activity is required'; |
|
if (!formData.nextSteps.trim()) newErrors.nextSteps = 'Next steps are required'; |
|
|
|
setErrors(newErrors); |
|
return Object.keys(newErrors).length === 0; |
|
}; |
|
|
|
const handleSubmit = async (e) => { |
|
e.preventDefault(); |
|
|
|
if (!validateForm()) { |
|
return; |
|
} |
|
|
|
setIsSubmitting(true); |
|
|
|
try { |
|
const response = await fetch('/api/save_opportunity', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
'Authorization': `Bearer ${token}`, |
|
}, |
|
body: JSON.stringify(formData), |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error('Submission failed'); |
|
} |
|
|
|
handleClear(); |
|
alert('Form submitted successfully!'); |
|
} catch (error) { |
|
alert('Error submitting form: ' + error.message); |
|
} finally { |
|
setIsSubmitting(false); |
|
} |
|
}; |
|
|
|
const handleClear = () => { |
|
setFormData({ |
|
...initialFormState, |
|
opportunityId: generateId() |
|
}); |
|
setErrors({}); |
|
}; |
|
|
|
const FormLabel = ({ children, required }) => ( |
|
<div className="flex gap-1 text-sm font-medium leading-none mb-2"> |
|
{children} |
|
{required && <span className="text-red-500">*</span>} |
|
</div> |
|
); |
|
|
|
const ErrorMessage = ({ message }) => message ? ( |
|
<div className="flex items-center gap-1 text-red-500 text-sm mt-1"> |
|
<AlertCircle className="w-4 h-4" /> |
|
<span>{message}</span> |
|
</div> |
|
) : null; |
|
|
|
return ( |
|
<Card className="w-full max-w-2xl mx-auto"> |
|
<CardHeader> |
|
<CardTitle>New Opportunity</CardTitle> |
|
</CardHeader> |
|
<CardContent> |
|
<form onSubmit={handleSubmit} className="space-y-4"> |
|
<input |
|
type="hidden" |
|
name="opportunityId" |
|
value={formData.opportunityId} |
|
/> |
|
|
|
<div> |
|
<FormLabel required>Customer Name</FormLabel> |
|
<Input |
|
name="customerName" |
|
value={formData.customerName} |
|
onChange={handleChange} |
|
className={errors.customerName ? 'border-red-500' : ''} |
|
/> |
|
<ErrorMessage message={errors.customerName} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Opportunity Name</FormLabel> |
|
<Input |
|
name="opportunityName" |
|
value={formData.opportunityName} |
|
onChange={handleChange} |
|
className={errors.opportunityName ? 'border-red-500' : ''} |
|
/> |
|
<ErrorMessage message={errors.opportunityName} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Opportunity State</FormLabel> |
|
<Select |
|
value={formData.opportunityState} |
|
onValueChange={handleSelectChange} |
|
> |
|
<SelectTrigger className={errors.opportunityState ? 'border-red-500' : ''}> |
|
<SelectValue placeholder="Select state" /> |
|
</SelectTrigger> |
|
<SelectContent> |
|
<SelectItem value="proposal">Proposal</SelectItem> |
|
<SelectItem value="negotiation">Negotiation</SelectItem> |
|
</SelectContent> |
|
</Select> |
|
<ErrorMessage message={errors.opportunityState} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Opportunity Description</FormLabel> |
|
<Textarea |
|
name="opportunityDescription" |
|
value={formData.opportunityDescription} |
|
onChange={handleChange} |
|
className={`h-24 ${errors.opportunityDescription ? 'border-red-500' : ''}`} |
|
/> |
|
<ErrorMessage message={errors.opportunityDescription} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Opportunity Value (USD)</FormLabel> |
|
<Input |
|
type="number" |
|
name="opportunityValue" |
|
value={formData.opportunityValue} |
|
onChange={handleChange} |
|
min="0" |
|
step="0.01" |
|
className={errors.opportunityValue ? 'border-red-500' : ''} |
|
/> |
|
<ErrorMessage message={errors.opportunityValue} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Close Date</FormLabel> |
|
<Input |
|
type="date" |
|
name="closeDate" |
|
value={formData.closeDate} |
|
onChange={handleChange} |
|
className={errors.closeDate ? 'border-red-500' : ''} |
|
/> |
|
<ErrorMessage message={errors.closeDate} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Customer Contact</FormLabel> |
|
<Input |
|
name="customerContact" |
|
value={formData.customerContact} |
|
onChange={handleChange} |
|
className={errors.customerContact ? 'border-red-500' : ''} |
|
/> |
|
<ErrorMessage message={errors.customerContact} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Customer Contact Role</FormLabel> |
|
<Input |
|
name="customerContactRole" |
|
value={formData.customerContactRole} |
|
onChange={handleChange} |
|
className={errors.customerContactRole ? 'border-red-500' : ''} |
|
/> |
|
<ErrorMessage message={errors.customerContactRole} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Activity</FormLabel> |
|
<Textarea |
|
name="activity" |
|
value={formData.activity} |
|
onChange={handleChange} |
|
className={`h-24 ${errors.activity ? 'border-red-500' : ''}`} |
|
/> |
|
<ErrorMessage message={errors.activity} /> |
|
</div> |
|
|
|
<div> |
|
<FormLabel required>Next Steps</FormLabel> |
|
<Textarea |
|
name="nextSteps" |
|
value={formData.nextSteps} |
|
onChange={handleChange} |
|
className={`h-24 ${errors.nextSteps ? 'border-red-500' : ''}`} |
|
/> |
|
<ErrorMessage message={errors.nextSteps} /> |
|
</div> |
|
|
|
<div className="flex gap-4 justify-end"> |
|
<Button |
|
type="button" |
|
variant="outline" |
|
onClick={handleClear} |
|
disabled={isSubmitting} |
|
> |
|
Clear |
|
</Button> |
|
<Button |
|
type="submit" |
|
disabled={isSubmitting} |
|
> |
|
{isSubmitting ? 'Submitting...' : 'Submit'} |
|
</Button> |
|
</div> |
|
</form> |
|
</CardContent> |
|
</Card> |
|
); |
|
}; |
|
|
|
export default OpportunityForm; |