added vendors table

This commit is contained in:
2026-01-27 12:23:59 -05:00
parent ecd7182153
commit 3aa9b6f325
20 changed files with 1058 additions and 5 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ data/postgres/
uploads/ uploads/
data/ data/
docker-compose.override.yml docker-compose.override.yml
.vscode

13
app/(auth)/vendors/page.tsx vendored Normal file
View File

@@ -0,0 +1,13 @@
import VendorsTable from '@/components/tables/VendorsTable'
import { queries } from '@/lib/queries'
import React from 'react'
export default async function VendorPage() {
const vendors = await queries.fetchAllVendors()
return (
<div>
<VendorsTable initialVendors={vendors} />
</div>
)
}

109
app/api/vendors/create/route.ts vendored Normal file
View File

@@ -0,0 +1,109 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { VendorType, VendorStatus } from '@prisma/client'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
console.log('Received data:', body) // Debug logging
// Validate required fields
if (!body.name || !body.type) {
return NextResponse.json(
{ error: 'Name and vendor type are required' },
{ status: 400 }
)
}
// Check if we have address data
const hasAddress = body.street && body.city && body.state && body.postalCode
// Parse numeric values
const parseFloatOrNull = (value: any): number | null => {
if (value === null || value === undefined || value === '') return null
const num = parseFloat(value)
return isNaN(num) ? null : num
}
const parseDateOrNull = (value: any): Date | null => {
if (!value) return null
const date = new Date(value)
return isNaN(date.getTime()) ? null : date
}
// Prepare vendor data WITHOUT addressId
const vendorData: any = {
name: body.name,
type: body.type as VendorType,
description: body.description || null,
website: body.website || null,
contactPerson: body.contactPerson || null,
email: body.email || null,
phone: body.phone || null,
status: (body.status as VendorStatus) || 'CONTACTING',
isBooked: Boolean(body.isBooked),
bookedDate: parseDateOrNull(body.bookedDate),
quotedPrice: parseFloatOrNull(body.quotedPrice),
finalCost: parseFloatOrNull(body.finalCost),
depositPaid: parseFloatOrNull(body.depositPaid),
depositDueDate: parseDateOrNull(body.depositDueDate),
finalPaymentDue: parseDateOrNull(body.finalPaymentDue),
paymentNotes: body.paymentNotes || null,
contractUrl: body.contractUrl || null,
proposalUrl: body.proposalUrl || null,
notes: body.notes || null,
}
// Add address relation if we have address data
if (hasAddress) {
vendorData.address = {
create: {
street: body.street,
city: body.city,
state: body.state,
zip: parseInt(body.postalCode),
}
}
}
// If no address, don't include address field at all
console.log('Creating vendor with data:', vendorData) // Debug logging
const vendor = await prisma.vendor.create({
data: vendorData,
include: {
address: hasAddress // Only include address if we created one
}
})
return NextResponse.json(vendor, { status: 201 })
} catch (error: any) {
console.error('Error creating vendor:', error)
// Provide helpful error messages
if (error.code === 'P2002') {
return NextResponse.json(
{ error: 'A vendor with this name already exists' },
{ status: 409 }
)
}
if (error.code === 'P2023') {
return NextResponse.json(
{ error: 'Invalid data format provided' },
{ status: 400 }
)
}
return NextResponse.json(
{
error: 'Failed to create vendor',
details: error.message,
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
},
{ status: 500 }
)
}
}

24
app/api/vendors/fetch/route.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET(request: NextRequest) {
try {
const vendors = await prisma.vendor.findMany({
include: {
address: true,
},
orderBy: {
createdAt: 'desc',
},
})
return NextResponse.json(vendors)
} catch (error) {
console.error('Error fetching vendors:', error)
return NextResponse.json(
{ error: 'Failed to fetch vendors' },
{ status: 500 }
)
}
}

BIN
bun.lockb Executable file

Binary file not shown.

View File

@@ -13,6 +13,7 @@ import {
IconSearch, IconSearch,
IconSettings, IconSettings,
IconUsers, IconUsers,
IconUsersGroup
} from "@tabler/icons-react" } from "@tabler/icons-react"
import { NavMain } from "@/components/nav-main" import { NavMain } from "@/components/nav-main"
@@ -51,6 +52,11 @@ const data = {
url: "/venues", url: "/venues",
icon: IconBuildingArch, icon: IconBuildingArch,
}, },
{
title: "Vendors",
url: "/vendors",
icon: IconUsersGroup,
},
], ],
// navClouds: [ // navClouds: [
// { // {

View File

