- Implemented TemplateCheckpointEditDialog for creating and editing inspection checkpoints. - Added VendorActions component for managing vendor actions including edit, activate/deactivate, and delete. - Created VendorContext for managing vendor state across components. - Developed VendorGeneralInfo component to display detailed vendor information. - Introduced AedSymbol and Money components for consistent currency representation. - Added PromptDialog for user input prompts throughout the application. - Implemented RelationLink component for unified related-data display in CRUD tables. - Created InspectionTemplatesClient for API interactions related to inspection templates.
165 lines
7.6 KiB
TypeScript
165 lines
7.6 KiB
TypeScript
"use client"
|
|
|
|
import { useRouter } from "next/navigation"
|
|
import {
|
|
BadgeDollarSignIcon,
|
|
CalendarIcon,
|
|
CreditCardIcon,
|
|
HashIcon,
|
|
UserIcon,
|
|
} from "lucide-react"
|
|
import { CrudResource } from "@/shared/data-view/resource-page"
|
|
import { ColumnHeader } from "@/shared/data-view/table-view"
|
|
import FormDialog from "@/shared/components/form-dialog"
|
|
import { Card, CardContent } from "@/shared/components/ui/card"
|
|
import { PAYMENT_MADE_ROUTES } from "@garage/api"
|
|
import { PaymentMadeForm } from "@/modules/payment-mades/payment-made-form"
|
|
import { useBill } from "./bill-context"
|
|
import { formatDate } from "@/shared/utils/formatters"
|
|
import { getFullName } from "@/shared/utils/getFullName"
|
|
|
|
export function BillPaymentsSection() {
|
|
const bill = useBill()
|
|
const router = useRouter()
|
|
|
|
return (
|
|
<CrudResource<any>
|
|
extraParams={{ bill_id: bill?.id }}
|
|
routeKey={PAYMENT_MADE_ROUTES.INDEX}
|
|
getClient={(api) => ({
|
|
list: (query?: any) => api.paymentMades.list(query),
|
|
destroy: (id: string) => api.paymentMades.destroy(id),
|
|
})}
|
|
tableHeader={({ invalidateQuery }) => (
|
|
<Card className="mb-4">
|
|
<CardContent className="flex items-center justify-between">
|
|
<h2 className="text-base font-semibold">Payments Made</h2>
|
|
<FormDialog title="Record Payment">
|
|
{(resourceId) => (
|
|
<PaymentMadeForm
|
|
initialData={{
|
|
// API-shape keys so PaymentMadeForm.mapToFormValues
|
|
// can pick them up (it reads `payment_made` / `vendor_id`
|
|
// / `vendor.*`, not the form-shape fields). bill_id +
|
|
// bill_number are required because the bill field is
|
|
// hidden when billId is supplied, but the schema still
|
|
// demands a populated bill relation.
|
|
bill_id: bill?.id,
|
|
bill_number: bill?.bill_number,
|
|
payment_made: bill?.balance_due,
|
|
vendor_id: bill?.vendor?.id,
|
|
vendor: bill?.vendor,
|
|
payment_for: "bill",
|
|
}}
|
|
billId={bill?.id}
|
|
resourceId={resourceId}
|
|
onSuccess={() => {
|
|
router.refresh()
|
|
invalidateQuery()
|
|
}}
|
|
/>
|
|
)}
|
|
</FormDialog>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
columns={({ actionsColumn }) => [
|
|
{
|
|
accessorKey: "payment_number",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Payment #" />,
|
|
cell: ({ row }) => {
|
|
const item = row.original as any
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<HashIcon className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">{item.payment_number || "—"}</span>
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "vendor",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Vendor" />,
|
|
cell: ({ row }) => {
|
|
const item = row.original as any
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<UserIcon className="h-4 w-4 text-muted-foreground" />
|
|
<span>{item.vendor?.company_name || getFullName(item.vendor) || item.vendor_name || "—"}</span>
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "payment_made",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Amount" />,
|
|
cell: ({ row }) => {
|
|
const item = row.original as any
|
|
const amount = item.payment_made != null
|
|
? Number(item.payment_made).toLocaleString(undefined, {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
})
|
|
: "—"
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<BadgeDollarSignIcon className="h-4 w-4 text-emerald-600" />
|
|
<span className="font-semibold text-emerald-700 dark:text-emerald-400">
|
|
{amount}
|
|
</span>
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "payment_mode",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Payment Mode" />,
|
|
cell: ({ row }) => {
|
|
const item = row.original as any
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<CreditCardIcon className="h-4 w-4 text-muted-foreground" />
|
|
<span className="capitalize">
|
|
{item.payment_mode?.name || item.payment_mode_name || "—"}
|
|
</span>
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "payment_date",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Date" />,
|
|
cell: ({ row }) => {
|
|
const item = row.original as any
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<CalendarIcon className="h-4 w-4 text-muted-foreground" />
|
|
<span>{formatDate(item.payment_date) || "—"}</span>
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "notes",
|
|
header: () => <span>Notes</span>,
|
|
enableSorting: false,
|
|
cell: ({ row }) => {
|
|
const item = row.original as any
|
|
const notes = item.notes
|
|
if (!notes) return <span className="text-muted-foreground">—</span>
|
|
return (
|
|
<span
|
|
className="max-w-50 truncate block text-muted-foreground"
|
|
title={notes}
|
|
>
|
|
{notes}
|
|
</span>
|
|
)
|
|
},
|
|
},
|
|
actionsColumn(),
|
|
]}
|
|
/>
|
|
)
|
|
}
|