Skip to content

Commit 947b160

Browse files
feat(Invoice): create invoice listing page with data table and actions
1 parent 09ddc9a commit 947b160

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { DataTable } from '@/components/data-table';
2+
import Heading from '@/components/heading';
3+
import Layout from '@/layouts/app-layout';
4+
import { column } from '@/pages/invoices/column';
5+
import invoices from '@/routes/invoices';
6+
import { BreadcrumbItem } from '@/types';
7+
import { PaginationInvoiceWithRelations } from '@/types/application/invoice';
8+
import { Head } from '@inertiajs/react';
9+
10+
const breadcrumbs: BreadcrumbItem[] = [
11+
{
12+
title: 'Invoices',
13+
href: invoices.index().url,
14+
},
15+
];
16+
17+
interface IndexProps {
18+
invoices: PaginationInvoiceWithRelations;
19+
}
20+
21+
export default function Index({ invoices }: Readonly<IndexProps>) {
22+
return (
23+
<Layout breadcrumbs={breadcrumbs}>
24+
<Head title="Listing Invoices" />
25+
26+
<div className="px-4 py-6">
27+
<Heading title="Invoices" description="Listing all the invoices in the system." />
28+
29+
<section aria-label="Invoices Table">
30+
<DataTable columns={column} data={invoices.data} pagination={invoices} />
31+
</section>
32+
</div>
33+
</Layout>
34+
);
35+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { DataTableColumnHeader } from '@/components/data-table-column-header';
2+
import DropdownMenuCopyButton from '@/components/ui-helpers/dropdown-menu-copy-button';
3+
import { Badge } from '@/components/ui/badge';
4+
import { Button } from '@/components/ui/button';
5+
import {
6+
DropdownMenu,
7+
DropdownMenuContent,
8+
DropdownMenuItem,
9+
DropdownMenuLabel,
10+
DropdownMenuSeparator,
11+
DropdownMenuTrigger,
12+
} from '@/components/ui/dropdown-menu';
13+
import { formatCurrency, normalizeString } from '@/lib/utils';
14+
import { generatePdf } from '@/routes/invoices';
15+
import { InvoiceWithRelations } from '@/types/application/invoice';
16+
import { ColumnDef } from '@tanstack/react-table';
17+
import { format } from 'date-fns';
18+
import { MoreHorizontal } from 'lucide-react';
19+
20+
export const column: ColumnDef<InvoiceWithRelations>[] = [
21+
{
22+
accessorKey: 'id',
23+
header: ({ column }) => <DataTableColumnHeader column={column} title="ID" />,
24+
enableSorting: true,
25+
},
26+
{
27+
accessorKey: 'consultation_date',
28+
header: ({ column }) => <DataTableColumnHeader column={column} title="Consultation Date" />,
29+
cell: ({ row }) => format(row.original.consultation_date, 'PPP'),
30+
enableSorting: true,
31+
},
32+
{
33+
id: 'patient_info.first_name',
34+
accessorKey: 'patient_info.first_name',
35+
header: ({ column }) => <DataTableColumnHeader column={column} title="Patient" />,
36+
cell: ({ row }) => row.original.patient_info.full_name,
37+
enableSorting: true,
38+
enableHiding: false,
39+
},
40+
{
41+
accessorKey: 'amount',
42+
header: ({ column }) => <DataTableColumnHeader column={column} title="Amount" />,
43+
cell: ({ row }) => formatCurrency(row.original.amount),
44+
enableSorting: true,
45+
},
46+
{
47+
accessorKey: 'due_date',
48+
header: ({ column }) => <DataTableColumnHeader column={column} title="Due Date" />,
49+
cell: ({ row }) => format(row.original.due_date, 'PPP'),
50+
enableSorting: true,
51+
},
52+
{
53+
accessorKey: 'payment_method',
54+
header: ({ column }) => <DataTableColumnHeader column={column} title="Payment Method" />,
55+
cell: ({ row }) => normalizeString(row.original.payment_method),
56+
enableSorting: true,
57+
},
58+
{
59+
accessorKey: 'status',
60+
header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
61+
cell: ({ row }) => {
62+
const variant = row.original.status === 'paid' ? 'default' : 'destructive';
63+
return (
64+
<Badge variant={variant} className="select-none">
65+
{normalizeString(row.original.status)}
66+
</Badge>
67+
);
68+
},
69+
enableSorting: true,
70+
},
71+
72+
{
73+
id: 'actions',
74+
cell: ({ row }) => {
75+
const notes = row.original.notes;
76+
77+
return (
78+
<DropdownMenu>
79+
<DropdownMenuTrigger asChild>
80+
<Button variant="ghost" className="h-8 w-8 p-0">
81+
<span className="sr-only">Open menu</span>
82+
<MoreHorizontal className="h-4 w-4" />
83+
</Button>
84+
</DropdownMenuTrigger>
85+
<DropdownMenuContent align="end">
86+
<DropdownMenuLabel>Actions</DropdownMenuLabel>
87+
<DropdownMenuItem asChild>
88+
<DropdownMenuCopyButton content={notes}>Copy Notes</DropdownMenuCopyButton>
89+
</DropdownMenuItem>
90+
91+
<DropdownMenuSeparator />
92+
<DropdownMenuItem asChild>
93+
<a href={generatePdf(row.original.id).url} target="_blank" rel="external">
94+
Generate PDF
95+
</a>
96+
</DropdownMenuItem>
97+
</DropdownMenuContent>
98+
</DropdownMenu>
99+
);
100+
},
101+
},
102+
];

0 commit comments

Comments
 (0)