diff --git a/backend/controllers/product.js b/backend/controllers/product.js index bbe4f1f..f385270 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -32,11 +32,49 @@ exports.addProduct = async (req, res) => { } }; +exports.removeProduct = async (req, res) => { + const { userID, productID } = req.body; + console.log(userID); + + try { + // First delete images + await db.execute(`DELETE FROM Image_URL WHERE ProductID = ?`, [productID]); + await db.execute(`DELETE FROM History WHERE ProductID = ?`, [productID]); + await db.execute(`DELETE FROM Favorites WHERE ProductID = ?`, [productID]); + await db.execute(`DELETE FROM Product_Category WHERE ProductID = ?`, [ + productID, + ]); + await db.execute(`DELETE FROM Product_Category WHERE ProductID = ?`, [ + productID, + ]); + await db.execute(`DELETE FROM Transaction WHERE ProductID = ?`, [ + productID, + ]); + await db.execute( + `DELETE FROM Recommendation WHERE RecommendedProductID = ?`, + [productID], + ); + + // Then delete the product + await db.execute(`DELETE FROM Product WHERE UserID = ? AND ProductID = ?`, [ + userID, + productID, + ]); + + res.json({ + success: true, + message: "Product removed successfully", + }); + } catch (error) { + console.error("Error removing product:", error); + return res.json({ error: "Could not remove product" }); + } +}; + exports.addFavorite = async (req, res) => { const { userID, productID } = req.body; console.log(userID); try { - // Use parameterized query to prevent SQL injection const [result] = await db.execute( `INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)`, [userID, productID], @@ -72,6 +110,60 @@ exports.removeFavorite = async (req, res) => { } }; +exports.updateProduct = async (req, res) => { + const { productId } = req.params; + const { name, description, price, category, images } = req.body; + + console.log(productId); + + const connection = await db.getConnection(); + try { + await connection.beginTransaction(); + + // Step 1: Check if the product exists + const [checkProduct] = await connection.execute( + "SELECT * FROM Product WHERE ProductID = ?", + [productId], + ); + if (checkProduct.length === 0) { + await connection.rollback(); + return res.status(404).json({ error: "Product not found" }); + } + + // Step 2: Update the product + await connection.execute( + ` + UPDATE Product + SET Name = ?, Description = ?, Price = ?, CategoryID = ? + WHERE ProductID = ? + `, + [name, description, price, category, productId], + ); + + // Step 3: Delete existing images + await connection.execute(`DELETE FROM Image_URL WHERE ProductID = ?`, [ + productId, + ]); + + // Step 4: Insert new image URLs + for (const imageUrl of images) { + await connection.execute( + `INSERT INTO Image_URL (ProductID, URL) VALUES (?, ?)`, + [productId, imageUrl], + ); + } + + await connection.commit(); + res.json({ success: true, message: "Product updated successfully" }); + } catch (error) { + await connection.rollback(); + console.error("Update product error:", error); + res.status(500).json({ error: "Failed to update product" }); + } finally { + connection.release(); + } +}; + exports.myProduct = async (req, res) => { const { userID } = req.body; @@ -253,33 +345,3 @@ exports.getProductById = async (req, res) => { }); } }; - -// db_con.query( -// "SELECT ProductID FROM product WHERE ProductID = ?", -// [productID], -// (err, results) => { -// if (err) { -// console.error("Error checking product:", err); -// return res.json({ error: "Database error" }); -// } - -// if (results.length === 0) { -// return res.json({ error: "Product does not exist" }); -// } -// }, -// ); - -// db_con.query( -// "INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)", -// [userID, productID], -// (err, result) => { -// if (err) { -// console.error("Error adding favorite product:", err); -// return res.json({ error: "Could not add favorite product" }); -// } -// res.json({ -// success: true, -// message: "Product added to favorites successfully", -// }); -// }, -// ); diff --git a/backend/index.js b/backend/index.js index 04c623e..3080f33 100644 --- a/backend/index.js +++ b/backend/index.js @@ -16,7 +16,6 @@ const { cleanupExpiredCodes, checkDatabaseConnection, } = require("./utils/helper"); -const { getAllCategory } = require("./controllers/category"); const app = express(); diff --git a/backend/routes/product.js b/backend/routes/product.js index 8343405..a3c75ba 100644 --- a/backend/routes/product.js +++ b/backend/routes/product.js @@ -8,6 +8,8 @@ const { getProductById, addProduct, myProduct, + removeProduct, + updateProduct, } = require("../controllers/product"); const router = express.Router(); @@ -21,9 +23,12 @@ router.post("/addFavorite", addFavorite); router.post("/getFavorites", getFavorites); router.post("/delFavorite", removeFavorite); +router.post("/delProduct", removeProduct); router.post("/myProduct", myProduct); router.post("/addProduct", addProduct); router.get("/getProduct", getAllProducts); router.get("/:id", getProductById); // Simplified route +router.put("/update/:productId", updateProduct); + module.exports = router; diff --git a/frontend/src/components/ProductForm.jsx b/frontend/src/components/ProductForm.jsx deleted file mode 100644 index 1461c1b..0000000 --- a/frontend/src/components/ProductForm.jsx +++ /dev/null @@ -1,403 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { X, ChevronLeft, Plus, Trash2 } from "lucide-react"; - -const ProductForm = ({ - editingProduct, - setEditingProduct, - onSave, - onCancel, -}) => { - const [selectedCategory, setSelectedCategory] = useState(""); - const [categories, setCategories] = useState([]); - const [categoryMapping, setCategoryMapping] = useState({}); - const storedUser = JSON.parse(sessionStorage.getItem("user")); - - // Fetch categories from API - useEffect(() => { - const fetchCategories = async () => { - try { - const response = await fetch("http://localhost:3030/api/category"); - if (!response.ok) throw new Error("Failed to fetch categories"); - - const responseJson = await response.json(); - const data = responseJson.data; - - // Create an array of category names for the dropdown - // Transform the object into an array of category names - const categoryNames = []; - const mapping = {}; - - // Process the data properly to avoid rendering objects - Object.entries(data).forEach(([id, name]) => { - // Make sure each category name is a string - const categoryName = String(name); - categoryNames.push(categoryName); - mapping[categoryName] = parseInt(id); - }); - - setCategories(categoryNames); - setCategoryMapping(mapping); - } catch (error) { - console.error("Error fetching categories:", error); - } - }; - - fetchCategories(); - }, []); - - const handleSave = async () => { - // Check if the user has selected at least one category - if (!(editingProduct.categories || []).length) { - alert("Please select at least one category"); - return; - } - - try { - // First, upload images if there are any - const imagePaths = []; - - // If we have files to upload, we'd handle the image upload here - if (editingProduct.images && editingProduct.images.length > 0) { - // Simulating image paths for demo purposes - editingProduct.images.forEach((file) => { - const simulatedPath = `/public/uploads/${file.name}`; - imagePaths.push(simulatedPath); - }); - } - - // Get the category ID from the first selected category - const categoryName = (editingProduct.categories || [])[0]; - const categoryID = categoryMapping[categoryName] || 1; // Default to 3 if not found - - // Prepare payload according to API expectations - const payload = { - name: editingProduct.name || "", - price: parseFloat(editingProduct.price) || 0, - qty: 1, - userID: storedUser.ID, - description: editingProduct.description || "", - category: categoryID, - images: imagePaths, - }; - - console.log("Sending payload:", payload); - - const response = await fetch( - "http://localhost:3030/api/product/addProduct", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), - }, - ); - - if (!response.ok) { - const errorData = await response.text(); - throw new Error(`Failed to add product: ${errorData}`); - } - - const data = await response.json(); - console.log("Product added:", data); - if (onSave) onSave(data); - } catch (error) { - console.error("Error saving product:", error); - alert(`Error saving product: ${error.message}`); - } - }; - - const markAsSold = async () => { - // This would call an API to move the product to the transaction table - try { - // API call would go here - console.log("Moving product to transaction table:", editingProduct.id); - - // Toggle the sold status in the UI - setEditingProduct((prev) => ({ - ...prev, - isSold: !prev.isSold, - })); - - // You would add your API call here to update the backend - } catch (error) { - console.error("Error marking product as sold:", error); - } - }; - - const addCategory = () => { - if ( - selectedCategory && - !(editingProduct.categories || []).includes(selectedCategory) - ) { - setEditingProduct((prev) => ({ - ...prev, - categories: [...(prev.categories || []), selectedCategory], - })); - setSelectedCategory(""); - } - }; - - const removeCategory = (categoryToRemove) => { - setEditingProduct((prev) => ({ - ...prev, - categories: (prev.categories || []).filter( - (cat) => cat !== categoryToRemove, - ), - })); - }; - - return ( -
- {/* Back Button */} - - -

- {editingProduct?.id ? "Edit Your Product" : "List a New Product"} -

- -
- {/* Product Name */} -
- - - setEditingProduct({ ...editingProduct, name: e.target.value }) - } - className="w-full px-3 py-2 border border-gray-300 focus:border-emerald-500 focus:outline-none" - /> -
- - {/* Price */} -
- - - setEditingProduct({ - ...editingProduct, - price: e.target.value, - }) - } - className="w-full px-3 py-2 border border-gray-300 focus:border-emerald-500 focus:outline-none" - /> -
- - {/* Sold Status */} -
-
- {editingProduct.isSold && ( - - Sold - - )} -
-
- - {/* Categories */} -
- -
- - -
- - {/* Selected Categories */} - {(editingProduct.categories || []).length > 0 ? ( -
- {(editingProduct.categories || []).map((category, index) => ( - - {category} - - - ))} -
- ) : ( -

- Please select at least one category -

- )} -
- - {/* Description */} -
- - -
- - {/* Image Upload */} -
- - - { - const files = Array.from(e.target.files).slice(0, 5); - setEditingProduct((prev) => ({ - ...prev, - images: [...(prev.images || []), ...files].slice(0, 5), - })); - }} - className="hidden" - id="image-upload" - /> - - - {/* Image previews */} - {(editingProduct.images || []).length > 0 && ( -
-
-

- {editingProduct.images.length}{" "} - {editingProduct.images.length === 1 ? "image" : "images"}{" "} - selected -

- -
-
- {editingProduct.images.map((img, idx) => ( -
- {`Product - -
- ))} -
-
- )} -
-
- - {/* Actions */} -
- - - {editingProduct.id && ( - - )} - - -
-
- ); -}; - -export default ProductForm; diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 9782f87..9acdd69 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -66,7 +66,6 @@ const Home = () => { location.reload(); } } - reloadPage(); useEffect(() => { const fetchrecomProducts = async () => { diff --git a/frontend/src/pages/Selling.jsx b/frontend/src/pages/Selling.jsx index 4fdcde1..d5926fe 100644 --- a/frontend/src/pages/Selling.jsx +++ b/frontend/src/pages/Selling.jsx @@ -1,13 +1,14 @@ import { useState, useEffect } from "react"; import { useLocation, Link } from "react-router-dom"; - -import ProductForm from "../components/ProductForm"; -import { X } from "lucide-react"; +import { X, ChevronLeft, Plus, Trash2 } from "lucide-react"; const Selling = () => { const [products, setProducts] = useState([]); const [showForm, setShowForm] = useState(false); const storedUser = JSON.parse(sessionStorage.getItem("user")); + const [categories, setCategories] = useState([]); + const [categoryMapping, setCategoryMapping] = useState({}); + const [selectedCategory, setSelectedCategory] = useState(""); const [editingProduct, setEditingProduct] = useState({ name: "", @@ -17,6 +18,47 @@ const Selling = () => { images: [], }); + function reloadPage() { + var doctTimestamp = new Date(performance.timing.domLoading).getTime(); + var now = Date.now(); + var tenSec = 10 * 1000; + if (now > doctTimestamp + tenSec) { + location.reload(); + } + } + + // Fetch categories from API + useEffect(() => { + const fetchCategories = async () => { + try { + const response = await fetch("http://localhost:3030/api/category"); + if (!response.ok) throw new Error("Failed to fetch categories"); + + const responseJson = await response.json(); + const data = responseJson.data; + + // Create an array of category names for the dropdown + const categoryNames = []; + const mapping = {}; + + // Process the data properly to avoid rendering objects + Object.entries(data).forEach(([id, name]) => { + // Make sure each category name is a string + const categoryName = String(name); + categoryNames.push(categoryName); + mapping[categoryName] = parseInt(id); + }); + + setCategories(categoryNames); + setCategoryMapping(mapping); + } catch (error) { + console.error("Error fetching categories:", error); + } + }; + + fetchCategories(); + }, []); + // Simulate fetching products from API/database on component mount useEffect(() => { const fetchProducts = async () => { @@ -30,7 +72,7 @@ const Selling = () => { "Content-Type": "application/json", }, body: JSON.stringify({ - userID: storedUser.ID, // Assuming you have userId defined elsewhere in your component + userID: storedUser.ID, }), }, ); @@ -51,48 +93,137 @@ const Selling = () => { }, []); // Add userId to dependency array if it might change // Handle creating or updating a product - const handleSaveProduct = () => { - if (editingProduct.id) { - // Update existing product - setProducts( - products.map((p) => (p.id === editingProduct.id ? editingProduct : p)), - ); - } else { - // Create new product - const newProduct = { - ...editingProduct, - id: Date.now().toString(), // Generate a temporary ID - }; - setProducts([...products, newProduct]); + const handleSaveProduct = async () => { + if (!(editingProduct.categories || []).length) { + alert("Please select at least one category"); + return; } - // Reset form and hide it - setShowForm(false); - setEditingProduct({ - name: "", - price: "", - description: "", - categories: [], - images: [], - }); + try { + const imagePaths = []; + + if (editingProduct.images && editingProduct.images.length > 0) { + editingProduct.images.forEach((file) => { + const simulatedPath = `/public/uploads/${file.name}`; + imagePaths.push(simulatedPath); + }); + } + + const categoryName = (editingProduct.categories || [])[0]; + const categoryID = categoryMapping[categoryName] || 1; + + const payload = { + name: editingProduct.name || "", + price: parseFloat(editingProduct.price) || 0, + qty: 1, + userID: storedUser.ID, + description: editingProduct.description || "", + category: categoryID, + images: imagePaths, + }; + + console.log("Sending payload:", payload); + + const endpoint = editingProduct.ProductID + ? `http://localhost:3030/api/product/update/${editingProduct.ProductID}` + : "http://localhost:3030/api/product/addProduct"; + + const method = editingProduct.ProductID ? "PUT" : "POST"; + + const response = await fetch(endpoint, { + method, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + const errorData = await response.text(); + throw new Error( + `${ + editingProduct.ProductID ? "Failed to update" : "Failed to add" + } product: ${errorData}`, + ); + } + + const data = await response.json(); + console.log("Product saved:", data); + + // Reset form and hide it + setShowForm(false); + setEditingProduct({ + name: "", + price: "", + description: "", + categories: [], + images: [], + }); + + // Reload products + reloadPage(); + } catch (error) { + console.error("Error saving product:", error); + alert(`Error saving product: ${error.message}`); + } }; // Handle product deletion - const handleDeleteProduct = (productId) => { - if (window.confirm("Are you sure you want to delete this product?")) { - setProducts(products.filter((p) => p.id !== productId)); + const handleDeleteProduct = async (productId) => { + try { + // Replace with your actual API endpoint + const response = await fetch( + "http://localhost:3030/api/product/delProduct", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userID: storedUser.ID, + productID: productId, + }), + }, + ); + console.log("deleteproodidt"); + reloadPage(); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + } catch (error) { + console.error("Error fetching products:", error); + // You might want to set an error state here } }; // Handle editing a product const handleEditProduct = (product) => { + // Convert category ID to category name if needed + const categoryName = getCategoryNameById(product.CategoryID); + setEditingProduct({ ...product, + categories: categoryName ? [categoryName] : [], images: product.images || [], // Ensure images array exists }); + setShowForm(true); }; + // Helper function to get category name from ID + const getCategoryNameById = (categoryId) => { + if (!categoryId || !categoryMapping) return null; + + // Find the category name by ID + for (const [name, id] of Object.entries(categoryMapping)) { + if (id === categoryId) { + return name; + } + } + return null; + }; + // Handle adding a new product const handleAddProduct = () => { setEditingProduct({ @@ -105,6 +236,49 @@ const Selling = () => { setShowForm(true); }; + const addCategory = () => { + if ( + selectedCategory && + !(editingProduct.categories || []).includes(selectedCategory) + ) { + setEditingProduct((prev) => ({ + ...prev, + categories: [...(prev.categories || []), selectedCategory], + })); + setSelectedCategory(""); + } + }; + + const removeCategory = (categoryToRemove) => { + setEditingProduct((prev) => ({ + ...prev, + categories: (prev.categories || []).filter( + (cat) => cat !== categoryToRemove, + ), + })); + }; + + const markAsSold = async () => { + // This would call an API to move the product to the transaction table + try { + // API call would go here + console.log( + "Moving product to transaction table:", + editingProduct.ProductID, + ); + + // Toggle the sold status in the UI + setEditingProduct((prev) => ({ + ...prev, + isSold: !prev.isSold, + })); + + // You would add your API call here to update the backend + } catch (error) { + console.error("Error marking product as sold:", error); + } + }; + return (
@@ -120,12 +294,279 @@ const Selling = () => {
{showForm ? ( - setShowForm(false)} - /> +
+ {/* Back Button */} + + +

+ {editingProduct?.ProductID + ? "Edit Your Product" + : "List a New Product"} +

+ +
+ {/* Product Name */} +
+ + + setEditingProduct({ + ...editingProduct, + Name: e.target.value, + name: e.target.value, + }) + } + className="w-full px-3 py-2 border border-gray-300 focus:border-emerald-500 focus:outline-none" + /> +
+ + {/* Price */} +
+ + + setEditingProduct({ + ...editingProduct, + Price: e.target.value, + price: e.target.value, + }) + } + className="w-full px-3 py-2 border border-gray-300 focus:border-emerald-500 focus:outline-none" + /> +
+ + {/* Sold Status */} +
+
+ {editingProduct.isSold && ( + + Sold + + )} +
+
+ + {/* Categories */} +
+ +
+ + +
+ + {/* Selected Categories */} + {(editingProduct.categories || []).length > 0 ? ( +
+ {(editingProduct.categories || []).map((category, index) => ( + + {category} + + + ))} +
+ ) : ( +

+ Please select at least one category +

+ )} +
+ + {/* Description */} +
+ + +
+ + {/* Image Upload */} +
+ + + { + const files = Array.from(e.target.files).slice(0, 5); + setEditingProduct((prev) => ({ + ...prev, + images: [...(prev.images || []), ...files].slice(0, 5), + })); + }} + className="hidden" + id="image-upload" + /> + + + {/* Image previews */} + {(editingProduct.images || []).length > 0 && ( +
+
+

+ {editingProduct.images.length}{" "} + {editingProduct.images.length === 1 ? "image" : "images"}{" "} + selected +

+ +
+
+ {editingProduct.images.map((img, idx) => ( +
+ {`Product + +
+ ))} +
+
+ )} + + {/* Show current image if editing */} + {editingProduct.image_url && ( +
+

Current image:

+
+ Current product +
+
+ )} +
+
+ + {/* Actions */} +
+ + + {editingProduct.ProductID && ( + + )} + + +
+
) : ( <> {products.length === 0 ? ( @@ -147,10 +588,7 @@ const Selling = () => { key={product.ProductID} to={`/product/${product.ProductID}`} > -
+
{product.image_url && product.image_url.length > 0 ? ( { ${product.Price}

- {product.categories && product.categories.length > 0 && ( + {product.CategoryID && (
- {product.CategoryID.map((category) => ( - - {category} - - ))} + + {getCategoryNameById(product.CategoryID) || + product.CategoryID} +
)} @@ -193,13 +627,21 @@ const Selling = () => {