Finish admin dashboard and update sql code
This commit is contained in:
10
frontend/package-lock.json
generated
10
frontend/package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"lucide-react": "^0.477.0",
|
"lucide-react": "^0.477.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.2.0"
|
"react-router-dom": "^7.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -4369,6 +4370,15 @@
|
|||||||
"react": "^19.0.0"
|
"react": "^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"lucide-react": "^0.477.0",
|
"lucide-react": "^0.477.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.2.0"
|
"react-router-dom": "^7.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ import Transactions from "./pages/Transactions";
|
|||||||
import Favorites from "./pages/Favorites";
|
import Favorites from "./pages/Favorites";
|
||||||
import ProductDetail from "./pages/ProductDetail";
|
import ProductDetail from "./pages/ProductDetail";
|
||||||
import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage
|
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 DashboardNav from "./components/DashboardNav";
|
||||||
|
import CategoryDashboard from "./pages/CategoryDashboard";
|
||||||
|
import { verifyIsAdmin } from "./api/admin";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// Authentication state - initialize from localStorage if available
|
// Authentication state - initialize from localStorage if available
|
||||||
@@ -56,6 +62,26 @@ function App() {
|
|||||||
sendSessionDataToServer();
|
sendSessionDataToServer();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
|
const [showAdminDashboard, setShowAdminDashboard] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const userInfo = sessionStorage.getItem("user")
|
||||||
|
? JSON.parse(sessionStorage.getItem("user"))
|
||||||
|
: "";
|
||||||
|
const id = userInfo?.ID;
|
||||||
|
verifyIsAdmin(id).then((data) => {
|
||||||
|
setIsAdmin(data.isAdmin);
|
||||||
|
});
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const handleShowAdminDashboard = () => {
|
||||||
|
setShowAdminDashboard(true);
|
||||||
|
};
|
||||||
|
const handleCloseAdminDashboard = () => {
|
||||||
|
setShowAdminDashboard(false);
|
||||||
|
};
|
||||||
|
|
||||||
// Send verification code
|
// Send verification code
|
||||||
const sendVerificationCode = async (userData) => {
|
const sendVerificationCode = async (userData) => {
|
||||||
try {
|
try {
|
||||||
@@ -76,7 +102,7 @@ function App() {
|
|||||||
email: userData.email,
|
email: userData.email,
|
||||||
// Add any other required fields
|
// Add any other required fields
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -125,7 +151,7 @@ function App() {
|
|||||||
email: tempUserData.email,
|
email: tempUserData.email,
|
||||||
code: code,
|
code: code,
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -169,7 +195,7 @@ function App() {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(userData),
|
body: JSON.stringify(userData),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -275,7 +301,7 @@ function App() {
|
|||||||
email: formValues.email,
|
email: formValues.email,
|
||||||
password: formValues.password,
|
password: formValues.password,
|
||||||
}),
|
}),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -672,12 +698,36 @@ function App() {
|
|||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If user is admin, show admin naviagtion
|
||||||
|
if (showAdminDashboard) {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<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="*" element={<Dashboard />} />
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
{/* Only show navbar when authenticated */}
|
{/* Only show navbar when authenticated */}
|
||||||
{isAuthenticated && (
|
{isAuthenticated && (
|
||||||
<Navbar onLogout={handleLogout} userName={user?.name} />
|
<Navbar
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
onLogout={handleLogout}
|
||||||
|
userName={user?.name}
|
||||||
|
handleShowAdminDashboard={handleShowAdminDashboard}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Public routes */}
|
{/* Public routes */}
|
||||||
|
|||||||
96
frontend/src/api/admin.js
Normal file
96
frontend/src/api/admin.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import client from "./client";
|
||||||
|
|
||||||
|
export const getUsers = async (page, limit = 10) => {
|
||||||
|
try {
|
||||||
|
const { data } = await client.get(
|
||||||
|
`/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 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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeUser = async (id) => {
|
||||||
|
try {
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifyIsAdmin = async (id) => {
|
||||||
|
try {
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
3
frontend/src/api/client.js
Normal file
3
frontend/src/api/client.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
const client = axios.create({ baseURL: "http://localhost:3030/api" });
|
||||||
|
export default client;
|
||||||
65
frontend/src/components/CategoryForm.jsx
Normal file
65
frontend/src/components/CategoryForm.jsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { MdAddBox } from "react-icons/md";
|
||||||
|
import { addCategory } from "../api/admin";
|
||||||
|
|
||||||
|
export default function CategoryForm({ visible, onAddCategory }) {
|
||||||
|
const [category, setCategory] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!category.trim()) {
|
||||||
|
document.getElementById("noti").innerHTML = "Category name is missing!";
|
||||||
|
document
|
||||||
|
.getElementById("noti")
|
||||||
|
.classList.add("bg-red-200", "text-red-500");
|
||||||
|
document.getElementById("noti").classList.remove("opacity-0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addCategory(category)
|
||||||
|
.then((message) => {
|
||||||
|
document
|
||||||
|
.getElementById("noti")
|
||||||
|
.classList.remove("opacity-0", "bg-red-200", "text-red-500");
|
||||||
|
document
|
||||||
|
.getElementById("noti")
|
||||||
|
.classList.add("bg-green-200", "text-green-800");
|
||||||
|
document.getElementById("noti").innerHTML = `${message.message}`;
|
||||||
|
setCategory("");
|
||||||
|
onAddCategory();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = ({ target }) => {
|
||||||
|
setCategory(target.value);
|
||||||
|
if (target.value.trim())
|
||||||
|
document.getElementById("noti").classList.add("opacity-0");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!visible) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} action="" className="flex p-2 items-center">
|
||||||
|
<label htmlFor="category" className="text-green-700">
|
||||||
|
Category:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-green-700 ml-2 rounded-sm focus:bg-green-100 text-green-900"
|
||||||
|
name="category"
|
||||||
|
id="category"
|
||||||
|
onChange={handleChange}
|
||||||
|
value={category}
|
||||||
|
/>
|
||||||
|
<button type="submit" className="text-2xl pl-1 text-green-700">
|
||||||
|
<MdAddBox className="text-3xl" />
|
||||||
|
</button>
|
||||||
|
<p
|
||||||
|
id="noti"
|
||||||
|
className="text-red-500 bg-red-200 px-2 rounded-sm opacity-0 mx-2"
|
||||||
|
></p>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
83
frontend/src/components/DashboardNav.jsx
Normal file
83
frontend/src/components/DashboardNav.jsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
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 "
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import UserDropdown from "./UserDropdown";
|
import UserDropdown from "./UserDropdown";
|
||||||
import { Search, Heart } from "lucide-react";
|
import { Search, Heart } from "lucide-react";
|
||||||
|
|
||||||
const Navbar = ({ onLogout, userName }) => {
|
const Navbar = ({ onLogout, userName, isAdmin, handleShowAdminDashboard }) => {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -76,7 +76,12 @@ const Navbar = ({ onLogout, userName }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* User Profile */}
|
{/* User Profile */}
|
||||||
<UserDropdown onLogout={onLogout} userName={userName} />
|
<UserDropdown
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
onLogout={onLogout}
|
||||||
|
userName={userName}
|
||||||
|
handleShowAdminDashboard={handleShowAdminDashboard}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
99
frontend/src/components/Pagination.jsx
Normal file
99
frontend/src/components/Pagination.jsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function Pagination({ pageNum, onChange }) {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
for (let i = 1; i <= pageNum; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = (page) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
onChange(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTogglePage = (type) => {
|
||||||
|
let current = currentPage;
|
||||||
|
if (type == "next")
|
||||||
|
current = current + 1 <= pageNum ? current + 1 : current;
|
||||||
|
else current = current - 1 >= 1 ? current - 1 : current;
|
||||||
|
setCurrentPage(current);
|
||||||
|
onChange(current);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<nav aria-label="Page navigation" className="flex justify-end">
|
||||||
|
<ul className="flex items-center -space-x-px h-8 text-sm mt-4 pr-0 font-bold">
|
||||||
|
<li>
|
||||||
|
<NavLink
|
||||||
|
onClick={() => {
|
||||||
|
handleTogglePage("previous");
|
||||||
|
}}
|
||||||
|
className=" flex items-center justify-center px-3 h-8 ms-0 leading-tight border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 text-white bg-green-700 border border-gray-300 hover:bg-green-600 hover:text-white"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Previous</span>
|
||||||
|
<svg
|
||||||
|
className="w-2.5 h-2.5 rtl:rotate-180"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 6 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M5 1 1 5l4 4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
{pages.map((page) => (
|
||||||
|
<li key={page}>
|
||||||
|
<NavLink
|
||||||
|
className={`${
|
||||||
|
currentPage == page ? "bg-green-600" : "bg-green-700"
|
||||||
|
} +
|
||||||
|
" flex items-center justify-center px-3 h-8 leading-tight text-white border border-gray-300 hover:bg-green-600 hover:text-white"`}
|
||||||
|
onClick={() => {
|
||||||
|
handleClick(page);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<li>
|
||||||
|
<NavLink
|
||||||
|
onClick={() => {
|
||||||
|
handleTogglePage("next");
|
||||||
|
}}
|
||||||
|
className="flex items-center justify-center px-3 h-8 leading-tight border border-gray-300 rounded-e-lg text-white bg-green-700 border border-gray-300 hover:bg-green-600 hover:text-white"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Next</span>
|
||||||
|
<svg
|
||||||
|
className="w-2.5 h-2.5 rtl:rotate-180"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 6 10"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="m1 9 4-4-4-4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { User, Settings, ShoppingBag, DollarSign, LogOut } from "lucide-react";
|
import { User, Settings, ShoppingBag, DollarSign, LogOut } from "lucide-react";
|
||||||
|
import { RiAdminLine } from "react-icons/ri";
|
||||||
|
|
||||||
const UserDropdown = ({ onLogout, userName }) => {
|
const UserDropdown = ({
|
||||||
|
onLogout,
|
||||||
|
userName,
|
||||||
|
isAdmin,
|
||||||
|
handleShowAdminDashboard,
|
||||||
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -89,6 +95,20 @@ const UserDropdown = ({ onLogout, userName }) => {
|
|||||||
Settings
|
Settings
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{isAdmin ? (
|
||||||
|
<Link
|
||||||
|
className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||||
|
onClick={() => {
|
||||||
|
handleShowAdminDashboard();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiAdminLine className="h-4 w-4 mr-2 text-gray-500" />
|
||||||
|
Admin
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
className="flex w-full items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
|
|||||||
94
frontend/src/pages/CategoryDashboard.jsx
Normal file
94
frontend/src/pages/CategoryDashboard.jsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
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((res) => {
|
||||||
|
fetchCategory(currentPage);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//Get user when initialize the component
|
||||||
|
useEffect(fetchCategory, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-10 p-20">
|
||||||
|
<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} />
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
frontend/src/pages/Dashboard.jsx
Normal file
7
frontend/src/pages/Dashboard.jsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Dashboard() {
|
||||||
|
return (
|
||||||
|
<div className="text-3xl font-bold p-3 text-green-800">
|
||||||
|
Welcome to admin dashboard
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
83
frontend/src/pages/ProductDashboard.jsx
Normal file
83
frontend/src/pages/ProductDashboard.jsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
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>
|
||||||
|
<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)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
91
frontend/src/pages/UserDashboard.jsx
Normal file
91
frontend/src/pages/UserDashboard.jsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -67,40 +67,42 @@ VALUES
|
|||||||
(1, TRUE, TRUE),
|
(1, TRUE, TRUE),
|
||||||
(2, TRUE, FALSE);
|
(2, TRUE, FALSE);
|
||||||
|
|
||||||
|
-- Insert Categories
|
||||||
-- Insert Categories
|
-- Insert Categories
|
||||||
INSERT INTO
|
INSERT INTO
|
||||||
Category (CategoryID, Name)
|
Category (Name)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'Textbooks'),
|
('Textbooks'),
|
||||||
(2, 'Electronics'),
|
('Electronics'),
|
||||||
(3, 'Furniture'),
|
('Furniture'),
|
||||||
(4, 'Clothing'),
|
('Clothing'),
|
||||||
(5, 'Sports Equipment'),
|
('Sports Equipment'),
|
||||||
(6, 'Musical Instruments'),
|
('Musical Instruments'),
|
||||||
(7, 'Art Supplies'),
|
('Art Supplies'),
|
||||||
(8, 'Kitchen Appliances'),
|
('Kitchen Appliances'),
|
||||||
(9, 'Gaming'),
|
('Gaming'),
|
||||||
(10, 'Bicycles'),
|
( 'Bicycles'),
|
||||||
(11, 'Computer Accessories'),
|
( 'Computer Accessories'),
|
||||||
(12, 'Stationery'),
|
( 'Stationery'),
|
||||||
(13, 'Fitness Equipment'),
|
( 'Fitness Equipment'),
|
||||||
(14, 'Winter Sports'),
|
( 'Winter Sports'),
|
||||||
(15, 'Lab Equipment'),
|
( 'Lab Equipment'),
|
||||||
(16, 'Camping Gear'),
|
( 'Camping Gear'),
|
||||||
(17, 'School Supplies'),
|
( 'School Supplies'),
|
||||||
(18, 'Office Furniture'),
|
( 'Office Furniture'),
|
||||||
(19, 'Books (Non-textbook)'),
|
( 'Books (-textbook)'),
|
||||||
(20, 'Math & Science Resources'),
|
( 'Math & Science Resources'),
|
||||||
(21, 'Engineering Tools'),
|
( 'Engineering Tools'),
|
||||||
(22, 'Backpacks & Bags'),
|
( 'Backpacks & Bags'),
|
||||||
(23, 'Audio Equipment'),
|
( 'Audio Equipment'),
|
||||||
(24, 'Dorm Essentials'),
|
( 'Dorm Essentials'),
|
||||||
(25, 'Smartphones & Tablets'),
|
( 'Smartphones & Tablets'),
|
||||||
(26, 'Winter Clothing'),
|
( 'Winter Clothing'),
|
||||||
(27, 'Photography Equipment'),
|
( 'Photography Equipment'),
|
||||||
(28, 'Event Tickets'),
|
( 'Event Tickets'),
|
||||||
(29, 'Software Licenses'),
|
( 'Software Licenses'),
|
||||||
(30, 'Transportation (Car Pool)');
|
( 'Transportation (Car Pool)');
|
||||||
|
|
||||||
|
|
||||||
-- Insert Products
|
-- Insert Products
|
||||||
INSERT INTO
|
INSERT INTO
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ CREATE TABLE UserRole (
|
|||||||
|
|
||||||
-- Category Entity (must be created before Product or else error)
|
-- Category Entity (must be created before Product or else error)
|
||||||
CREATE TABLE Category (
|
CREATE TABLE Category (
|
||||||
CategoryID INT PRIMARY KEY,
|
CategoryID INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
Name VARCHAR(255) NOT NULL
|
Name VARCHAR(255) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -38,15 +38,15 @@ CREATE TABLE Product (
|
|||||||
Description TEXT,
|
Description TEXT,
|
||||||
CategoryID INT NOT NULL,
|
CategoryID INT NOT NULL,
|
||||||
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (UserID) REFERENCES User (UserID),
|
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE SET NULL,
|
||||||
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID)
|
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Fixed Image_URL table
|
-- Fixed Image_URL table
|
||||||
CREATE TABLE Image_URL (
|
CREATE TABLE Image_URL (
|
||||||
URL VARCHAR(255),
|
URL VARCHAR(255),
|
||||||
ProductID INT,
|
ProductID INT,
|
||||||
FOREIGN KEY (ProductID) REFERENCES Product (ProductID)
|
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Fixed Review Entity (Many-to-One with User, Many-to-One with Product)
|
-- Fixed Review Entity (Many-to-One with User, Many-to-One with Product)
|
||||||
@@ -60,8 +60,8 @@ CREATE TABLE Review (
|
|||||||
AND Rating <= 5
|
AND Rating <= 5
|
||||||
),
|
),
|
||||||
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (UserID) REFERENCES User (UserID),
|
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE SET NULL,
|
||||||
FOREIGN KEY (ProductID) REFERENCES Product (ProductID)
|
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Transaction Entity (Many-to-One with User, Many-to-One with Product)
|
-- Transaction Entity (Many-to-One with User, Many-to-One with Product)
|
||||||
@@ -71,8 +71,8 @@ CREATE TABLE Transaction (
|
|||||||
ProductID INT,
|
ProductID INT,
|
||||||
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
PaymentStatus VARCHAR(50),
|
PaymentStatus VARCHAR(50),
|
||||||
FOREIGN KEY (UserID) REFERENCES User (UserID),
|
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (ProductID) REFERENCES Product (ProductID)
|
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Recommendation Entity (Many-to-One with User, Many-to-One with Product)
|
-- Recommendation Entity (Many-to-One with User, Many-to-One with Product)
|
||||||
@@ -81,8 +81,8 @@ CREATE TABLE Recommendation (
|
|||||||
UserID INT,
|
UserID INT,
|
||||||
RecommendedProductID INT,
|
RecommendedProductID INT,
|
||||||
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (UserID) REFERENCES User (UserID),
|
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID)
|
FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- History Entity (Many-to-One with User, Many-to-One with Product)
|
-- History Entity (Many-to-One with User, Many-to-One with Product)
|
||||||
@@ -91,8 +91,8 @@ CREATE TABLE History (
|
|||||||
UserID INT,
|
UserID INT,
|
||||||
ProductID INT,
|
ProductID INT,
|
||||||
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (UserID) REFERENCES User (UserID),
|
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (ProductID) REFERENCES Product (ProductID)
|
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Favorites Entity (Many-to-One with User, Many-to-One with Product)
|
-- Favorites Entity (Many-to-One with User, Many-to-One with Product)
|
||||||
@@ -100,8 +100,8 @@ CREATE TABLE Favorites (
|
|||||||
FavoriteID INT AUTO_INCREMENT PRIMARY KEY,
|
FavoriteID INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
UserID INT,
|
UserID INT,
|
||||||
ProductID INT,
|
ProductID INT,
|
||||||
FOREIGN KEY (UserID) REFERENCES User (UserID),
|
FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (ProductID) REFERENCES Product (ProductID),
|
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE,
|
||||||
UNIQUE (UserID, ProductID)
|
UNIQUE (UserID, ProductID)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -110,8 +110,8 @@ CREATE TABLE Product_Category (
|
|||||||
ProductID INT,
|
ProductID INT,
|
||||||
CategoryID INT,
|
CategoryID INT,
|
||||||
PRIMARY KEY (ProductID, CategoryID),
|
PRIMARY KEY (ProductID, CategoryID),
|
||||||
FOREIGN KEY (ProductID) REFERENCES Product (ProductID),
|
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID)
|
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Login Authentication table
|
-- Login Authentication table
|
||||||
|
|||||||
Reference in New Issue
Block a user