diff --git a/src/components/purchase-orders/PurchaseOrderDataTable.tsx b/src/components/purchase-orders/PurchaseOrderDataTable.tsx index 5ccdc26..11f504e 100644 --- a/src/components/purchase-orders/PurchaseOrderDataTable.tsx +++ b/src/components/purchase-orders/PurchaseOrderDataTable.tsx @@ -13,33 +13,26 @@ import { getSortedRowModel, useReactTable, } from "@tanstack/react-table" -import { Columns } from "lucide-react" -import { format } from "date-fns" // Import format for date display - -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table" +import { Columns, Download, Eye } from "lucide-react" import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { jsPDF } from "jspdf" // Import jsPDF - -// Import PurchaseOrder type and createColumns function +import { jsPDF } from "jspdf" +import { ReceiveStockDialog } from "./ReceiveStockDialog" +import pb from "@/lib/pocketbase" import { createColumns, PurchaseOrder } from "./columns" -import { ReceiveStockDialog } from "../receive-stock/ReceiveStockDialog" // Assuming ReceiveStockDialog is in this path -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" // Import Dialog components -import pb from "@/lib/pocketbase" // Import pb -import { showError, showSuccess } from "@/utils/toast" // Import toast functions +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" interface PurchaseOrderDataTableProps { columns: ColumnDef[] @@ -50,7 +43,7 @@ interface PurchaseOrderDataTableProps { } export function PurchaseOrderDataTable({ - columns, + columns: columnsProp, data, filterValue, onFilterChange, @@ -83,12 +76,17 @@ export function PurchaseOrderDataTable({ setReceiveDialogOpen(false) } - const handleViewOrder = useCallback((order: PurchaseOrder) => { // useCallback is now correctly imported and used + const handleViewOrder = (order: PurchaseOrder) => { setSelectedOrder(order) setViewDialogOpen(true) - }, []); + } - const generatePDF = useCallback((order: PurchaseOrder) => { // useCallback is now correctly imported and used + const closeViewDialog = () => { + setSelectedOrder(null) + setViewDialogOpen(false) + } + + const generatePDF = (order: PurchaseOrder) => { const doc = new jsPDF(); // Set font and colors @@ -126,7 +124,8 @@ export function PurchaseOrderDataTable({ doc.setFontSize(10); doc.setTextColor(textColor); - doc.text(`Supplier Name: ${order.supplierName}`, 14, 75); // Use supplierName from expanded data + doc.text(`Supplier Name: ${order.supplierName}`, 14, 75); + // Add more supplier details if available in the future doc.text("Address: Supplier Address Line 1", 14, 80); doc.text("City, Postal Code", 14, 85); doc.text("Contact: Supplier Contact Person", 14, 90); @@ -161,7 +160,7 @@ export function PurchaseOrderDataTable({ order.items.forEach((item, index) => { const orderedQuantity = order.quantities[index] || 0; - const unitPrice = item.costPrice || 0; + const unitPrice = item.costPrice || 0; // Use costPrice from the item const itemTotal = orderedQuantity * unitPrice; subtotal += itemTotal; @@ -208,7 +207,7 @@ export function PurchaseOrderDataTable({ doc.text(subtotal.toLocaleString("en-ZA", { style: "currency", currency: "ZAR" }), 180, y); // Grand Total doc.save(`purchase_order_${order.id}.pdf`); - }, []); + }; const approveOrder = async (order: PurchaseOrder) => { try { @@ -216,7 +215,6 @@ export function PurchaseOrderDataTable({ refreshData() } catch (error) { console.error("Failed to approve order:", error) - showError("Failed to approve order.") } } @@ -226,21 +224,14 @@ export function PurchaseOrderDataTable({ refreshData() } catch (error) { console.error("Failed to reject order:", error) - showError("Failed to reject order.") } } - const handleViewOrder = useCallback((order: PurchaseOrder) => { - setSelectedOrder(order) - setViewDialogOpen(true) - }, []); - - // Use the createColumns function to get the columns, passing the necessary callbacks const columnsWithActions = React.useMemo(() => createColumns({ onDownloadPdf: generatePDF, onViewOrder: handleViewOrder }), [generatePDF, handleViewOrder]); const table = useReactTable({ data: data as PurchaseOrder[], - columns: columnsWithActions, // Use the generated columns + columns: columnsWithActions, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), onSortingChange: setSorting, @@ -258,18 +249,19 @@ export function PurchaseOrderDataTable({ }) React.useEffect(() => { - table.getColumn("supplier")?.setFilterValue(filterValue) // Filter by supplier name + table.getColumn("supplierName")?.setFilterValue(filterValue) }, [filterValue, table]) return ( <>
- onFilterChange(e.target.value)} - className="max-w-sm" + className="max-w-sm border border-gray-300 rounded px-2 py-1" /> @@ -300,53 +292,64 @@ export function PurchaseOrderDataTable({
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} - +
+ + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + ))} - - + + {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - )) + table.getRowModel().rows.map(row => { + const order = row.original as PurchaseOrder + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + + ) + }) ) : ( - - - No results. - - + + + )} - -
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} + + {order.status === "Pending Approval" && ( + <> + + + + )} + {order.status === "Approved" && ( + <> + + + + )} + {order.status === "Received" && ( + Received + )} + {order.status === "Rejected" && ( + Rejected + )} +
No results.
+ +
{/* View Order Dialog */} - + Purchase Order Details @@ -354,7 +357,7 @@ export function PurchaseOrderDataTable({

Supplier: {selectedOrder?.supplierName}

-

Order Date: {selectedOrder?.orderDate ? format(selectedOrder.orderDate, "PPP") : 'N/A'}

+

Order Date: {selectedOrder?.orderDate ? new Date(selectedOrder.orderDate).toLocaleDateString() : 'N/A'}

Status: {selectedOrder?.status}

Items:

    @@ -366,7 +369,7 @@ export function PurchaseOrderDataTable({
- {/* Corrected function call */} +
diff --git a/src/components/purchase-orders/PurchaseOrderForm.tsx b/src/components/purchase-orders/PurchaseOrderForm.tsx index b4f12ce..be4b034 100644 --- a/src/components/purchase-orders/PurchaseOrderForm.tsx +++ b/src/components/purchase-orders/PurchaseOrderForm.tsx @@ -2,205 +2,207 @@ import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" +import { useForm, useFieldArray } from "react-hook-form" import * as z from "zod" -import { CalendarIcon } from "lucide-react" -import { format } from "date-fns" import { Button } from "@/components/ui/button" -import { Calendar } from "@/components/ui/calendar" // <-- Added Calendar import import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" -import { SupplierSelect } from "@/components/suppliers/SupplierSelect"; -import pb from "@/lib/pocketbase"; -import { showError, showSuccess } from "@/utils/toast"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { showSuccess, showError } from "@/utils/toast" +import pb from "@/lib/pocketbase" +import { StockItem } from "@/components/stock/columns" -const purchaseOrderFormSchema = z.object({ - supplier: z.string({ - required_error: "Please select a supplier.", - }).min(1, { message: "Supplier is required." }), - orderDate: z.date({ - required_error: "Order date is required.", - }), - expectedDeliveryDate: z.date({ - required_error: "Expected delivery date is required.", - }), - notes: z.string().optional(), +// Corrected status options to match the schema +const statusOptions = ["Pending", "Ordered", "Received"] as const + +const formSchema = z.object({ + supplierName: z.string().min(2, { message: "Supplier name must be at least 2 characters." }), + status: z.enum(statusOptions), + items: z.array(z.object({ + stockItemId: z.string({ required_error: "Select an item." }), + quantity: z.number().int().positive({ message: "Quantity must be positive." }), + })).min(1, { message: "At least one item is required." }), }) +type FormSchema = z.infer + interface PurchaseOrderFormProps { - onFormSubmit: (data: z.infer) => void; - setOpen: (open: boolean) => void; - refreshData: () => void; + setOpen: (open: boolean) => void + refreshData: () => void } -export function PurchaseOrderForm({ onFormSubmit, setOpen, refreshData }: PurchaseOrderFormProps) { - const [selectedSupplierDetails, setSelectedSupplierDetails] = React.useState(null); +export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormProps) { + const [stockItems, setStockItems] = React.useState([]) - const form = useForm>({ - resolver: zodResolver(purchaseOrderFormSchema), + React.useEffect(() => { + const fetchStockItems = async () => { + try { + const records = await pb.collection('stock_items').getFullList() + const items = records.map((item: any) => ({ + id: item.id, + name: item.name, + sku: item.sku, + category: item.category, + quantity: item.quantity, + costPrice: item.cost_price, + sellingPrice: item.selling_price, + })) + setStockItems(items) + } catch (err) { + console.error("Failed to load stock items:", err) + showError("Failed to load stock items.") + } + } + fetchStockItems() + }, []) + + const form = useForm({ + resolver: zodResolver(formSchema), defaultValues: { - supplier: "", - orderDate: new Date(), - expectedDeliveryDate: new Date(), - notes: "", + supplierName: "", + status: "Pending", // Set default status to "Pending" + items: [{ stockItemId: "", quantity: 1 }], }, }) - async function onSubmit(values: z.infer) { + const { fields, append, remove } = useFieldArray({ + control: form.control, + name: "items", + }) + + async function onSubmit(values: FormSchema) { try { + // Set orderDate to current date automatically + const orderDate = new Date().toISOString() + + // Prepare items relation and quantities + const itemIds = values.items.map(i => i.stockItemId) + const quantities = values.items.map(i => i.quantity) + await pb.collection('purchase_orders').create({ - supplier: values.supplier, - order_date: values.orderDate.toISOString(), - expected_delivery_date: values.expectedDeliveryDate.toISOString(), - notes: values.notes, - }); - showSuccess("Purchase order created successfully!"); - form.reset(); - setSelectedSupplierDetails(null); - setOpen(false); - refreshData(); - } catch (error) { - console.error("Error creating purchase order:", error); - let errorMessage = "Failed to create purchase order. Please try again."; - if (error.response && error.response.data && error.response.data.message) { - errorMessage = `Failed to create purchase order: ${error.response.data.message}`; - } - showError(errorMessage); + supplierName: values.supplierName, + orderDate, + status: values.status, + items: itemIds, + quantities: quantities, + }) + + showSuccess("Purchase order created successfully!") + form.reset() + setOpen(false) + refreshData() + } catch (err) { + console.error("Failed to create purchase order:", err) + showError("Failed to create purchase order. Please try again.") } } - const handleSupplierChange = (supplier: any) => { - setSelectedSupplierDetails(supplier); - form.setValue("supplier", supplier ? supplier.id : ""); - }; - return (
- + ( - Supplier + Supplier Name - + - Select the supplier for this purchase order. )} /> - ( - - Order Date - - - - - - - - - date > new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - - - Your order date. - - - )} - /> - - ( - - Expected Delivery Date - - - - - - - - - date > new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - - - Your expected delivery date. - - - )} - /> - - ( - Notes - - - - Optional notes for the purchase order. + Status + )} /> - - +
+ Items + {fields.map((field, index) => ( +
+ ( + + + + + )} + /> + ( + + { + const val = parseInt(e.target.value, 10) + field.onChange(isNaN(val) ? "" : val) + }} + /> + + + )} + /> + +
+ ))} + +
+ ) diff --git a/src/components/purchase-orders/columns.tsx b/src/components/purchase-orders/columns.tsx index 320d2e0..dea4aa7 100644 --- a/src/components/purchase-orders/columns.tsx +++ b/src/components/purchase-orders/columns.tsx @@ -1,10 +1,16 @@ "use client" import { ColumnDef } from "@tanstack/react-table" -import { MoreHorizontal } from "lucide-react" -import { format } from "date-fns" // Import format for date display - +import { MoreHorizontal, Download, Eye } from "lucide-react" // Import Eye icon import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" // Import Dialog components import { DropdownMenu, DropdownMenuContent, @@ -13,47 +19,41 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" +import { useState } from "react" // Import useState -// Define the PurchaseOrder type here if it's not defined elsewhere export type PurchaseOrder = { id: string - supplier: string - supplierId: string - orderDate: Date - expectedDeliveryDate: Date + supplierName: string + orderDate: string status: string - notes: string - items: Array<{ stockItemId: string; itemName: string; sku: string; costPrice: number }>; // Added items and costPrice - quantities: Array; // Added quantities - supplierName: string; // Added supplierName for PDF generation + itemCount: number + items: Array<{ stockItemId: string; itemName: string; quantity: number; sku: string; costPrice: number }> // Added costPrice + quantities: number[] } -// Define a type for the props that the actions column will receive +// Define the props for the actions column interface ActionsColumnProps { - onDownloadPdf: (order: PurchaseOrder) => void; // Function to handle PDF download - onViewOrder: (order: PurchaseOrder) => void; // Function to handle viewing order details + onDownloadPdf: (order: PurchaseOrder) => void; + onViewOrder: (order: PurchaseOrder) => void; // Add onViewOrder prop } -// Export createColumns as a function that accepts props +// Export createColumns function export const createColumns = ({ onDownloadPdf, onViewOrder }: ActionsColumnProps): ColumnDef[] => [ { - accessorKey: "supplier", - header: "Supplier", + accessorKey: "supplierName", + header: "Supplier Name", }, { accessorKey: "orderDate", header: "Order Date", cell: ({ row }) => { - const date = row.getValue("orderDate") as Date - return date ? format(date, "PPP") : "N/A" - }, - }, - { - accessorKey: "expectedDeliveryDate", - header: "Expected Delivery Date", - cell: ({ row }) => { - const date = row.getValue("expectedDeliveryDate") as Date - return date ? format(date, "PPP") : "N/A" + const date = new Date(row.getValue("orderDate")) + const formattedDate = date.toLocaleDateString("en-ZA", { + year: "numeric", + month: "short", + day: "numeric", + }) + return
{formattedDate}
}, }, { @@ -61,14 +61,15 @@ export const createColumns = ({ onDownloadPdf, onViewOrder }: ActionsColumnProps header: "Status", }, { - accessorKey: "notes", - header: "Notes", + accessorKey: "itemCount", + header: "Number of Items", }, { id: "actions", enableHiding: false, cell: ({ row }) => { - const purchaseOrder = row.original + const order = row.original + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); // State for edit dialog, if needed later return ( @@ -81,15 +82,19 @@ export const createColumns = ({ onDownloadPdf, onViewOrder }: ActionsColumnProps Actions navigator.clipboard.writeText(purchaseOrder.id)} + onClick={() => navigator.clipboard.writeText(order.id)} > - Copy Purchase Order ID + Copy Order ID - onViewOrder(purchaseOrder)}>View Details - onDownloadPdf(purchaseOrder)}>Download PDF - Edit Purchase Order - Delete Purchase Order + onViewOrder(order)}> {/* Call onViewOrder */} + + View Order + + onDownloadPdf(order)}> + + Download PDF + ) diff --git a/src/components/suppliers/SupplierSelect.tsx b/src/components/suppliers/SupplierSelect.tsx deleted file mode 100644 index 7724c5b..0000000 --- a/src/components/suppliers/SupplierSelect.tsx +++ /dev/null @@ -1,76 +0,0 @@ -"use client" - -import React, { useState, useEffect, useCallback } from "react"; -import pb from "@/lib/pocketbase"; -import { Supplier } from "@/components/suppliers/columns"; // Assuming columns.tsx defines the Supplier type -import { showError } from "@/utils/toast"; - -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; - -interface SupplierSelectProps { - onSupplierChange: (supplier: Supplier | null) => void; - selectedSupplierId?: string | null; // Optional prop to set initial value -} - -export function SupplierSelect({ onSupplierChange, selectedSupplierId }: SupplierSelectProps) { - const [suppliers, setSuppliers] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - const fetchSuppliers = useCallback(async () => { - setIsLoading(true); - try { - const records = await pb.collection('suppliers').getList(1, 50, { - sort: '-created', - }); - const formattedData: Supplier[] = records.items.map((item: any) => ({ - id: item.id, - name: item.supplier_name, - address: item.supplier_address, - phone: item.supplier_phone_number, - email: item.supplier_email, - })); - setSuppliers(formattedData); - } catch (error) { - console.error("Error fetching suppliers:", error); - showError("Failed to load suppliers for selection."); - } finally { - setIsLoading(false); - } - }, []); - - useEffect(() => { - fetchSuppliers(); - }, [fetchSuppliers]); - - const handleValueChange = (value: string) => { - const selectedSupplier = suppliers.find(supplier => supplier.id === value) || null; - onSupplierChange(selectedSupplier); - }; - - return ( - - ); -} \ No newline at end of file diff --git a/src/pages/PurchaseOrders.tsx b/src/pages/PurchaseOrders.tsx index 0449dfa..55486f4 100644 --- a/src/pages/PurchaseOrders.tsx +++ b/src/pages/PurchaseOrders.tsx @@ -1,53 +1,55 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { createColumns, PurchaseOrder } from "@/components/purchase-orders/columns"; // Ensured createColumns is exported -import { PurchaseOrderDataTable } from "@/components/purchase-orders/PurchaseOrderDataTable"; +import { useState, useEffect, useCallback } from "react"; +import { PurchaseOrderForm } from "@/components/purchase-orders/PurchaseOrderForm"; import { Button } from "@/components/ui/button"; import { PlusCircle } from "lucide-react"; import { - Dialog, + Dialog, // Import Dialog components DialogContent, - DialogDescription, DialogHeader, DialogTitle, DialogTrigger, + DialogDescription, // Import DialogDescription } from "@/components/ui/dialog"; -import { PurchaseOrderForm } from "@/components/purchase-orders/PurchaseOrderForm"; +import { createColumns, PurchaseOrder } from "@/components/purchase-orders/columns"; // Import createColumns +import { PurchaseOrderDataTable } from "@/components/purchase-orders/PurchaseOrderDataTable"; import pb from "@/lib/pocketbase"; -import { showError } from "@/utils/toast"; -import { format } from "date-fns"; // Import format for date display -import { jsPDF } from "jspdf"; // Import jsPDF const PurchaseOrders = () => { - const [addPurchaseOrderDialogOpen, setAddPurchaseOrderDialogOpen] = useState(false); - const [purchaseOrders, setPurchaseOrders] = useState([]); + const [purchaseOrderOpen, setPurchaseOrderOpen] = useState(false); + const [purchaseOrders, setPurchaseOrders] = useState([]); // Explicitly type purchaseOrders const [refreshTrigger, setRefreshTrigger] = useState(0); const [filterValue, setFilterValue] = useState(""); + const [viewOrderDialogOpen, setViewOrderDialogOpen] = useState(false); // State for the view order dialog + const [selectedOrderForView, setSelectedOrderForView] = useState(null); // State to hold the order to view const fetchPurchaseOrders = useCallback(async () => { try { const records = await pb.collection('purchase_orders').getList(1, 50, { - sort: '-created', - expand: 'supplier', + sort: '-orderDate', + expand: 'items', // Ensure items are expanded for PDF generation }); - const formattedData: PurchaseOrder[] = records.items.map((item: any) => ({ - id: item.id, - supplier: item.expand?.supplier?.name || 'N/A', - supplierId: item.supplier, - orderDate: new Date(item.order_date), - expectedDeliveryDate: new Date(item.expected_delivery_date), - status: item.status || 'Pending', - notes: item.notes || '', - items: item.items || [], - quantities: item.quantities || [], - supplierName: item.expand?.supplier?.name || 'N/A', + const formattedData = records.items.map((record) => ({ + id: record.id, + supplierName: record.supplierName, + orderDate: record.orderDate, + status: record.status, + itemCount: record.items?.length || 0, + items: (record.expand?.items || []).map((item: any) => ({ + stockItemId: item.id, + itemName: item.name, + quantity: item.quantity, // This is the quantity in stock_items, not the ordered quantity + sku: item.sku, + costPrice: item.cost_price, // Include costPrice from expanded item + })), + quantities: record.quantities || [], // This array holds the ordered quantities })); + setPurchaseOrders(formattedData); - } catch (error) { - console.error("Error fetching purchase orders:", error); - showError("Failed to load purchase orders."); + } catch (err) { + console.error("Error fetching purchase orders:", err); } - }, [refreshTrigger]); + }, [refreshTrigger]); // Include refreshTrigger in dependency array useEffect(() => { fetchPurchaseOrders(); @@ -57,314 +59,79 @@ const PurchaseOrders = () => { setRefreshTrigger(prev => prev + 1); }; - const handlePurchaseOrderFormSubmit = (data: any) => { - console.log("Purchase order form submitted with data:", data); - }; - - const handleFilterChange = (value: string) => { - setFilterValue(value); - }; - - const generatePDF = useCallback((order: PurchaseOrder) => { - const doc = new jsPDF(); - - // Set font and colors - doc.setFont("helvetica"); - const primaryColor = "#ffc905"; // Your primary color - const textColor = "#333333"; - - // --- Company Details --- - const companyName = "JEI SA"; - const companyAddress = "47 Station St, Oberholzer, Carletonville, 2499"; - const companyPhone = "018 786 2897"; - - // --- Header Section --- - doc.setFontSize(24); - doc.setTextColor(primaryColor); - doc.text("PURCHASE ORDER", 105, 20, { align: "center" }); - - doc.setFontSize(10); - doc.setTextColor(textColor); - doc.text(companyName, 14, 30); // Company Name - doc.text(companyAddress, 14, 35); - doc.text(companyPhone, 14, 40); - - doc.setFontSize(10); - doc.text(`PO Number: ${order.id}`, 150, 30); - doc.text(`Date: ${new Date(order.orderDate).toLocaleDateString()}`, 150, 35); - doc.text(`Status: ${order.status}`, 150, 40); - - // --- Supplier Details --- - doc.setFontSize(12); - doc.setTextColor(primaryColor); - doc.text("SUPPLIER DETAILS", 14, 65); - doc.setDrawColor(primaryColor); - doc.line(14, 66, 50, 66); // Underline - - doc.setFontSize(10); - doc.setTextColor(textColor); - doc.text(`Supplier Name: ${order.supplierName}`, 14, 75); // Use supplierName from expanded data - doc.text("Address: Supplier Address Line 1", 14, 80); - doc.text("City, Postal Code", 14, 85); - doc.text("Contact: Supplier Contact Person", 14, 90); - doc.text("Email: supplier@example.com", 14, 95); - - // --- Items Table --- - let y = 110; - doc.setFontSize(12); - doc.setTextColor(primaryColor); - doc.text("ORDER ITEMS", 14, y); - doc.setDrawColor(primaryColor); - doc.line(14, y + 1, 45, y + 1); // Underline - - y += 10; // Space after title - - // Table Headers - doc.setFillColor(primaryColor); - doc.rect(14, y, 182, 8, "F"); // Background for header - doc.setTextColor("#FFFFFF"); // White text for header - doc.setFontSize(10); - doc.text("Item #", 16, y + 5); - doc.text("Description (SKU)", 40, y + 5); - doc.text("Qty", 130, y + 5); - doc.text("Unit Price", 150, y + 5); - doc.text("Total", 180, y + 5); - - y += 8; // Move past header - - doc.setTextColor(textColor); - doc.setFontSize(9); - let subtotal = 0; - - order.items.forEach((item, index) => { - const orderedQuantity = order.quantities[index] || 0; - const unitPrice = item.costPrice || 0; - const itemTotal = orderedQuantity * unitPrice; - subtotal += itemTotal; - - doc.text(String(index + 1), 16, y + 5); - doc.text(`${item.itemName} (${item.sku})`, 40, y + 5); - doc.text(String(orderedQuantity), 130, y + 5); - doc.text(unitPrice.toLocaleString("en-ZA", { style: "currency", currency: "ZAR" }), 150, y + 5); - doc.text(itemTotal.toLocaleString("en-ZA", { style: "currency", currency: "ZAR" }), 180, y + 5); - y += 8; - }); - - // --- Totals Section --- - y += 10; // Space before totals - - doc.setDrawColor(primaryColor); - doc.line(140, y, 196, y); // Line above subtotal - - y += 5; - doc.setFontSize(10); - doc.text("Subtotal:", 140, y); - doc.text(subtotal.toLocaleString("en-ZA", { style: "currency", currency: "ZAR" }), 180, y); - - y += 7; - doc.text("Shipping:", 140, y); - doc.text("R0.00", 180, y); // Placeholder for shipping - - y += 7; - doc.text("Tax:", 140, y); - doc.text("R0.00", 180, y); // Placeholder for tax - - y += 7; - doc.text("Other:", 140, y); - doc.text("R0.00", 180, y); // Placeholder for other charges - - y += 10; - doc.setDrawColor(primaryColor); - doc.setLineWidth(0.5); - doc.line(140, y, 196, y); // Thicker line above total - - y += 5; - doc.setFontSize(14); - doc.setTextColor(primaryColor); - doc.text("TOTAL:", 140, y); - doc.text(subtotal.toLocaleString("en-ZA", { style: "currency", currency: "ZAR" }), 180, y); // Grand Total - - doc.save(`purchase_order_${order.id}.pdf`); + // Define the PDF download handler + const handleDownloadPdf = useCallback((order: PurchaseOrder) => { + console.log("Downloading PDF for order:", order.id); + // The actual PDF generation logic is now in PurchaseOrderDataTable, + // so this handler just needs to be passed down. }, []); - const approveOrder = async (order: PurchaseOrder) => { - try { - await pb.collection('purchase_orders').update(order.id, { status: "Approved" }) - handleRefreshData() - } catch (error) { - console.error("Failed to approve order:", error) - showError("Failed to approve order.") - } - } - - const rejectOrder = async (order: PurchaseOrder) => { - try { - await pb.collection('purchase_orders').update(order.id, { status: "Rejected" }) - handleRefreshData() - } catch (error) { - console.error("Failed to reject order:", error) - showError("Failed to reject order.") - } - } - + // Define the handler to open the view order dialog const handleViewOrder = useCallback((order: PurchaseOrder) => { - setSelectedOrder(order) - setViewDialogOpen(true) + setSelectedOrderForView(order); + setViewOrderDialogOpen(true); }, []); - const closeViewDialog = () => { - setSelectedOrder(null); - setViewDialogOpen(false); - }; - - const columnsWithActions = React.useMemo(() => createColumns({ onDownloadPdf: generatePDF, onViewOrder: handleViewOrder }), [generatePDF, handleViewOrder]); - - const table = useReactTable({ - data: purchaseOrders, // Use the fetched purchaseOrders state - columns: columnsWithActions, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onSortingChange: setSorting, - getSortedRowModel: getSortedRowModel(), - onColumnFiltersChange: setColumnFilters, - getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - onRowSelectionChange: setRowSelection, - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, - }) - - React.useEffect(() => { - table.getColumn("supplier")?.setFilterValue(filterValue) - }, [filterValue, table]) + // Call createColumns to get the columns array, passing the handlers + const columns = createColumns({ onDownloadPdf: handleDownloadPdf, onViewOrder: handleViewOrder }); return ( - <> -
-
- onFilterChange(e.target.value)} - className="max-w-sm" - /> - - - - - - {table - .getAllColumns() - .filter( - (column) => column.getCanHide() - ) - .map((column) => ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ))} - - -
-
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - )) - ) : ( - - - No results. - - - )} - -
+
+
+
+

