103 lines
4.4 KiB
TypeScript
103 lines
4.4 KiB
TypeScript
"use client"
|
||
import { Card, CardContent } from "@/shared/components/ui/card"
|
||
import { Separator } from "@/shared/components/ui/separator"
|
||
import { formatCurrency, formatEnum } from "@/shared/utils/formatters"
|
||
import { cn } from "@/shared/lib/utils"
|
||
import { useInvoice } from "./invoice-context"
|
||
|
||
export function InvoiceTotalsSummary() {
|
||
const invoice = useInvoice()
|
||
|
||
if (!invoice) return null
|
||
|
||
const parts = invoice.invoice_parts ?? []
|
||
const services = invoice.invoice_services ?? []
|
||
const expenses = invoice.invoice_expenses ?? []
|
||
const discount = invoice.discount
|
||
const displayTotal = parseFloat(String(invoice.total ?? 0)) || 0
|
||
const paid = parseFloat(String(invoice.payments_recieved ?? invoice.received_payment ?? 0)) || 0
|
||
const balanceDue = parseFloat(String(invoice.balance_due ?? 0)) || 0
|
||
|
||
const hasItems = parts.length > 0 || services.length > 0 || expenses.length > 0
|
||
|
||
if (!hasItems && displayTotal === 0) return null
|
||
|
||
const subTotal = parseFloat(String(invoice.sub_total ?? 0)) || 0
|
||
|
||
function lineTotal(items: { quantity?: string | number; rate?: string | number }[]) {
|
||
return items.reduce((sum, item) => {
|
||
const qty = parseFloat(String(item.quantity ?? 0))
|
||
const rate = parseFloat(String(item.rate ?? 0))
|
||
return sum + (isNaN(qty) || isNaN(rate) ? 0 : qty * rate)
|
||
}, 0)
|
||
}
|
||
|
||
return (
|
||
<Card>
|
||
<CardContent className="pt-6">
|
||
<div className="ml-auto max-w-sm space-y-2">
|
||
{hasItems && (
|
||
<>
|
||
{parts.length > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-muted-foreground">Parts ({parts.length})</span>
|
||
<span>{formatCurrency(lineTotal(parts))}</span>
|
||
</div>
|
||
)}
|
||
{services.length > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-muted-foreground">Services ({services.length})</span>
|
||
<span>{formatCurrency(lineTotal(services))}</span>
|
||
</div>
|
||
)}
|
||
{expenses.length > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-muted-foreground">Expenses ({expenses.length})</span>
|
||
<span>{formatCurrency(lineTotal(expenses))}</span>
|
||
</div>
|
||
)}
|
||
<Separator />
|
||
</>
|
||
)}
|
||
|
||
<div className="flex justify-between text-sm font-medium">
|
||
<span>Subtotal</span>
|
||
<span>{formatCurrency(subTotal)}</span>
|
||
</div>
|
||
|
||
{discount && discount !== "no" && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-muted-foreground">Discount ({formatEnum(discount)})</span>
|
||
<span className="text-muted-foreground">Applied</span>
|
||
</div>
|
||
)}
|
||
|
||
<Separator />
|
||
|
||
<div className="flex justify-between text-base font-semibold">
|
||
<span>Total</span>
|
||
<span>{formatCurrency(displayTotal)}</span>
|
||
</div>
|
||
|
||
{paid > 0 && (
|
||
<div className="flex justify-between text-sm text-muted-foreground">
|
||
<span>Amount Received</span>
|
||
<span>– {formatCurrency(paid)}</span>
|
||
</div>
|
||
)}
|
||
|
||
<Separator />
|
||
|
||
<div className={cn(
|
||
"flex justify-between rounded-lg px-3 py-2 text-base font-bold",
|
||
balanceDue > 0 ? "bg-primary/10 text-primary" : "bg-green-500/10 text-green-600",
|
||
)}>
|
||
<span>Balance Due</span>
|
||
<span>{formatCurrency(balanceDue)}</span>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|