garage-erp/apps/dashboard/modules/estimates/create-invoice-from-estimate-button.tsx
2026-04-24 12:28:30 +03:00

142 lines
4.8 KiB
TypeScript

"use client"
import { useState } from "react"
import { FileText } from "lucide-react"
import { useQuery } from "@tanstack/react-query"
import { Button } from "@/shared/components/ui/button"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/shared/components/ui/dialog"
import { ScrollArea } from "@/shared/components/ui/scroll-area"
import { InvoiceForm } from "@/modules/invoices/invoice-form"
import { toRelation } from "@/shared/lib/utils"
import { useAuthApi } from "@/shared/useApi"
import { ESTIMATE_ROUTES } from "@garage/api"
import { useEstimate } from "./estimate-context"
function mapEstimateToInvoiceInitialData(
estimate: Record<string, any>,
services: any[],
parts: any[],
expenseItems: any[],
) {
return {
subject: estimate.title ?? "",
notes: estimate.footer ?? "",
estimate: toRelation(estimate.id, estimate.title ?? `#${estimate.estimate_number ?? estimate.id}`),
customer: toRelation(
estimate.customer_id,
estimate.customer
? `${estimate.customer.first_name ?? ""} ${estimate.customer.last_name ?? ""}`.trim()
: undefined
),
vehicle: toRelation(
estimate.vehicle_id,
estimate.vehicle
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} (${estimate.vehicle.registration_number ?? ""})`.trim()
: undefined
),
department: toRelation(estimate.department_id, estimate.department?.name),
invoice_number: "",
invoice_date: estimate.date ?? "",
due_date: "",
status: "draft" as const,
services: services.map((s: any) => ({
service_id: s.service_id ?? s.id,
title: s.service.labor_name ?? s.title ?? "",
quantity: Number(s.quantity) || 1,
rate: Number(s.rate) || 0,
description: s.description ?? "",
})),
parts: parts.map((p: any) => ({
part_id: p.part_id ?? p.id,
title: p.part.title ?? "",
quantity: Number(p.quantity) || 1,
rate: Number(p.rate) || 0,
description: p.description ?? "",
})),
expense_items: expenseItems.map((e: any) => ({
expense_id: e.expense_item_id ?? e.id,
title: e.expense_item.item_name ?? e.title ?? "",
quantity: Number(e.quantity) || 1,
rate: Number(e.rate) || 0,
description: e.description ?? "",
})),
}
}
export function CreateInvoiceFromEstimateButton() {
const [open, setOpen] = useState(false)
const estimateContext = useEstimate()
const api = useAuthApi()
const estimateId = String(estimateContext?.id ?? "")
const { data: servicesData } = useQuery({
queryKey: [ESTIMATE_ROUTES.SERVICES, estimateId, "for-invoice"],
queryFn: async () => {
const res = await api.estimates.listServices(estimateId)
return ((res as any)?.data ?? []) as any[]
},
enabled: open && !!estimateId,
})
const { data: partsData } = useQuery({
queryKey: [ESTIMATE_ROUTES.PARTS, estimateId, "for-invoice"],
queryFn: async () => {
const res = await api.estimates.listParts(estimateId)
return ((res as any)?.data ?? []) as any[]
},
enabled: open && !!estimateId,
})
const { data: expenseItemsData } = useQuery({
queryKey: [ESTIMATE_ROUTES.EXPENSE_ITEMS, estimateId, "for-invoice"],
queryFn: async () => {
const res = await api.estimates.listExpenseItems(estimateId)
return ((res as any)?.data ?? []) as any[]
},
enabled: open && !!estimateId,
})
if (!estimateContext) return null
const initialData = estimateContext.data
? mapEstimateToInvoiceInitialData(
estimateContext.data,
servicesData ?? [],
partsData ?? [],
expenseItemsData ?? [],
)
: undefined
return (
<>
<Button variant="outline" size="sm" onClick={() => setOpen(true)}>
<FileText className="me-2 size-4" />
Generate Invoice
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="min-w-2xl">
<DialogHeader>
<DialogTitle>Generate Invoice from Estimate</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[75vh] px-1">
<InvoiceForm
initialData={initialData}
onSuccess={() => setOpen(false)}
/>
</ScrollArea>
</DialogContent>
</Dialog>
</>
)
}