@@ -0,0 +1,449 @@
'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 { Address, Vendor } from '@prisma/client'
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>
)
}

View File

@@ -0,0 +1,225 @@
// VendorsTable.tsx
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { DataTable } from './DataTable'
import { Button } from '../ui/button'
import DialogWrapper from '../dialogs/DialogWrapper'
import CreateVendorForm from '../forms/CreateVendorForm'
import { useState } from 'react'
import { fetchVendorsClient } from '@/lib/helper/fetchVendors' // You'll need to create this
import { Vendor, VendorType, VendorStatus } from '@prisma/client'
import { Badge } from '../ui/badge' // You'll need Badge component
interface VendorRow {
id: string
name: string
type: VendorType
description?: string | null
website?: string | null
contactPerson?: string | null
email?: string | null
phone?: string | null
status: VendorStatus
isBooked: boolean
bookedDate?: Date | null
quotedPrice?: number | null
finalCost?: number | null
depositPaid?: number | null
depositDueDate?: Date | null
finalPaymentDue?: Date | null
createdAt: Date
}
interface Props {
initialVendors: VendorRow[]
}
// Format vendor type for display
const formatVendorType = (type: VendorType): string => {
return type.charAt(0) + type.slice(1).toLowerCase()
}
// Format vendor status with colors
const formatVendorStatus = (status: VendorStatus): { label: string, color: string } => {
const colors: Record<VendorStatus, string> = {
RESEARCHING: 'bg-gray-100 text-gray-800',
CONTACTING: 'bg-blue-100 text-blue-800',
RESPONDED: 'bg-yellow-100 text-yellow-800',
PROPOSAL_RECEIVED: 'bg-purple-100 text-purple-800',
NEGOTIATING: 'bg-orange-100 text-orange-800',
CONTRACT_SENT: 'bg-indigo-100 text-indigo-800',
CONTRACT_SIGNED: 'bg-green-100 text-green-800',
DECLINED: 'bg-red-100 text-red-800',
BACKUP: 'bg-slate-100 text-slate-800',
}
const label = status.charAt(0) + status.slice(1).toLowerCase().replace('_', ' ')
return { label, color: colors[status] }
}
// Format currency
const formatCurrency = (amount?: number | null): string => {
if (!amount) return '—'
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount)
}
// Format date
const formatDate = (date?: Date | null): string => {
if (!date) return '—'
return new Date(date).toLocaleDateString()
}
const columns: ColumnDef<VendorRow>[] = [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => (
<div>
<div className="font-medium">{row.original.name}</div>
{row.original.contactPerson && (
<div className="text-sm text-muted-foreground">
{row.original.contactPerson}
</div>
)}
</div>
),
},
{
accessorKey: 'type',
header: 'Type',
cell: ({ row }) => (
<Badge variant="outline" className="capitalize">
{formatVendorType(row.original.type)}
</Badge>
),
},
{
accessorKey: 'contact',
header: 'Contact',
cell: ({ row }) => (
<div className="text-sm">
{row.original.email && (
<div className="truncate max-w-[180px]">{row.original.email}</div>
)}
{row.original.phone && (
<div>{row.original.phone}</div>
)}
</div>
),
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const status = formatVendorStatus(row.original.status)
return (
<Badge className={status.color}>
{status.label}
</Badge>
)
},
},
{
accessorKey: 'isBooked',
header: 'Booked',
cell: ({ row }) => (
<Badge variant={row.original.isBooked ? "default" : "outline"}>
{row.original.isBooked ? 'Booked' : 'Not Booked'}
</Badge>
),
},
{
accessorKey: 'cost',
header: 'Cost',
cell: ({ row }) => (
<div className="text-sm">
{row.original.finalCost ? (
<div className="font-medium">{formatCurrency(row.original.finalCost)}</div>
) : row.original.quotedPrice ? (
<div className="font-medium">{formatCurrency(row.original.quotedPrice)}</div>
) : (
<div className="text-muted-foreground"></div>
)}
</div>
),
},
{
accessorKey: 'deposit',
header: 'Deposit',
cell: ({ row }) => (
<div className="text-sm">
{row.original.depositPaid ? (
<div>{formatCurrency(row.original.depositPaid)}</div>
) : (
<div className="text-muted-foreground"></div>
)}
{row.original.depositDueDate && (
<div className="text-xs text-muted-foreground">
Due: {formatDate(row.original.depositDueDate)}
</div>
)}
</div>
),
},
{
accessorKey: 'createdAt',
header: 'Added',
cell: ({ row }) => formatDate(row.original.createdAt),
},
]
export default function VendorsTable({ initialVendors }: Props) {
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [vendors, setVendors] = useState<VendorRow[]>(initialVendors)
async function refreshVendors() {
try {
const updated = await fetchVendorsClient()
setVendors(updated)
} catch (err) {
console.error('Failed to refresh vendors:', err)
}
}
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<div>
<h2 className="text-2xl font-bold tracking-tight">Vendors</h2>
<p className="text-muted-foreground">
Manage your wedding vendors and suppliers
</p>
</div>
<Button
onClick={() => setIsDialogOpen(true)}
>
Add Vendor
</Button>
</div>
<DataTable
columns={columns}
data={vendors}
/>
<DialogWrapper
title="Add New Vendor"
description="Enter the vendor information below"
open={isDialogOpen}
onOpenChange={setIsDialogOpen}
form={
<CreateVendorForm
onSuccess={async () => {
await refreshVendors()
setIsDialogOpen(false)
}}
/>
}
/>
</div>
)
}

