From f7e768f6d63e53fd051b81f69f9f89f5a1fb0f7d Mon Sep 17 00:00:00 2001 From: g Date: Mon, 12 Jan 2026 06:59:21 +0000 Subject: [PATCH] Improve product image handling and add costPerUnit Added a utility to generate product image URLs, ensuring images are displayed correctly in the product table. Updated the Product model to include an optional costPerUnit field. Minor UI and code formatting improvements were made for consistency. --- app/dashboard/products/page.tsx | 80 ++++++++++++++--------------- components/tables/product-table.tsx | 26 +++++++--- models/products.ts | 1 + 3 files changed, 60 insertions(+), 47 deletions(-) diff --git a/app/dashboard/products/page.tsx b/app/dashboard/products/page.tsx index e2de997..f6aa4a7 100644 --- a/app/dashboard/products/page.tsx +++ b/app/dashboard/products/page.tsx @@ -45,7 +45,7 @@ const ProfitAnalysisModal = dynamic(() => import("@/components/modals/profit-ana function ProductTableSkeleton() { return ( - +
@@ -60,8 +60,8 @@ function ProductTableSkeleton() {
{['Product', 'Category', 'Price', 'Stock', 'Status', 'Actions'].map((header, i) => ( -
- + {[...Array(8)].map((_, i) => ( -
(null); + const [selectedProductForAnalysis, setSelectedProductForAnalysis] = useState<{ id: string, name: string } | null>(null); // Fetch products and categories useEffect(() => { @@ -169,7 +169,7 @@ export default function ProductsPage() { const fetchDataAsync = async () => { try { setLoading(true); - + const [fetchedProducts, fetchedCategories] = await Promise.all([ clientFetch('/products'), clientFetch('/categories'), @@ -210,7 +210,7 @@ export default function ProductsPage() { })); }; - // Handle input changes + // Handle input changes const handleChange = ( e: ChangeEvent ) => setProductData({ ...productData, [e.target.name]: e.target.value }); @@ -226,7 +226,7 @@ export default function ProductsPage() { setProductData({ ...productData, pricing: updatedPricing }); }; - + const handleSaveProduct = async (data: Product, file?: File | null) => { try { setLoading(true); @@ -247,7 +247,7 @@ export default function ProductsPage() { // Save the product data const endpoint = editing ? `/products/${data._id}` : "/products"; const method = editing ? "PUT" : "POST"; - + const productResponse = await clientFetch(endpoint, { method, headers: { @@ -259,10 +259,10 @@ export default function ProductsPage() { // If there's a new image to upload if (file) { const imageEndpoint = `/products/${productResponse._id || data._id}/image`; - + const formData = new FormData(); formData.append("file", file); - + await fetch(`${process.env.NEXT_PUBLIC_API_URL}${imageEndpoint}`, { method: "PUT", headers: { @@ -279,10 +279,10 @@ export default function ProductsPage() { // Refresh products list const fetchedProducts = await clientFetch('/products'); setProducts(fetchedProducts); - + setModalOpen(false); setLoading(false); - + toast.success( editing ? "Product updated successfully" : "Product added successfully" ); @@ -296,18 +296,18 @@ export default function ProductsPage() { // Handle delete product const handleDeleteProduct = async (productId: string) => { if (!confirm("Are you sure you want to delete this product?")) return; - + try { setLoading(true); - + await clientFetch(`/products/${productId}`, { method: "DELETE", }); - + // Refresh products list const fetchedProducts = await clientFetch('/products'); setProducts(fetchedProducts); - + toast.success("Product deleted successfully"); setLoading(false); } catch (error) { @@ -323,9 +323,9 @@ export default function ProductsPage() { ...product, pricing: product.pricing ? product.pricing.map((tier) => ({ - minQuantity: tier.minQuantity, - pricePerUnit: tier.pricePerUnit, - })) + minQuantity: tier.minQuantity, + pricePerUnit: tier.pricePerUnit, + })) : [{ minQuantity: 1, pricePerUnit: 0 }], costPerUnit: product.costPerUnit || 0, }); @@ -343,16 +343,16 @@ export default function ProductsPage() { image: null, // Clear image so user can upload a new one pricing: product.pricing ? product.pricing.map((tier) => ({ - minQuantity: tier.minQuantity, - pricePerUnit: tier.pricePerUnit, - })) + minQuantity: tier.minQuantity, + pricePerUnit: tier.pricePerUnit, + })) : [{ minQuantity: 1, pricePerUnit: 0 }], costPerUnit: product.costPerUnit || 0, // Reset stock to defaults for cloned product currentStock: 0, stockStatus: 'out_of_stock' as const, }; - + setProductData(clonedProduct); setEditing(false); // Set to false so it creates a new product setAddProductOpen(true); @@ -390,19 +390,19 @@ export default function ProductsPage() { // Filter products based on search term const filteredProducts = products.filter(product => { if (!searchTerm) return true; - + const searchLower = searchTerm.toLowerCase(); - + // Search in product name if (product.name.toLowerCase().includes(searchLower)) return true; - + // Search in product description if it exists if (product.description && product.description.toLowerCase().includes(searchLower)) return true; - + // Search in category name const categoryName = getCategoryNameById(product.category).toLowerCase(); if (categoryName.includes(searchLower)) return true; - + return false; }); @@ -437,19 +437,19 @@ export default function ProductsPage() { const handleToggleEnabled = async (productId: string, enabled: boolean) => { try { setLoading(true); - + await clientFetch(`/products/${productId}`, { method: "PATCH", body: JSON.stringify({ enabled }), }); - + // Update the local state - setProducts(products.map(product => - product._id === productId - ? { ...product, enabled } + setProducts(products.map(product => + product._id === productId + ? { ...product, enabled } : product )); - + toast.success(`Product ${enabled ? 'enabled' : 'disabled'} successfully`); setLoading(false); } catch (error) { @@ -489,9 +489,9 @@ export default function ProductsPage() { )}
-