forked from CSI-KJSCE/Travel-policy-
code base
This commit is contained in:
141
frontend/src/components/BaseData.jsx
Normal file
141
frontend/src/components/BaseData.jsx
Normal file
@@ -0,0 +1,141 @@
|
||||
const institutes = [
|
||||
{ label: "K J Somaiya Institute of Dharma Studies", value: "KJSIDS" },
|
||||
{ label: "S K Somaiya College", value: "SKSC" },
|
||||
{ label: "K J Somaiya College of Engineering", value: "KJSCE" },
|
||||
{ label: "Somaiya Institute for Research and Consultancy", value: "SIRC" },
|
||||
{ label: "K J Somaiya Institute of Management", value: "KJSIM" },
|
||||
{ label: "Somaiya Sports Academy", value: "SSA" },
|
||||
{ label: "K J Somaiya College of Education", value: "KJSCEd" },
|
||||
{ label: "Department of Library and Information Science", value: "DLIS" },
|
||||
{
|
||||
label: "Maya Somaiya School of Music and Performing Arts",
|
||||
value: "MSSMPA",
|
||||
},
|
||||
];
|
||||
|
||||
const instituteDepartmentMapping = {
|
||||
KJSIDS: [
|
||||
{ label: "Academics", value: "Academics" },
|
||||
{
|
||||
label: "Bharatiya Sanskriti Peetham",
|
||||
value: "Bharatiya Sanskriti Peetham",
|
||||
},
|
||||
{
|
||||
label: "Center for Studies in Jainism",
|
||||
value: "Center for Studies in Jainism",
|
||||
},
|
||||
{
|
||||
label: "Department of Ancient Indian History Culture and Archaeology",
|
||||
value: "Department of Ancient Indian History Culture and Archaeology",
|
||||
},
|
||||
{
|
||||
label: "Centre For Buddhist Studies",
|
||||
value: "Centre For Buddhist Studies",
|
||||
},
|
||||
],
|
||||
SKSC: [
|
||||
{
|
||||
label: "Information Technology & Computer Science",
|
||||
value: "Information Technology & Computer Science",
|
||||
},
|
||||
{ label: "Mathematics & Statistics", value: "Mathematics & Statistics" },
|
||||
{ label: "Mass Communication", value: "Mass Communication" },
|
||||
{ label: "Life Science", value: "Life Science" },
|
||||
{ label: "Business Studies", value: "Business Studies" },
|
||||
{ label: "Polymer Science", value: "Polymer Science" },
|
||||
{
|
||||
label: "Commerce & Business Studies",
|
||||
value: "Commerce & Business Studies",
|
||||
},
|
||||
{ label: "Accounting & Finance", value: "Accounting & Finance" },
|
||||
{ label: "Commerce", value: "Commerce" },
|
||||
{ label: "Economics", value: "Economics" },
|
||||
{ label: "ENVIRONMENTAL SCIENCES", value: "ENVIRONMENTAL SCIENCES" },
|
||||
{ label: "Language & Literature", value: "Language & Literature" },
|
||||
{ label: "Computer Science & IT", value: "Computer Science & IT" },
|
||||
{ label: "SciSER", value: "SciSER" },
|
||||
{ label: "STATISTICS", value: "STATISTICS" },
|
||||
{ label: "International Studies", value: "International Studies" },
|
||||
{ label: "Banking & Finance", value: "Banking & Finance" },
|
||||
{ label: "Psychology", value: "Psychology" },
|
||||
{ label: "Financial Market", value: "Financial Market" },
|
||||
{ label: "NEUTRACEUTICALS", value: "NEUTRACEUTICALS" },
|
||||
{ label: "Faculty of Science - SVU", value: "Faculty of Science - SVU" },
|
||||
],
|
||||
KJSCE: [
|
||||
{ label: "Mechanical", value: "Mechanical" },
|
||||
{ label: "Electronics", value: "Electronics" },
|
||||
{ label: "CBE", value: "CBE" },
|
||||
{
|
||||
label: "Electronics & Telecommunication",
|
||||
value: "Electronics & Telecommunication",
|
||||
},
|
||||
{ label: "Computer", value: "Computer" },
|
||||
{ label: "Information Technology", value: "Information Technology" },
|
||||
{ label: "Science & Humanities", value: "Science & Humanities" },
|
||||
{ label: "Admin", value: "Admin" },
|
||||
{ label: "Library", value: "Library" },
|
||||
],
|
||||
SIRC: [
|
||||
{
|
||||
label: "Somaiya Institute for Research & Consultancy",
|
||||
value: "Somaiya Institute for Research & Consultancy",
|
||||
},
|
||||
],
|
||||
KJSIM: [
|
||||
{
|
||||
label: "Marketing and International Business",
|
||||
value: "Marketing and International Business",
|
||||
},
|
||||
{
|
||||
label:
|
||||
"General Management (Entrepreneurship, Business Communication, Strategy)",
|
||||
value:
|
||||
"General Management (Entrepreneurship, Business Communication, Strategy)",
|
||||
},
|
||||
{ label: "IT", value: "IT" },
|
||||
{
|
||||
label: "Data Science and Technology",
|
||||
value: "Data Science and Technology",
|
||||
},
|
||||
{
|
||||
label: "HUMAN RESOURCES MANAGEMENT",
|
||||
value: "HUMAN RESOURCES MANAGEMENT",
|
||||
},
|
||||
{ label: "MBA-Sports Management", value: "MBA-Sports Management" },
|
||||
{ label: "HCM", value: "HCM" },
|
||||
{ label: "FINANCE AND LAW", value: "FINANCE AND LAW" },
|
||||
{ label: "Business Analytics", value: "Business Analytics" },
|
||||
{
|
||||
label: "PR, Social Media & Data Mining",
|
||||
value: "PR, Social Media & Data Mining",
|
||||
},
|
||||
{ label: "Economics", value: "Economics" },
|
||||
{
|
||||
label: "Operations and Supply Chain Management",
|
||||
value: "Operations and Supply Chain Management",
|
||||
},
|
||||
{ label: "Community Medicine", value: "Community Medicine" },
|
||||
{ label: "Accreditation", value: "Accreditation" },
|
||||
{ label: "Accounts & Finance", value: "Accounts & Finance" },
|
||||
{ label: "GENERAL ADMINISTRATION", value: "GENERAL ADMINISTRATION" },
|
||||
{ label: "Human Resource", value: "Human Resource" },
|
||||
],
|
||||
SSA: [{ label: "Sports", value: "Sports" }],
|
||||
KJSCEd: [{ label: "Education", value: "Education" }],
|
||||
DLIS: [
|
||||
{
|
||||
label: "Department of Library & Information Science",
|
||||
value: "Department of Library & Information Science",
|
||||
},
|
||||
],
|
||||
MSSMPA: [
|
||||
{
|
||||
label: "Maya Somaiya School of Music & Performing Art",
|
||||
value: "Maya Somaiya School of Music & Performing Art",
|
||||
},
|
||||
],
|
||||
"": [],
|
||||
};
|
||||
|
||||
export { institutes, instituteDepartmentMapping };
|
||||
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;
|
||||
19
frontend/src/components/ErrorComponent.jsx
Normal file
19
frontend/src/components/ErrorComponent.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { useRouteError } from 'react-router-dom';
|
||||
|
||||
// Error component to display error messages
|
||||
const ErrorComponent = () => {
|
||||
const error = useRouteError();
|
||||
// Extracting status and message from the error
|
||||
const status = error.status || 500;
|
||||
const message = error.data.message || 'Something went wrong.';
|
||||
|
||||
return (
|
||||
<div style={{ padding: '1em', border: '1px solid red', borderRadius: '5px', backgroundColor: '#fdd', color: '#d00' }}>
|
||||
<h2>Error {status}</h2>
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorComponent;
|
||||
13
frontend/src/components/Loading.jsx
Normal file
13
frontend/src/components/Loading.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { TbLoader3 } from "react-icons/tb";
|
||||
|
||||
function Loading() {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center h-full animate-pulse">
|
||||
<TbLoader3 className="animate-spin text-xl size-24 text-red-700" />
|
||||
<p className="mt-2">Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Loading;
|
||||
94
frontend/src/components/LoginRoot/LoginRoot.css
Normal file
94
frontend/src/components/LoginRoot/LoginRoot.css
Normal file
@@ -0,0 +1,94 @@
|
||||
/* login styles for the Navbar */
|
||||
.login-navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px 20px;
|
||||
width: 100vw;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.login-navbar-brand .login-logo {
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-navbar-brand .login-logo {
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-navbar-toggler {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login-navbar-toggler-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.login-navbar-collapse {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.login-navbar-nav {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-nav-item {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.login-nav-link {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.login-nav-link.active {
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.login-trust-logo {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-navbar-toggler {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login-navbar-collapse {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-navbar-collapse.show {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-navbar-nav {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-nav-item {
|
||||
margin-left: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
54
frontend/src/components/LoginRoot/LoginRoot.jsx
Normal file
54
frontend/src/components/LoginRoot/LoginRoot.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Outlet,NavLink } from 'react-router-dom';
|
||||
import './LoginRoot.css';
|
||||
|
||||
//Navlinks to be used later & will be parent route for policy,services and login
|
||||
|
||||
const LoginRoot = () => {
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<nav className="login-navbar">
|
||||
<a className="login-navbar-brand" href="#">
|
||||
<img src="/images/KJSCE-Logo.svg" alt="Somaiya Vidyavihar University" className="login-logo" />
|
||||
</a>
|
||||
<div className="login-navbar-toggler">
|
||||
<span className="login-navbar-toggler-icon"></span>
|
||||
</div>
|
||||
<div className="login-navbar-collapse" id="loginNavbarNav">
|
||||
<ul className="login-navbar-nav">
|
||||
<li className="login-nav-item">
|
||||
<NavLink className="login-nav-link" to="policy" end>Policy</NavLink>
|
||||
</li>
|
||||
<li className="login-nav-item">
|
||||
<NavLink className="login-nav-link" to="about" end>About</NavLink>
|
||||
</li>
|
||||
<li className="login-nav-item">
|
||||
<NavLink className="login-nav-link" to="" end>Login</NavLink>
|
||||
</li>
|
||||
<li className="login-nav-item">
|
||||
<a className="login-nav-link" href="#">
|
||||
<img src="/images/Trust.jpg" alt="Somaiya Trust" className="login-trust-logo" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<Outlet/>
|
||||
|
||||
<footer className="flex items-center justify-center h-6 w-full bg-gray-100 text-gray-800 fixed bottom-0 left-0 z-50">
|
||||
<div className="text-center text-sm">
|
||||
©2024 KJSCE, All Rights Reserved.
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default LoginRoot;
|
||||
33
frontend/src/components/Modal/Modal.jsx
Normal file
33
frontend/src/components/Modal/Modal.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
const Modal = ({ onClose, children }) => {
|
||||
return (
|
||||
<div
|
||||
className="fixed top-0 inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-white p-6 rounded-lg relative w-11/12 md:w-3/5 lg:w-2/5 max-h-[85%] h-min overflow-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex justify-start p-2">
|
||||
<button
|
||||
type='button'
|
||||
className="absolute top-3 right-3 text-xl font-bold text-gray-700 hover:text-gray-900"
|
||||
onClick={onClose}
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-full overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
|
||||
|
||||
|
||||
105
frontend/src/components/Pagination.jsx
Normal file
105
frontend/src/components/Pagination.jsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
|
||||
function Pagination({ numOfItems, itemsPerPage = 10, currentPage, onPageChange }) {
|
||||
const totalPages = Math.ceil(numOfItems / itemsPerPage);
|
||||
const pages = Array.from({ length: totalPages }, (_, index) => index + 1);
|
||||
const maxPageButtons = 5; // Maximum number of page buttons to display
|
||||
|
||||
const handlePrevious = () => {
|
||||
if (currentPage > 1) onPageChange(currentPage - 1);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentPage < totalPages) onPageChange(currentPage + 1);
|
||||
};
|
||||
|
||||
const getPageButtons = () => {
|
||||
if (totalPages <= maxPageButtons) {
|
||||
return pages;
|
||||
}
|
||||
|
||||
const half = Math.floor(maxPageButtons / 2);
|
||||
let start = Math.max(1, currentPage - half);
|
||||
let end = Math.min(totalPages, currentPage + half);
|
||||
|
||||
if (currentPage <= half) {
|
||||
end = maxPageButtons;
|
||||
} else if (currentPage + half >= totalPages) {
|
||||
start = totalPages - maxPageButtons + 1;
|
||||
}
|
||||
|
||||
return Array.from({ length: end - start + 1 }, (_, index) => start + index);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6">
|
||||
<div className="flex flex-1 justify-between sm:hidden">
|
||||
<button
|
||||
type='button'
|
||||
onClick={handlePrevious}
|
||||
className="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleNext}
|
||||
className="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-700">
|
||||
Showing <span className="font-medium">{(currentPage - 1) * itemsPerPage + 1}</span> to <span className="font-medium">{Math.min(currentPage * itemsPerPage, numOfItems)}</span> of <span className="font-medium">{numOfItems}</span> applications
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav className="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
||||
<button
|
||||
type='button'
|
||||
onClick={handlePrevious}
|
||||
className="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0"
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<span className="sr-only">Previous</span>
|
||||
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fillRule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{getPageButtons().map(page => (
|
||||
<button
|
||||
type='button'
|
||||
key={page}
|
||||
onClick={() => onPageChange(page)}
|
||||
aria-current={currentPage === page ? 'page' : undefined}
|
||||
className={`relative inline-flex items-center px-4 py-2 text-sm font-semibold ${currentPage === page ? 'bg-red-700 text-white' : 'text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50'} focus:z-20 focus:outline-offset-0`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleNext}
|
||||
className="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0"
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
<span className="sr-only">Next</span>
|
||||
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fillRule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Pagination;
|
||||
25
frontend/src/components/PdfViewer.jsx
Normal file
25
frontend/src/components/PdfViewer.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
// PdfViewer.js
|
||||
import React from "react";
|
||||
import Modal from "./Modal/Modal";
|
||||
|
||||
function PdfViewer({ fileUrl, setIsModalOpen }) {
|
||||
if (!fileUrl) {
|
||||
return <p>Loading PDF...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal onClose={() => setIsModalOpen(false)}>
|
||||
<object data={fileUrl} type="application/pdf" width="100%" height="600px">
|
||||
<p>
|
||||
PDF preview failed. Please{" "}
|
||||
<a href={fileUrl} target="_blank" rel="noopener noreferrer">
|
||||
open the PDF
|
||||
</a>{" "}
|
||||
in a new tab.
|
||||
</p>
|
||||
</object>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default PdfViewer;
|
||||
Reference in New Issue
Block a user