View File

@@ -0,0 +1,18 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 rounded-lg border bg-transparent px-2.5 py-2 text-base transition-colors focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
)
}
export { Textarea }

View File

@@ -0,0 +1,21 @@
// lib/helper/fetchVendors.ts
import { Vendor } from '@prisma/client'
export interface VendorWithAddress extends Vendor {
address: {
street: string
city: string
state: string
zip: number
}
}
export async function fetchVendorsClient(): Promise<VendorWithAddress[]> {
const res = await fetch('/api/vendors/fetch')
if (!res.ok) {
throw new Error('Failed to fetch vendors')
}
return res.json()
}

View File

@@ -163,4 +163,8 @@ export const queries = {
return await prisma.venue.findMany() return await prisma.venue.findMany()
}, },
async fetchAllVendors() {
return await prisma.vendor.findMany()
}
} }

View File

@@ -0,0 +1,84 @@
-- CreateEnum
CREATE TYPE "VendorType" AS ENUM ('VENUE', 'CATERER', 'PHOTOGRAPHER', 'VIDEOGRAPHER', 'FLORIST', 'BAKERY', 'DJ', 'BAND', 'OFFICIANT', 'HAIR_MAKEUP', 'TRANSPORTATION', 'RENTALS', 'DECOR', 'PLANNER', 'STATIONERY', 'OTHER');
-- CreateEnum
CREATE TYPE "VendorStatus" AS ENUM ('RESEARCHING', 'CONTACTING', 'RESPONDED', 'PROPOSAL_RECEIVED', 'NEGOTIATING', 'CONTRACT_SENT', 'CONTRACT_SIGNED', 'DECLINED', 'BACKUP');
-- AlterTable
ALTER TABLE "Event" ADD COLUMN "vendorId" TEXT;
-- CreateTable
CREATE TABLE "Vendor" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
"type" "VendorType" NOT NULL,
"description" TEXT,
"website" TEXT,
"contactPerson" TEXT,
"email" TEXT,
"phone" TEXT,
"addressId" TEXT NOT NULL,
"status" "VendorStatus" NOT NULL DEFAULT 'CONTACTING',
"isBooked" BOOLEAN NOT NULL DEFAULT false,
"bookedDate" TIMESTAMP(3),
"quotedPrice" DOUBLE PRECISION,
"finalCost" DOUBLE PRECISION,
"depositPaid" DOUBLE PRECISION,
"depositDueDate" TIMESTAMP(3),
"finalPaymentDue" TIMESTAMP(3),
"paymentNotes" TEXT,
"contactUrl" TEXT,
"proposalUrl" TEXT,
"notes" TEXT,
CONSTRAINT "Vendor_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Address" (
"id" TEXT NOT NULL,
"street" TEXT NOT NULL,
"city" TEXT NOT NULL,
"state" TEXT NOT NULL,
"zip" INTEGER NOT NULL,
CONSTRAINT "Address_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Category" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"color" TEXT DEFAULT '#3B82F6',
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_CategoryToVendor" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_CategoryToVendor_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
-- CreateIndex
CREATE INDEX "_CategoryToVendor_B_index" ON "_CategoryToVendor"("B");
-- AddForeignKey
ALTER TABLE "Event" ADD CONSTRAINT "Event_vendorId_fkey" FOREIGN KEY ("vendorId") REFERENCES "Vendor"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Vendor" ADD CONSTRAINT "Vendor_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_CategoryToVendor" ADD CONSTRAINT "_CategoryToVendor_A_fkey" FOREIGN KEY ("A") REFERENCES "Category"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_CategoryToVendor" ADD CONSTRAINT "_CategoryToVendor_B_fkey" FOREIGN KEY ("B") REFERENCES "Vendor"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,28 @@
/*
Warnings:
- You are about to drop the column `vendorId` on the `Event` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "Event" DROP CONSTRAINT "Event_vendorId_fkey";
-- AlterTable
ALTER TABLE "Event" DROP COLUMN "vendorId";
-- CreateTable
CREATE TABLE "_EventToVendor" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_EventToVendor_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_EventToVendor_B_index" ON "_EventToVendor"("B");
-- AddForeignKey
ALTER TABLE "_EventToVendor" ADD CONSTRAINT "_EventToVendor_A_fkey" FOREIGN KEY ("A") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_EventToVendor" ADD CONSTRAINT "_EventToVendor_B_fkey" FOREIGN KEY ("B") REFERENCES "Vendor"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,20 @@
/*
Warnings:
- You are about to drop the column `addressId` on the `Vendor` table. All the data in the column will be lost.
- Added the required column `address` to the `Vendor` table without a default value. This is not possible if the table is not empty.
- Added the required column `city` to the `Vendor` table without a default value. This is not possible if the table is not empty.
- Added the required column `postalCode` to the `Vendor` table without a default value. This is not possible if the table is not empty.
- Added the required column `state` to the `Vendor` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "Vendor" DROP CONSTRAINT "Vendor_addressId_fkey";
-- AlterTable
ALTER TABLE "Vendor" DROP COLUMN "addressId",
ADD COLUMN "address" TEXT NOT NULL,
ADD COLUMN "city" TEXT NOT NULL,
ADD COLUMN "country" TEXT NOT NULL DEFAULT 'United States',
ADD COLUMN "postalCode" TEXT NOT NULL,
ADD COLUMN "state" TEXT NOT NULL;

View File

@@ -0,0 +1,10 @@
/*
Warnings:
- You are about to drop the column `address` on the `Vendor` table. All the data in the column will be lost.
- Added the required column `street` to the `Vendor` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Vendor" DROP COLUMN "address",
ADD COLUMN "street" TEXT NOT NULL;

View File

@@ -0,0 +1,21 @@
/*
Warnings:
- You are about to drop the column `city` on the `Vendor` table. All the data in the column will be lost.
- You are about to drop the column `country` on the `Vendor` table. All the data in the column will be lost.
- You are about to drop the column `postalCode` on the `Vendor` table. All the data in the column will be lost.
- You are about to drop the column `state` on the `Vendor` table. All the data in the column will be lost.
- You are about to drop the column `street` on the `Vendor` table. All the data in the column will be lost.
- Added the required column `addressId` to the `Vendor` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Vendor" DROP COLUMN "city",
DROP COLUMN "country",
DROP COLUMN "postalCode",
DROP COLUMN "state",
DROP COLUMN "street",
ADD COLUMN "addressId" TEXT NOT NULL;
-- AddForeignKey
ALTER TABLE "Vendor" ADD CONSTRAINT "Vendor_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,9 @@
/*
Warnings:
- You are about to drop the column `contactUrl` on the `Vendor` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Vendor" DROP COLUMN "contactUrl",
ADD COLUMN "contractUrl" TEXT;

View File

@@ -0,0 +1,8 @@
-- DropForeignKey
ALTER TABLE "Vendor" DROP CONSTRAINT "Vendor_addressId_fkey";
-- AlterTable
ALTER TABLE "Vendor" ALTER COLUMN "addressId" DROP NOT NULL;
-- AddForeignKey
ALTER TABLE "Vendor" ADD CONSTRAINT "Vendor_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -41,6 +41,8 @@ model Event {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
FileUpload FileUpload[] FileUpload FileUpload[]
vendors Vendor[]
} }
model Venue { model Venue {
@@ -155,8 +157,8 @@ model Vendor {
contactPerson String? contactPerson String?
email String? email String?
phone String? phone String?
address Address @relation(fields: [addressId], references: [id]) address Address? @relation(fields: [addressId], references: [id])
addressId String addressId String?
status VendorStatus @default(CONTACTING) status VendorStatus @default(CONTACTING)
isBooked Boolean @default(false) isBooked Boolean @default(false)
@@ -169,7 +171,7 @@ model Vendor {
finalPaymentDue DateTime? finalPaymentDue DateTime?
paymentNotes String? paymentNotes String?
contactUrl String? contractUrl String?
proposalUrl String? proposalUrl String?
notes String? notes String?
@@ -184,7 +186,8 @@ model Address {
city String city String
state String state String
zip Int zip Int
Vendor Vendor[]
vendors Vendor[]
} }
model Category { model Category {