234 lines
7.2 KiB
TypeScript
234 lines
7.2 KiB
TypeScript
// 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'
|
|
import { Vendor, VendorType, VendorStatus } from '@prisma/client'
|
|
import { Badge } from '../ui/badge'
|
|
import { useRouter } from 'next/navigation'
|
|
|
|
interface VendorRow {
|
|
id: string
|
|
slug: 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)
|
|
const router = useRouter()
|
|
|
|
async function refreshVendors() {
|
|
try {
|
|
const updated = await fetchVendorsClient()
|
|
setVendors(updated)
|
|
} catch (err) {
|
|
console.error('Failed to refresh vendors:', err)
|
|
}
|
|
}
|
|
|
|
// Handle row click
|
|
const handleRowClick = (vendor: VendorRow) => {
|
|
router.push(`/vendors/${vendor.slug}`)
|
|
}
|
|
|
|
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}
|
|
onRowClick={handleRowClick}
|
|
/>
|
|
|
|
<DialogWrapper
|
|
title="Add New Vendor"
|
|
description="Enter the vendor information below"
|
|
open={isDialogOpen}
|
|
onOpenChange={setIsDialogOpen}
|
|
form={
|
|
<CreateVendorForm
|
|
onSuccess={async () => {
|
|
await refreshVendors()
|
|
setIsDialogOpen(false)
|
|
}}
|
|
/>
|
|
}
|
|
/>
|
|
</div>
|
|
)
|
|
} |