Reverted all changes back to version 04dee1a0832bdeb0f99ed5e69532fcdeae8b659c
This commit is contained in:
parent
92f9ad0413
commit
ec62bf32ca
5 changed files with 355 additions and 654 deletions
|
|
@ -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<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
|
|
@ -50,7 +43,7 @@ interface PurchaseOrderDataTableProps<TData, TValue> {
|
|||
}
|
||||
|
||||
export function PurchaseOrderDataTable<TData, TValue>({
|
||||
columns,
|
||||
columns: columnsProp,
|
||||
data,
|
||||
filterValue,
|
||||
onFilterChange,
|
||||
|
|
@ -83,12 +76,17 @@ export function PurchaseOrderDataTable<TData, TValue>({
|
|||
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<TData, TValue>({
|
|||
|
||||
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<TData, TValue>({
|
|||
|
||||
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<TData, TValue>({
|
|||
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<TData, TValue>({
|
|||
refreshData()
|
||||
} catch (error) {
|
||||
console.error("Failed to approve order:", error)
|
||||
showError("Failed to approve order.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,21 +224,14 @@ export function PurchaseOrderDataTable<TData, TValue>({
|
|||
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<PurchaseOrder>({
|
||||
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<TData, TValue>({
|
|||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
table.getColumn("supplier")?.setFilterValue(filterValue) // Filter by supplier name
|
||||
table.getColumn("supplierName")?.setFilterValue(filterValue)
|
||||
}, [filterValue, table])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex items-center py-4">
|
||||
<Input
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter by supplier name..."
|
||||
value={filterValue}
|
||||
onChange={(e) => onFilterChange(e.target.value)}
|
||||
className="max-w-sm"
|
||||
className="max-w-sm border border-gray-300 rounded px-2 py-1"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -300,53 +292,64 @@ export function PurchaseOrderDataTable<TData, TValue>({
|
|||
</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>
|
||||
<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>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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>
|
||||
))
|
||||
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>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<tr>
|
||||
<td colSpan={table.getAllColumns().length} className="h-24 text-center">No results.</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* View Order Dialog */}
|
||||
<Dialog open={viewDialogOpen} onOpenChange={setViewDialogOpen}>
|
||||
<Dialog open={viewDialogOpen} onOpenChange={closeViewDialog}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Purchase Order Details</DialogTitle>
|
||||
|
|
@ -354,7 +357,7 @@ export function PurchaseOrderDataTable<TData, TValue>({
|
|||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<p><strong>Supplier:</strong> {selectedOrder?.supplierName}</p>
|
||||
<p><strong>Order Date:</strong> {selectedOrder?.orderDate ? format(selectedOrder.orderDate, "PPP") : 'N/A'}</p>
|
||||
<p><strong>Order Date:</strong> {selectedOrder?.orderDate ? new Date(selectedOrder.orderDate).toLocaleDateString() : 'N/A'}</p>
|
||||
<p><strong>Status:</strong> {selectedOrder?.status}</p>
|
||||
<h4 className="font-semibold">Items:</h4>
|
||||
<ul className="list-disc list-inside">
|
||||
|
|
@ -366,7 +369,7 @@ export function PurchaseOrderDataTable<TData, TValue>({
|
|||
</ul>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button variant="outline" onClick={() => setViewDialogOpen(false)}>Close</Button> {/* Corrected function call */}
|
||||
<Button variant="outline" onClick={closeViewDialog}>Close</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -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<typeof formSchema>
|
||||
|
||||
interface PurchaseOrderFormProps {
|
||||
onFormSubmit: (data: z.infer<typeof purchaseOrderFormSchema>) => 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<any>(null);
|
||||
export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormProps) {
|
||||
const [stockItems, setStockItems] = React.useState<StockItem[]>([])
|
||||
|
||||
const form = useForm<z.infer<typeof purchaseOrderFormSchema>>({
|
||||
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<FormSchema>({
|
||||
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<typeof purchaseOrderFormSchema>) {
|
||||
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 (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 max-w-md">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="supplier"
|
||||
name="supplierName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Supplier</FormLabel>
|
||||
<FormLabel>Supplier Name</FormLabel>
|
||||
<FormControl>
|
||||
<SupplierSelect
|
||||
onSupplierChange={handleSupplierChange}
|
||||
selectedSupplierId={field.value}
|
||||
/>
|
||||
<Input placeholder="Supplier name" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Select the supplier for this purchase order.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="orderDate"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Order Date</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={
|
||||
"w-[240px] pl-3 text-left font-normal"
|
||||
}
|
||||
>
|
||||
{field.value ? format(field.value, "PPP") : <span>Pick a date</span>}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
disabled={(date) =>
|
||||
date > new Date() || date < new Date("1900-01-01")
|
||||
}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormDescription>Your order date.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="expectedDeliveryDate"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Expected Delivery Date</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={
|
||||
"w-[240px] pl-3 text-left font-normal"
|
||||
}
|
||||
>
|
||||
{field.value ? format(field.value, "PPP") : <span>Pick a date</span>}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
disabled={(date) =>
|
||||
date > new Date() || date < new Date("1900-01-01")
|
||||
}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormDescription>Your expected delivery date.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notes"
|
||||
name="status"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notes</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Any special instructions or notes" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Optional notes for the purchase order.</FormDescription>
|
||||
<FormLabel>Status</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{statusOptions.map((status) => (
|
||||
<SelectItem key={status} value={status}>
|
||||
{status}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit">Create Purchase Order</Button>
|
||||
<div>
|
||||
<FormLabel>Items</FormLabel>
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="flex space-x-2 items-center mb-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`items.${index}.stockItemId`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select item" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{stockItems.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>
|
||||
{item.name} ({item.sku})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`items.${index}.quantity`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-24">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value, 10)
|
||||
field.onChange(isNaN(val) ? "" : val)
|
||||
}}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="icon"
|
||||
onClick={() => remove(index)}
|
||||
aria-label="Remove item"
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button type="button" onClick={() => append({ stockItemId: "", quantity: 1 })}>
|
||||
Add Item
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="submit" className="w-full mt-4">
|
||||
Create Purchase Order
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<number>; // 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<PurchaseOrder>[] => [
|
||||
{
|
||||
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 <div className="font-medium">{formattedDate}</div>
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -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 (
|
||||
<DropdownMenu>
|
||||
|
|
@ -81,15 +82,19 @@ export const createColumns = ({ onDownloadPdf, onViewOrder }: ActionsColumnProps
|
|||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigator.clipboard.writeText(purchaseOrder.id)}
|
||||
onClick={() => navigator.clipboard.writeText(order.id)}
|
||||
>
|
||||
Copy Purchase Order ID
|
||||
Copy Order ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => onViewOrder(purchaseOrder)}>View Details</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onDownloadPdf(purchaseOrder)}>Download PDF</DropdownMenuItem>
|
||||
<DropdownMenuLabel>Edit Purchase Order</DropdownMenuLabel>
|
||||
<DropdownMenuLabel className="text-red-600">Delete Purchase Order</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => onViewOrder(order)}> {/* Call onViewOrder */}
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
View Order
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onDownloadPdf(order)}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download PDF
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<Supplier[]>([]);
|
||||
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 (
|
||||
<Select onValueChange={handleValueChange} defaultValue={selectedSupplierId || undefined}>
|
||||
<SelectTrigger className="w-full" disabled={isLoading}>
|
||||
<SelectValue placeholder="Select a supplier" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{isLoading ? (
|
||||
<SelectItem value="" disabled>Loading suppliers...</SelectItem>
|
||||
) : suppliers.length === 0 ? (
|
||||
<SelectItem value="" disabled>No suppliers found</SelectItem>
|
||||
) : (
|
||||
suppliers.map((supplier) => (
|
||||
<SelectItem key={supplier.id} value={supplier.id}>
|
||||
{supplier.name}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
|
@ -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<PurchaseOrder[]>([]);
|
||||
const [purchaseOrderOpen, setPurchaseOrderOpen] = useState(false);
|
||||
const [purchaseOrders, setPurchaseOrders] = useState<PurchaseOrder[]>([]); // 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<PurchaseOrder | null>(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<PurchaseOrder>({
|
||||
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 (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex items-center py-4">
|
||||
<Input
|
||||
placeholder="Filter by supplier name..."
|
||||
value={filterValue}
|
||||
onChange={(e) => onFilterChange(e.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) => (
|
||||
<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 flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Purchase Orders</h1>
|
||||
<p className="text-gray-500">Manage your purchase orders here.</p>
|
||||
</div>
|
||||
<Dialog open={purchaseOrderOpen} onOpenChange={setPurchaseOrderOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
New Purchase Order
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Purchase Order</DialogTitle>
|
||||
</DialogHeader>
|
||||
<PurchaseOrderForm setOpen={setPurchaseOrderOpen} refreshData={handleRefreshData} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<PurchaseOrderDataTable
|
||||
columns={columns} // Pass the generated columns array
|
||||
data={purchaseOrders}
|
||||
filterValue={filterValue}
|
||||
onFilterChange={setFilterValue}
|
||||
refreshData={handleRefreshData}
|
||||
/>
|
||||
|
||||
{/* View Order Dialog */}
|
||||
<Dialog open={viewDialogOpen} onOpenChange={setViewDialogOpen}>
|
||||
<Dialog open={viewOrderDialogOpen} onOpenChange={setViewOrderDialogOpen}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Purchase Order Details</DialogTitle>
|
||||
<DialogDescription>Order ID: {selectedOrder?.id}</DialogDescription>
|
||||
<DialogDescription>Order ID: {selectedOrderForView?.id}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<p><strong>Supplier:</strong> {selectedOrder?.supplierName}</p>
|
||||
<p><strong>Order Date:</strong> {selectedOrder?.orderDate ? format(selectedOrder.orderDate, "PPP") : 'N/A'}</p>
|
||||
<p><strong>Status:</strong> {selectedOrder?.status}</p>
|
||||
<p><strong>Supplier:</strong> {selectedOrderForView?.supplierName}</p>
|
||||
<p><strong>Order Date:</strong> {selectedOrderForView?.orderDate ? new Date(selectedOrderForView.orderDate).toLocaleDateString() : 'N/A'}</p>
|
||||
<p><strong>Status:</strong> {selectedOrderForView?.status}</p>
|
||||
<h4 className="font-semibold">Items:</h4>
|
||||
<ul className="list-disc list-inside">
|
||||
{selectedOrder?.items.map((item, index) => (
|
||||
{selectedOrderForView?.items.map((item, index) => (
|
||||
<li key={item.stockItemId}>
|
||||
{item.itemName} (SKU: {item.sku}) - Quantity: {selectedOrder.quantities[index]}
|
||||
{item.itemName} (SKU: {item.sku}) - Quantity: {selectedOrderForView.quantities[index]}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button variant="outline" onClick={closeViewDialog}>Close</Button>
|
||||
<Button variant="outline" onClick={() => setViewOrderDialogOpen(false)}>Close</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</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={handleRefreshData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default PurchaseOrders;
|
||||
Loading…
Reference in a new issue