[dyad] Added a data table to the Book Out page to display booked-out items - wrote 3 file(s)

This commit is contained in:
[dyad] 2025-09-10 12:47:33 +02:00
parent 3729a13e96
commit f48d7fdb2f
3 changed files with 302 additions and 6 deletions

View file

@ -0,0 +1,186 @@
"use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Columns } from "lucide-react"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
interface BookOutDataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function BookOutDataTable<TData, TValue>({
columns,
data,
}: BookOutDataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(() => {
if (typeof window !== 'undefined' && window.innerWidth < 768) {
return {
sku: false,
dateBookedOut: false,
actions: false,
};
}
return {};
});
const [rowSelection, setRowSelection] = React.useState({})
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div>
<div className="flex items-center py-4">
<Input
placeholder="Filter by customer name..."
value={(table.getColumn("customerName")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("customerName")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="ml-auto">
<Columns className="h-4 w-4" />
<span className="sr-only">Toggle columns</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter(
(column) => column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border bg-white">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
}

View file

@ -0,0 +1,84 @@
"use client"
import { ColumnDef } from "@tanstack/react-table"
import { MoreHorizontal } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export type BookOutRecord = {
id: string
customerName: string
itemName: string
sku: string
quantity: number
dateBookedOut: string
}
export const columns: ColumnDef<BookOutRecord>[] = [
{
accessorKey: "customerName",
header: "Customer",
},
{
accessorKey: "itemName",
header: "Item Name",
},
{
accessorKey: "sku",
header: "SKU",
},
{
accessorKey: "quantity",
header: "Quantity",
},
{
accessorKey: "dateBookedOut",
header: "Date Booked Out",
cell: ({ row }) => {
const date = new Date(row.getValue("dateBookedOut"));
const formattedDate = date.toLocaleDateString("en-ZA", {
year: "numeric",
month: "short",
day: "numeric",
});
return <div className="font-medium">{formattedDate}</div>;
},
},
{
id: "actions",
enableHiding: false,
cell: ({ row }) => {
const record = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(record.id)}
>
Copy record ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View Details</DropdownMenuItem>
{/* Add more actions like 'Edit' or 'Delete' if needed */}
</DropdownMenuContent>
</DropdownMenu>
)
},
},
]

View file

@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect, useCallback } from "react";
import { BookOutForm } from "../components/book-out/BookOutForm";
import { Button } from "@/components/ui/button";
import { PlusCircle } from "lucide-react";
@ -10,13 +10,42 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { columns, BookOutRecord } from "../components/book-out/columns";
import { BookOutDataTable } from "../components/book-out/BookOutDataTable";
import pb from "@/lib/pocketbase";
const BookOut = () => {
const [bookOutOpen, setBookOutOpen] = useState(false);
const [bookOutRecords, setBookOutRecords] = useState<BookOutRecord[]>([]);
const [refreshTrigger, setRefreshTrigger] = useState(0); // State to trigger data refresh
const fetchBookOutRecords = useCallback(async () => {
try {
const records = await pb.collection('book_out_records').getList(1, 50, {
sort: '-created',
expand: 'customer,items', // Expand related customer and stock items
});
const formattedData: BookOutRecord[] = records.items.map((record: any) => ({
id: record.id,
customerName: record.expand?.customer?.comapny_name || 'N/A',
itemName: record.expand?.items?.name || 'N/A',
sku: record.expand?.items?.sku || 'N/A',
quantity: record.quantity,
dateBookedOut: record.date_booked_out,
}));
setBookOutRecords(formattedData);
} catch (error) {
console.error("Error fetching book out records:", error);
}
}, []);
useEffect(() => {
fetchBookOutRecords();
}, [fetchBookOutRecords, refreshTrigger]);
const handleRefreshData = () => {
setRefreshTrigger(prev => prev + 1); // Increment to trigger any future data fetching
setRefreshTrigger(prev => prev + 1); // Increment to trigger useEffect
};
return (
@ -44,10 +73,7 @@ const BookOut = () => {
</DialogContent>
</Dialog>
</div>
{/* Placeholder for a table or list of booked out items, if needed later */}
<div className="mt-8 p-4 border rounded-md text-gray-600">
No booked out items recorded yet. Use the "Book Out Item" button to add one.
</div>
<BookOutDataTable columns={columns} data={bookOutRecords} />
</div>
)
}