code base

This commit is contained in:
ANUJ7MADKE
2025-07-13 22:49:55 +05:30
parent d4f21c9a99
commit cd43f0e98e
96 changed files with 17779 additions and 0 deletions

View 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 };

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

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

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

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

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

View 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;
}
}

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

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

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

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