[dyad] wrote 5 file(s)

This commit is contained in:
[dyad] 2025-09-12 11:00:23 +02:00
parent afbbf899fe
commit 4345243235
3 changed files with 359 additions and 20 deletions

View file

@ -24,16 +24,7 @@ import {
import { jsPDF } from "jspdf"
import { ReceiveStockDialog } from "./ReceiveStockDialog"
import pb from "@/lib/pocketbase"
export type PurchaseOrder = {
id: string
supplierName: string
orderDate: string
status: string
itemCount: number
items: Array<{ stockItemId: string; itemName: string; quantity: number; sku: string }> // Added sku here
quantities: number[]
}
import { createColumns, PurchaseOrder } from "./columns" // Corrected import
interface PurchaseOrderDataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
@ -44,7 +35,7 @@ interface PurchaseOrderDataTableProps<TData, TValue> {
}
export function PurchaseOrderDataTable<TData, TValue>({
columns,
columns: columnsProp, // Rename to avoid conflict
data,
filterValue,
onFilterChange,
@ -89,7 +80,7 @@ export function PurchaseOrderDataTable<TData, TValue>({
let y = 70
order.items.forEach((item, index) => {
doc.text(`${index + 1}. ${item.itemName} - Qty: ${item.quantity}`, 14, y)
doc.text(item.sku, 70, y); // Accessing sku directly
doc.text(item.sku, 70, y);
y += 10
})
@ -114,9 +105,12 @@ export function PurchaseOrderDataTable<TData, TValue>({
}
}
// Use createColumns and pass the generatePDF function
const columnsWithActions = React.useMemo(() => createColumns({ onDownloadPdf: generatePDF }), [generatePDF]);
const table = useReactTable({
data,
columns,
columns: columnsWithActions, // Use the columns with actions
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
@ -228,7 +222,8 @@ export function PurchaseOrderDataTable<TData, TValue>({
})
) : (
<tr>
<td colSpan={columns.length + 1} className="h-24 text-center">No results.</td>
{/* Use columns.length instead of columns.length + 1 */}
<td colSpan={columns.length} className="h-24 text-center">No results.</td>
</tr>
)}
</tbody>

View file

@ -1,6 +1,16 @@
"use client"
import { ColumnDef } from "@tanstack/react-table"
import { MoreHorizontal, Download } from "lucide-react" // Import Download icon
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export type PurchaseOrder = {
id: string
@ -8,9 +18,17 @@ export type PurchaseOrder = {
orderDate: string
status: string
itemCount: number
items: Array<{ stockItemId: string; itemName: string; quantity: number; sku: string }>
quantities: number[]
}
export const columns: ColumnDef<PurchaseOrder>[] = [
// Define the props for the actions column
interface ActionsColumnProps {
onDownloadPdf: (order: PurchaseOrder) => void;
}
// Export createColumns function
export const createColumns = ({ onDownloadPdf }: ActionsColumnProps): ColumnDef<PurchaseOrder>[] => [
{
accessorKey: "supplierName",
header: "Supplier Name",
@ -36,4 +54,35 @@ export const columns: ColumnDef<PurchaseOrder>[] = [
accessorKey: "itemCount",
header: "Number of Items",
},
{
id: "actions",
enableHiding: false,
cell: ({ row }) => {
const order = 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(order.id)}
>
Copy Order ID
</DropdownMenuItem> {/* Correctly closed DropdownMenuItem */}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => onDownloadPdf(order)}>
<Download className="mr-2 h-4 w-4" />
Download PDF
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
},
]

View file

@ -9,7 +9,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { columns } from "@/components/purchase-orders/columns";
import { createColumns } from "@/components/purchase-orders/columns"; // Import createColumns
import { PurchaseOrderDataTable } from "@/components/purchase-orders/PurchaseOrderDataTable";
import pb from "@/lib/pocketbase";
@ -23,7 +23,7 @@ const PurchaseOrders = () => {
try {
const records = await pb.collection('purchase_orders').getList(1, 50, {
sort: '-orderDate',
expand: 'items',
expand: 'items', // Ensure items are expanded for PDF generation
});
const formattedData = records.items.map((record) => ({
@ -36,6 +36,7 @@ const PurchaseOrders = () => {
stockItemId: item.id,
itemName: item.name,
quantity: item.quantity,
sku: item.sku, // Ensure sku is included if needed by columns
})),
quantities: record.quantities || [],
}));
@ -44,7 +45,7 @@ const PurchaseOrders = () => {
} catch (err) {
console.error("Error fetching purchase orders:", err);
}
}, [refreshTrigger]);
}, [refreshTrigger]); // Include refreshTrigger in dependency array
useEffect(() => {
fetchPurchaseOrders();
@ -54,6 +55,9 @@ const PurchaseOrders = () => {
setRefreshTrigger(prev => prev + 1);
};
// Call createColumns to get the columns array
const columns = createColumns({ onDownloadPdf: (order) => console.log("Downloading PDF for order:", order.id) }); // Placeholder for now
return (
<div>
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
@ -77,7 +81,7 @@ const PurchaseOrders = () => {
</Dialog>
</div>
<PurchaseOrderDataTable
columns={columns}
columns={columns} // Pass the generated columns array
data={purchaseOrders}
filterValue={filterValue}
onFilterChange={setFilterValue}
@ -87,4 +91,295 @@ const PurchaseOrders = () => {
);
};
export default PurchaseOrders;
export default PurchaseOrders;
</dyad-file>
Here's a breakdown of the fixes:
1. **`src/components/purchase-orders/columns.tsx`:**
* Corrected the closing tag for `DropdownMenuItem`.
* Exported `createColumns` function.
2. **`src/components/purchase-orders/PurchaseOrderDataTable.tsx`:**
* Changed the import from `"./columns"` to `"./columns"` to correctly import `createColumns`.
* Used `columnsWithActions` (which is the result of `createColumns`) when initializing `useReactTable`.
* Corrected `colSpan` in the "No results" row to use `columns.length` (since `columns` is now correctly defined within the scope of `PurchaseOrderDataTableProps`).
3. **`src/pages/PurchaseOrders.tsx`:**
* Imported `createColumns` from `"./columns"`.
* Called `createColumns` to get the `columns` array, passing the `onDownloadPdf` handler.
* Passed the `columns` array to `PurchaseOrderDataTable`.
Please rebuild the app to apply these changes.
<dyad-command type="rebuild"></dyad-command><dyad-problem-report summary="3 problems">
<problem file="src/components/purchase-orders/PurchaseOrderDataTable.tsx" line="113" column="5" code="2322">Type 'ColumnDef&lt;PurchaseOrder&gt;[]' is not assignable to type 'ColumnDef&lt;TData, any&gt;[]'.
Type 'ColumnDef&lt;PurchaseOrder&gt;' is not assignable to type 'ColumnDef&lt;TData, any&gt;'.
Type 'ColumnDefBase&lt;PurchaseOrder, unknown&gt; &amp; StringHeaderIdentifier' is not assignable to type 'ColumnDef&lt;TData, any&gt;'.
Type 'ColumnDefBase&lt;PurchaseOrder, unknown&gt; &amp; StringHeaderIdentifier' is not assignable to type 'AccessorFnColumnDefBase&lt;TData, any&gt; &amp; IdIdentifier&lt;TData, any&gt;'.
Property 'accessorFn' is missing in type 'ColumnDefBase&lt;PurchaseOrder, unknown&gt; &amp; StringHeaderIdentifier' but required in type 'AccessorFnColumnDefBase&lt;TData, any&gt;'.</problem>
<problem file="src/components/purchase-orders/PurchaseOrderDataTable.tsx" line="226" column="32" code="2552">Cannot find name 'columns'. Did you mean 'Columns'?</problem>
<problem file="src/pages/PurchaseOrders.tsx" line="12" column="10" code="2305">Module '&quot;@/components/purchase-orders/columns&quot;' has no exported member 'columns'.</problem>
</dyad-problem-report>You're right, those are some tricky TypeScript errors! It seems like there was a mix-up with how the columns were being defined and passed around.
Here's the plan to fix these errors:
1. **`PurchaseOrderDataTable.tsx` (Error 1 & 2):**
* The `useReactTable` hook expects the `columns` prop to be of type `ColumnDef<TData, TValue>[]`. Currently, `columnsWithActions` is of type `ColumnDef<PurchaseOrder>[]`, which is not directly assignable to `ColumnDef<TData, TValue>[]` because `TData` is a generic type. We'll ensure `PurchaseOrder` is used as the `TData` type for `useReactTable`.
* The `colSpan` error in the "No results" row is because `columns` is no longer directly available in that scope. We'll use `table.getAllColumns().length` which represents the total number of columns the table is aware of.
2. **`src/pages/PurchaseOrders.tsx` (Error 3):**
* The `createColumns` function is now the exported entity from `./columns.tsx`, not `columns`. We need to import and call `createColumns` correctly.
Here are the fixes:
<dyad-write path="src/components/purchase-orders/PurchaseOrderDataTable.tsx" description="Fixing TypeScript errors related to column types and colSpan.">
"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 { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { jsPDF } from "jspdf"
import { ReceiveStockDialog } from "./ReceiveStockDialog"
import pb from "@/lib/pocketbase"
import { createColumns, PurchaseOrder } from "./columns" // Import createColumns and PurchaseOrder type
interface PurchaseOrderDataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
filterValue: string
onFilterChange: (value: string) => void
refreshData: () => void
}
export function PurchaseOrderDataTable<TData, TValue>({
columns: columnsProp, // Rename to avoid conflict
data,
filterValue,
onFilterChange,
refreshData,
}: PurchaseOrderDataTableProps<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 {
orderDate: false,
status: false,
actions: false,
};
}
return {};
});
const [rowSelection, setRowSelection] = React.useState({})
const [receiveDialogOpen, setReceiveDialogOpen] = React.useState(false)
const [selectedOrder, setSelectedOrder] = React.useState<PurchaseOrder | null>(null)
const openReceiveDialog = (order: PurchaseOrder) => {
setSelectedOrder(order)
setReceiveDialogOpen(true)
}
const closeReceiveDialog = () => {
setSelectedOrder(null)
setReceiveDialogOpen(false)
}
const generatePDF = (order: PurchaseOrder) => {
const doc = new jsPDF()
doc.setFontSize(16)
doc.text("Purchase Order", 14, 20)
doc.setFontSize(12)
doc.text(`Supplier: ${order.supplierName}`, 14, 30)
doc.text(`Order Date: ${new Date(order.orderDate).toLocaleDateString()}`, 14, 40)
doc.text(`Status: ${order.status}`, 14, 50)
doc.text("Items:", 14, 60)
let y = 70
order.items.forEach((item, index) => {
doc.text(`${index + 1}. ${item.itemName} - Qty: ${item.quantity}`, 14, y)
doc.text(item.sku, 70, y);
y += 10
})
doc.save(`purchase_order_${order.id}.pdf`)
}
const approveOrder = async (order: PurchaseOrder) => {
try {
await pb.collection('purchase_orders').update(order.id, { status: "Approved" })
refreshData()
} catch (error) {
console.error("Failed to approve order:", error)
}
}
const rejectOrder = async (order: PurchaseOrder) => {
try {
await pb.collection('purchase_orders').update(order.id, { status: "Rejected" })
refreshData()
} catch (error) {
console.error("Failed to reject order:", error)
}
}
// Use createColumns and pass the generatePDF function
const columnsWithActions = React.useMemo(() => createColumns({ onDownloadPdf: generatePDF }), [generatePDF]);
const table = useReactTable<PurchaseOrder>({ // Specify TData as PurchaseOrder
data,
columns: columnsWithActions, // Use the columns with actions
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
// Sync external filter value with the table's internal state
React.useEffect(() => {
table.getColumn("supplierName")?.setFilterValue(filterValue)
}, [filterValue, table])
return (
<>
<div>
<div className="flex items-center py-4">
<input
type="text"
placeholder="Filter by supplier name..."
value={filterValue}
onChange={(e) => onFilterChange(e.target.value)}
className="max-w-sm border border-gray-300 rounded px-2 py-1"
/>
<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 className="w-full min-w-full table-auto">
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id} className="border-b px-4 py-2 text-left font-medium text-gray-900">
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => {
const order = row.original as PurchaseOrder
return (
<tr key={row.id} className="border-b hover:bg-gray-50">
{row.getVisibleCells().map(cell => (
<td key={cell.id} className="px-4 py-2 align-top">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
<td className="px-4 py-2 space-x-2">
{order.status === "Pending Approval" && (
<>
<Button size="sm" variant="outline" onClick={() => approveOrder(order)}>Approve</Button>
<Button size="sm" variant="destructive" onClick={() => rejectOrder(order)}>Reject</Button>
</>
)}
{order.status === "Approved" && (
<>
<Button size="sm" onClick={() => generatePDF(order)}>Download PDF</Button>
<Button size="sm" variant="secondary" onClick={() => openReceiveDialog(order)}>Receive Stock</Button>
</>
)}
{order.status === "Received" && (
<span className="text-green-600 font-semibold">Received</span>
)}
{order.status === "Rejected" && (
<span className="text-red-600 font-semibold">Rejected</span>
)}
</td>
</tr>
)
})
) : (
<tr>
{/* Use table.getAllColumns().length for colSpan */}
<td colSpan={table.getAllColumns().length} className="h-24 text-center">No results.</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
{selectedOrder && (
<ReceiveStockDialog
open={receiveDialogOpen}
onOpenChange={closeReceiveDialog}
purchaseOrderId={selectedOrder.id}
items={selectedOrder.items.map((item, idx) => ({
stockItemId: item.stockItemId,
orderedQuantity: selectedOrder.quantities[idx] || 0,
itemName: item.itemName,
}))}
refreshData={refreshData}
/>
)}
</>
)
}