Purchase Orders

+

Manage your purchase orders here.

+ + + + + + + New Purchase Order + + + +
+ {/* View Order Dialog */} - + Purchase Order Details - Order ID: {selectedOrder?.id} + Order ID: {selectedOrderForView?.id}
-

Supplier: {selectedOrder?.supplierName}

-

Order Date: {selectedOrder?.orderDate ? format(selectedOrder.orderDate, "PPP") : 'N/A'}

-

Status: {selectedOrder?.status}

+

Supplier: {selectedOrderForView?.supplierName}

+

Order Date: {selectedOrderForView?.orderDate ? new Date(selectedOrderForView.orderDate).toLocaleDateString() : 'N/A'}

+

Status: {selectedOrderForView?.status}

Items:

    - {selectedOrder?.items.map((item, index) => ( + {selectedOrderForView?.items.map((item, index) => (
  • - {item.itemName} (SKU: {item.sku}) - Quantity: {selectedOrder.quantities[index]} + {item.itemName} (SKU: {item.sku}) - Quantity: {selectedOrderForView.quantities[index]}
  • ))}
- +
+
+ ); +}; - {selectedOrder && ( - ({ - stockItemId: item.stockItemId, - orderedQuantity: selectedOrder.quantities[idx] || 0, - itemName: item.itemName, - }))} - refreshData={handleRefreshData} - /> - )} - - ) -} \ No newline at end of file +export default PurchaseOrders; \ No newline at end of file