Add transaction section to admin dashboard
This commit is contained in:
40
backend/controllers/transaction.js
Normal file
40
backend/controllers/transaction.js
Normal 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!" });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
12
backend/routes/transaction.js
Normal file
12
backend/routes/transaction.js
Normal 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;
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,6 +60,8 @@ export default function CategoryDashboard() {
|
|||||||
<IoAddCircleSharp />
|
<IoAddCircleSharp />
|
||||||
</button>
|
</button>
|
||||||
<CategoryForm onAddCategory={notiChange} visible={visible} />
|
<CategoryForm onAddCategory={notiChange} visible={visible} />
|
||||||
|
{categories.length > 0 ? (
|
||||||
|
<>
|
||||||
<table className="table-fixed w-full text-center border border-green-600">
|
<table className="table-fixed w-full text-center border border-green-600">
|
||||||
<thead className="bg-green-600 h-10">
|
<thead className="bg-green-600 h-10">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -70,7 +72,10 @@ export default function CategoryDashboard() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<tr key={category.UserID} className="border border-green-600 h-10">
|
<tr
|
||||||
|
key={category.UserID}
|
||||||
|
className="border border-green-600 h-10"
|
||||||
|
>
|
||||||
<td>{category.CategoryID}</td>
|
<td>{category.CategoryID}</td>
|
||||||
<td>{category.Name}</td>
|
<td>{category.Name}</td>
|
||||||
<td className="flex justify-center pt-2">
|
<td className="flex justify-center pt-2">
|
||||||
@@ -89,6 +94,12 @@ export default function CategoryDashboard() {
|
|||||||
pageNum={Math.ceil(total / pageLimit)}
|
pageNum={Math.ceil(total / pageLimit)}
|
||||||
onChange={onChangePage}
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ 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>
|
||||||
|
{products.length > 0 ? (
|
||||||
|
<>
|
||||||
<table className="table-fixed w-full text-center border border-green-600">
|
<table className="table-fixed w-full text-center border border-green-600">
|
||||||
<thead className="bg-green-600 h-10">
|
<thead className="bg-green-600 h-10">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -78,6 +80,12 @@ export default function ProductDashboard() {
|
|||||||
onChange={onChangePage}
|
onChange={onChangePage}
|
||||||
pageNum={Math.ceil(total / pageLimit)}
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
91
frontend/src/pages/TransactionDashboard.jsx
Normal file
91
frontend/src/pages/TransactionDashboard.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user