134 lines
5.6 KiB
TypeScript
134 lines
5.6 KiB
TypeScript
"use client"
|
||
|
||
import { ShoppingCartIcon } from "lucide-react"
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/shared/components/ui/table"
|
||
import { formatCurrency } from "@/shared/utils/formatters"
|
||
|
||
type ExpenseItem = {
|
||
id?: number
|
||
expense_id?: number
|
||
expense_item_id?: number
|
||
quantity?: number | string
|
||
rate?: string | number
|
||
description?: string | null
|
||
expense_item?: {
|
||
id?: number
|
||
item_name?: string
|
||
name?: string
|
||
item_code?: string
|
||
sku?: string
|
||
}
|
||
}
|
||
|
||
type ExpenseItemsSectionProps = {
|
||
items?: ExpenseItem[]
|
||
discountType?: string | null
|
||
subTotal?: number | null
|
||
discountAmount?: number | null
|
||
taxAmount?: number | null
|
||
total?: number | null
|
||
taxLabel?: string | null
|
||
}
|
||
|
||
export function ExpenseItemsSection({
|
||
items,
|
||
discountType,
|
||
subTotal,
|
||
discountAmount,
|
||
taxAmount,
|
||
total,
|
||
taxLabel,
|
||
}: ExpenseItemsSectionProps) {
|
||
if (!items || items.length === 0) return null
|
||
|
||
return (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<ShoppingCartIcon className="size-4" />
|
||
Expense Items ({items.length})
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="overflow-x-auto">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow className="hover:bg-transparent">
|
||
<TableHead>Item</TableHead>
|
||
<TableHead>Description</TableHead>
|
||
<TableHead className="text-right w-20">Qty</TableHead>
|
||
<TableHead className="text-right w-28">Rate</TableHead>
|
||
<TableHead className="text-right w-28">Amount</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{items.map((item) => {
|
||
const qty = Number(item.quantity || 0)
|
||
const rate = Number(item.rate || 0)
|
||
const amount = qty * rate
|
||
const name = item.expense_item?.item_name || item.expense_item?.name || "—"
|
||
return (
|
||
<TableRow key={item.id}>
|
||
<TableCell className="font-medium">
|
||
<div>{name}</div>
|
||
{(item.expense_item?.sku || item.expense_item?.item_code) && (
|
||
<div className="text-xs text-muted-foreground font-mono">
|
||
{item.expense_item?.sku || item.expense_item?.item_code}
|
||
</div>
|
||
)}
|
||
</TableCell>
|
||
<TableCell className="text-sm text-muted-foreground">
|
||
{item.description || "—"}
|
||
</TableCell>
|
||
<TableCell className="text-right text-sm">{qty.toLocaleString()}</TableCell>
|
||
<TableCell className="text-right text-sm">{formatCurrency(rate)}</TableCell>
|
||
<TableCell className="text-right font-semibold">{formatCurrency(amount)}</TableCell>
|
||
</TableRow>
|
||
)
|
||
})}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
|
||
{/* ── Totals summary ── */}
|
||
<div className="mt-4 flex justify-end">
|
||
<div className="w-64 space-y-1 text-sm">
|
||
{subTotal != null && (
|
||
<div className="flex justify-between">
|
||
<span className="text-muted-foreground">Subtotal</span>
|
||
<span className="font-medium">{formatCurrency(subTotal)}</span>
|
||
</div>
|
||
)}
|
||
{discountAmount != null && discountAmount > 0 && discountType !== "no" && (
|
||
<div className="flex justify-between text-destructive">
|
||
<span>Discount</span>
|
||
<span>− {formatCurrency(discountAmount)}</span>
|
||
</div>
|
||
)}
|
||
{taxAmount != null && taxAmount > 0 && (
|
||
<div className="flex justify-between">
|
||
<span className="text-muted-foreground">{taxLabel || "Tax"}</span>
|
||
<span className="font-medium">{formatCurrency(taxAmount)}</span>
|
||
</div>
|
||
)}
|
||
{total != null && (
|
||
<div className="flex justify-between border-t pt-2 text-base font-semibold">
|
||
<span>Total</span>
|
||
<span>{formatCurrency(total)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|