449 lines
16 KiB
TypeScript
449 lines
16 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import { Input } from '../ui/input'
|
|
import { Button } from '../ui/button'
|
|
import { Label } from '../ui/label'
|
|
import { toast } from 'sonner'
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
|
import { Textarea } from '../ui/textarea'
|
|
import { Checkbox } from '../ui/checkbox'
|
|
|
|
interface CreateVendorFormProps {
|
|
onSuccess?: (newVendorId?: string) => void
|
|
}
|
|
|
|
const VENDOR_TYPES = [
|
|
'VENUE', 'CATERER', 'PHOTOGRAPHER', 'VIDEOGRAPHER', 'FLORIST', 'BAKERY',
|
|
'DJ', 'BAND', 'OFFICIANT', 'HAIR_MAKEUP', 'TRANSPORTATION', 'RENTALS',
|
|
'DECOR', 'PLANNER', 'STATIONERY', 'OTHER'
|
|
] as const
|
|
|
|
const VENDOR_STATUSES = [
|
|
'RESEARCHING', 'CONTACTING', 'RESPONDED', 'PROPOSAL_RECEIVED',
|
|
'NEGOTIATING', 'CONTRACT_SENT', 'CONTRACT_SIGNED', 'DECLINED', 'BACKUP'
|
|
] as const
|
|
|
|
|
|
const defaultFormValues = {
|
|
name: '',
|
|
type: '' as typeof VENDOR_TYPES[number],
|
|
description: '',
|
|
website: '',
|
|
|
|
contactPerson: '',
|
|
email: '',
|
|
phone: '',
|
|
|
|
street: '',
|
|
city: '',
|
|
state: '',
|
|
postalCode: '',
|
|
country: 'United States',
|
|
|
|
status: 'CONTACTING' as typeof VENDOR_STATUSES[number],
|
|
isBooked: false,
|
|
bookedDate: '',
|
|
|
|
quotedPrice: '',
|
|
finalCost: '',
|
|
depositPaid: '',
|
|
depositDueDate: '',
|
|
finalPaymentDue: '',
|
|
paymentNotes: '',
|
|
|
|
notes: '',
|
|
contractUrl: '',
|
|
proposalUrl: '',
|
|
}
|
|
|
|
export default function CreateVendorForm({ onSuccess }: CreateVendorFormProps) {
|
|
const [formData, setFormData] = useState(defaultFormValues)
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
|
|
const { name, value, type } = e.target
|
|
setFormData(prev => ({
|
|
...prev,
|
|
[name]: type === 'number' ? parseFloat(value) || 0 : value
|
|
}))
|
|
}
|
|
|
|
function handleSelectChange(name: string, value: string) {
|
|
setFormData(prev => ({ ...prev, [name]: value }))
|
|
}
|
|
|
|
function handleCheckboxChange(name: string, checked: boolean) {
|
|
setFormData(prev => ({ ...prev, [name]: checked }))
|
|
}
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault()
|
|
|
|
if (!formData.name || !formData.type) {
|
|
toast.error('Please fill in required fields (Name and Type)')
|
|
return
|
|
}
|
|
|
|
setLoading(true)
|
|
try {
|
|
const res = await fetch('/api/vendors/create', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
...formData,
|
|
quotedPrice: formData.quotedPrice ? parseFloat(formData.quotedPrice) : null,
|
|
finalCost: formData.finalCost ? parseFloat(formData.finalCost) : null,
|
|
depositPaid: formData.depositPaid ? parseFloat(formData.depositPaid) : null,
|
|
bookedDate: formData.bookedDate || null,
|
|
depositDueDate: formData.depositDueDate || null,
|
|
finalPaymentDue: formData.finalPaymentDue || null,
|
|
})
|
|
})
|
|
|
|
if (!res.ok) {
|
|
const error = await res.json()
|
|
throw new Error(error.message || 'Failed to create vendor')
|
|
}
|
|
|
|
const data = await res.json()
|
|
const newVendorId = data?.id
|
|
|
|
toast.success('Vendor created successfully!')
|
|
setFormData(defaultFormValues)
|
|
|
|
if (onSuccess) onSuccess(newVendorId)
|
|
} catch (err) {
|
|
console.error(err)
|
|
toast.error(err instanceof Error ? err.message : 'Something went wrong')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* Basic Information */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-semibold">Basic Information</h3>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="name">
|
|
Vendor Name *
|
|
</Label>
|
|
<Input
|
|
id="name"
|
|
name="name"
|
|
value={formData.name}
|
|
onChange={handleChange}
|
|
required
|
|
placeholder="Acme Wedding Services"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="type">
|
|
Vendor Type *
|
|
</Label>
|
|
<Select
|
|
value={formData.type}
|
|
onValueChange={(value) => handleSelectChange('type', value)}
|
|
required
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select vendor type" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{VENDOR_TYPES.map(type => (
|
|
<SelectItem key={type} value={type}>
|
|
{type.charAt(0) + type.slice(1).toLowerCase()}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="description">
|
|
Description
|
|
</Label>
|
|
<Textarea
|
|
id="description"
|
|
name="description"
|
|
value={formData.description}
|
|
onChange={handleChange}
|
|
placeholder="Brief description of services..."
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="website">
|
|
Website
|
|
</Label>
|
|
<Input
|
|
id="website"
|
|
name="website"
|
|
value={formData.website}
|
|
onChange={handleChange}
|
|
type="url"
|
|
placeholder="https://example.com"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Contact Information */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-semibold">Contact Information</h3>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="contactPerson">
|
|
Contact Person
|
|
</Label>
|
|
<Input
|
|
id="contactPerson"
|
|
name="contactPerson"
|
|
value={formData.contactPerson}
|
|
onChange={handleChange}
|
|
placeholder="John Doe"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="email">
|
|
Email
|
|
</Label>
|
|
<Input
|
|
id="email"
|
|
name="email"
|
|
value={formData.email}
|
|
onChange={handleChange}
|
|
type="email"
|
|
placeholder="contact@example.com"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="phone">
|
|
Phone
|
|
</Label>
|
|
<Input
|
|
id="phone"
|
|
name="phone"
|
|
value={formData.phone}
|
|
onChange={handleChange}
|
|
type="tel"
|
|
placeholder="(123) 456-7890"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="status">
|
|
Status
|
|
</Label>
|
|
<Select
|
|
value={formData.status}
|
|
onValueChange={(value) => handleSelectChange('status', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{VENDOR_STATUSES.map(status => (
|
|
<SelectItem key={status} value={status}>
|
|
{status.charAt(0) + status.slice(1).toLowerCase().replace('_', ' ')}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Address Section */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-semibold">Address</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-1">
|
|
<Label htmlFor="street">
|
|
Street Address
|
|
</Label>
|
|
<Input
|
|
id="street"
|
|
name="street"
|
|
value={formData.street}
|
|
onChange={handleChange}
|
|
placeholder="123 Main St"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="city">
|
|
City
|
|
</Label>
|
|
<Input
|
|
id="city"
|
|
name="city"
|
|
value={formData.city}
|
|
onChange={handleChange}
|
|
placeholder="Anytown"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="state">
|
|
State
|
|
</Label>
|
|
<Input
|
|
id="state"
|
|
name="state"
|
|
value={formData.state}
|
|
onChange={handleChange}
|
|
placeholder="CA"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="postalCode">
|
|
Postal Code
|
|
</Label>
|
|
<Input
|
|
id="postalCode"
|
|
name="postalCode"
|
|
value={formData.postalCode}
|
|
onChange={handleChange}
|
|
placeholder="12345"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="country">
|
|
Country
|
|
</Label>
|
|
<Input
|
|
id="country"
|
|
name="country"
|
|
value={formData.country}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Financial Information */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-semibold">Financial Information</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-1">
|
|
<Label htmlFor="quotedPrice">
|
|
Quoted Price ($)
|
|
</Label>
|
|
<Input
|
|
id="quotedPrice"
|
|
name="quotedPrice"
|
|
value={formData.quotedPrice}
|
|
onChange={handleChange}
|
|
type="number"
|
|
step="0.01"
|
|
placeholder="0.00"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="finalCost">
|
|
Final Cost ($)
|
|
</Label>
|
|
<Input
|
|
id="finalCost"
|
|
name="finalCost"
|
|
value={formData.finalCost}
|
|
onChange={handleChange}
|
|
type="number"
|
|
step="0.01"
|
|
placeholder="0.00"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="depositPaid">
|
|
Deposit Paid ($)
|
|
</Label>
|
|
<Input
|
|
id="depositPaid"
|
|
name="depositPaid"
|
|
value={formData.depositPaid}
|
|
onChange={handleChange}
|
|
type="number"
|
|
step="0.01"
|
|
placeholder="0.00"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Additional Information */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-semibold">Additional Information</h3>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="notes">
|
|
Notes
|
|
</Label>
|
|
<Textarea
|
|
id="notes"
|
|
name="notes"
|
|
value={formData.notes}
|
|
onChange={handleChange}
|
|
placeholder="Any additional notes..."
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="contractUrl">
|
|
Contract URL
|
|
</Label>
|
|
<Input
|
|
id="contractUrl"
|
|
name="contractUrl"
|
|
value={formData.contractUrl}
|
|
onChange={handleChange}
|
|
type="url"
|
|
placeholder="https://drive.google.com/..."
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label htmlFor="proposalUrl">
|
|
Proposal URL
|
|
</Label>
|
|
<Input
|
|
id="proposalUrl"
|
|
name="proposalUrl"
|
|
value={formData.proposalUrl}
|
|
onChange={handleChange}
|
|
type="url"
|
|
placeholder="https://drive.google.com/..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id="isBooked"
|
|
checked={formData.isBooked}
|
|
onCheckedChange={(checked) => handleCheckboxChange('isBooked', checked as boolean)}
|
|
/>
|
|
<Label htmlFor="isBooked" className="cursor-pointer">
|
|
Vendor is booked
|
|
</Label>
|
|
</div>
|
|
|
|
<Button type="submit" disabled={loading} className="w-full">
|
|
{loading ? 'Creating Vendor...' : 'Create Vendor'}
|
|
</Button>
|
|
</form>
|
|
)
|
|
}
|