20 Commits

Author SHA1 Message Date
Mann Patel
505f6cd134 updating alerts ui/ux 2025-04-21 01:19:39 -06:00
Mann Patel
53686bd71d Merge branch 'aaqil-Branch' into mann-Branch 2025-04-21 01:08:26 -06:00
Mann Patel
635ba76ed4 Merge branch 'main' into mann-Branch 2025-04-21 01:04:03 -06:00
Mann Patel
5228bf73c9 refactor for redundant code 2025-04-21 01:01:58 -06:00
aruhani
6d2f736541 Updated transaction route file 2025-04-20 23:52:28 -06:00
aruhani
7670bb2b99 Pushing new delete icon 2025-04-20 23:46:22 -06:00
noahnghg
3c7a1a876a fix duplicate products 2025-04-20 23:42:22 -06:00
noahnghg
46bd77025f fix the routers and UI 2025-04-20 23:36:58 -06:00
aruhani
eff9d9d91b Updating Image URL 2025-04-20 23:22:15 -06:00
aruhani
89f5032212 Added a delete function 2025-04-20 23:07:01 -06:00
noahnghg
691980bf7c fix the controller again to show every transaction 2025-04-20 23:03:04 -06:00
noahnghg
3ea45b5400 fix the transaction UI 2025-04-20 23:01:16 -06:00
aruhani
644db7707c Update Frontend Transaction 2025-04-20 22:33:12 -06:00
Mann Patel
f82e111e39 uploads image folder 2025-04-20 22:07:02 -06:00
aruhani
d381ef7973 Transaction frontend update
Included a default message when no items are in the table
2025-04-20 22:03:04 -06:00
noahnghg
25f2c3d8af adding stuff to the onClick 2025-04-20 21:18:43 -06:00
aruhani
f7e4b49ac8 Backend for Transactions
Added in 3 files, for the backend
2025-04-20 10:42:52 -06:00
aruhani
90789b6fd4 Merge branch 'main' into aaqil 2025-04-19 00:00:44 -06:00
Mann Patel
d78b0c32e0 Merge branch 'mannBranch' into aaqil 2025-03-24 23:12:43 -06:00
aruhani
c2e13d56f2 Removing Password Temporarily 2025-03-24 22:43:06 -06:00
21 changed files with 1200 additions and 704 deletions

View File

