From 2c3bf04d15409facacce540821475390b7dc4f9b Mon Sep 17 00:00:00 2001 From: "[dyad]" Date: Fri, 12 Sep 2025 14:37:10 +0200 Subject: [PATCH] [dyad] Supplier selection dropdown added to Purchase Order form - wrote 2 file(s) --- .../purchase-orders/PurchaseOrderForm.tsx | 353 +++++++++++------- 1 file changed, 227 insertions(+), 126 deletions(-) diff --git a/src/components/purchase-orders/PurchaseOrderForm.tsx b/src/components/purchase-orders/PurchaseOrderForm.tsx index be4b034..d7afcad 100644 --- a/src/components/purchase-orders/PurchaseOrderForm.tsx +++ b/src/components/purchase-orders/PurchaseOrderForm.tsx @@ -2,8 +2,9 @@ import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" -import { useForm, useFieldArray } from "react-hook-form" +import { useForm } from "react-hook-form" import * as z from "zod" +import { useState, useEffect, useCallback } from "react" // Import useState and useEffect import { Button } from "@/components/ui/button" import { @@ -15,126 +16,194 @@ import { FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" // Import Select components import { showSuccess, showError } from "@/utils/toast" import pb from "@/lib/pocketbase" -import { StockItem } from "@/components/stock/columns" -// 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." }), +// Define the schema for a single item in the purchase order +const itemSchema = z.object({ + stockItemId: z.string(), + itemName: z.string(), + quantity: z.number().int().positive(), + sku: z.string(), + costPrice: z.number(), }) -type FormSchema = z.infer +// Define the type for the items array +type ItemSchemaType = z.infer; + +// Define the schema for the purchase order form +const purchaseOrderFormSchema = z.object({ + supplierName: z.string().min(1, { message: "Supplier name is required." }), + orderDate: z.date({ required_error: "Order date is required." }), + items: z.array(itemSchema).min(1, { message: "At least one item is required." }), +}) interface PurchaseOrderFormProps { setOpen: (open: boolean) => void refreshData: () => void } +// Define the type for a supplier +type Supplier = { + id: string + name: string +} + export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormProps) { - const [stockItems, setStockItems] = React.useState([]) + const [suppliers, setSuppliers] = useState([]) // State to hold suppliers - 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.") - } + // Fetch suppliers when the component mounts + const fetchSuppliers = useCallback(async () => { + try { + const records = await pb.collection('suppliers').getList(1, 50, { + sort: '-created', + }); + const formattedSuppliers: Supplier[] = records.items.map((item: any) => ({ + id: item.id, + name: item.supplier_name, // Use the correct field name from PocketBase + })); + setSuppliers(formattedSuppliers); + } catch (error) { + console.error("Error fetching suppliers:", error); + showError("Failed to load suppliers."); } - fetchStockItems() - }, []) + }, []); - const form = useForm({ - resolver: zodResolver(formSchema), + useEffect(() => { + fetchSuppliers(); + }, [fetchSuppliers]); + + const form = useForm>({ + resolver: zodResolver(purchaseOrderFormSchema), defaultValues: { supplierName: "", - status: "Pending", // Set default status to "Pending" - items: [{ stockItemId: "", quantity: 1 }], + orderDate: new Date(), + items: [{ + stockItemId: "", + itemName: "", + quantity: 1, + sku: "", + costPrice: 0, + }], }, }) - const { fields, append, remove } = useFieldArray({ - control: form.control, - name: "items", - }) + // Function to add a new item row + const addItem = () => { + form.control.register("items", { shouldUnregister: true }) // Ensure registration + form.setValue("items", [...form.getValues("items"), { + stockItemId: "", + itemName: "", + quantity: 1, + sku: "", + costPrice: 0, + }]) + } - async function onSubmit(values: FormSchema) { + // Function to remove an item row + const removeItem = (index: number) => { + const currentItems = form.getValues("items"); + const newItems = currentItems.filter((_, i) => i !== index); + form.setValue("items", newItems); + } + + // Function to handle changes in item fields + // Explicitly type 'field' to be a key of ItemSchemaType + const handleItemChange = (index: number, field: keyof ItemSchemaType, value: any) => { + const currentItems = form.getValues("items"); + const newItems = [...currentItems]; + + // Type assertion for newItems[index] to ensure it's treated as a mutable object + const currentItem = newItems[index] as ItemSchemaType; + + if (field === "quantity") { + currentItem[field] = parseInt(value, 10) || 0; + } else if (field === "costPrice") { + currentItem[field] = parseFloat(value) || 0; + } else { + currentItem[field] = value; + } + newItems[index] = currentItem; // Assign the updated item back to the array + form.setValue("items", newItems); + } + + async function onSubmit(values: z.infer) { try { - // Set orderDate to current date automatically - const orderDate = new Date().toISOString() + // Find the selected supplier's ID + const selectedSupplier = suppliers.find(s => s.name === values.supplierName); + if (!selectedSupplier) { + showError("Invalid supplier selected."); + return; + } - // Prepare items relation and quantities - const itemIds = values.items.map(i => i.stockItemId) - const quantities = values.items.map(i => i.quantity) + // Prepare data for PocketBase + const purchaseOrderData = { + supplierName: values.supplierName, // This will be the supplier's name for display + supplier: selectedSupplier.id, // This will be the relation to the suppliers collection + orderDate: values.orderDate.toISOString(), + status: "Pending Approval", + items: values.items.map(item => ({ + stockItemId: item.stockItemId, + quantity: item.quantity, + // You might need to adjust this based on how you store item details in purchase_orders + })), + // PocketBase expects an array of item IDs and quantities separately if using relations + // For simplicity here, we'll store item details directly in the purchase_order record + // If you have a separate 'purchase_order_items' collection, you'd create records there. + // For now, let's assume we're embedding item details. + // We need to map the item details to match what's expected by the 'purchase_orders' collection. + // The 'items' field in 'purchase_orders' is likely an array of objects. + // Let's assume it expects { stockItemId: string, quantity: number, itemName: string, sku: string, costPrice: number } + embedded_items: values.items.map(item => ({ + stockItemId: item.stockItemId, + itemName: item.itemName, + quantity: item.quantity, + sku: item.sku, + costPrice: item.costPrice, + })), + quantities: values.items.map(item => item.quantity), // Keep quantities array as well if needed elsewhere + }; - await pb.collection('purchase_orders').create({ - 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.") + await pb.collection('purchase_orders').create(purchaseOrderData); + showSuccess("Purchase order created successfully!"); + form.reset(); + setOpen(false); + refreshData(); + } catch (error) { + console.error("Error creating purchase order:", error); + showError("Failed to create purchase order. Please check the form and try again."); } } return (
- + ( - Supplier Name - - - - - - )} - /> - ( - - Status - { + field.onChange(value); // Update form state with the selected supplier name + // You might want to also set the supplier ID here if needed for the relation + }} value={field.value}> + + + + + - {statusOptions.map((status) => ( - - {status} + {suppliers.map((supplier) => ( + + {supplier.name} ))} @@ -143,27 +212,53 @@ export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormPro )} /> -
- Items - {fields.map((field, index) => ( -
+ + ( + + Order Date + + field.onChange(new Date(e.target.value))} + value={field.value instanceof Date ? field.value.toISOString().split('T')[0] : ''} + /> + + + + )} + /> + + {/* Items Section */} +
+

Order Items

+ {form.getValues("items").map((item, index) => ( +
( - - + + Item Name + + handleItemChange(index, "itemName", e.target.value)} /> + + + + )} + /> + ( + + SKU + + handleItemChange(index, "sku", e.target.value)} /> + )} @@ -172,37 +267,43 @@ export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormPro control={form.control} name={`items.${index}.quantity`} render={({ field }) => ( - - { - const val = parseInt(e.target.value, 10) - field.onChange(isNaN(val) ? "" : val) - }} - /> + + Quantity + + handleItemChange(index, "quantity", e.target.value)} /> + )} /> - + ( + + Unit Price (ZAR) + + handleItemChange(index, "costPrice", e.target.value)} /> + + + + )} + /> +
+ {form.getValues("items").length > 1 && ( + + )} +
))} -
- + + )