@@ -3,9 +3,16 @@
|
||||
import { createContext, useContext } from "react"
|
||||
|
||||
export type EstimateContextValue = {
|
||||
id: string
|
||||
label: string
|
||||
data?: Record<string, any>
|
||||
id?: number | string
|
||||
title?: string | null
|
||||
estimate_number?: string | null
|
||||
discount?: string | null
|
||||
sub_total?: number | string | null
|
||||
total?: number | string | null
|
||||
estimate_parts?: { quantity?: string | number; rate?: string | number }[]
|
||||
estimate_services?: { quantity?: string | number; rate?: string | number }[]
|
||||
estimate_expense_items?: { quantity?: string | number; rate?: string | number }[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const EstimateContext = createContext<EstimateContextValue | null>(null)
|
||||
|
||||
@@ -84,6 +84,18 @@ function InfoItem({
|
||||
export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
||||
return (
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="col-span-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{estimate?.labels?.map((label) => (
|
||||
<Badge
|
||||
key={label.id}
|
||||
style={label.color_code ? { backgroundColor: label.color_code } : undefined}
|
||||
>
|
||||
{label.title}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
@@ -136,7 +148,7 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
||||
label="Vehicle"
|
||||
value={
|
||||
estimate.vehicle
|
||||
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} (${estimate.vehicle.registration_number ?? ""})`.trim()
|
||||
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} ${(estimate as any).vehicle.year ?? ''}`.trim()
|
||||
: estimate.vehicle_id ? `#${estimate.vehicle_id}` : null
|
||||
}
|
||||
/>
|
||||
@@ -162,27 +174,7 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{estimate.labels && estimate.labels.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Tag className="size-4" /> Labels
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{estimate.labels.map((label) => (
|
||||
<Badge
|
||||
key={label.id}
|
||||
style={label.color_code ? { backgroundColor: label.color_code } : undefined}
|
||||
>
|
||||
{label.title}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
{estimate.customer_remarks && estimate.customer_remarks.length > 0 && (
|
||||
<Card>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"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 { useEstimate } from "./estimate-context"
|
||||
|
||||
export function EstimateTotalsSummary() {
|
||||
const estimate = useEstimate()
|
||||
|
||||
if (!estimate) return null
|
||||
|
||||
const parts = estimate.estimate_parts ?? []
|
||||
const services = estimate.estimate_services ?? []
|
||||
const expenses = estimate.estimate_expense_items ?? []
|
||||
const discount = estimate.discount
|
||||
const displayTotal = parseFloat(String(estimate.total ?? 0)) || 0
|
||||
|
||||
const hasItems = parts.length > 0 || services.length > 0 || expenses.length > 0
|
||||
|
||||
if (!hasItems && displayTotal === 0) return null
|
||||
|
||||
const subTotal = parseFloat(String(estimate.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>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user