garage-erp/apps/dashboard/modules/invoices/invoice-general-info.tsx

336 lines
13 KiB
TypeScript

import {
FileText,
Calendar,
Hash,
Users,
Car,
Building2,
CircleDollarSign,
Clock,
Mail,
Phone,
DollarSign,
} from "lucide-react"
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import { Separator } from "@/shared/components/ui/separator"
import { formatDate, formatCurrency, formatEnum, formatNumber } from "@/shared/utils/formatters"
type InvoiceData = {
id?: number
subject?: string
invoice_number?: string
invoice_title?: string
invoice_date?: string
due_date?: string
status?: string
notes?: string
terms_and_conditions?: string
customer_name?: string
customer_id?: number
customer?: any
vehicle_name?: string
vehicle_id?: number
vehicle?: any
department_name?: string
department_id?: number
payment_terms_id?: number
payment_mode_id?: number
amount?: number | string | null
received_payment?: number | string | null
discount?: string
has_insurance?: number | boolean
insurer_id?: number | null
insurer?: any
kms_in?: number | null
invoice_to_id?: number | null
billing_address_id?: number | null
delivery_address_id?: number | null
created_at?: string
updated_at?: string
}
type InvoiceGeneralInfoProps = {
invoice: InvoiceData
}
function InfoItem({
icon: Icon,
label,
value,
}: {
icon: React.ComponentType<{ className?: string }>
label: string
value?: string | null
}) {
return (
<div className="flex items-start gap-3">
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
<Icon className="size-4" />
</div>
<div className="flex flex-col gap-0.5">
<span className="text-xs text-muted-foreground">{label}</span>
<span className="text-sm font-medium">
{value || <span className="text-muted-foreground"></span>}
</span>
</div>
</div>
)
}
const statusColorMap: Record<string, string> = {
draft: "secondary",
open: "default",
paid: "default",
overdue: "destructive",
void: "outline",
}
export function InvoiceGeneralInfo({ invoice }: InvoiceGeneralInfoProps) {
const customer = invoice.customer || {}
const vehicle = invoice.vehicle || {}
const insurer = invoice.insurer || {}
return (
<div className="grid gap-6">
{/* Invoice Details */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="size-4" />
Invoice Details
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="flex flex-wrap items-center gap-2">
{invoice.subject && (
<Badge variant="secondary">{invoice.subject}</Badge>
)}
{invoice.status && (
<Badge variant={statusColorMap[invoice.status] as any ?? "outline"}>
{formatEnum(invoice.status)}
</Badge>
)}
</div>
<Separator />
<div className="grid gap-4 sm:grid-cols-3">
<InfoItem
icon={Hash}
label="Invoice Number"
value={invoice.invoice_number}
/>
<InfoItem
icon={Calendar}
label="Invoice Date"
value={formatDate(invoice.invoice_date)}
/>
<InfoItem
icon={Calendar}
label="Due Date"
value={formatDate(invoice.due_date)}
/>
</div>
</CardContent>
</Card>
{/* Customer & Vehicle Information */}
<div className="grid gap-6 md:grid-cols-2">
{/* Customer Details */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="size-4" />
Customer Information
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-4 sm:grid-cols-2">
<InfoItem
icon={Users}
label="Customer Name"
value={customer.first_name && customer.last_name ? `${customer.first_name} ${customer.last_name}` : invoice.customer_name}
/>
<InfoItem
icon={Mail}
label="Email"
value={customer.email}
/>
<InfoItem
icon={Phone}
label="Phone"
value={customer.phone}
/>
<InfoItem
icon={Phone}
label="Alternate Phone"
value={customer.alternate_phone}
/>
</div>
{customer.address_line_1 && (
<>
<Separator />
<div className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">Address</span>
<p className="text-sm">
{customer.address_line_1}
{customer.address_line_2 ? `, ${customer.address_line_2}` : ""}
<br />
{customer.city ? `${customer.city}` : ""}
{customer.zip_code ? `, ${customer.zip_code}` : ""}
</p>
</div>
</>
)}
</CardContent>
</Card>
{/* Vehicle Details */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Car className="size-4" />
Vehicle Information
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-4 sm:grid-cols-2">
<InfoItem
icon={Car}
label="Vehicle"
value={vehicle.make && vehicle.model ? `${vehicle.make} ${vehicle.model}` : invoice.vehicle_name}
/>
<InfoItem
icon={Hash}
label="License Plate"
value={vehicle.license_plate}
/>
<InfoItem
icon={Hash}
label="VIN"
value={vehicle.vin_number}
/>
<InfoItem
icon={Hash}
label="Engine Number"
value={vehicle.engine_number}
/>
</div>
{vehicle.mileage && (
<>
<Separator />
<InfoItem
icon={Clock}
label="Mileage"
value={formatNumber(vehicle.mileage)}
/>
</>
)}
</CardContent>
</Card>
</div>
{/* Payment & Insurance Information */}
<div className="grid gap-6 md:grid-cols-2">
{/* Payment Information */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<DollarSign className="size-4" />
Payment Information
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-4 sm:grid-cols-2">
<InfoItem
icon={CircleDollarSign}
label="Amount"
value={invoice.amount ? formatCurrency(invoice.amount) : null}
/>
<InfoItem
icon={CircleDollarSign}
label="Received Payment"
value={invoice.received_payment ? formatCurrency(invoice.received_payment) : null}
/>
<InfoItem
icon={Hash}
label="Discount"
value={invoice.discount}
/>
</div>
</CardContent>
</Card>
{/* Insurance & Additional Info */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="size-4" />
Additional Information
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-4 sm:grid-cols-2">
<InfoItem
icon={Building2}
label="Department"
value={invoice.department_name}
/>
<InfoItem
icon={Hash}
label="Has Insurance"
value={invoice.has_insurance ? "Yes" : "No"}
/>
{invoice.has_insurance && insurer.id && (
<InfoItem
icon={Users}
label="Insurer"
value={insurer.name}
/>
)}
{invoice.kms_in && (
<InfoItem
icon={Clock}
label="KMs In"
value={formatNumber(invoice.kms_in)}
/>
)}
</div>
</CardContent>
</Card>
</div>
{/* Notes & Terms */}
{(invoice.notes || invoice.terms_and_conditions) && (
<div className="grid gap-6 md:grid-cols-2">
{invoice.notes && (
<Card>
<CardHeader>
<CardTitle className="text-base">Notes</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm whitespace-pre-wrap">{invoice.notes}</p>
</CardContent>
</Card>
)}
{invoice.terms_and_conditions && (
<Card>
<CardHeader>
<CardTitle className="text-base">Terms & Conditions</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm whitespace-pre-wrap">{invoice.terms_and_conditions}</p>
</CardContent>
</Card>
)}
</div>
)}
</div>
)
}