forked from CSI-KJSCE/Travel-policy-
code base
This commit is contained in:
156
frontend/src/components/DashboardRoot/Navbar.jsx
Normal file
156
frontend/src/components/DashboardRoot/Navbar.jsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import logo from "/images/logo.jpeg";
|
||||
import { IoNotifications, IoPerson } from "react-icons/io5";
|
||||
import Hamburger from "hamburger-react";
|
||||
import { FaSignOutAlt } from "react-icons/fa";
|
||||
|
||||
const Navbar = ({ userData, sidebarIsVisible, setSidebarIsVisible }) => {
|
||||
// Mouse cursor tracking for the pull-down effect
|
||||
const [showNavbar, setShowNavbar] = useState(false);
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(false);
|
||||
|
||||
const handleLogout = async () => {
|
||||
let res = await fetch(`${import.meta.env.VITE_APP_API_URL}/logout`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
const userDesignation = userData.designation;
|
||||
const userName = userData.userName;
|
||||
|
||||
const [profileData] = useState({
|
||||
name: userName,
|
||||
university: "Somaiya Vidyavihar University",
|
||||
role: userDesignation,
|
||||
});
|
||||
|
||||
const links = [];
|
||||
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
setShowNavbar(true);
|
||||
setIsSmallScreen(true);
|
||||
} else {
|
||||
setShowNavbar(false);
|
||||
setIsSmallScreen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Set initial visibility based on screen width
|
||||
handleResize();
|
||||
|
||||
// Add event listener to handle resize
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// Cleanup the event listener on component unmount
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
if (e.clientY < 60 && !isSmallScreen) {
|
||||
setShowNavbar(true);
|
||||
} else {
|
||||
setShowNavbar(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Add event listener for mousemove only for large screens
|
||||
if (!isSmallScreen) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
}
|
||||
|
||||
// Clean up the event listener
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}, [isSmallScreen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
{/* Navbar with the pull-down effect */}
|
||||
<nav
|
||||
className={`bg-white shadow-md border-b-4 border-gray-200 w-full px-2 z-50 transition-all duration-300 ease-in-out transform ${
|
||||
isSmallScreen
|
||||
? ""
|
||||
: `fixed top-0 left-0 ${showNavbar ? "translate-y-0" : "-translate-y-full"}`
|
||||
}`}
|
||||
>
|
||||
<div className="w-full flex items-center justify-between px-4 py-3">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
{/* Hamburger Menu for Mobile */}
|
||||
<div className="md:hidden">
|
||||
<Hamburger
|
||||
toggled={sidebarIsVisible}
|
||||
toggle={setSidebarIsVisible}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Logo for Desktop */}
|
||||
<Link to="/" className="hidden md:flex items-center">
|
||||
<img src={logo} alt="Somaiya" className="object-contain w-48" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4 text-lg font-medium">
|
||||
{/* Navbar Links */}
|
||||
<div className="hidden sm:flex items-center space-x-2">
|
||||
{links?.map((link, index) => (
|
||||
<div key={index} className="flex items-center space-x-2">
|
||||
<Link
|
||||
to={link.path}
|
||||
className="text-gray-700 hover:bg-red-700 hover:text-white px-4 py-2 rounded-md transition-all duration-200"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
<span>|</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Logout Button */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Link
|
||||
to="/"
|
||||
onClick={handleLogout}
|
||||
className="flex items-center text-gray-700 hover:bg-red-700 hover:text-white px-1 sm:px-4 py-2 rounded-md transition-all duration-200"
|
||||
>
|
||||
{/* Logout icon */}
|
||||
<FaSignOutAlt className="w-4 h-4 sm:mr-2" />
|
||||
{/* Text hidden on small screens */}
|
||||
<span className="hidden sm:block">Logout</span>
|
||||
</Link>
|
||||
<span>|</span>
|
||||
</div>
|
||||
|
||||
{/* User Profile */}
|
||||
{profileData.name && profileData.role && (
|
||||
<div className="flex items-center space-x-2 bg-red-100 p-2 rounded-md">
|
||||
<IoPerson className="text-red-700 text-xl" />
|
||||
<div className="hidden sm:block">
|
||||
<div className="text-gray-700 font-semibold">
|
||||
{profileData.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{`${userData.department} ${profileData.role} at ${userData.institute}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
46
frontend/src/components/DashboardRoot/Root.jsx
Normal file
46
frontend/src/components/DashboardRoot/Root.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Outlet, useLoaderData } from "react-router-dom";
|
||||
import Navbar from "./Navbar";
|
||||
import Sidebar from "./Sidebar";
|
||||
|
||||
const Root = () => {
|
||||
const { user, role } = useLoaderData()?.data;
|
||||
const [sidebarIsVisible, setSidebarIsVisible] = useState(true)
|
||||
const urlPath = window.location.pathname;
|
||||
|
||||
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
setSidebarIsVisible(false); // Hide sidebar on small screens
|
||||
} else {
|
||||
setSidebarIsVisible(true); // Show sidebar on larger screens
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Set initial visibility based on screen width
|
||||
setSidebarIsVisible(window.innerWidth >= 768);
|
||||
|
||||
// Add event listener to handle resize
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// Cleanup the event listener on component unmount
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
<Navbar userData={user} role={role} setSidebarIsVisible={setSidebarIsVisible} sidebarIsVisible={sidebarIsVisible} />
|
||||
<div className= "flex h-full bg-gray-100 overflow-auto">
|
||||
{sidebarIsVisible && !(urlPath.split("/").at(-1).includes("dashboard")) && <Sidebar role={role} />}
|
||||
<div className="w-full min-h-full h-screen overflow-y-scroll">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Root;
|
||||
258
frontend/src/components/DashboardRoot/Sidebar.jsx
Normal file
258
frontend/src/components/DashboardRoot/Sidebar.jsx
Normal file
@@ -0,0 +1,258 @@
|
||||
import React from "react";
|
||||
import { FaSignOutAlt } from "react-icons/fa";
|
||||
import { Link, NavLink } from "react-router-dom";
|
||||
|
||||
export const handleLogout = async () => {
|
||||
let res = await fetch(`${import.meta.env.VITE_APP_API_URL}/logout`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
const Sidebar = ({ role }) => (
|
||||
<div className="w-72 h-screen bg-white p-6 shadow-lg z-10 flex flex-col overflow-y-auto">
|
||||
<div className="mb-8 text-center border-b-2 border-gray-200 pb-6">
|
||||
<div className="bg-white shadow-lg rounded-lg px-4 py-4 border border-gray-300">
|
||||
<h2 className="text-xl font-semibold text-red-700 tracking-tight">
|
||||
{`${role} Portal`}
|
||||
</h2>
|
||||
<p className="text-gray-700 text-sm font-medium py-1">
|
||||
Travel Policy SVU
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="flex-grow">
|
||||
<ul className="space-y-4 text-sm">
|
||||
<li className="border-b border-gray-200 pb-2">
|
||||
<NavLink
|
||||
to={`/${role.toLowerCase()}/dashboard`}
|
||||
end
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 hover:text-white hover:bg-red-700 p-2 rounded ${
|
||||
isActive ? "font-extrabold" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M3 13l4 4L10 13m5-5h6a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6m-4 6l4 4 4-4"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Dashboard
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="text-gray-700 border-b border-gray-200 pb-2">
|
||||
<span className="flex items-center text-gray-800 hover:text-white hover:bg-red-700 p-2 rounded cursor-pointer">
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Application Status
|
||||
</span>
|
||||
<ul className="pl-4 mt-2 border-l border-gray-200 ml-2">
|
||||
<li className="border-b border-gray-200 pb-2">
|
||||
<NavLink
|
||||
to={`/${role.toLowerCase()}/dashboard/pending`}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-600 hover:text-white hover:bg-red-700 p-2 rounded ${
|
||||
isActive ? "font-black" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 3v18m9-9H3"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Pending
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="border-b border-gray-200 pb-2">
|
||||
<NavLink
|
||||
to={`/${role.toLowerCase()}/dashboard/accepted`}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-600 hover:text-white hover:bg-red-700 p-2 rounded ${
|
||||
isActive ? "font-black" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Accepted
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="pb-2">
|
||||
<NavLink
|
||||
to={`/${role.toLowerCase()}/dashboard/rejected`}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-600 hover:text-white hover:bg-red-700 p-2 rounded ${
|
||||
isActive ? "font-black" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Rejected
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{role === "Applicant" ? (
|
||||
<>
|
||||
<li className="border-b border-gray-200 pb-2">
|
||||
<NavLink
|
||||
to="/applicant/policy"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 hover:text-white hover:bg-red-700 p-2 rounded ${
|
||||
isActive ? "font-extrabold" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19 10v6a2 2 0 01-2 2H7a2 2 0 01-2-2V10a2 2 0 012-2h10a2 2 0 012 2zM10 14h4m-2-2v4"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Policy
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/applicant/contact-us"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 hover:text-white hover:bg-red-700 p-2 rounded ${
|
||||
isActive ? "font-extrabold" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M3 10h5l2 6h6l2-6h5"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Contact Us
|
||||
</NavLink>
|
||||
</li>
|
||||
</>
|
||||
) : (
|
||||
//role = "Validator"
|
||||
<>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/validator/report"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 hover:text-white hover:bg-red-700 p-2 rounded ${
|
||||
isActive ? "font-extrabold" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M3 10h5l2 6h6l2-6h5"
|
||||
></path>
|
||||
</svg>{" "}
|
||||
Report
|
||||
</NavLink>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
{/* Spacer to push logout to the bottom */}
|
||||
<div className="mt-14">
|
||||
<Link
|
||||
to="/"
|
||||
onClick={handleLogout}
|
||||
className="flex items-center text-gray-700 hover:bg-red-700 hover:text-white px-4 py-2 rounded-md transition-all duration-200"
|
||||
>
|
||||
<FaSignOutAlt className="w-4 h-4 mr-2" />
|
||||
Logout
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Sidebar;
|
||||
Reference in New Issue
Block a user