diff --git a/backend/controllers/transaction.js b/backend/controllers/transaction.js new file mode 100644 index 0000000..ace31bb --- /dev/null +++ b/backend/controllers/transaction.js @@ -0,0 +1,40 @@ +const db = require("../utils/database"); + +exports.getTransactionWithPagination = async (req, res) => { + const limit = +req.query?.limit; + const page = +req.query?.page; + const offset = (page - 1) * limit; + try { + const [data, _] = await db.execute( + `SELECT T.TransactionID, DATE_FORMAT(T.Date, '%b-%d-%Y %h:%i %p') as Date, T.PaymentStatus, U.Name as UserName, P.Name as ProductName + FROM Transaction T + 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()] + ); + + const [result] = await db.execute( + "SELECT COUNT(*) AS count FROM Transaction" + ); + const { count: total } = result[0]; + return res.json({ data, total }); + } catch (error) { + res.json({ error: "Cannot fetch transactions from database!" }); + } +}; + +exports.removeTransation = async (req, res) => { + const { id } = req.params; + try { + const [result] = await db.execute( + "DELETE FROM Transaction WHERE TransactionID = ?;", + [id.toString()] + ); + return res.json({ message: "Remove transaction successfully!" }); + } catch (error) { + return res + .status(500) + .json({ error: "Cannot remove transactions from database!" }); + } +}; diff --git a/backend/index.js b/backend/index.js index 5ba3b27..796860f 100644 --- a/backend/index.js +++ b/backend/index.js @@ -10,6 +10,7 @@ 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"); const { @@ -44,6 +45,7 @@ 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); // Set up a scheduler to run cleanup every hour clean_up_time = 30 * 60 * 1000; diff --git a/backend/routes/transaction.js b/backend/routes/transaction.js new file mode 100644 index 0000000..0788b96 --- /dev/null +++ b/backend/routes/transaction.js @@ -0,0 +1,12 @@ +const express = require("express"); +const { + getTransactionWithPagination, + removeTransation, +} = require("../controllers/transaction"); + +const router = express.Router(); + +router.get("/getTransactions", getTransactionWithPagination); +router.delete("/:id", removeTransation); + +module.exports = router; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index eefdb63..ac28ad5 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -19,6 +19,7 @@ import ProductDashboard from "./pages/ProductDashboard"; 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 @@ -710,6 +711,10 @@ function App() { } /> } /> } /> + } + /> } /> diff --git a/frontend/src/api/admin.js b/frontend/src/api/admin.js index f2728c9..bea2a15 100644 --- a/frontend/src/api/admin.js +++ b/frontend/src/api/admin.js @@ -40,6 +40,19 @@ export const getCategories = async (page, limit = 10) => { } }; +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 }); @@ -94,3 +107,14 @@ export const verifyIsAdmin = async (id) => { return { error: error.message || error }; } }; + +export const removeTransaction = async (id) => { + try { + const { data } = await client.delete(`/transaction/${id}`); + return { message: data.message }; + } catch (error) { + const { response } = error; + if (response?.data) return response.data; + return { error: error.message || error }; + } +}; diff --git a/frontend/src/components/DashboardNav.jsx b/frontend/src/components/DashboardNav.jsx index 75e287c..5f19de2 100644 --- a/frontend/src/components/DashboardNav.jsx +++ b/frontend/src/components/DashboardNav.jsx @@ -3,6 +3,7 @@ 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 = () => { @@ -69,6 +70,20 @@ export default function DashboardNav({ handleCloseAdminDashboard }) { Categories +
  • + + (isActive + ? "text-green-400" + : "text-white transition-all hover:text-green-200") + + " flex items-center px-5 text-lg pt-5" + } + > + + Transaction + +
  • { removeCategory(id) - .then((res) => { + .then(() => { fetchCategory(currentPage); }) .catch((err) => { @@ -48,7 +48,7 @@ export default function CategoryDashboard() { useEffect(fetchCategory, []); return ( -
    +

    CATEGORIES

    @@ -60,35 +60,46 @@ export default function CategoryDashboard() { - - - - - - - - - - {categories.map((category) => ( - - - - - - ))} - -
    CategoryIDNameAction
    {category.CategoryID}{category.Name} - { - handleRemove(category.CategoryID); - }} - className="hover:text-red-600 cursor-pointer transition-all text-xl" - /> -
    - + {categories.length > 0 ? ( + <> + + + + + + + + + + {categories.map((category) => ( + + + + + + ))} + +
    CategoryIDNameAction
    {category.CategoryID}{category.Name} + { + handleRemove(category.CategoryID); + }} + className="hover:text-red-600 cursor-pointer transition-all text-xl" + /> +
    + + + ) : ( +

    + No category exists! +

    + )}
    ); } diff --git a/frontend/src/pages/ProductDashboard.jsx b/frontend/src/pages/ProductDashboard.jsx index c3ef4d2..35ce13e 100644 --- a/frontend/src/pages/ProductDashboard.jsx +++ b/frontend/src/pages/ProductDashboard.jsx @@ -40,44 +40,52 @@ export default function ProductDashboard() {

    PRODUCTS

    - - - - - - - - - - - - - {products.map((product) => ( - - - - - - - - - ))} - -
    ProductIDNamePriceCategorySellerAction
    {product.ProductID}{product.ProductName}{product.Price}{product.Category ? product.Category : "N/A"}{product.SellerName ? product.SellerName : "N/A"} - { - handleRemoveProduct(product.ProductID); - }} - className="hover:text-red-600 cursor-pointer transition-all text-xl" - /> -
    - + {products.length > 0 ? ( + <> + + + + + + + + + + + + + {products.map((product) => ( + + + + + + + + + ))} + +
    ProductIDNamePriceCategorySellerAction
    {product.ProductID}{product.ProductName}{product.Price}{product.Category ? product.Category : "N/A"}{product.SellerName ? product.SellerName : "N/A"} + { + handleRemoveProduct(product.ProductID); + }} + className="hover:text-red-600 cursor-pointer transition-all text-xl" + /> +
    + + + ) : ( +

    + No product exists! +

    + )}
    ); } diff --git a/frontend/src/pages/TransactionDashboard.jsx b/frontend/src/pages/TransactionDashboard.jsx new file mode 100644 index 0000000..167d5ee --- /dev/null +++ b/frontend/src/pages/TransactionDashboard.jsx @@ -0,0 +1,91 @@ +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 ( +
    +

    + TRANSACTIONS +

    + {transactions.length > 0 ? ( + <> + + + + + + + + + + + + + {transactions.map((t) => ( + + + + + + + + + ))} + +
    TransactionIDUserProductDateStatusAction
    {t.TransactionID}{t.UserName ? t.UserName : "N/A"}{t.ProductName ? t.ProductName : "N/A"}{t.Date}{t.PaymentStatus} + { + handleRemoveTransaction(t.TransactionID); + }} + className="hover:text-red-600 cursor-pointer transition-all text-xl" + /> +
    + + + ) : ( +

    + No transaction exists! +

    + )} +
    + ); +}