garage-erp/apps/dashboard/modules/bills/bill-payments-section.tsx
humam kerdiah 4bfd8c84a9 feat: add template checkpoint edit dialog and vendor management components
- 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.
2026-05-18 12:08:42 +04:00

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(),
]}
/>
)
}