added idividual vendor pages

This commit is contained in:
2026-01-27 12:59:24 -05:00
parent 3aa9b6f325
commit aa2f30c086
11 changed files with 1212 additions and 19 deletions

441
components/vendor/EditVendorModal.tsx vendored Normal file
View File

@@ -0,0 +1,441 @@
// components/vendors/EditVendorModal.tsx
'use client'
import { useState } from 'react'
import { Vendor, VendorType, VendorStatus } from '@prisma/client'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
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 { Checkbox } from '@/components/ui/checkbox'
import { toast } from 'sonner'
interface EditVendorModalProps {
vendor: {
id: string
name: string
type: VendorType
description?: string | null
website?: string | null
contactPerson?: string | null
email?: string | null
phone?: string | null
address?: {
street: string
city: string
state: string
zip: number
} | null
status: VendorStatus
isBooked: boolean
bookedDate?: Date | null
quotedPrice?: number | null
finalCost?: number | null
depositPaid?: number | null
depositDueDate?: Date | null
finalPaymentDue?: Date | null
paymentNotes?: string | null
contractUrl?: string | null
proposalUrl?: string | null
notes?: string | null
}
isOpen: boolean
onClose: () => void
onSave: (updatedVendor: any) => Promise<void>
}
const VENDOR_TYPES = Object.values(VendorType)
const VENDOR_STATUSES = Object.values(VendorStatus)
export function EditVendorModal({ vendor, isOpen, onClose, onSave }: EditVendorModalProps) {
const [loading, setLoading] = useState(false)
const [formData, setFormData] = useState({
name: vendor.name,
type: vendor.type,
description: vendor.description || '',
website: vendor.website || '',
contactPerson: vendor.contactPerson || '',
email: vendor.email || '',
phone: vendor.phone || '',
street: vendor.address?.street || '',
city: vendor.address?.city || '',
state: vendor.address?.state || '',
postalCode: vendor.address?.zip?.toString() || '',
status: vendor.status,
isBooked: vendor.isBooked,
bookedDate: vendor.bookedDate ? new Date(vendor.bookedDate).toISOString().split('T')[0] : '',
quotedPrice: vendor.quotedPrice?.toString() || '',
finalCost: vendor.finalCost?.toString() || '',
depositPaid: vendor.depositPaid?.toString() || '',
depositDueDate: vendor.depositDueDate ? new Date(vendor.depositDueDate).toISOString().split('T')[0] : '',
finalPaymentDue: vendor.finalPaymentDue ? new Date(vendor.finalPaymentDue).toISOString().split('T')[0] : '',
paymentNotes: vendor.paymentNotes || '',
contractUrl: vendor.contractUrl || '',
proposalUrl: vendor.proposalUrl || '',
notes: vendor.notes || '',
})
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value, type } = e.target
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked : value
}))
}
const handleSelectChange = (name: string, value: string) => {
setFormData(prev => ({ ...prev, [name]: value }))
}
const handleCheckboxChange = (name: string, checked: boolean) => {
setFormData(prev => ({ ...prev, [name]: checked }))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
try {
const response = await fetch(`/api/vendors/${vendor.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
})
if (!response.ok) throw new Error('Failed to update vendor')
const updatedVendor = await response.json()
await onSave(updatedVendor)
toast.success('Vendor updated successfully!')
onClose()
} catch (error) {
console.error('Error updating vendor:', error)
toast.error('Failed to update vendor')
} finally {
setLoading(false)
}
}
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Edit Vendor</DialogTitle>
<DialogDescription>
Update the vendor information below
</DialogDescription>
</DialogHeader>
<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">
<div className="space-y-1">
<Label htmlFor="name">Vendor Name *</Label>
<Input
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
</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}
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"
/>
</div>
</div>
{/* Contact Information */}
<div className="space-y-4">
<div className="space-y-1">
<Label htmlFor="contactPerson">Contact Person</Label>
<Input
id="contactPerson"
name="contactPerson"
value={formData.contactPerson}
onChange={handleChange}
/>
</div>
<div className="space-y-1">
<Label htmlFor="email">Email</Label>
<Input
id="email"
name="email"
value={formData.email}
onChange={handleChange}
type="email"
/>
</div>
<div className="space-y-1">
<Label htmlFor="phone">Phone</Label>
<Input
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
type="tel"
/>
</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 (Optional)</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}
/>
</div>
<div className="space-y-1">
<Label htmlFor="city">City</Label>
<Input
id="city"
name="city"
value={formData.city}
onChange={handleChange}
/>
</div>
<div className="space-y-1">
<Label htmlFor="state">State</Label>
<Input
id="state"
name="state"
value={formData.state}
onChange={handleChange}
/>
</div>
<div className="space-y-1">
<Label htmlFor="postalCode">Postal Code</Label>
<Input
id="postalCode"
name="postalCode"
value={formData.postalCode}
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-3 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"
/>
</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"
/>
</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"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1">
<Label htmlFor="depositDueDate">Deposit Due Date</Label>
<Input
id="depositDueDate"
name="depositDueDate"
value={formData.depositDueDate}
onChange={handleChange}
type="date"
/>
</div>
<div className="space-y-1">
<Label htmlFor="finalPaymentDue">Final Payment Due</Label>
<Input
id="finalPaymentDue"
name="finalPaymentDue"
value={formData.finalPaymentDue}
onChange={handleChange}
type="date"
/>
</div>
</div>
<div className="space-y-1">
<Label htmlFor="paymentNotes">Payment Notes</Label>
<Textarea
id="paymentNotes"
name="paymentNotes"
value={formData.paymentNotes}
onChange={handleChange}
rows={2}
/>
</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}
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"
/>
</div>
<div className="space-y-1">
<Label htmlFor="proposalUrl">Proposal URL</Label>
<Input
id="proposalUrl"
name="proposalUrl"
value={formData.proposalUrl}
onChange={handleChange}
type="url"
/>
</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>
{formData.isBooked && (
<div className="space-y-1">
<Label htmlFor="bookedDate">Booked Date</Label>
<Input
id="bookedDate"
name="bookedDate"
value={formData.bookedDate}
onChange={handleChange}
type="date"
/>
</div>
)}
</div>
<div className="flex justify-end gap-3 pt-4">
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Saving...' : 'Save Changes'}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
)
}