garage-erp/apps/dashboard/modules/invoices/invoice-form-summary.tsx
2026-04-23 14:38:41 +03:00

105 lines
3.4 KiB
TypeScript

"use client"
import { useFormContext } from "react-hook-form"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import {
useDocumentTotals,
type DocumentLineItem,
} from "@/shared/hooks/use-document-totals"
import { DocumentTotalsSummary } from "@/shared/components/document-totals-summary"
import type { InvoiceFormValues } from "./invoice.schema"
/**
* Parse the numeric rate from an option label formatted as "Title (15%)".
* Returns undefined when no match.
*/
function parseTaxRateFromLabel(label: string | undefined | null): number | undefined {
if (!label) return undefined
const match = label.match(/\((\d+(?:\.\d+)?)%\)/)
return match ? Number(match[1]) : undefined
}
export function InvoiceFormSummary() {
const { watch } = useFormContext<InvoiceFormValues>()
const parts = watch("parts") ?? []
const services = watch("services") ?? []
const expenseItems = watch("expense_items") ?? []
const discountType = watch("discount")
const discountAmount = watch("discount_amount")
const taxRelation = watch("tax")
// Rate is embedded in the label: "VAT 15% (15%)" → parseTaxRateFromLabel → 15
const taxRate = parseTaxRateFromLabel(taxRelation?.label)
const taxLabel = taxRelation?.label ?? "Tax"
// Flatten all line items into the generic shape
const lineItems: DocumentLineItem[] = [
...parts.map((p) => ({
quantity: p.quantity,
rate: p.rate,
discount_amount: p.discount_amount,
})),
...services.map((s) => ({
quantity: s.quantity,
rate: s.rate,
discount_amount: s.discount_amount,
})),
...expenseItems.map((e) => ({
quantity: e.quantity,
rate: e.rate,
discount_amount: e.discount_amount,
})),
]
// Group breakdowns for display (only when more than one group has items)
const groupLabels: Record<string, number> = {}
if (parts.length > 0) {
groupLabels[`Parts (${parts.length})`] = parts.reduce(
(s, p) => s + (Number(p.quantity) || 0) * (Number(p.rate) || 0),
0,
)
}
if (services.length > 0) {
groupLabels[`Services (${services.length})`] = services.reduce(
(s, sv) => s + (Number(sv.quantity) || 0) * (Number(sv.rate) || 0),
0,
)
}
if (expenseItems.length > 0) {
groupLabels[`Expenses (${expenseItems.length})`] = expenseItems.reduce(
(s, e) => s + (Number(e.quantity) || 0) * (Number(e.rate) || 0),
0,
)
}
const totals = useDocumentTotals({
lineItems,
discountType,
discountAmount,
taxRate,
})
if (!totals.hasLineItems) return null
return (
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
Summary
</CardTitle>
</CardHeader>
<CardContent>
<DocumentTotalsSummary
totals={totals}
discountType={discountType}
taxLabel={taxLabel}
groupLabels={Object.keys(groupLabels).length > 1 ? groupLabels : undefined}
/>
</CardContent>
</Card>
)
}