Add transaction section to admin dashboard

This commit is contained in:
estherdev03
2025-04-20 20:56:14 -06:00
parent 26cd50ab6f
commit 444b436983
9 changed files with 277 additions and 69 deletions

View File

@@ -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!" });
}
};

View File

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

View File

@@ -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;

View File

@@ -19,6 +19,7 @@ import ProductDashboard from "./pages/ProductDashboard";
import DashboardNav from "./components/DashboardNav"; import DashboardNav from "./components/DashboardNav";
import CategoryDashboard from "./pages/CategoryDashboard"; import CategoryDashboard from "./pages/CategoryDashboard";
import { verifyIsAdmin } from "./api/admin"; import { verifyIsAdmin } from "./api/admin";
import TransactionDashboard from "./pages/TransactionDashboard";
function App() { function App() {
// Authentication state - initialize from localStorage if available // Authentication state - initialize from localStorage if available
@@ -710,6 +711,10 @@ function App() {
<Route path="/admin/user" element={<UserDashboard />} /> <Route path="/admin/user" element={<UserDashboard />} />
<Route path="/admin/product" element={<ProductDashboard />} /> <Route path="/admin/product" element={<ProductDashboard />} />
<Route path="/admin/category" element={<CategoryDashboard />} /> <Route path="/admin/category" element={<CategoryDashboard />} />
<Route
path="/admin/transaction"
element={<TransactionDashboard />}
/>
<Route path="*" element={<Dashboard />} /> <Route path="*" element={<Dashboard />} />
</Routes> </Routes>
</div> </div>

View File

@@ -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) => { export const addCategory = async (name) => {
try { try {
const { data } = await client.post(`/category/addCategory`, { name: name }); const { data } = await client.post(`/category/addCategory`, { name: name });
@@ -94,3 +107,14 @@ export const verifyIsAdmin = async (id) => {
return { error: error.message || error }; 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 };
}
};

View File

@@ -3,6 +3,7 @@ import { FaUserTag } from "react-icons/fa";
import { FaBoxArchive } from "react-icons/fa6"; import { FaBoxArchive } from "react-icons/fa6";
import { MdOutlineCategory } from "react-icons/md"; import { MdOutlineCategory } from "react-icons/md";
import { FaArrowLeft } from "react-icons/fa"; import { FaArrowLeft } from "react-icons/fa";
import { FaMoneyBillTransfer } from "react-icons/fa6";
export default function DashboardNav({ handleCloseAdminDashboard }) { export default function DashboardNav({ handleCloseAdminDashboard }) {
const handleClick = () => { const handleClick = () => {
@@ -69,6 +70,20 @@ export default function DashboardNav({ handleCloseAdminDashboard }) {
<span className="pl-3">Categories</span> <span className="pl-3">Categories</span>
</NavLink> </NavLink>
</li> </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> </ul>
<div <div
onClick={handleClick} onClick={handleClick}

View File

@@ -36,7 +36,7 @@ export default function CategoryDashboard() {
const handleRemove = (id) => { const handleRemove = (id) => {
removeCategory(id) removeCategory(id)
.then((res) => { .then(() => {
fetchCategory(currentPage); fetchCategory(currentPage);
}) })
.catch((err) => { .catch((err) => {
@@ -48,7 +48,7 @@ export default function CategoryDashboard() {
useEffect(fetchCategory, []); useEffect(fetchCategory, []);
return ( return (
<div className="pt-10 p-20"> <div className="pt-10 p-20 w-full">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline"> <h1 className="text-4xl pb-3 font-bold text-green-800 underline">
CATEGORIES CATEGORIES
</h1> </h1>
@@ -60,35 +60,46 @@ export default function CategoryDashboard() {
<IoAddCircleSharp /> <IoAddCircleSharp />
</button> </button>
<CategoryForm onAddCategory={notiChange} visible={visible} /> <CategoryForm onAddCategory={notiChange} visible={visible} />
<table className="table-fixed w-full text-center border border-green-600"> {categories.length > 0 ? (
<thead className="bg-green-600 h-10"> <>
<tr> <table className="table-fixed w-full text-center border border-green-600">
<th>CategoryID</th> <thead className="bg-green-600 h-10">
<th>Name</th> <tr>
<th>Action</th> <th>CategoryID</th>
</tr> <th>Name</th>
</thead> <th>Action</th>
<tbody> </tr>
{categories.map((category) => ( </thead>
<tr key={category.UserID} className="border border-green-600 h-10"> <tbody>
<td>{category.CategoryID}</td> {categories.map((category) => (
<td>{category.Name}</td> <tr
<td className="flex justify-center pt-2"> key={category.UserID}
<MdDelete className="border border-green-600 h-10"
onClick={() => { >
handleRemove(category.CategoryID); <td>{category.CategoryID}</td>
}} <td>{category.Name}</td>
className="hover:text-red-600 cursor-pointer transition-all text-xl" <td className="flex justify-center pt-2">
/> <MdDelete
</td> onClick={() => {
</tr> handleRemove(category.CategoryID);
))} }}
</tbody> className="hover:text-red-600 cursor-pointer transition-all text-xl"
</table> />
<Pagination </td>
pageNum={Math.ceil(total / pageLimit)} </tr>
onChange={onChangePage} ))}
/> </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> </div>
); );
} }

View File

@@ -40,44 +40,52 @@ export default function ProductDashboard() {
<h1 className="text-4xl pb-3 font-bold text-green-800 underline"> <h1 className="text-4xl pb-3 font-bold text-green-800 underline">
PRODUCTS PRODUCTS
</h1> </h1>
<table className="table-fixed w-full text-center border border-green-600"> {products.length > 0 ? (
<thead className="bg-green-600 h-10"> <>
<tr> <table className="table-fixed w-full text-center border border-green-600">
<th>ProductID</th> <thead className="bg-green-600 h-10">
<th>Name</th> <tr>
<th>Price</th> <th>ProductID</th>
<th>Category</th> <th>Name</th>
<th>Seller</th> <th>Price</th>
<th>Action</th> <th>Category</th>
</tr> <th>Seller</th>
</thead> <th>Action</th>
<tbody> </tr>
{products.map((product) => ( </thead>
<tr <tbody>
key={product.ProductID} {products.map((product) => (
className="border border-green-600 h-10" <tr
> key={product.ProductID}
<td>{product.ProductID}</td> className="border border-green-600 h-10"
<td>{product.ProductName}</td> >
<td>{product.Price}</td> <td>{product.ProductID}</td>
<td>{product.Category ? product.Category : "N/A"}</td> <td>{product.ProductName}</td>
<td>{product.SellerName ? product.SellerName : "N/A"}</td> <td>{product.Price}</td>
<td className="flex justify-center pt-2"> <td>{product.Category ? product.Category : "N/A"}</td>
<MdDelete <td>{product.SellerName ? product.SellerName : "N/A"}</td>
onClick={() => { <td className="flex justify-center pt-2">
handleRemoveProduct(product.ProductID); <MdDelete
}} onClick={() => {
className="hover:text-red-600 cursor-pointer transition-all text-xl" handleRemoveProduct(product.ProductID);
/> }}
</td> className="hover:text-red-600 cursor-pointer transition-all text-xl"
</tr> />
))} </td>
</tbody> </tr>
</table> ))}
<Pagination </tbody>
onChange={onChangePage} </table>
pageNum={Math.ceil(total / pageLimit)} <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> </div>
); );
} }

View File

@@ -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 (
<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>
);
}