This commit is contained in:
2026-04-23 14:38:41 +03:00
parent f17dd1486c
commit c0f78c6e18
90 changed files with 10819 additions and 1583 deletions
@@ -0,0 +1,42 @@
"use client"
import { use } from "react"
import { useQuery } from "@tanstack/react-query"
import { useAuthApi } from "@/shared/useApi"
import { useRouter } from "next/navigation"
import { BillForm } from "@/modules/bills/bill-form"
import DashboardPage from "@/base/components/layout/dashboard/dashboard-page"
import { BILL_ROUTES } from "@garage/api"
export default function BillEditPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
const api = useAuthApi()
const router = useRouter()
const { data, isLoading } = useQuery({
queryKey: [BILL_ROUTES.BY_ID, id],
queryFn: () => api.bills.show(id),
})
if (isLoading) {
return (
<DashboardPage header={null}>
<div className="flex items-center justify-center p-8 text-muted-foreground">
Loading...
</div>
</DashboardPage>
)
}
return (
<DashboardPage header={null}>
<div className="p-6">
<BillForm
resourceId={id}
initialData={data}
onSuccess={() => router.push(`/purchase/bill/${id}`)}
/>
</div>
</DashboardPage>
)
}
@@ -0,0 +1,43 @@
import { DashboardDetailsPage } from '@/base/components/layout/dashboard'
import { getServerApi } from '@garage/api/server'
import { BillActions } from '@/modules/bills/bill-actions'
import { BillProvider, type BillResponse } from '@/modules/bills/bill-context'
import BillStatusBadge from '@/modules/bills/bill-status-badge'
import { ReceiptIcon } from 'lucide-react'
import React from 'react'
export default async function BillDetailLayout(props: {
params: Promise<{ id: string }>
children: React.ReactNode
}) {
const { id } = await props.params
const api = await getServerApi()
const bill = await api.bills.show(id)
const data = bill.data as BillResponse
const title = data?.title || data?.bill_number || 'Bill Details'
return (
<BillProvider bill={data}>
<DashboardDetailsPage
className="p-0 lg:p-0"
title={title}
description={data?.bill_number ? `Bill #: ${data.bill_number}` : undefined}
icon={<ReceiptIcon className="size-5" />}
backHref="/purchase/bill"
actions={
<div className="flex space-x-2 items-center">
<BillStatusBadge bill={{id, status:data?.status}} />
<BillActions billId={id} />
</div>
}
tabs={[
{
href: `/purchase/bill/${id}`,
label: 'Details',
},
]}
>
{props.children}
</DashboardDetailsPage>
</BillProvider>
)
}
@@ -0,0 +1,32 @@
import { getServerApi } from '@garage/api/server'
import { BillGeneralInfo } from '@/modules/bills/bill-general-info'
import { BillPartsSection } from '@/modules/bills/bill-parts-section'
import { BillServicesSection } from '@/modules/bills/bill-services-section'
import { BillExpensesSection } from '@/modules/bills/bill-expenses-section'
import { BillTotalsSummary } from '@/modules/bills/bill-totals-summary'
import { BillPaymentsSection } from '@/modules/bills/bill-payments-section'
import DashboardPage from '@/base/components/layout/dashboard/dashboard-page'
export default async function BillDetailPage(props: { params: Promise<{ id: string }> }) {
const { id } = await props.params
const api = await getServerApi()
const bill = await api.bills.show(id)
const data = (bill as any)?.data ?? bill
if (!data) {
return <div className="text-muted-foreground">Bill not found.</div>
}
return (
<DashboardPage header={null}>
<div className="grid gap-6">
<BillGeneralInfo />
<BillPartsSection parts={data.parts} />
<BillServicesSection services={data.services} />
<BillExpensesSection expenses={data.expenses} />
<BillPaymentsSection />
<BillTotalsSummary />
</div>
</DashboardPage>
)
}
@@ -1,5 +1,6 @@
"use client"
import { useRouter } from "next/navigation"
import FormDialog from "@/shared/components/form-dialog"
import { Badge } from "@/shared/components/ui/badge"
import { ResourcePage } from "@/shared/data-view/resource-page"
@@ -7,16 +8,20 @@ import { ColumnHeader } from "@/shared/data-view/table-view"
import { BillForm } from "@/modules/bills/bill-form"
import { BILL_ROUTES } from "@garage/api"
import type { BillsClient } from "@garage/api"
import { formatDate } from "@/shared/utils/formatters"
export default function BillsPage() {
const router = useRouter()
return (
<ResourcePage<BillsClient>
pageTitle="Bills"
routeKey={BILL_ROUTES.INDEX}
getClient={(api) => api.bills}
onRowClick={(row) => router.push(`/purchase/bill/${(row as any).id}`)}
headerProps={({ selectedItem, invalidateQuery }) => ({
actions: (
<FormDialog title="Bill">
<FormDialog classNames={{ dialogContent: "lg:min-w-6xl" }} title="Bill">
{(resourceId) => (
<BillForm
resourceId={resourceId}
@@ -33,37 +38,45 @@ export default function BillsPage() {
header: ({ column }) => <ColumnHeader column={column} title="Bill #" />,
cell: ({ row }) => (row.original as any).bill_number || "—",
},
{
accessorKey: "total",
header: ({ column }) => <ColumnHeader column={column} title="Total" />,
cell: ({ row }) => (row.original as any).total || "—",
},
{
accessorKey: "balance_due",
header: ({ column }) => <ColumnHeader column={column} title="Balance Due" />,
cell: ({ row }) => (row.original as any).balance_due || "—",
},
{
accessorKey: "title",
header: ({ column }) => <ColumnHeader column={column} title="Title" />,
},
{
accessorKey: "vendor_name",
accessorKey: "vendor",
header: ({ column }) => <ColumnHeader column={column} title="Vendor" />,
cell: ({ row }) => (row.original as any).vendor_name || "—",
cell: ({ row }) => (row.original as any).vendor?.name || "—",
},
{
accessorKey: "bill_date",
header: ({ column }) => <ColumnHeader column={column} title="Bill Date" />,
cell: ({ row }) => {
const value = (row.original as any).bill_date
return value ? new Date(value).toLocaleDateString() : "—"
},
cell: ({ row }) => formatDate((row.original as any).bill_date) || "—",
},
{
accessorKey: "bill_due_date",
header: ({ column }) => <ColumnHeader column={column} title="Due Date" />,
cell: ({ row }) => {
const value = (row.original as any).bill_due_date
return value ? new Date(value).toLocaleDateString() : "—"
},
cell: ({ row }) => formatDate((row.original as any).bill_due_date) || "—",
},
{
accessorKey: "status",
header: ({ column }) => <ColumnHeader column={column} title="Status" />,
cell: ({ row }) => {
const status = (row.original as any).status
return <Badge variant={status === "paid" ? "default" : "secondary"}>{status || "—"}</Badge>
return (
<Badge variant={status === "paid" ? "default" : "secondary"}>
{status?.replace(/_/g, " ") || "—"}
</Badge>
)
},
},
actionsColumn(),
@@ -71,3 +84,4 @@ export default function BillsPage() {
/>
)
}