@@ -391,9 +391,9 @@ exports.getProductWithPagination = async (req, res) => {
}
};
exports.removeProduct = async (req, res) => {
exports.removeAnyProduct = async (req, res) => {
const { id } = req.params;
console.log(id);
try {
const [result] = await db.execute(
`DELETE FROM Product WHERE ProductID = ?`,

View File

@@ -11,11 +11,11 @@ exports.getTransactionWithPagination = async (req, res) => {
LEFT JOIN User U ON T.UserID = U.UserID
LEFT JOIN Product P ON T.ProductID = P.ProductID
ORDER BY T.TransactionID ASC LIMIT ? OFFSET ?`,
[limit.toString(), offset.toString()]
[limit.toString(), offset.toString()],
);
const [result] = await db.execute(
"SELECT COUNT(*) AS count FROM Transaction"
"SELECT COUNT(*) AS count FROM Transaction",
);
const { count: total } = result[0];
return res.json({ data, total });
@@ -29,7 +29,7 @@ exports.removeTransation = async (req, res) => {
try {
const [result] = await db.execute(
"DELETE FROM Transaction WHERE TransactionID = ?;",
[id.toString()]
[id.toString()],
);
return res.json({ message: "Remove transaction successfully!" });
} catch (error) {
@@ -38,3 +38,188 @@ exports.removeTransation = async (req, res) => {
.json({ error: "Cannot remove transactions from database!" });
}
};
// Create a new transaction
exports.createTransaction = async (req, res) => {
const { userID, productID, date, paymentStatus } = req.body;
try {
// Check if the transaction already exists for the same user and product
const [existingTransaction] = await db.execute(
`SELECT TransactionID FROM Transaction WHERE UserID = ? AND ProductID = ?`,
[userID, productID],
);
if (existingTransaction.length > 0) {
return res.status(400).json({
success: false,
message: "Transaction already exists for this user and product",
});
}
// Format the date
const formattedDate = new Date(date)
.toISOString()
.slice(0, 19)
.replace("T", " ");
// Insert the new transaction
const [result] = await db.execute(
`INSERT INTO Transaction (UserID, ProductID, Date, PaymentStatus)
VALUES (?, ?, ?, ?)`,
[userID, productID, formattedDate, paymentStatus],
);
res.json({
success: true,
message: "Transaction created successfully",
transactionID: result.insertId,
});
} catch (error) {
console.error("Error creating transaction:", error);
res.status(500).json({ error: "Could not create transaction" });
}
};
// Get all transactions for a given product
exports.getTransactionsByProduct = async (req, res) => {
const { productID } = req.params;
try {
const [transactions] = await db.execute(
`SELECT
T.TransactionID,
T.UserID,
T.ProductID,
T.Date,
T.PaymentStatus,
P.Name AS ProductName,
MIN(I.URL) AS Image_URL
FROM Transaction T
JOIN Product P ON T.ProductID = P.ProductID
LEFT JOIN Image_URL I ON P.ProductID = I.ProductID
GROUP BY T.TransactionID, T.UserID, T.ProductID, T.Date, T.PaymentStatus, P.Name`,
);
res.json({
success: true,
transactions,
});
} catch (error) {
console.error("Error fetching transactions by product:", error);
res.status(500).json({ error: "Could not retrieve transactions" });
}
};
// Get all transactions for a given user
exports.getTransactionsByUser = async (req, res) => {
const { userID } = req.body;
try {
const [transactions] = await db.execute(
`SELECT
T.TransactionID,
T.UserID,
T.ProductID,
T.Date,
T.PaymentStatus,
P.Name AS ProductName,
I.URL AS Image_URL
FROM Transaction T
JOIN Product P ON T.ProductID = P.ProductID
LEFT JOIN Image_URL I ON P.ProductID = I.ProductID
WHERE T.UserID = ?`,
[userID],
);
res.json({
success: true,
transactions,
});
} catch (error) {
console.error("Error fetching transactions by user:", error);
res.status(500).json({ error: "Could not retrieve transactions" });
}
};
// Get all transactions in the system
exports.getAllTransactions = async (req, res) => {
try {
const [transactions] = await db.execute(
`SELECT
T.TransactionID,
T.UserID,
T.ProductID,
T.Date,
T.PaymentStatus,
P.Name AS ProductName,
MIN(I.URL) AS Image_URL
FROM Transaction T
JOIN Product P ON T.ProductID = P.ProductID
LEFT JOIN Image_URL I ON P.ProductID = I.ProductID
GROUP BY T.TransactionID, T.UserID, T.ProductID, T.Date, T.PaymentStatus, P.Name`,
);
res.json({
success: true,
transactions,
});
} catch (error) {
console.error("Error fetching all transactions:", error);
res.status(500).json({ error: "Could not retrieve transactions" });
}
};
// Update the payment status of a transaction
exports.updatePaymentStatus = async (req, res) => {
const { transactionID, paymentStatus } = req.body;
try {
const [result] = await db.execute(
`UPDATE Transaction
SET PaymentStatus = ?
WHERE TransactionID = ?`,
[paymentStatus, transactionID],
);
if (result.affectedRows === 0) {
return res
.status(404)
.json({ success: false, message: "Transaction not found" });
}
res.json({
success: true,
message: "Payment status updated successfully",
});
} catch (error) {
console.error("Error updating payment status:", error);
res.status(500).json({ error: "Could not update payment status" });
}
};
// Delete a transaction
exports.deleteTransaction = async (req, res) => {
const { transactionID } = req.body;
try {
const [result] = await db.execute(
`DELETE FROM Transaction
WHERE TransactionID = ?`,
[transactionID],
);
if (result.affectedRows === 0) {
return res
.status(404)
.json({ success: false, message: "Transaction not found" });
}
res.json({
success: true,
message: "Transaction deleted successfully",
});
} catch (error) {
console.error("Error deleting transaction:", error);
res.status(500).json({ error: "Could not delete transaction" });
}
};

View File

@@ -9,7 +9,9 @@ const searchRouter = require("./routes/search");
const recommendedRouter = require("./routes/recommendation");
const history = require("./routes/history");
const review = require("./routes/review");
const categoryRouter = require("./routes/category");
const transactionRouter = require("./routes/transaction");
const { generateEmailTransporter } = require("./utils/mail");
@@ -44,9 +46,9 @@ app.use("/api/search", searchRouter);
app.use("/api/engine", recommendedRouter);
app.use("/api/history", history);
app.use("/api/review", review);
app.use("/api/category", categoryRouter);
app.use("/api/transaction", transactionRouter);
app.use("/api/category", categoryRouter);
app.use("/api/transaction", transactionRouter);
// Set up a scheduler to run cleanup every hour
clean_up_time = 30 * 60 * 1000;

View File

@@ -8,6 +8,7 @@ const {
getProductById,
addProduct,
removeProduct,
removeAnyProduct,
getProductWithPagination,
myProduct,
updateProduct,
@@ -30,7 +31,7 @@ router.post("/addProduct", addProduct);
router.get("/getProduct", getAllProducts);
//Remove product
router.delete("/:id", removeProduct);
router.delete("/any/:id", removeAnyProduct);
//Get products with pagination
router.get("/getProductWithPagination", getProductWithPagination);

View File

@@ -1,11 +1,41 @@
// routes/transaction.js
const express = require("express");
const {
createTransaction,
getTransactionsByProduct,
getTransactionsByUser,
getAllTransactions,
updatePaymentStatus,
deleteTransaction,
getTransactionWithPagination,
removeTransation,
} = require("../controllers/transaction");
const router = express.Router();
// logging middleware
router.use((req, res, next) => {
console.log(`Incoming ${req.method} ${req.originalUrl}`);
next();
});
// Create a new transaction
router.post("/createTransaction", createTransaction);
// Get all transactions for a specific product
router.get("/getTransactionsByProduct/:productID", getTransactionsByProduct);
// Get all transactions for a specific user
router.post("/getTransactionsByUser", getTransactionsByUser);
// Get all transactions in the system
router.post("/getAllTransactions", getAllTransactions);
// Update payment status on a transaction
router.patch("/updatePaymentStatus", updatePaymentStatus);
// Delete a transaction
router.delete("/deleteTransaction", deleteTransaction);
router.get("/getTransactions", getTransactionWithPagination);
router.delete("/:id", removeTransation);

View File

@@ -18,7 +18,7 @@ async function sendVerificationEmail(email, verificationCode) {
// Clean up expired verification codes (run this periodically)
function cleanupExpiredCodes() {
db_con.query(
db.query(
"DELETE FROM AuthVerification WHERE Date < DATE_SUB(NOW(), INTERVAL 15 MINUTE) AND Authenticated = 0",
(err, result) => {
if (err) {

View File

@@ -4,6 +4,7 @@ import {
Routes,
Route,
Navigate,
useLocation,
} from "react-router-dom";
import Navbar from "./components/Navbar";
import Home from "./pages/Home";
@@ -12,14 +13,10 @@ import Selling from "./pages/Selling";
import Transactions from "./pages/Transactions";
import Favorites from "./pages/Favorites";
import ProductDetail from "./pages/ProductDetail";
import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage
import Dashboard from "./pages/Dashboard";
import UserDashboard from "./pages/UserDashboard";
import ProductDashboard from "./pages/ProductDashboard";
import SearchPage from "./pages/SearchPage";
import Dashboard from "./pages/Dashboard"; // The single consolidated dashboard component
import DashboardNav from "./components/DashboardNav";
import CategoryDashboard from "./pages/CategoryDashboard";
import { verifyIsAdmin } from "./api/admin";
import TransactionDashboard from "./pages/TransactionDashboard";
function App() {
// Authentication state - initialize from localStorage if available
@@ -42,6 +39,18 @@ function App() {
useState(false);
const [recommendations, setRecommendations] = useState([]);
// Admin state
const [isAdmin, setIsAdmin] = useState(false);
const [showAdminDashboard, setShowAdminDashboard] = useState(false);
// Check URL to determine if we're in admin mode
useEffect(() => {
// If URL contains /admin, set showAdminDashboard to true
if (window.location.pathname.includes("/admin")) {
setShowAdminDashboard(true);
}
}, []);
// New verification states
const [verificationStep, setVerificationStep] = useState("initial"); // 'initial', 'code-sent', 'verifying'
const [tempUserData, setTempUserData] = useState(null);
@@ -127,9 +136,6 @@ function App() {
}
};
const [isAdmin, setIsAdmin] = useState(false);
const [showAdminDashboard, setShowAdminDashboard] = useState(false);
useEffect(() => {
const userInfo = sessionStorage.getItem("user")
? JSON.parse(sessionStorage.getItem("user"))
@@ -142,9 +148,14 @@ function App() {
const handleShowAdminDashboard = () => {
setShowAdminDashboard(true);
// Update URL without reloading page
window.history.pushState({}, "", "/admin");
};
const handleCloseAdminDashboard = () => {
setShowAdminDashboard(false);
// Update URL without reloading page
window.history.pushState({}, "", "/");
};
// Send verification code
@@ -436,6 +447,7 @@ function App() {
setVerificationStep("initial");
setTempUserData(null);
setRecommendations([]);
setShowAdminDashboard(false);
// Clear localStorage
sessionStorage.removeItem("user");
@@ -774,31 +786,21 @@ function App() {
return children;
};
// If user is admin, show admin naviagtion
if (showAdminDashboard) {
return (
<Router>
{/* If admin dashboard should be shown */}
{showAdminDashboard ? (
<div className="flex">
<DashboardNav handleCloseAdminDashboard={handleCloseAdminDashboard} />
<Routes>
{/* Admin routes */}
<Route path="/admin" element={<Dashboard />} />
<Route path="/admin/user" element={<UserDashboard />} />
<Route path="/admin/product" element={<ProductDashboard />} />
<Route path="/admin/category" element={<CategoryDashboard />} />
<Route
path="/admin/transaction"
element={<TransactionDashboard />}
/>
<Route path="*" element={<Dashboard />} />
{/* Single admin route for consolidated dashboard */}
<Route path="/admin/*" element={<Dashboard />} />
{/* Any other path in admin mode should go to dashboard */}
<Route path="*" element={<Navigate to="/admin" />} />
</Routes>
</div>
</Router>
);
}
return (
<Router>
) : (
/* Normal user interface */
<div className="min-h-screen bg-gray-50">
{/* Show loading overlay when generating recommendations */}
{isGeneratingRecommendations && <LoadingOverlay />}
@@ -816,7 +818,9 @@ function App() {
{/* Public routes */}
<Route
path="/login"
element={isAuthenticated ? <Navigate to="/" /> : <LoginComponent />}
element={
isAuthenticated ? <Navigate to="/" /> : <LoginComponent />
}
/>
{/* Protected routes */}
<Route
@@ -894,6 +898,7 @@ function App() {
/>
</Routes>
</div>
)}
</Router>
);
}

View File

@@ -1,77 +1,20 @@
import client from "./client";
// api.js
import axios from "axios";
const client = axios.create({
baseURL: "http://localhost:3030/api",
});
// Users
export const getUsers = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/user/getUserWithPagination?page=${page}&limit=${limit}`
`/user/getUserWithPagination?page=${page}&limit=${limit}`,
);
return { users: data.users, total: data.total };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const getProducts = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/product/getProductWithPagination?limit=${limit}&page=${page}`
);
return { products: data.products, total: data.totalProd };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const getCategories = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/category/getCategories?page=${page}&limit=${limit}`
);
return { data: data.data, total: data.total };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const getTransactions = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/transaction/getTransactions?limit=${limit}&page=${page}`
);
return { transactions: data.data, total: data.total };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const addCategory = async (name) => {
try {
const { data } = await client.post(`/category/addCategory`, { name: name });
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const removeCategory = async (id) => {
try {
const { data } = await client.delete(`/category/${id}`);
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
return handleError(error);
}
};
@@ -80,20 +23,7 @@ export const removeUser = async (id) => {
const { data } = await client.post(`/user/delete`, { userId: id });
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const removeProduct = async (id) => {
try {
const { data } = await client.delete(`/product/${id}`);
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
return handleError(error);
}
};
@@ -102,9 +32,70 @@ export const verifyIsAdmin = async (id) => {
const { data } = await client.get(`/user/isAdmin/${id}`);
return { isAdmin: data.isAdmin };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
return handleError(error);
}
};
// Products
export const getProducts = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/product/getProductWithPagination?limit=${limit}&page=${page}`,
);
return { products: data.products, total: data.totalProd };
} catch (error) {
return handleError(error);
}
};
export const removeProduct = async (id) => {
try {
const { data } = await client.delete(`/product/any/${id}`);
return { message: data.message };
} catch (error) {
return handleError(error);
}
};
// Categories
export const getCategories = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/category/getCategories?page=${page}&limit=${limit}`,
);
return { data: data.data, total: data.total };
} catch (error) {
return handleError(error);
}
};
export const addCategory = async (name) => {
try {
const { data } = await client.post(`/category/addCategory`, { name });
return { message: data.message };
} catch (error) {
return handleError(error);
}
};
export const removeCategory = async (id) => {
try {
const { data } = await client.delete(`/category/${id}`);
return { message: data.message };
} catch (error) {
return handleError(error);
}
};
// Transactions
export const getTransactions = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/transaction/getTransactions?limit=${limit}&page=${page}`,
);
return { transactions: data.data, total: data.total };
} catch (error) {
return handleError(error);
}
};
@@ -113,8 +104,16 @@ export const removeTransaction = async (id) => {
const { data } = await client.delete(`/transaction/${id}`);
return { message: data.message };
} catch (error) {
return handleError(error);
}
};
// Shared Error Handler
const handleError = (error) => {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
// Optional: export client if you want to use it elsewhere
export default client;

View File

@@ -1,3 +0,0 @@
import axios from "axios";
const client = axios.create({ baseURL: "http://localhost:3030/api" });
export default client;

View File

@@ -1,98 +1,15 @@
import { Link, NavLink } from "react-router-dom";
import { FaUserTag } from "react-icons/fa";
import { FaBoxArchive } from "react-icons/fa6";
import { MdOutlineCategory } from "react-icons/md";
import { FaArrowLeft } from "react-icons/fa";
import { FaMoneyBillTransfer } from "react-icons/fa6";
export default function DashboardNav({ handleCloseAdminDashboard }) {
const handleClick = () => {
handleCloseAdminDashboard();
};
return (
<div>
<div className="w-3xs h-screen bg-green-700 border border-green-700">
<ul>
<li>
<div className="flex-shrink-0 p-6 bg-green-200">
<Link to="/admin" className="flex items-center">
<img
src="/icon/icon-512.png"
alt="Campus Plug"
className="h-8 px-2 "
/>
<span className="hidden md:block text-green-700 font-bold text-2xl">
Campus Plug
</span>
</Link>
</div>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/user"
className={({ isActive }) =>
(isActive
? " text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5 "
}
<div className="w-48 min-w-[12rem] bg-gray-100 text-emerald-600 flex flex-col p-4 shadow-md">
<button
onClick={handleCloseAdminDashboard}
className="flex items-center gap-2 text-sm font-medium hover:text-emerald-700 underline underline-offset-4 transition"
>
<FaUserTag />
<span className="pl-3">Users</span>
</NavLink>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/product"
className={({ isActive }) =>
(isActive
? "text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5"
}
>
<FaBoxArchive />
<span className="pl-3">Products</span>
</NavLink>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/category"
className={({ isActive }) =>
(isActive
? "text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5"
}
>
<MdOutlineCategory />
<span className="pl-3">Categories</span>
</NavLink>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/transaction"
className={({ isActive }) =>
(isActive
? "text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5"
}
>
<FaMoneyBillTransfer />
<span className="pl-3">Transaction</span>
</NavLink>
</li>
</ul>
<div
onClick={handleClick}
className=" text-center my-8 underline text-white underline-offset-4 flex justify-center items-center hover:cursor-pointer h-fit w-fit mx-auto hover:text-green-200 transition"
>
<FaArrowLeft className="text-sm mx-2 mt-1" />
<span>Go back to user page</span>
</div>
</div>
<FaArrowLeft className="text-xs mt-[1px]" />
Back to User Page
</button>
</div>
);
}

View File

@@ -1,105 +0,0 @@
import { useEffect, useState } from "react";
import { getCategories, removeCategory } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
import { IoAddCircleSharp } from "react-icons/io5";
import CategoryForm from "../components/CategoryForm";
export default function CategoryDashboard() {
const [categories, setCategories] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const [visible, setVisible] = useState(false);
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchCategory(page, limit);
};
const fetchCategory = (page = 1, limit = 10) => {
getCategories(page, limit).then(({ data, total }) => {
setCategories(data);
setTotal(total);
});
};
const notiChange = () => {
fetchCategory(currentPage);
};
const handleToggleForm = () => {
setVisible((curr) => !curr);
};
const handleRemove = (id) => {
removeCategory(id)
.then(() => {
fetchCategory(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchCategory, []);
return (
<div className="pt-10 p-20 w-full">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
CATEGORIES
</h1>
<button
onClick={handleToggleForm}
className="flex justify-end items-center bg-blue-500 rounded-md px-2 text-white text-sm p-1 mb-1 ml-auto hover:cursor-pointer hover:bg-blue-600 transtion"
>
<span className="pr-1 text-xs">Add</span>
<IoAddCircleSharp />
</button>
<CategoryForm onAddCategory={notiChange} visible={visible} />
{categories.length > 0 ? (
<>
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>CategoryID</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{categories.map((category) => (
<tr
key={category.UserID}
className="border border-green-600 h-10"
>
<td>{category.CategoryID}</td>
<td>{category.Name}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemove(category.CategoryID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
pageNum={Math.ceil(total / pageLimit)}
onChange={onChangePage}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1 w-fit">
No category exists!
</p>
)}
</div>
);
}

View File

@@ -1,7 +1,362 @@
export default function Dashboard() {
import { useEffect, useState, useCallback } from "react";
import {
getUsers,
removeUser,
getTransactions,
removeTransaction,
getProducts,
removeProduct,
getCategories,
removeCategory,
} from "../api/admin";
import { MdDelete } from "react-icons/md";
import { IoAddCircleSharp } from "react-icons/io5";
import { FaHome } from "react-icons/fa";
import Pagination from "../components/Pagination";
import CategoryForm from "../components/CategoryForm";
import DashboardNav from "../components/DashboardNav";
import { useNavigate } from "react-router-dom";
// Spinner Component
const Spinner = () => (
<div className="flex justify-center items-center h-40 w-full">
<div className="w-12 h-12 border-4 border-green-500 border-dashed rounded-full animate-spin"></div>
</div>
);
// Empty State Component
const EmptyState = () => (
<div className="flex flex-col items-center justify-center bg-gray-50 py-10 rounded-lg w-full">
<svg
className="w-16 h-16 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
></path>
</svg>
<p className="mt-4 text-lg font-medium text-gray-600">No data found</p>
<p className="mt-1 text-sm text-gray-500">
No records are currently available.
</p>
</div>
);
// Generic Dashboard Component
const Dashboard = ({
fetchDataFn,
deleteFn,
columns,
idKey,
refreshKey = 0,
headerAction = null,
}) => {
const navigate = useNavigate();
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const [loading, setLoading] = useState(true);
const pageLimit = 10;
const fetchItems = useCallback(
(page = 1, limit = 10) => {
setLoading(true);
fetchDataFn(page, limit)
.then((res) => {
const data =
res.users || res.products || res.transactions || res.data || [];
setItems(data);
setTotal(res.total);
})
.catch((error) => {
console.error("Error fetching data:", error);
setItems([]);
setTotal(0);
})
.finally(() => setLoading(false));
},
[fetchDataFn],
);
const handleRemove = (id) => {
if (window.confirm("Are you sure you want to delete this item?")) {
setLoading(true);
deleteFn(id)
.then(() => fetchItems(currentPage))
.catch((error) => {
console.error("Error removing item:", error);
setLoading(false);
});
}
};
useEffect(() => {
fetchItems(currentPage);
}, [fetchItems, currentPage, refreshKey]);
const onChangePage = (page) => {
setCurrentPage(page);
};
if (loading) return <Spinner />;
return (
<div className="text-3xl font-bold p-3 text-green-800">
Welcome to admin dashboard
<div className="w-full mt-6">
<div className="flex flex-col md:flex-row justify-between items-center mb-4 w-full">
<h2 className="text-xl font-semibold text-gray-700 mb-2 md:mb-0">
Total: <span className="text-green-600">{total}</span>
</h2>
{headerAction && <div>{headerAction}</div>}
</div>
{items.length > 0 ? (
<div className="w-full overflow-x-auto bg-white rounded-lg shadow">
<table className="min-w-full divide-y divide-gray-200 table-fixed">
<thead className="bg-green-600">
<tr>
{columns.map((col) => (
<th
key={col.label}
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider"
>
{col.label}
</th>
))}
<th
scope="col"
className="px-6 py-3 text-center text-xs font-medium text-white uppercase tracking-wider w-20"
>
Action
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{items.map((item) => (
<tr
key={item[idKey]}
className="hover:bg-gray-50 transition-colors"
>
{columns.map((col) => (
<td
key={`${item[idKey]}-${col.key}`}
className="px-6 py-4 whitespace-nowrap text-sm text-gray-700 overflow-hidden text-ellipsis"
>
{item[col.key] || "—"}
</td>
))}
<td className="px-6 py-4 whitespace-nowrap text-center text-sm font-medium">
<button
onClick={() => handleRemove(item[idKey])}
className="text-red-500 hover:text-red-700 transition-colors"
title="Delete"
>
<MdDelete size={20} />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<EmptyState />
)}
{total > pageLimit && (
<div className="mt-4 w-full">
<Pagination
pageNum={Math.ceil(total / pageLimit)}
onChange={onChangePage}
/>
</div>
)}
</div>
);
};
// Main Admin Tabs
export default function AdminDashboardTabs() {
const [activeTab, setActiveTab] = useState(0);
const [tabData, setTabData] = useState({
users: { loaded: false, data: null },
products: { loaded: false, data: null },
transactions: { loaded: false, data: null },
categories: { loaded: false, data: null },
});
const [visible, setVisible] = useState(false);
const [categoryRefreshKey, setCategoryRefreshKey] = useState(0);
const toggleForm = () => setVisible((v) => !v);
// Preload all tab data
useEffect(() => {
const tabKeys = ["users", "products", "transactions", "categories"];
const loadTabData = async (index) => {
if (index === tabKeys.length) return;
setTabData((prev) => ({
...prev,
[tabKeys[index]]: { ...prev[tabKeys[index]], loaded: true },
}));
// Load next tab after a short delay
setTimeout(() => loadTabData(index + 1), 100);
};
loadTabData(0);
}, []);
const tabs = [
{
title: "Users",
icon: "👥",
key: "users",
component: () => (
<Dashboard
fetchDataFn={getUsers}
deleteFn={removeUser}
idKey="UserID"
columns={[
{ label: "ID", key: "UserID" },
{ label: "UCID", key: "UCID" },
{ label: "Name", key: "Name" },
{ label: "Email", key: "Email" },
{ label: "Phone", key: "Phone" },
{ label: "Address", key: "Address" },
]}
/>
),
},
{
title: "Products",
icon: "📦",
key: "products",
component: () => (
<Dashboard
fetchDataFn={getProducts}
deleteFn={removeProduct}
idKey="ProductID"
columns={[
{ label: "ID", key: "ProductID" },
{ label: "Name", key: "ProductName" },
{ label: "Price", key: "Price" },
{ label: "Category", key: "Category" },
{ label: "Seller", key: "SellerName" },
]}
/>
),
},
{
title: "Transactions",
icon: "💰",
key: "transactions",
component: () => (
<Dashboard
fetchDataFn={getTransactions}
deleteFn={removeTransaction}
idKey="TransactionID"
columns={[
{ label: "ID", key: "TransactionID" },
{ label: "User", key: "UserName" },
{ label: "Product", key: "ProductName" },
{ label: "Date", key: "Date" },
{ label: "Status", key: "PaymentStatus" },
]}
/>
),
},
{
title: "Categories",
icon: "🏷️",
key: "categories",
component: () => (
<>
<Dashboard
fetchDataFn={getCategories}
deleteFn={removeCategory}
idKey="CategoryID"
columns={[
{ label: "ID", key: "CategoryID" },
{ label: "Name", key: "Name" },
]}
refreshKey={categoryRefreshKey}
headerAction={
<button
onClick={toggleForm}
className="flex items-center bg-green-500 rounded-md px-4 py-2 text-white text-sm hover:bg-green-600 transition-colors font-medium shadow"
>
<IoAddCircleSharp className="mr-1" size={18} />
Add Category
</button>
}
/>
<CategoryForm
visible={visible}
onAddCategory={() => {
setCategoryRefreshKey((prev) => prev + 1);
toggleForm();
}}
/>
</>
),
},
];
return (
<div className="w-full min-h-screen bg-gray-50">
<div className="w-full px-4 py-8">
<div className="mb-8 flex justify-between items-center w-full">
<h1 className="text-2xl md:text-3xl font-bold text-gray-800">
Admin Dashboard
</h1>
</div>
{/* Mobile Tabs */}
<div className="md:hidden w-full mb-4">
<select
className="w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring focus:ring-green-500 focus:ring-opacity-50 p-2"
value={activeTab}
onChange={(e) => setActiveTab(parseInt(e.target.value))}
>
{tabs.map((tab, index) => (
<option key={tab.key} value={index}>
{tab.icon} {tab.title}
</option>
))}
</select>
</div>
{/* Desktop Tabs */}
<div className="hidden md:flex space-x-1 border-b border-gray-200 w-full overflow-x-auto">
{tabs.map((tab, index) => (
<button
key={tab.key}
className={`px-6 py-3 font-medium text-sm rounded-t-lg transition-colors ${
index === activeTab
? "text-green-700 bg-white border-l border-t border-r border-gray-200 border-b-0"
: "text-gray-600 hover:text-green-700 bg-gray-50"
}`}
onClick={() => setActiveTab(index)}
>
<span className="inline-block mr-2">{tab.icon}</span>
{tab.title}
</button>
))}
</div>
<div className="bg-white p-4 md:p-6 rounded-lg shadow-sm border border-gray-200 mt-4 w-full">
<div className="w-full">
{tabData[tabs[activeTab].key].loaded && tabs[activeTab].component()}
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,91 +0,0 @@
import { useEffect, useState } from "react";
import { getProducts, removeProduct } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
export default function ProductDashboard() {
const [products, setProducts] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchProducts(page, limit);
};
const fetchProducts = (page = 1, limit = 10) => {
getProducts(page, limit).then(({ products, total }) => {
setTotal(total);
setProducts(products);
});
};
const handleRemoveProduct = (id) => {
removeProduct(id)
.then((res) => {
fetchProducts(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchProducts, []);
return (
<div className="pt-10 p-20">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
PRODUCTS
</h1>
{products.length > 0 ? (
<>
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>ProductID</th>
<th>Name</th>
<th>Price</th>
<th>Category</th>
<th>Seller</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{products.map((product) => (
<tr
key={product.ProductID}
className="border border-green-600 h-10"
>
<td>{product.ProductID}</td>
<td>{product.ProductName}</td>
<td>{product.Price}</td>
<td>{product.Category ? product.Category : "N/A"}</td>
<td>{product.SellerName ? product.SellerName : "N/A"}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemoveProduct(product.ProductID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
onChange={onChangePage}
pageNum={Math.ceil(total / pageLimit)}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1 w-fit">
No product exists!
</p>
)}
</div>
);
}

View File

@@ -30,6 +30,8 @@ const ProductDetail = () => {
const [reviews, setReviews] = useState([]);
const [showReviewForm, setShowReviewForm] = useState(false);
const [showAlert, setShowAlert] = useState(false);
const [showAlert1, setShowAlert1] = useState(false);
const storedUser = JSON.parse(sessionStorage.getItem("user"));
const toggleFavorite = async (id) => {
@@ -303,6 +305,12 @@ const ProductDetail = () => {
onClose={() => setShowAlert(false)}
/>
)}
{showAlert1 && (
<FloatingAlert
message="Product added to transaction!"
onClose={() => setShowAlert1(false)}
/>
)}
<div className="flex flex-col md:flex-row gap-8">
<div className="md:w-3/5">
@@ -414,8 +422,40 @@ const ProductDetail = () => {
<div className="relative">
<button
onClick={() => setShowContactOptions(!showContactOptions)}
className="w-full bg-emerald-700 hover:bg-emerald-700 text-white font-medium py-3 px-4 mb-3"
onClick={async () => {
try {
// Create a transaction record
const transactionData = {
userID: storedUser.ID, // User ID from session storage
productID: product.ProductID, // Product ID from the product details
date: new Date().toISOString(), // Current date in ISO format
paymentStatus: "Pending", // Default payment status
};
const response = await fetch(
"http://localhost:3030/api/transaction/createTransaction",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(transactionData),
},
);
const result = await response.json();
if (result.success) {
setShowAlert1(true);
}
// Toggle contact options visibility
setShowContactOptions(!showContactOptions);
} catch (error) {
console.error("Error creating transaction:", error);
alert(`Error: ${error.message}`);
}
}}
className="w-full bg-green-500 hover:bg-green-600 text-white font-medium py-3 px-4 mb-3"
>
Contact Seller
</button>

View File

@@ -1,91 +0,0 @@
import { useEffect, useState } from "react";
import { getTransactions, removeTransaction } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
export default function TransactionDashboard() {
const [transactions, setTransactions] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchTransactions(page, limit);
};
const fetchTransactions = (page = 1, limit = 10) => {
getTransactions(page, limit).then(({ transactions, total }) => {
setTotal(total);
setTransactions(transactions);
});
};
const handleRemoveTransaction = (id) => {
removeTransaction(id)
.then(() => {
fetchTransactions(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchTransactions, []);
return (
<div className="pt-10 p-20">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
TRANSACTIONS
</h1>
{transactions.length > 0 ? (
<>
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>TransactionID</th>
<th>User</th>
<th>Product</th>
<th>Date</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{transactions.map((t) => (
<tr
key={t.TransactionID}
className="border border-green-600 h-10"
>
<td>{t.TransactionID}</td>
<td>{t.UserName ? t.UserName : "N/A"}</td>
<td>{t.ProductName ? t.ProductName : "N/A"}</td>
<td>{t.Date}</td>
<td>{t.PaymentStatus}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemoveTransaction(t.TransactionID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
onChange={onChangePage}
pageNum={Math.ceil(total / pageLimit)}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1 w-fit">
No transaction exists!
</p>
)}
</div>
);
}

View File

@@ -1,8 +1,213 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Calendar, CreditCard, Trash2 } from "lucide-react";
const Transactions = () => {
return <div></div>;
const [transactions, setTransactions] = useState([]);
useEffect(() => {
const fetchTransactions = async () => {
try {
const response = await fetch(
"http://localhost:3030/api/transaction/getAllTransactions",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userID: 1 }), // replace with actual userID
}
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { transactions: txData } = await response.json();
if (!Array.isArray(txData)) return;
setTransactions(
txData.map((tx) => ({
id: tx.TransactionID,
productId: tx.ProductID,
name: tx.ProductName || "Unnamed Product",
price: tx.Price != null ? parseFloat(tx.Price) : null,
image: tx.Image_URL || "/default-image.jpg",
date: tx.Date,
status: tx.PaymentStatus,
}))
);
} catch (error) {
console.error("Failed to fetch transactions:", error);
}
};
fetchTransactions();
}, []);
const deleteTransaction = async (id) => {
try {
const res = await fetch(
"http://localhost:3030/api/transaction/deleteTransaction",
{
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ transactionID: id }),
}
);
const data = await res.json();
if (data.success) {
setTransactions((prev) => prev.filter((tx) => tx.id !== id));
} else {
console.error("Delete failed:", data.message);
}
} catch (err) {
console.error("Error deleting transaction:", err);
}
};
const formatDate = (dateString) => {
const d = new Date(dateString);
return d.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
};
return (
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-gray-800">My Transactions</h1>
</div>
{transactions.length === 0 ? (
<div className="bg-white border border-gray-200 p-8 text-center">
<CreditCard className="h-12 w-12 text-gray-300 mx-auto mb-4" />
<h3 className="text-xl font-medium text-gray-700 mb-2">
No transactions found
</h3>
<p className="text-gray-500 mb-4">
Once transactions are created, theyll appear here.
</p>
<Link
to="/"
className="inline-block bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-4"
>
Browse Listings
</Link>
</div>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{transactions.map((tx) => (
<div
key={tx.id}
className="relative border-2 border-gray-200 overflow-hidden hover:shadow-md transition-shadow"
>
{/* Delete Button */}
<button
onClick={(e) => {
e.preventDefault();
deleteTransaction(tx.id);
}}
className="absolute bottom-2 right-2 text-red-500 hover:text-red-600 z-10"
>
<Trash2 size={20} />
</button>
<Link to={`/product/${tx.productId}`}>
<div className="h-48 bg-gray-200 flex items-center justify-center">
{tx.image ? (
<img
src={tx.image}
alt={tx.name}
className="w-full h-full object-cover"
/>
) : (
<div className="text-gray-400">No image</div>
)}
</div>
<div className="p-4">
<h3 className="text-lg font-semibold text-gray-800">
{tx.name}
</h3>
{tx.price !== null && (
<p className="text-emerald-600 font-bold mt-1">
${tx.price.toFixed(2)}
</p>
)}
<div className="flex items-center text-gray-500 text-sm mt-2">
<Calendar className="mr-1" size={16} />
{formatDate(tx.date)}
</div>
<p className="text-gray-600 text-sm mt-1">
Status: <span className="font-medium">{tx.status}</span>
</p>
</div>
</Link>
</div>
))}
</div>
<div className="mt-6 text-sm text-gray-500">
Showing {transactions.length}{" "}
{transactions.length === 1 ? "transaction" : "transactions"}
</div>
</>
)}
<footer className="bg-gray-800 text-white py-6 mt-12">
<div className="container mx-auto px-4">
<div className="flex flex-col md:flex-row justify-between items-center">
<div className="mb-4 md:mb-0">
<h3 className="text-lg font-semibold mb-2">
Campus Marketplace
</h3>
<p className="text-gray-400 text-sm">
Your trusted university trading platform
</p>
</div>
<div className="flex space-x-6">
<div>
<h4 className="font-medium mb-2">Quick Links</h4>
<ul className="text-sm text-gray-400">
<li className="mb-1">
<Link to="/" className="hover:text-white transition">
Home
</Link>
</li>
<li className="mb-1">
<Link
to="/selling"
className="hover:text-white transition"
>
Sell an Item
</Link>
</li>
<li className="mb-1">
<Link
to="/favorites"
className="hover:text-white transition"
>
My Favorites
</Link>
</li>
</ul>
</div>
<div>
<h4 className="font-medium mb-2">Contact</h4>
<ul className="text-sm text-gray-400">
<li className="mb-1">support@campusmarket.com</li>
<li className="mb-1">University of Calgary</li>
</ul>
</div>
</div>
</div>
<div className="border-t border-gray-700 mt-6 pt-6 text-center text-sm text-gray-400">
<p>
© {new Date().getFullYear()} Campus Marketplace. All rights
reserved.
</p>
</div>
</div>
</footer>
</div>
);
};
export default Transactions;

View File

@@ -1,91 +0,0 @@
import { useEffect, useState } from "react";
import { getUsers, removeUser } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
export default function UserDashboard() {
const [users, setUsers] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchUsers(page, limit);
};
const fetchUsers = (page = 1, limit = 10) => {
getUsers(page, limit).then(({ users, total }) => {
setUsers(users);
setTotal(total);
});
};
const handleRemoveUser = (id) => {
removeUser(id)
.then((res) => {
fetchUsers(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchUsers, []);
return (
<div className="pt-10 p-20">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
USERS
</h1>
{users.length > 0 ? (
<>
{" "}
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>UserID</th>
<th>UCID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Address</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.UserID} className="border border-green-600 h-10">
<td>{user.UserID}</td>
<td>{user.UCID}</td>
<td>{user.Name}</td>
<td>{user.Email}</td>
<td>{user.Phone}</td>
<td>{user.Address}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemoveUser(user.UserID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
pageNum={Math.ceil(total / pageLimit)}
onChange={onChangePage}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1">
No user exists!
</p>
)}
</div>
);
}

124
frontend/src/schema.sql Normal file
View File

@@ -0,0 +1,124 @@
-- MySql Version 9.2.0
CREATE DATABASE Marketplace;
USE Marketplace;
-- User Entity
CREATE TABLE User (
UserID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
Email VARCHAR(100) UNIQUE NOT NULL,
UCID VARCHAR(20) UNIQUE NOT NULL,
Password VARCHAR(255) NOT NULL,
Phone VARCHAR(20),
Address VARCHAR(255)
);
CREATE TABLE UserRole (
UserID INT,
Client BOOLEAN DEFAULT True,
Admin BOOLEAN DEFAULT FALSE,
PRIMARY KEY (UserID),
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE
);
-- Category Entity (must be created before Product or else error)
CREATE TABLE Category (
CategoryID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL
);
-- Product Entity
CREATE TABLE Product (
ProductID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
Price DECIMAL(10, 2) NOT NULL,
StockQuantity INT,
UserID INT,
Description TEXT,
CategoryID INT NOT NULL,
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE SET NULL
);
-- Fixed Image_URL table
CREATE TABLE Image_URL (
URL VARCHAR(255),
ProductID INT,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
);
-- Fixed Review Entity (Many-to-One with User, Many-to-One with Product)
CREATE TABLE Review (
ReviewID INT AUTO_INCREMENT PRIMARY KEY,
UserID INT,
ProductID INT,
Comment TEXT,
Rating INT CHECK (
Rating >= 1
AND Rating <= 5
),
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
);
-- Transaction Entity (Many-to-One with User, Many-to-One with Product)
CREATE TABLE Transaction (
TransactionID INT PRIMARY KEY,
UserID INT,
ProductID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
PaymentStatus VARCHAR(50),
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
);
-- Recommendation Entity (Many-to-One with User, Many-to-One with Product)
CREATE TABLE Recommendation (
RecommendationID_PK INT AUTO_INCREMENT PRIMARY KEY,
UserID INT,
RecommendedProductID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
);
-- History Entity (Many-to-One with User, Many-to-One with Product)
CREATE TABLE History (
HistoryID INT AUTO_INCREMENT PRIMARY KEY,
UserID INT,
ProductID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
);
-- Favorites Entity (Many-to-One with User, Many-to-One with Product)
CREATE TABLE Favorites (
FavoriteID INT AUTO_INCREMENT PRIMARY KEY,
UserID INT,
ProductID INT,
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE,
UNIQUE (UserID, ProductID)
);
-- Product-Category Junction Table (Many-to-Many)
CREATE TABLE Product_Category (
ProductID INT,
CategoryID INT,
PRIMARY KEY (ProductID, CategoryID),
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE,
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE CASCADE
);
-- Login Authentication table
CREATE TABLE AuthVerification (
UserID INT AUTO_INCREMENT PRIMARY KEY,
Email VARCHAR(100) UNIQUE NOT NULL,
VerificationCode VARCHAR(6) NOT NULL,
Authenticated BOOLEAN DEFAULT FALSE,
Date DATETIME DEFAULT CURRENT_TIMESTAMP
);

View File

@@ -101,7 +101,8 @@ VALUES
('Photography Equipment'),
('Event Tickets'),
('Software Licenses'),
('Transportation (Car Pool)');
('Transportation (Car Pool)'),
('Other');
-- Insert Products
INSERT INTO

View File

@@ -28,7 +28,6 @@ CREATE TABLE Category (
Name VARCHAR(255) NOT NULL
);
-- Product Entity
CREATE TABLE Product (
ProductID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
@@ -38,8 +37,22 @@ CREATE TABLE Product (
Description TEXT,
CategoryID INT NOT NULL,
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID),
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID)
);
-- Product Entity
CREATE TABLE Product (
ProductID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
Price DECIMAL(10, 2) NOT NULL,
StockQuantity INT,
UserID INT,
Description TEXT,
CategoryID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE SET NULL,
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE SET NULL
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID)
);
-- Fixed Image_URL table
@@ -66,7 +79,7 @@ CREATE TABLE Review (
-- Transaction Entity (Many-to-One with User, Many-to-One with Product)
CREATE TABLE Transaction (
TransactionID INT PRIMARY KEY,
TransactionID INT AUTO_INCREMENT PRIMARY KEY,
UserID INT,
ProductID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP,