224 lines
8.3 KiB
TypeScript
224 lines
8.3 KiB
TypeScript
import {
|
|
FileText,
|
|
Hash,
|
|
Calendar,
|
|
User,
|
|
Car,
|
|
Building2,
|
|
Shield,
|
|
Tag,
|
|
MessageSquare,
|
|
} from "lucide-react"
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/shared/components/ui/card"
|
|
import { Badge } from "@/shared/components/ui/badge"
|
|
import { formatDate } from "@/shared/utils/formatters"
|
|
|
|
type EstimateData = {
|
|
id?: number
|
|
title?: string
|
|
estimate_number?: string
|
|
date?: string
|
|
customer_id?: number
|
|
vehicle_id?: number
|
|
department_id?: number
|
|
has_insurance?: boolean
|
|
enable_digital_authorisation?: boolean
|
|
insurance_type_id?: string | number | null
|
|
insurer_id?: string | number | null
|
|
service_writer_id?: number
|
|
footer?: string | null
|
|
created_at?: string
|
|
updated_at?: string
|
|
labels?: {
|
|
id?: number
|
|
title?: string
|
|
color_code?: string
|
|
}[]
|
|
customer_remarks?: {
|
|
id?: number
|
|
remark?: string
|
|
created_at?: string
|
|
}[]
|
|
// Nested relation objects from the API
|
|
customer?: { id?: number; first_name?: string; last_name?: string }
|
|
vehicle?: { id?: number; registration_number?: string; make?: string; model?: string }
|
|
department?: { id?: number; name?: string }
|
|
insurance_type?: { id?: number; title?: string }
|
|
insurer?: { id?: number; first_name?: string; last_name?: string }
|
|
service_writer?: { id?: number; first_name?: string; last_name?: string }
|
|
}
|
|
|
|
type EstimateGeneralInfoProps = {
|
|
estimate: EstimateData
|
|
}
|
|
|
|
function InfoItem({
|
|
icon: Icon,
|
|
label,
|
|
value,
|
|
}: {
|
|
icon: React.ComponentType<{ className?: string }>
|
|
label: string
|
|
value?: string | null
|
|
}) {
|
|
return (
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
|
<Icon className="size-4" />
|
|
</div>
|
|
<div className="flex flex-col gap-0.5">
|
|
<span className="text-xs text-muted-foreground">{label}</span>
|
|
<span className="text-sm font-medium">
|
|
{value || <span className="text-muted-foreground">—</span>}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
|
return (
|
|
<div className="grid gap-6 md:grid-cols-2">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<FileText className="size-4" /> Estimate Details
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-4">
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<InfoItem icon={FileText} label="Title" value={estimate.title} />
|
|
<InfoItem icon={Hash} label="Estimate #" value={estimate.estimate_number} />
|
|
</div>
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<InfoItem icon={Calendar} label="Date" value={formatDate(estimate.date)} />
|
|
<InfoItem
|
|
icon={Shield}
|
|
label="Insurance"
|
|
value={estimate.has_insurance ? "Yes" : "No"}
|
|
/>
|
|
</div>
|
|
<InfoItem
|
|
icon={Calendar}
|
|
label="Created"
|
|
value={
|
|
estimate.created_at
|
|
? new Date(estimate.created_at).toLocaleDateString()
|
|
: null
|
|
}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<User className="size-4" /> Related Records
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-4">
|
|
<InfoItem
|
|
icon={User}
|
|
label="Customer"
|
|
value={
|
|
estimate.customer
|
|
? `${estimate.customer.first_name ?? ""} ${estimate.customer.last_name ?? ""}`.trim()
|
|
: estimate.customer_id ? `#${estimate.customer_id}` : null
|
|
}
|
|
/>
|
|
<InfoItem
|
|
icon={Car}
|
|
label="Vehicle"
|
|
value={
|
|
estimate.vehicle
|
|
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} (${estimate.vehicle.registration_number ?? ""})`.trim()
|
|
: estimate.vehicle_id ? `#${estimate.vehicle_id}` : null
|
|
}
|
|
/>
|
|
<InfoItem
|
|
icon={Building2}
|
|
label="Department"
|
|
value={estimate.department?.name ?? (estimate.department_id ? `#${estimate.department_id}` : null)}
|
|
/>
|
|
<InfoItem
|
|
icon={Shield}
|
|
label="Insurance Type"
|
|
value={estimate.insurance_type?.title ?? null}
|
|
/>
|
|
<InfoItem
|
|
icon={User}
|
|
label="Service Writer"
|
|
value={
|
|
estimate.service_writer
|
|
? `${estimate.service_writer.first_name ?? ""} ${estimate.service_writer.last_name ?? ""}`.trim()
|
|
: null
|
|
}
|
|
/>
|
|
</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>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<MessageSquare className="size-4" /> Customer Remarks
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-3">
|
|
{estimate.customer_remarks.map((remark) => (
|
|
<div key={remark.id} className="rounded-md border p-3 text-sm">
|
|
<p>{remark.remark}</p>
|
|
{remark.created_at && (
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
{new Date(remark.created_at).toLocaleDateString()}
|
|
</p>
|
|
)}
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{estimate.footer && (
|
|
<Card className="md:col-span-2">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<FileText className="size-4" /> Footer
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm">{estimate.footer}</p>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|