garage-erp/apps/dashboard/modules/bills/bill-totals-summary.tsx
2026-04-23 14:38:41 +03:00

118 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 { useBill } from "./bill-context"
export function BillTotalsSummary() {
const bill = useBill()
if (!bill) return null
const parts = bill.parts ?? []
const services = bill.services ?? []
const expenses = bill.expenses ?? []
const hasItems = parts.length > 0 || services.length > 0 || expenses.length > 0
if (!hasItems) return null
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)
}
const partsTotal = lineTotal(parts)
const servicesTotal = lineTotal(services)
const expensesTotal = lineTotal(expenses)
// Use API-computed values when available, fall back to manual calc
const subTotal = bill.sub_total ?? (partsTotal + servicesTotal + expensesTotal)
const taxAmount = bill.tax_amount ?? 0
const discountAmount = bill.discount_amount_major ?? 0
const total = bill.total ?? subTotal
const paymentsMade = bill.payments_made ?? 0
const balanceDue = bill.balance_due ?? total
const discount = bill.discount_type
return (
<Card>
<CardContent className="pt-6">
<div className="ml-auto max-w-sm space-y-2">
{parts.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Parts ({parts.length})</span>
<span>{formatCurrency(partsTotal)}</span>
</div>
)}
{services.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Services ({services.length})</span>
<span>{formatCurrency(servicesTotal)}</span>
</div>
)}
{expenses.length > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Expenses ({expenses.length})</span>
<span>{formatCurrency(expensesTotal)}</span>
</div>
)}
<Separator />
<div className="flex justify-between text-sm font-medium">
<span>Subtotal</span>
<span>{formatCurrency(subTotal)}</span>
</div>
{discountAmount > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Discount{discount && discount !== "no" ? ` (${formatEnum(discount)})` : ""}</span>
<span className="text-muted-foreground">{formatCurrency(discountAmount)}</span>
</div>
)}
{taxAmount > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Tax{bill.tax?.name ? ` (${bill.tax.name})` : ""}</span>
<span>{formatCurrency(taxAmount)}</span>
</div>
)}
<Separator />
<div className={cn(
"flex justify-between rounded-lg px-3 py-2 text-base font-bold bg-muted/50",
)}>
<span>Total</span>
<span>{formatCurrency(total)}</span>
</div>
{paymentsMade > 0 && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Payments Made</span>
<span className="text-emerald-600">{formatCurrency(paymentsMade)}</span>
</div>
)}
{paymentsMade > 0 && (
<>
<Separator />
<div className={cn(
"flex justify-between rounded-lg px-3 py-2 text-base font-semibold",
balanceDue > 0 ? "text-destructive bg-destructive/5" : "text-emerald-700 bg-emerald-50 dark:bg-emerald-950/20",
)}>
<span>Balance Due</span>
<span>{formatCurrency(balanceDue)}</span>
</div>
</>
)}
</div>
</CardContent>
</Card>
)
}