forked from CSI-KJSCE/Travel-policy-
Add Dark Mode, Password Hashing for better security , Settings Page, Policy PDF in Policy section,UI Changes
This commit is contained in:
13
frontend/package-lock.json
generated
13
frontend/package-lock.json
generated
@@ -92,6 +92,7 @@
|
||||
"version": "7.24.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz",
|
||||
"integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
@@ -405,6 +406,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
|
||||
"integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0"
|
||||
}
|
||||
@@ -1716,6 +1718,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2153,6 +2156,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"electron-to-chromium": "^1.5.4",
|
||||
@@ -2252,6 +2256,7 @@
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz",
|
||||
"integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
@@ -2868,6 +2873,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
||||
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -5138,6 +5144,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.8",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -5355,6 +5362,7 @@
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -5376,6 +5384,7 @@
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -5502,6 +5511,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
||||
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
@@ -6169,6 +6179,7 @@
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz",
|
||||
"integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"@babel/traverse": "^7.4.5",
|
||||
@@ -6458,6 +6469,7 @@
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6791,6 +6803,7 @@
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import React, { Suspense } from "react";
|
||||
import { ThemeProvider } from "./context/ThemeContext";
|
||||
import "./App.css";
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
@@ -15,7 +16,10 @@ const Applications = React.lazy(() => import("./pages/Applications/Applications"
|
||||
const Report = React.lazy(() => import("./pages/Report/Report"));
|
||||
const LoginRoot = React.lazy(() => import("./components/LoginRoot/LoginRoot"));
|
||||
const ContactUs = React.lazy(() => import("./pages/ContactUs/ContactUs"));
|
||||
const ApplicationView = React.lazy(() => import("./pages/ApplicationView/ApplicationView"));
|
||||
const ApplicationView = React.lazy(() =>
|
||||
import("./pages/ApplicationView/ApplicationView")
|
||||
);
|
||||
const Settings = React.lazy(() => import("./pages/Settings/Settings"));
|
||||
|
||||
import userDataLoader from "./services/userDataLoader";
|
||||
import { upsertApplicationAction } from "./services/upsertApplicationAction";
|
||||
@@ -41,9 +45,14 @@ const router = createBrowserRouter([
|
||||
children: [
|
||||
{ path: "dashboard", element: <Dashboard /> },
|
||||
{ path: "dashboard/:status", element: <Applications /> },
|
||||
{ path: "dashboard/:status/:applicationId", element: <ApplicationView />, action: upsertApplicationAction },
|
||||
{
|
||||
path: "dashboard/:status/:applicationId",
|
||||
element: <ApplicationView />,
|
||||
action: upsertApplicationAction,
|
||||
},
|
||||
{ path: "form", element: <Form />, action: upsertApplicationAction },
|
||||
{ path: "contact-us", element: <ContactUs /> },
|
||||
{ path: "settings", element: <Settings /> },
|
||||
{ path: "policy", element: <Policy /> },
|
||||
],
|
||||
},
|
||||
@@ -55,8 +64,13 @@ const router = createBrowserRouter([
|
||||
children: [
|
||||
{ path: "dashboard", element: <Dashboard /> },
|
||||
{ path: "dashboard/:status", element: <Applications /> },
|
||||
{ path: "dashboard/:status/:applicationId", element: <ApplicationView />, action: applicationStatusAction },
|
||||
{
|
||||
path: "dashboard/:status/:applicationId",
|
||||
element: <ApplicationView />,
|
||||
action: applicationStatusAction,
|
||||
},
|
||||
{ path: "report", element: <Report /> },
|
||||
{ path: "settings", element: <Settings /> },
|
||||
{ path: "policy", element: <Policy /> },
|
||||
],
|
||||
},
|
||||
@@ -64,12 +78,12 @@ const router = createBrowserRouter([
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider>
|
||||
<ToastContainer position="top-center" />
|
||||
<Suspense fallback={<Loading/>}>
|
||||
<RouterProvider router={router} />
|
||||
</Suspense>
|
||||
</>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,17 @@ const Root = () => {
|
||||
|
||||
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} />}
|
||||
<Navbar
|
||||
userData={user}
|
||||
role={role}
|
||||
setSidebarIsVisible={setSidebarIsVisible}
|
||||
sidebarIsVisible={sidebarIsVisible}
|
||||
/>
|
||||
<div className="flex h-full bg-gray-100 dark:bg-google-dark overflow-auto transition-colors duration-200">
|
||||
{sidebarIsVisible &&
|
||||
!urlPath.split("/").at(-1).includes("dashboard") && (
|
||||
<Sidebar role={role} />
|
||||
)}
|
||||
<div className="w-full min-h-full h-screen overflow-y-scroll">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -1,258 +1,388 @@
|
||||
import React from "react";
|
||||
import { FaSignOutAlt } from "react-icons/fa";
|
||||
import { FaSignOutAlt, FaMoon, FaSun } from "react-icons/fa";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
import { Link, NavLink } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const handleLogout = async () => {
|
||||
let res = await fetch(`${import.meta.env.VITE_APP_API_URL}/logout`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
const handleLogout = async () => {
|
||||
// Step 1: Call the backend logout route
|
||||
// We need to do this to clear the httpOnly cookie from the browser
|
||||
try {
|
||||
let res = await fetch(`${import.meta.env.VITE_APP_API_URL}/logout`, {
|
||||
method: "GET",
|
||||
credentials: "include", // Important: This ensures the cookie is sent/cleared
|
||||
});
|
||||
|
||||
return res;
|
||||
// Step 2: If successful, we can redirect or show a message
|
||||
if (res.ok) {
|
||||
toast.info("Logged out successfully");
|
||||
}
|
||||
return res;
|
||||
} catch (error) {
|
||||
// console.log("Logout failed", error);
|
||||
toast.error("Logout failed, please check your connection.");
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
const Sidebar = ({ role }) => {
|
||||
// Using our custom ThemeContext to switch between Light and Dark mode
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<div className="w-72 h-full md:h-screen fixed md:relative top-0 left-0 z-40 bg-white dark:bg-google-dark dark:text-google-text p-6 shadow-lg flex flex-col overflow-y-auto transition-colors duration-200">
|
||||
<div className="mb-8 text-center border-b-2 border-gray-200 pb-6">
|
||||
<div className="bg-white dark:bg-google-gray shadow-lg rounded-lg px-4 py-4 border border-gray-300 dark:border-gray-600 transition-colors duration-200">
|
||||
<h2 className="text-xl font-semibold text-red-700 dark:text-red-500 tracking-tight">
|
||||
{`${role} Portal`}
|
||||
</h2>
|
||||
<p className="text-gray-700 dark:text-google-text-secondary 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 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to={`/${role.toLowerCase()}/dashboard`}
|
||||
end
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 dark:text-google-text 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 dark:text-google-text border-b border-gray-200 dark:border-gray-600 pb-2">
|
||||
<span className="flex items-center text-gray-800 dark:text-google-text 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 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to={`/${role.toLowerCase()}/dashboard/pending`}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-600 dark:text-google-text 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 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to={`/${role.toLowerCase()}/dashboard/accepted`}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-600 dark:text-google-text 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 dark:text-google-text 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>
|
||||
|
||||
{/*
|
||||
Conditional Rendering based on User Role.
|
||||
If role is "Applicant", show Applicant links.
|
||||
Otherwise (Validator), show Validator links.
|
||||
*/}
|
||||
{role === "Applicant" ? (
|
||||
<>
|
||||
<li className="border-b border-gray-200 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to="/applicant/policy"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 dark:text-google-text 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 dark:text-google-text 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>
|
||||
<li className="border-b border-gray-200 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to="/applicant/settings"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 dark:text-google-text 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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
></path>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
></path>
|
||||
</svg>
|
||||
Settings
|
||||
</NavLink>
|
||||
</li>
|
||||
</>
|
||||
) : (
|
||||
//role = "Validator"
|
||||
<>
|
||||
<li className="border-b border-gray-200 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to="/validator/policy"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 dark:text-google-text 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 className="border-b border-gray-200 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to="/validator/report"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 dark:text-google-text 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>
|
||||
<li className="border-b border-gray-200 dark:border-gray-600 pb-2">
|
||||
<NavLink
|
||||
to="/validator/settings"
|
||||
className={({ isActive }) =>
|
||||
`flex items-center text-gray-800 dark:text-google-text 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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
></path>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
></path>
|
||||
</svg>
|
||||
Settings
|
||||
</NavLink>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
{/* Spacer to push logout to the bottom */}
|
||||
<div className="mt-14 space-y-4">
|
||||
{/* Theme Toggle */}
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="flex items-center w-full text-gray-700 dark:text-google-text hover:bg-gray-100 dark:hover:bg-gray-700 px-4 py-2 rounded-md transition-all duration-200"
|
||||
>
|
||||
{theme === "light" ? (
|
||||
<>
|
||||
<FaMoon className="w-4 h-4 mr-2" />
|
||||
<span>Dark Mode</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaSun className="w-4 h-4 mr-2 text-yellow-400" />
|
||||
<span>Light Mode</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<Link
|
||||
to="/"
|
||||
onClick={handleLogout}
|
||||
className="flex items-center text-gray-700 dark:text-google-text 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>
|
||||
|
||||
<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;
|
||||
|
||||
@@ -40,7 +40,7 @@ const LoginRoot = () => {
|
||||
|
||||
<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.
|
||||
©{new Date().getFullYear()} KJSCE, All Rights Reserved.
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ const Modal = ({ onClose, children }) => {
|
||||
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"
|
||||
className="bg-white dark:bg-[#303134] p-6 rounded-lg relative w-11/12 md:w-3/5 lg:w-2/5 max-h-[85%] h-min overflow-auto transition-colors duration-200"
|
||||
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"
|
||||
type="button"
|
||||
className="absolute top-3 right-3 text-xl font-bold text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
|
||||
onClick={onClose}
|
||||
>
|
||||
X
|
||||
|
||||
35
frontend/src/context/ThemeContext.jsx
Normal file
35
frontend/src/context/ThemeContext.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
const ThemeContext = createContext({
|
||||
theme: "light",
|
||||
toggleTheme: () => {},
|
||||
});
|
||||
|
||||
export const ThemeProvider = ({ children }) => {
|
||||
const [theme, setTheme] = useState(() => {
|
||||
// Check local storage or default to light
|
||||
return localStorage.getItem("theme") || "light";
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
if (theme === "dark") {
|
||||
root.classList.add("dark");
|
||||
} else {
|
||||
root.classList.remove("dark");
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}, [theme]);
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme((prev) => (prev === "light" ? "dark" : "light"));
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
console.log("Main.jsx is executing...");
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App.jsx";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@@ -2,99 +2,131 @@ import React from "react";
|
||||
|
||||
const About = () => {
|
||||
return (
|
||||
<div className="bg-white text-gray-800">
|
||||
{/* Hero Section */}
|
||||
<div className="relative bg-red-600 text-white h-[60vh] flex items-center justify-center">
|
||||
<div className="absolute inset-0 bg-red-800 bg-opacity-50"></div>
|
||||
<div className="relative z-10 text-center px-6">
|
||||
<h1 className="text-4xl md:text-6xl font-bold mb-4">
|
||||
<div className="bg-white text-gray-800 transition-colors duration-200 min-h-screen">
|
||||
{/*
|
||||
Hero Section:
|
||||
Displays a welcoming background image with a call-to-action button.
|
||||
We use inline styles for the background image to easily swap it if needed.
|
||||
*/}
|
||||
<div
|
||||
className="relative h-[60vh] flex items-center justify-center bg-cover bg-center"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"url('https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?ixlib=rb-4.0.3&auto=format&fit=crop&w=1600&q=80')",
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-red-900 via-red-800 to-red-900 opacity-80"></div>
|
||||
<div className="relative z-10 text-center px-6 animate-fade-in">
|
||||
<h1 className="text-4xl md:text-6xl font-extrabold mb-4 text-white drop-shadow-lg">
|
||||
Welcome to Our Travel Policy
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl">
|
||||
Structured, efficient, and research-focused travel planning.
|
||||
<p className="text-lg md:text-xl text-gray-100 max-w-2xl mx-auto font-medium">
|
||||
Structured, efficient, and research-focused travel planning for the
|
||||
modern academic world.
|
||||
</p>
|
||||
<button type='button' className="mt-6 px-6 py-3 bg-white text-red-600 font-semibold rounded-lg shadow-md hover:bg-red-700 hover:text-white transition duration-300">
|
||||
<a
|
||||
href="#approach"
|
||||
className="inline-block mt-8 px-8 py-3 bg-white text-red-700 font-bold rounded-full shadow-lg hover:bg-gray-100 hover:scale-105 transition-all duration-300"
|
||||
>
|
||||
Learn More
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mission & Vision */}
|
||||
<div className="flex flex-col md:flex-row items-center py-12 px-6 md:px-12 gap-12">
|
||||
{/*
|
||||
Our Approach Section:
|
||||
Explains the philosophy behind the travel policy.
|
||||
*/}
|
||||
<div
|
||||
id="approach"
|
||||
className="flex flex-col md:flex-row items-center py-16 px-6 md:px-12 gap-12 max-w-7xl mx-auto"
|
||||
>
|
||||
<div className="md:w-1/2">
|
||||
<h2 className="text-3xl font-bold mb-4">Our Approach</h2>
|
||||
<p className="text-lg leading-relaxed">
|
||||
<h2 className="text-3xl font-bold mb-6 text-red-700">Our Approach</h2>
|
||||
<p className="text-lg leading-relaxed text-gray-700">
|
||||
Our travel policy for research students and associates ensures a
|
||||
structured process for approvals and financial support, fostering
|
||||
efficiency and alignment with academic and research objectives.
|
||||
efficiency and alignment with academic and research objectives. We
|
||||
believe in transparency and speed, enabling you to focus on your
|
||||
work.
|
||||
</p>
|
||||
</div>
|
||||
<div className="md:w-1/2">
|
||||
<img
|
||||
src="https://via.placeholder.com/600x400"
|
||||
src="https://images.unsplash.com/photo-1436491865332-7a61a109cc05?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
||||
alt="Travel Policy"
|
||||
className="rounded-lg shadow-lg transform transition duration-300 hover:scale-105"
|
||||
className="rounded-xl shadow-2xl transform transition duration-500 hover:scale-105 w-full object-cover h-64 md:h-80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Achievements & History */}
|
||||
<div className="bg-gray-100 py-12 px-6 md:px-12">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">Policy Highlights</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
title: "Travel Request Form",
|
||||
description:
|
||||
"Submit detailed forms including purpose, destination, budget, and supporting documentation.",
|
||||
icon: "📄",
|
||||
},
|
||||
{
|
||||
title: "Approval Process",
|
||||
description:
|
||||
"Supervisor, department head, and Office of Research must approve for major travels.",
|
||||
icon: "✔️",
|
||||
},
|
||||
{
|
||||
title: "Financial Support",
|
||||
description:
|
||||
"Eligibility depends on travel relevance, available funds, and research alignment.",
|
||||
icon: "💰",
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
description:
|
||||
"Attach conference invitations or research collaboration letters for approval.",
|
||||
icon: "📜",
|
||||
},
|
||||
{
|
||||
title: "International Travel",
|
||||
description:
|
||||
"Managed through the Office of Research for additional oversight and funding.",
|
||||
icon: "✈️",
|
||||
},
|
||||
{
|
||||
title: "Funding Sources",
|
||||
description:
|
||||
"Includes department funds, institutional grants, or scholarships.",
|
||||
icon: "🏛️",
|
||||
},
|
||||
].map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center p-6 bg-white shadow-md rounded-lg transition transform hover:scale-105"
|
||||
>
|
||||
<div className="text-4xl mb-4">{item.icon}</div>
|
||||
<h3 className="text-xl font-semibold mb-2">{item.title}</h3>
|
||||
<p className="text-gray-700 text-center">{item.description}</p>
|
||||
</div>
|
||||
))}
|
||||
<div className="bg-gray-50 py-16 px-6 md:px-12 transition-colors duration-200">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-center mb-12 text-gray-900">
|
||||
Policy Highlights
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
title: "Travel Request Form",
|
||||
description:
|
||||
"Submit detailed forms including purpose, destination, budget, and supporting documentation.",
|
||||
icon: "📄",
|
||||
},
|
||||
{
|
||||
title: "Approval Process",
|
||||
description:
|
||||
"Supervisor, department head, and Office of Research must approve for major travels.",
|
||||
icon: "✔️",
|
||||
},
|
||||
{
|
||||
title: "Financial Support",
|
||||
description:
|
||||
"Eligibility depends on travel relevance, available funds, and research alignment.",
|
||||
icon: "💰",
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
description:
|
||||
"Attach conference invitations or research collaboration letters for approval.",
|
||||
icon: "📜",
|
||||
},
|
||||
{
|
||||
title: "International Travel",
|
||||
description:
|
||||
"Managed through the Office of Research for additional oversight and funding.",
|
||||
icon: "✈️",
|
||||
},
|
||||
{
|
||||
title: "Funding Sources",
|
||||
description:
|
||||
"Includes department funds, institutional grants, or scholarships.",
|
||||
icon: "🏛️",
|
||||
},
|
||||
].map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center p-8 bg-white shadow-lg rounded-xl transition-all duration-300 hover:-translate-y-2 hover:shadow-xl border border-transparent"
|
||||
>
|
||||
<div className="text-5xl mb-6">{item.icon}</div>
|
||||
<h3 className="text-xl font-semibold mb-3 text-gray-900">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-gray-600 text-center leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Why Choose Us */}
|
||||
<div className="py-12 px-6 md:px-12">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">Why Our Policy Stands Out</h2>
|
||||
<div className="py-16 px-6 md:px-12 max-w-7xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-center mb-12 text-gray-900">
|
||||
Why Our Policy Stands Out
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
@@ -112,7 +144,8 @@ const About = () => {
|
||||
},
|
||||
{
|
||||
title: "Global Outlook",
|
||||
description: "Facilitates international collaborations and exchanges.",
|
||||
description:
|
||||
"Facilitates international collaborations and exchanges.",
|
||||
},
|
||||
{
|
||||
title: "Comprehensive",
|
||||
@@ -120,24 +153,25 @@ const About = () => {
|
||||
},
|
||||
{
|
||||
title: "Transparent",
|
||||
description: "Approval criteria and funding sources clearly defined.",
|
||||
description:
|
||||
"Approval criteria and funding sources clearly defined.",
|
||||
},
|
||||
].map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-start p-6 bg-white shadow-md rounded-lg transition transform hover:scale-105"
|
||||
className="flex flex-col items-start p-6 bg-white shadow-md rounded-lg transition transform hover:scale-105 border-l-4 border-red-600"
|
||||
>
|
||||
<h3 className="text-xl font-semibold mb-2">{item.title}</h3>
|
||||
<p className="text-gray-700">{item.description}</p>
|
||||
<h3 className="text-xl font-bold mb-2 text-gray-800">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-gray-600">{item.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Back to Top */}
|
||||
<div className="text-center py-6">
|
||||
|
||||
</div>
|
||||
{/* Footer / Back to Top */}
|
||||
<div className="text-center py-8 border-t border-gray-200"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -39,9 +39,11 @@ function Input({
|
||||
return (
|
||||
<div
|
||||
key={sectionIndex}
|
||||
className="space-y-4 bg-white p-6 rounded-lg shadow-md min-w-fit border-t-4 border-red-700 mb-4"
|
||||
className="space-y-4 bg-white dark:bg-google-gray p-6 rounded-lg shadow-md min-w-fit border-t-4 border-red-700 mb-4 transition-colors duration-200"
|
||||
>
|
||||
<h3 className="text-xl font-semibold mt-2 mb-4">{section.label}</h3>
|
||||
<h3 className="text-xl font-semibold mt-2 mb-4 dark:text-google-text">
|
||||
{section.label}
|
||||
</h3>
|
||||
<div
|
||||
className={`${
|
||||
section.label === "Expense Details"
|
||||
@@ -76,11 +78,11 @@ function Input({
|
||||
return (
|
||||
<div
|
||||
key={formFeild.name}
|
||||
className="space-y-1 bg-slate-50 p-3 rounded-md"
|
||||
className="space-y-1 bg-slate-50 dark:bg-[#3c4043] p-3 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<label
|
||||
htmlFor={formFeild.name}
|
||||
className="block font-medium"
|
||||
className="block font-medium dark:text-google-text"
|
||||
>
|
||||
{formFeild.label}
|
||||
</label>
|
||||
@@ -90,7 +92,7 @@ function Input({
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values[formFeild.name] || ""}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out"
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out dark:bg-[#303134] dark:text-white"
|
||||
disabled={formFeild?.disabled}
|
||||
>
|
||||
<option value="" label="Select option" />
|
||||
@@ -119,11 +121,11 @@ function Input({
|
||||
return (
|
||||
<div
|
||||
key={formFeild.name}
|
||||
className="space-y-1 bg-slate-50 p-3 rounded-md"
|
||||
className="space-y-1 bg-slate-50 dark:bg-[#3c4043] p-3 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<label
|
||||
htmlFor={formFeild.name}
|
||||
className="inline-flex items-center space-x-2"
|
||||
className="inline-flex items-center space-x-2 dark:text-google-text"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -149,11 +151,11 @@ function Input({
|
||||
return (
|
||||
<div
|
||||
key={formFeild.name}
|
||||
className="space-y-1 bg-slate-50 p-3 rounded-md"
|
||||
className="space-y-1 bg-slate-50 dark:bg-[#3c4043] p-3 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<label
|
||||
htmlFor={formFeild.name}
|
||||
className="block font-medium"
|
||||
className="block font-medium dark:text-google-text"
|
||||
>
|
||||
{formFeild.label}
|
||||
</label>
|
||||
@@ -163,7 +165,7 @@ function Input({
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values[formFeild.name] || ""}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md max-h-32 min-h-20 focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out"
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md max-h-32 min-h-20 focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out dark:bg-[#303134] dark:text-white"
|
||||
disabled={formFeild?.disabled}
|
||||
/>
|
||||
<p className="text-red-500 text-sm">
|
||||
@@ -178,11 +180,11 @@ function Input({
|
||||
return (
|
||||
<div
|
||||
key={formFeild.name}
|
||||
className="space-y-1 bg-slate-50 p-3 rounded-md"
|
||||
className="space-y-1 bg-slate-50 dark:bg-[#3c4043] p-3 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<label
|
||||
htmlFor={formFeild.name}
|
||||
className="block font-medium"
|
||||
className="block font-medium dark:text-google-text"
|
||||
>
|
||||
{formFeild.label}
|
||||
</label>
|
||||
@@ -206,7 +208,7 @@ function Input({
|
||||
setFieldValue(formFeild.name, e.target.files[0]);
|
||||
}}
|
||||
onBlur={handleBlur}
|
||||
className="w-full bg-white px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out"
|
||||
className="w-full bg-white dark:bg-[#303134] dark:text-white px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out"
|
||||
/>
|
||||
<p className="text-red-500 text-sm">
|
||||
{errors[formFeild.name] &&
|
||||
@@ -222,7 +224,7 @@ function Input({
|
||||
return (
|
||||
<div
|
||||
key={formFeild.name}
|
||||
className="space-y-4 bg-slate-50 p-6 rounded-md w-full"
|
||||
className="space-y-4 bg-slate-50 dark:bg-[#3c4043] p-6 rounded-md w-full transition-colors duration-200"
|
||||
>
|
||||
{pdfIsVisible && (
|
||||
<PdfViewer
|
||||
@@ -235,7 +237,7 @@ function Input({
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-4">
|
||||
<label
|
||||
htmlFor={formFeild.name}
|
||||
className="block text-lg font-medium text-gray-800 mb-3 sm:mb-0 sm:w-1/2"
|
||||
className="block text-lg font-medium text-gray-800 dark:text-google-text mb-3 sm:mb-0 sm:w-1/2"
|
||||
>
|
||||
{`${formFeild.label}: ₹${values[formFeild.name]
|
||||
?.reduce(
|
||||
@@ -341,11 +343,11 @@ function Input({
|
||||
return (
|
||||
<div
|
||||
key={formFeild.name}
|
||||
className="space-y-1 bg-slate-50 p-3 rounded-md"
|
||||
className="space-y-1 bg-slate-50 dark:bg-[#3c4043] p-3 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<label
|
||||
htmlFor={formFeild.name}
|
||||
className="block font-medium"
|
||||
className="block font-medium dark:text-google-text"
|
||||
>
|
||||
{formFeild.label}
|
||||
</label>
|
||||
@@ -356,7 +358,7 @@ function Input({
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values[formFeild.name] || ""}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out"
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-red-700 transition duration-300 ease-in-out dark:bg-[#303134] dark:text-white"
|
||||
disabled={formFeild?.disabled}
|
||||
max={formFeild?.max}
|
||||
min={formFeild?.min}
|
||||
|
||||
@@ -14,11 +14,11 @@ function AcceptChoice({
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose}>
|
||||
<div className="bg-white rounded-lg p-6 shadow-lg mx-auto">
|
||||
<h2 className="text-2xl font-semibold text-red-700 mb-4">
|
||||
<div className="bg-white dark:bg-google-gray rounded-lg p-6 shadow-lg mx-auto transition-colors duration-200">
|
||||
<h2 className="text-2xl font-semibold text-red-700 dark:text-red-500 mb-4">
|
||||
Confirm Application Approval
|
||||
</h2>
|
||||
<p className="text-gray-600 mb-6">
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
{(() => {
|
||||
switch (designation) {
|
||||
case "FACULTY":
|
||||
|
||||
@@ -128,7 +128,7 @@ function ApplicationView() {
|
||||
setCopySuccess(true);
|
||||
setTimeout(() => setCopySuccess(false), 2000);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
console.error("Failed to copy application ID: ", err);
|
||||
alert("Failed to copy application ID. Please try again.");
|
||||
});
|
||||
@@ -138,10 +138,12 @@ function ApplicationView() {
|
||||
const isTravelIntimationForm = applicationDisplay?.formData?.formName === "Travel Intimation Form";
|
||||
|
||||
return (
|
||||
<div className="min-w-min bg-white shadow rounded-lg p-2 sm:p-4 md:p-6 m-4">
|
||||
<div className="min-w-min bg-white dark:bg-google-gray shadow rounded-lg p-2 sm:p-4 md:p-6 m-4 transition-colors duration-200">
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start mb-6 gap-3">
|
||||
<div>
|
||||
<h1 className="text-3xl font-extrabold text-gray-800">{title}</h1>
|
||||
<h1 className="text-3xl font-extrabold text-gray-800 dark:text-google-text">
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
{isTravelIntimationForm && (
|
||||
<button
|
||||
|
||||
@@ -17,15 +17,18 @@ function RejectionFeedback({ onClose, onSubmit }) {
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose}>
|
||||
<div className="bg-white rounded-lg p-1 shadow-lg">
|
||||
<h2 className="text-2xl font-semibold text-red-700 mb-4">Confirm Application Rejection</h2>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Please provide a reason for rejecting the application.<br/> This will help the applicant to improve future applications.
|
||||
<div className="bg-white dark:bg-google-gray rounded-lg p-1 shadow-lg transition-colors duration-200">
|
||||
<h2 className="text-2xl font-semibold text-red-700 dark:text-red-500 mb-4">
|
||||
Confirm Application Rejection
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
Please provide a reason for rejecting the application.
|
||||
<br /> This will help the applicant to improve future applications.
|
||||
</p>
|
||||
|
||||
<form className="space-y-4">
|
||||
<textarea
|
||||
className="w-full p-4 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-700"
|
||||
className="w-full p-4 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-700 dark:bg-[#303134] dark:text-white transition-colors duration-200"
|
||||
placeholder="Enter the reason for rejection"
|
||||
rows="4"
|
||||
value={reason}
|
||||
|
||||
@@ -37,15 +37,13 @@ function ValidationStatus({ validations, rejectionFeedback }) {
|
||||
role.status
|
||||
)}`}
|
||||
></div>
|
||||
<p>{role.name}</p>
|
||||
<p className="dark:text-google-text">{role.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{rejectionFeedback && (
|
||||
<div
|
||||
className="mt-4 p-4 bg-red-100 border-l-4 border-red-500 text-red-700 rounded-lg shadow-md w-fit min-w-[30%]"
|
||||
>
|
||||
<div className="mt-4 p-4 bg-red-100 dark:bg-red-900/30 border-l-4 border-red-500 text-red-700 dark:text-red-400 rounded-lg shadow-md w-fit min-w-[30%]">
|
||||
<div className="flex justify-start items-center gap-2">
|
||||
<MdWarning className="w-6 h-6 text-red-500" />
|
||||
<p className="font-semibold">Rejection Reason:</p>
|
||||
|
||||
@@ -74,9 +74,25 @@ const Applications = () => {
|
||||
applications={applications}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-gray-600">
|
||||
No {status.toLowerCase()} applications found.
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center py-10 opacity-70">
|
||||
<svg
|
||||
className="w-24 h-24 text-gray-400 dark:text-gray-500 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<p className="text-xl font-medium text-gray-600 dark:text-google-text-secondary">
|
||||
No {status.toLowerCase()} applications found.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
@@ -91,7 +107,7 @@ const Applications = () => {
|
||||
|
||||
return (
|
||||
<main className="flex flex-col p-6">
|
||||
<div className="min-w-min bg-white shadow rounded-lg p-6 mb-20">
|
||||
<div className="min-w-min bg-white dark:bg-google-gray shadow rounded-lg p-6 mb-20 transition-colors duration-200">
|
||||
<ApplicationsStatusDescription />
|
||||
|
||||
{role === "Validator" && (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const ApplicationTable = ({ title, applications,}) => {
|
||||
|
||||
const ApplicationTable = ({ title, applications }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
@@ -11,11 +10,21 @@ const ApplicationTable = ({ title, applications,}) => {
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="border-b p-4 text-gray-700">Topic</th>
|
||||
<th className="border-b p-4 text-gray-700">Name</th>
|
||||
<th className="border-b p-4 text-gray-700">Submitted</th>
|
||||
<th className="border-b p-4 text-gray-700">Branch</th>
|
||||
<th className="border-b p-4 text-gray-700">Status</th>
|
||||
<th className="border-b p-4 text-gray-700 dark:text-google-text">
|
||||
Topic
|
||||
</th>
|
||||
<th className="border-b p-4 text-gray-700 dark:text-google-text">
|
||||
Name
|
||||
</th>
|
||||
<th className="border-b p-4 text-gray-700 dark:text-google-text">
|
||||
Submitted
|
||||
</th>
|
||||
<th className="border-b p-4 text-gray-700 dark:text-google-text">
|
||||
Branch
|
||||
</th>
|
||||
<th className="border-b p-4 text-gray-700 dark:text-google-text">
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -28,14 +37,16 @@ const ApplicationTable = ({ title, applications,}) => {
|
||||
const newPath = location.split('/').slice(0, -1).join('/');
|
||||
navigate(`${newPath}/${title.split(" ")[0]?.toLowerCase()}/${app.applicationId}`);
|
||||
}}
|
||||
className="odd:bg-gray-50 even:bg-white hover:bg-gray-200 cursor-pointer"
|
||||
style={{ height: '50px' }}
|
||||
className="odd:bg-gray-50 even:bg-white dark:odd:bg-[#303134] dark:even:bg-[#202124] hover:bg-gray-200 dark:hover:bg-[#3c4043] cursor-pointer text-gray-900 dark:text-google-text transition-colors duration-200"
|
||||
style={{ height: "50px" }}
|
||||
>
|
||||
<td className="p-4">{app.formData.eventName}</td>
|
||||
<td className="p-4">{app.applicantName}</td>
|
||||
<td className="p-4">{formatDateToDDMMYYYY(app.createdAt)}</td>
|
||||
<td className="p-4">{app.formData.applicantDepartment}</td>
|
||||
<td className="p-4 text-green-500">{title.split(" ")[0]}</td>
|
||||
<td className="p-4 text-green-500 dark:text-green-400">
|
||||
{title.split(" ")[0]}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -9,16 +9,16 @@ function ApplicationsStatusDescription() {
|
||||
const { status } = useParams();
|
||||
|
||||
return (
|
||||
<div className="bg-slate-50 shadow-md rounded-lg p-6 mb-8 border border-slate-400">
|
||||
<div className="flex justify-between items-center mb-6 gap-5">
|
||||
<h1 className="text-3xl font-semibold text-gray-800">
|
||||
<div className="bg-slate-50 dark:bg-[#3c4043] shadow-md rounded-lg p-6 mb-8 border border-slate-400 dark:border-gray-600 transition-colors duration-200">
|
||||
<div className="relative flex flex-col md:flex-row items-center justify-center mb-6 gap-5">
|
||||
<h1 className="text-3xl font-semibold text-gray-800 dark:text-google-text text-center">
|
||||
{`${status.toUpperCase()} APPLICATIONS`}
|
||||
</h1>
|
||||
{role === "Applicant" && (
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => navigate("../form")}
|
||||
className="flex items-center bg-gradient-to-r from-red-600 to-red-800 hover:from-red-800 hover:to-red-600 text-white font-semibold py-2 px-4 rounded-lg shadow-lg transform transition duration-300 ease-in-out hover:scale-105"
|
||||
className="md:absolute md:right-0 flex items-center bg-gradient-to-r from-red-600 to-red-800 hover:from-red-800 hover:to-red-600 text-white font-semibold py-2 px-4 rounded-lg shadow-lg transform transition duration-300 ease-in-out hover:scale-105"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -38,7 +38,7 @@ function ApplicationsStatusDescription() {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-gray-600 text-lg leading-relaxed sm:block hidden">
|
||||
<p className="text-gray-600 dark:text-google-text-secondary text-lg leading-relaxed sm:block hidden">
|
||||
Easily track the details and statuses of all your submitted applications
|
||||
in one place.
|
||||
<br />
|
||||
|
||||
@@ -13,7 +13,7 @@ function ContactUs() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full bg-white text-gray-900">
|
||||
<div className="h-full bg-white dark:bg-google-dark text-gray-900 dark:text-google-text transition-colors duration-200">
|
||||
{/* Hero Section */}
|
||||
<section className="relative bg-red-600 text-white h-[60vh] flex items-center justify-center">
|
||||
<div className="absolute inset-0 bg-red-700 opacity-60"></div>
|
||||
@@ -25,44 +25,67 @@ function ContactUs() {
|
||||
</section>
|
||||
|
||||
{/* Contact Form */}
|
||||
<section id="contact-form" className="py-12 px-4 sm:px-6 lg:px-8 bg-gray-100">
|
||||
<section
|
||||
id="contact-form"
|
||||
className="py-12 px-4 sm:px-6 lg:px-8 bg-gray-100 dark:bg-google-gray transition-colors duration-200"
|
||||
>
|
||||
<div className="max-w-lg mx-auto">
|
||||
<h2 className="text-2xl font-semibold text-center mb-6">Contact Form</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Name</label>
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-google-text"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
required
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-700"
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-red-700 dark:bg-[#3c4043] dark:text-white transition-colors duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-google-text"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
required
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-700"
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-red-700 dark:bg-[#3c4043] dark:text-white transition-colors duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-gray-700">Subject</label>
|
||||
<label
|
||||
htmlFor="subject"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-google-text"
|
||||
>
|
||||
Subject
|
||||
</label>
|
||||
<input
|
||||
id="subject"
|
||||
type="text"
|
||||
required
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-700"
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-red-700 dark:bg-[#3c4043] dark:text-white transition-colors duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-gray-700">Message</label>
|
||||
<label
|
||||
htmlFor="message"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-google-text"
|
||||
>
|
||||
Message
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
rows="4"
|
||||
required
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-700"
|
||||
className="w-full px-4 py-2 mt-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-red-700 dark:bg-[#3c4043] dark:text-white transition-colors duration-200"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@@ -82,7 +105,7 @@ function ContactUs() {
|
||||
</section>
|
||||
|
||||
{/* Location Map */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 bg-white dark:bg-google-dark transition-colors duration-200">
|
||||
<div className="max-w-screen-xl mx-auto">
|
||||
<h2 className="text-2xl font-semibold text-center mb-6">Our Location</h2>
|
||||
<div className="relative">
|
||||
@@ -95,17 +118,20 @@ function ContactUs() {
|
||||
</section>
|
||||
|
||||
{/* Contact Details */}
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 bg-gray-100">
|
||||
<section className="py-12 px-4 sm:px-6 lg:px-8 bg-gray-100 dark:bg-google-gray transition-colors duration-200">
|
||||
<div className="max-w-screen-xl mx-auto flex flex-col sm:flex-row justify-between items-center">
|
||||
<div className="flex flex-col items-center sm:items-start mb-6 sm:mb-0">
|
||||
<h3 className="text-xl font-semibold text-gray-800">Contact Details</h3>
|
||||
<p className="mt-2 text-gray-600">
|
||||
<span className="font-medium">Address:</span> K. J. Somaiya College of Engineering, Vidya Nagar, Mumbai, India
|
||||
<h3 className="text-xl font-semibold text-gray-800 dark:text-google-text">
|
||||
Contact Details
|
||||
</h3>
|
||||
<p className="mt-2 text-gray-600 dark:text-google-text-secondary">
|
||||
<span className="font-medium">Address:</span> K. J. Somaiya
|
||||
College of Engineering, Vidya Nagar, Mumbai, India
|
||||
</p>
|
||||
<p className="mt-2 text-gray-600">
|
||||
<p className="mt-2 text-gray-600 dark:text-google-text-secondary">
|
||||
<span className="font-medium">Phone:</span> (022) 6728 8000
|
||||
</p>
|
||||
<p className="mt-2 text-gray-600">
|
||||
<p className="mt-2 text-gray-600 dark:text-google-text-secondary">
|
||||
<span className="font-medium">Email:</span> info@somaiya.edu
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ function Dashboard() {
|
||||
const greetingLine2 = `${designation} in ${department} Department, ${institute}`;
|
||||
|
||||
return (
|
||||
<div className="font-sans bg-white overflow-y-scroll scroll-smooth snap-y h-screen" >
|
||||
<div className="font-sans bg-white dark:bg-google-dark overflow-y-scroll scroll-smooth snap-y h-screen transition-colors duration-200">
|
||||
{/* Hero Section */}
|
||||
<section
|
||||
className="relative w-full h-screen flex items-center justify-center text-white overflow-hidden bg-cover bg-center snap-start"
|
||||
@@ -68,19 +68,21 @@ function Dashboard() {
|
||||
{/* Features Section */}
|
||||
<section
|
||||
id="features"
|
||||
className="py-12 sm:py-16 px-6 sm:px-8 md:px-12 lg:px-16 bg-gradient-to-b from-white via-gray-100 to-red-50 min-h-screen snap-start"
|
||||
className="py-12 sm:py-16 px-6 sm:px-8 md:px-12 lg:px-16 bg-gradient-to-b from-white via-gray-100 to-red-50 dark:from-google-dark dark:via-[#25262a] dark:to-[#1e1f21] min-h-screen snap-start transition-colors duration-200"
|
||||
>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-semibold text-center text-red-700 mb-10 sm:mb-12">
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-semibold text-center text-red-700 dark:text-red-500 mb-10 sm:mb-12">
|
||||
Our Key Features
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-12">
|
||||
{/* Feature 1 */}
|
||||
<div className="flex flex-col items-center bg-white p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105">
|
||||
<div className="text-5xl mb-4 text-red-700">🔍</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 text-center">
|
||||
<div className="flex flex-col items-center bg-white dark:bg-google-gray p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105 duration-200">
|
||||
<div className="text-5xl mb-4 text-red-700 dark:text-red-500">
|
||||
🔍
|
||||
</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 dark:text-red-500 text-center">
|
||||
View Your Applications
|
||||
</h3>
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700">
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700 dark:text-gray-300">
|
||||
{role === "Applicant"
|
||||
? "Manage and track the status of your funding applications. Review feedback and make necessary updates."
|
||||
: "Review and validate applications submitted by applicants. Approve or reject based on eligibility and guidelines."}
|
||||
@@ -97,12 +99,14 @@ function Dashboard() {
|
||||
|
||||
{/* Feature 2 */}
|
||||
{role === "Validator" && (
|
||||
<div className="flex flex-col items-center bg-white p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105">
|
||||
<div className="text-5xl mb-4 text-red-700">📊</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 text-center">
|
||||
<div className="flex flex-col items-center bg-white dark:bg-google-gray p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105 duration-200">
|
||||
<div className="text-5xl mb-4 text-red-700 dark:text-red-500">
|
||||
📊
|
||||
</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 dark:text-red-500 text-center">
|
||||
View Insights
|
||||
</h3>
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700">
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700 dark:text-gray-300">
|
||||
Analyze and gain insights about funding and related data.
|
||||
</p>
|
||||
<button
|
||||
@@ -116,12 +120,14 @@ function Dashboard() {
|
||||
|
||||
{/* Feature 3 */}
|
||||
{role === "Applicant" && (
|
||||
<div className="flex flex-col items-center bg-white p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105">
|
||||
<div className="text-5xl mb-4 text-red-700">📝</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 text-center">
|
||||
<div className="flex flex-col items-center bg-white dark:bg-google-gray p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105 duration-200">
|
||||
<div className="text-5xl mb-4 text-red-700 dark:text-red-500">
|
||||
📝
|
||||
</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 dark:text-red-500 text-center">
|
||||
Create New Application
|
||||
</h3>
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700">
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700 dark:text-gray-300">
|
||||
Start your application process to apply for funding and
|
||||
financial assistance.
|
||||
</p>
|
||||
@@ -135,12 +141,14 @@ function Dashboard() {
|
||||
)}
|
||||
|
||||
{/* Feature 4 */}
|
||||
<div className="flex flex-col items-center bg-white p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105">
|
||||
<div className="text-5xl mb-4 text-red-700">📚</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 text-center">
|
||||
<div className="flex flex-col items-center bg-white dark:bg-google-gray p-6 sm:p-8 rounded-2xl shadow-lg hover:shadow-xl transition-transform transform hover:-translate-y-4 hover:scale-105 duration-200">
|
||||
<div className="text-5xl mb-4 text-red-700 dark:text-red-500">
|
||||
📚
|
||||
</div>
|
||||
<h3 className="text-lg sm:text-xl md:text-2xl font-semibold mb-2 text-red-700 dark:text-red-500 text-center">
|
||||
Understand the Policy
|
||||
</h3>
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700">
|
||||
<p className="text-center text-sm sm:text-base md:text-lg mb-6 text-gray-700 dark:text-gray-300">
|
||||
Learn about the eligibility, funding process, and guidelines for
|
||||
financial assistance.
|
||||
</p>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
function Policy() {
|
||||
const Policy = () => {
|
||||
return (
|
||||
<div>
|
||||
Policy
|
||||
<div className="flex flex-col h-screen w-full bg-gray-100 dark:bg-google-dark">
|
||||
<div className="flex-grow w-full h-full">
|
||||
<iframe
|
||||
src="https://svu-iqac.somaiya.edu/University+Policies/12.Travel+Policy.pdf"
|
||||
className="w-full h-full border-none"
|
||||
title="Travel Policy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Policy
|
||||
export default Policy;
|
||||
|
||||
@@ -11,7 +11,7 @@ function Report() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
return (
|
||||
<main className="flex flex-col p-6">
|
||||
<div className="bg-white shadow rounded-lg p-6 w-full">
|
||||
<div className="bg-white dark:bg-google-gray shadow rounded-lg p-6 w-full transition-colors duration-200">
|
||||
<FilterDataForm setReportData={setReportData} setLoading={setLoading} />
|
||||
{loading ? <Loading /> : <Charts reportData={reportData} />}
|
||||
</div>
|
||||
|
||||
@@ -3,29 +3,42 @@ import React from "react";
|
||||
const Table = ({ tableData }) => {
|
||||
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
<table
|
||||
style={{
|
||||
width: "100%",
|
||||
borderCollapse: "collapse",
|
||||
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
|
||||
}}
|
||||
>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: "#f4f4f4" }}>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>ID</th>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>Stream</th>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>Scholarship</th>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>Funds</th>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse shadow-sm rounded-lg overflow-hidden bg-white dark:bg-[#3c4043] transition-colors duration-200">
|
||||
<thead className="bg-gray-100 dark:bg-[#303134]">
|
||||
<tr className="text-left">
|
||||
<th className="p-3 border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-200 font-semibold">
|
||||
ID
|
||||
</th>
|
||||
<th className="p-3 border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-200 font-semibold">
|
||||
Stream
|
||||
</th>
|
||||
<th className="p-3 border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-200 font-semibold">
|
||||
Scholarship
|
||||
</th>
|
||||
<th className="p-3 border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-200 font-semibold">
|
||||
Funds
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tableData?.map((row) => (
|
||||
<tr key={row.id}>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.id}</td>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.Stream}</td>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.Scholarship}</td>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.Funds}</td>
|
||||
<tr
|
||||
key={row.id}
|
||||
className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<td className="p-3 border border-gray-200 dark:border-gray-600 text-gray-800 dark:text-gray-300">
|
||||
{row.id}
|
||||
</td>
|
||||
<td className="p-3 border border-gray-200 dark:border-gray-600 text-gray-800 dark:text-gray-300">
|
||||
{row.Stream}
|
||||
</td>
|
||||
<td className="p-3 border border-gray-200 dark:border-gray-600 text-gray-800 dark:text-gray-300">
|
||||
{row.Scholarship}
|
||||
</td>
|
||||
<td className="p-3 border border-gray-200 dark:border-gray-600 text-gray-800 dark:text-gray-300">
|
||||
{row.Funds}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
import ChartDataLabels from "chartjs-plugin-datalabels";
|
||||
import Table from "./Table";
|
||||
import { PDFDownloadLink, PDFViewer } from "@react-pdf/renderer";
|
||||
import ApprovalVsRejectionTrends from "./map";
|
||||
@@ -46,19 +46,24 @@ function Charts({ reportData }) {
|
||||
);
|
||||
}
|
||||
|
||||
const { acceptedApplications, rejectedApplications, pendingApplications } = data;
|
||||
const { acceptedApplications, rejectedApplications, pendingApplications } =
|
||||
data;
|
||||
|
||||
const tableData = [];
|
||||
const groupedData = {};
|
||||
|
||||
// --- Data Processing for Table --- (Simple aggregation logic)
|
||||
if (acceptedApplications) {
|
||||
for (const item of acceptedApplications) {
|
||||
const { institute, department, formData } = item;
|
||||
const { totalExpense } = formData;
|
||||
|
||||
// Initialize institute object if not exists
|
||||
if (!groupedData[institute]) {
|
||||
groupedData[institute] = {};
|
||||
}
|
||||
|
||||
// If filtering by specific institute, we group by Department (e.g. Computer, IT, Mech)
|
||||
if (query.institute) {
|
||||
if (!groupedData[institute][department]) {
|
||||
groupedData[institute][department] = {
|
||||
@@ -67,11 +72,12 @@ function Charts({ reportData }) {
|
||||
};
|
||||
}
|
||||
|
||||
// Aggregate the data
|
||||
// Add expense and increment count
|
||||
groupedData[institute][department].totalExpense +=
|
||||
parseFloat(totalExpense); // Summing the expenses
|
||||
parseFloat(totalExpense);
|
||||
groupedData[institute][department].applications += 1;
|
||||
} else {
|
||||
// If viewing all institutes, we group by Institute (e.g. KJSCE, KJSIM)
|
||||
if (!groupedData[institute].applications) {
|
||||
groupedData[institute] = {
|
||||
totalExpense: 0,
|
||||
@@ -79,38 +85,40 @@ function Charts({ reportData }) {
|
||||
};
|
||||
}
|
||||
|
||||
// Aggregate the data
|
||||
groupedData[institute].totalExpense += parseFloat(totalExpense); // Summing the expenses
|
||||
// Add expense and increment count
|
||||
groupedData[institute].totalExpense += parseFloat(totalExpense);
|
||||
groupedData[institute].applications += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Transform grouped data into desired table format
|
||||
// --- Transform Groups to Array for Display ---
|
||||
if (query.institute) {
|
||||
// Loop through departments for the selected institute
|
||||
for (const institute in groupedData) {
|
||||
for (const department in groupedData[institute]) {
|
||||
const departmentData = groupedData[institute][department];
|
||||
|
||||
tableData.push({
|
||||
id: tableData.length + 1,
|
||||
Stream: department,
|
||||
Scholarship: departmentData.applications, // Assuming each application is one scholarship
|
||||
Purpose_of_Travel: departmentData.purposeOfTravel,
|
||||
Funds: departmentData.totalExpense.toFixed(2), // Formatting funds to 2 decimal places
|
||||
Stream: department, // 'Stream' here refers to the Department name
|
||||
Scholarship: departmentData.applications, // Number of applications
|
||||
Purpose_of_Travel: departmentData.purposeOfTravel, // (Placeholder)
|
||||
Funds: departmentData.totalExpense.toFixed(2), // Total money spent
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Loop through all institutes
|
||||
for (const institute in groupedData) {
|
||||
const instituteData = groupedData[institute];
|
||||
|
||||
tableData.push({
|
||||
id: tableData.length + 1,
|
||||
Stream: institute,
|
||||
Scholarship: instituteData.applications, // Assuming each application is one scholarship
|
||||
Stream: institute, // 'Stream' here is the Institute name
|
||||
Scholarship: instituteData.applications, // Number of applications
|
||||
Purpose_of_Travel: instituteData.purposeOfTravel,
|
||||
Funds: instituteData.totalExpense.toFixed(2), // Formatting funds to 2 decimal places
|
||||
Funds: instituteData.totalExpense.toFixed(2), // Total money spent
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -122,6 +130,8 @@ function Charts({ reportData }) {
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
// --- Chart Configuration (Preserved for future use) ---
|
||||
|
||||
// Line Chart Data and Options
|
||||
const lineOptions = {
|
||||
responsive: true,
|
||||
@@ -281,80 +291,86 @@ function Charts({ reportData }) {
|
||||
],
|
||||
};
|
||||
|
||||
// const barChartRef = useRef();
|
||||
// const pieChartRef1 = useRef();
|
||||
// const pieChartRef2 = useRef();
|
||||
const barChartRef = useRef();
|
||||
const pieChartRef1 = useRef();
|
||||
const pieChartRef2 = useRef();
|
||||
|
||||
// const loadChartsInPdf = () => {
|
||||
// const barChartInstance = barChartRef.current;
|
||||
// const pieChartInstance1 = pieChartRef1.current;
|
||||
// const pieChartInstance2 = pieChartRef2.current;
|
||||
// Note: Chart generation logic for PDF is currently disabled as we focus on the Table view.
|
||||
// Uncomment this when we are ready to integrate charts into the PDF report.
|
||||
/*
|
||||
const loadChartsInPdf = () => {
|
||||
const barChartInstance = barChartRef.current;
|
||||
const pieChartInstance1 = pieChartRef1.current;
|
||||
const pieChartInstance2 = pieChartRef2.current;
|
||||
|
||||
// if (barChartInstance) {
|
||||
// const barBase64Image = barChartInstance.toBase64Image();
|
||||
// setChartImages((prevImages) => ({
|
||||
// ...prevImages,
|
||||
// barChart: barBase64Image,
|
||||
// }));
|
||||
// }
|
||||
if (barChartInstance) {
|
||||
const barBase64Image = barChartInstance.toBase64Image();
|
||||
setChartImages((prevImages) => ({
|
||||
...prevImages,
|
||||
barChart: barBase64Image,
|
||||
}));
|
||||
}
|
||||
|
||||
// if (pieChartInstance1) {
|
||||
// const pieBase64Image = pieChartInstance1.toBase64Image();
|
||||
// setChartImages((prevImages) => ({
|
||||
// ...prevImages,
|
||||
// pieChart1: pieBase64Image,
|
||||
// }));
|
||||
// }
|
||||
if (pieChartInstance1) {
|
||||
const pieBase64Image = pieChartInstance1.toBase64Image();
|
||||
setChartImages((prevImages) => ({
|
||||
...prevImages,
|
||||
pieChart1: pieBase64Image,
|
||||
}));
|
||||
}
|
||||
|
||||
// if (pieChartInstance2) {
|
||||
// const pieBase64Image = pieChartInstance2.toBase64Image();
|
||||
// setChartImages((prevImages) => ({
|
||||
// ...prevImages,
|
||||
// pieChart2: pieBase64Image,
|
||||
// }));
|
||||
// }
|
||||
// };
|
||||
if (pieChartInstance2) {
|
||||
const pieBase64Image = pieChartInstance2.toBase64Image();
|
||||
setChartImages((prevImages) => ({
|
||||
...prevImages,
|
||||
pieChart2: pieBase64Image,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// setChartImages((prevImages) => ({ ...prevImages, isLoading: true }));
|
||||
useEffect(() => {
|
||||
setChartImages((prevImages) => ({ ...prevImages, isLoading: true }));
|
||||
|
||||
// const handleRender = () => {
|
||||
// loadChartsInPdf();
|
||||
// setChartImages((prevImages) => ({ ...prevImages, isLoading: false }));
|
||||
// };
|
||||
const handleRender = () => {
|
||||
loadChartsInPdf();
|
||||
setChartImages((prevImages) => ({ ...prevImages, isLoading: false }));
|
||||
};
|
||||
|
||||
// const barChartInstance = barChartRef.current;
|
||||
// const pieChartInstance1 = pieChartRef1.current;
|
||||
// const pieChartInstance2 = pieChartRef2.current;
|
||||
const barChartInstance = barChartRef.current;
|
||||
const pieChartInstance1 = pieChartRef1.current;
|
||||
const pieChartInstance2 = pieChartRef2.current;
|
||||
|
||||
// if (barChartInstance) {
|
||||
// barChartInstance.options.animation.onComplete = handleRender;
|
||||
// }
|
||||
if (barChartInstance) {
|
||||
barChartInstance.options.animation.onComplete = handleRender;
|
||||
}
|
||||
|
||||
// if (pieChartInstance1) {
|
||||
// pieChartInstance1.options.animation.onComplete = handleRender;
|
||||
// }
|
||||
if (pieChartInstance1) {
|
||||
pieChartInstance1.options.animation.onComplete = handleRender;
|
||||
}
|
||||
|
||||
// if (pieChartInstance2) {
|
||||
// pieChartInstance2.options.animation.onComplete = handleRender;
|
||||
// }
|
||||
if (pieChartInstance2) {
|
||||
pieChartInstance2.options.animation.onComplete = handleRender;
|
||||
}
|
||||
|
||||
// return () => {
|
||||
// if (barChartInstance) {
|
||||
// barChartInstance.options.animation.onComplete = null;
|
||||
// }
|
||||
// if (pieChartInstance1) {
|
||||
// pieChartInstance1.options.animation.onComplete = null;
|
||||
// }
|
||||
// if (pieChartInstance2) {
|
||||
// pieChartInstance2.options.animation.onComplete = null;
|
||||
// }
|
||||
// };
|
||||
// }, []);
|
||||
return () => {
|
||||
if (barChartInstance) {
|
||||
barChartInstance.options.animation.onComplete = null;
|
||||
}
|
||||
if (pieChartInstance1) {
|
||||
pieChartInstance1.options.animation.onComplete = null;
|
||||
}
|
||||
if (pieChartInstance2) {
|
||||
pieChartInstance2.options.animation.onComplete = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
*/
|
||||
|
||||
return (
|
||||
<div className="p-10">
|
||||
<h1 className="text-3xl mb-6">Travel Policy Report</h1>
|
||||
<h1 className="text-3xl mb-6 text-gray-800 dark:text-google-text">
|
||||
Travel Policy Report
|
||||
</h1>
|
||||
|
||||
{/* Container for all three charts */}
|
||||
{/* <div className="grid grid-cols-1 md:grid-cols-1 lg:grid-cols-[2fr,1fr] gap-6">
|
||||
@@ -387,9 +403,11 @@ function Charts({ reportData }) {
|
||||
|
||||
<div className="flex flex-col gap-10 items-center justify-center my-10">
|
||||
<div className="w-full">
|
||||
{/* Reuse the Table component to show aggregated data */}
|
||||
<Table tableData={tableData} />
|
||||
</div>
|
||||
{/*
|
||||
|
||||
{/*
|
||||
<div>
|
||||
<Pie options={pie_Options} data={pie_Data} ref={pieChartRef2} />
|
||||
</div> */}
|
||||
@@ -428,9 +446,11 @@ function Charts({ reportData }) {
|
||||
}
|
||||
</PDFDownloadLink>
|
||||
|
||||
<PDFViewer style={{ width: "70vw", height: "100vh" }}>
|
||||
<ReportPDF tableData={tableData} chartImages={chartImages} />
|
||||
</PDFViewer>
|
||||
<div className="mt-8 hidden md:block">
|
||||
<PDFViewer style={{ width: "70vw", height: "100vh" }}>
|
||||
<ReportPDF tableData={tableData} chartImages={chartImages} />
|
||||
</PDFViewer>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
122
frontend/src/pages/Settings/Settings.jsx
Normal file
122
frontend/src/pages/Settings/Settings.jsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import React, { useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import axios from "axios";
|
||||
|
||||
// Settings Change Password Component
|
||||
const Settings = () => {
|
||||
const [passwords, setPasswords] = useState({
|
||||
oldPassword: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
|
||||
// Handle input changes
|
||||
const handleChange = (e) => {
|
||||
setPasswords({ ...passwords, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
// Submit the form to the backend
|
||||
const handleSubmit = async (e) => {
|
||||
// Step 1: Prevent the page from reloading
|
||||
e.preventDefault();
|
||||
|
||||
// Step 2: Check if the new password and confirm password are the same
|
||||
if (passwords.newPassword !== passwords.confirmPassword) {
|
||||
toast.error("New passwords do not match!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 3: Send the old and new password to the server
|
||||
const res = await axios.post(
|
||||
`${import.meta.env.VITE_APP_API_URL}/general/changePassword`,
|
||||
{
|
||||
oldPassword: passwords.oldPassword,
|
||||
newPassword: passwords.newPassword,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
);
|
||||
|
||||
// Step 4: If everything is good, show success message
|
||||
if (res.status === 200) {
|
||||
toast.success("Password updated successfully!");
|
||||
// Clear the form inputs
|
||||
setPasswords({ oldPassword: "", newPassword: "", confirmPassword: "" });
|
||||
}
|
||||
} catch (error) {
|
||||
// Step 5: If there is an error (like wrong old password), show it
|
||||
console.log(error);
|
||||
toast.error(error.response?.data?.message || "Failed to update password");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-8 bg-gray-50 dark:bg-google-dark min-h-screen transition-colors duration-200">
|
||||
<h1 className="text-3xl font-bold text-gray-800 dark:text-google-text mb-6 border-b-2 border-red-700 pb-2 inline-block">
|
||||
Account Settings
|
||||
</h1>
|
||||
|
||||
<div className="bg-white dark:bg-google-gray p-8 rounded-xl shadow-lg border border-gray-100 dark:border-gray-700 max-w-lg mt-4 mx-auto transition-colors duration-200">
|
||||
<h2 className="text-2xl font-semibold mb-6 text-red-700 dark:text-red-500 flex items-center">
|
||||
Change Password
|
||||
</h2>
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
|
||||
<div>
|
||||
<label className="block text-gray-700 dark:text-google-text mb-2 font-medium">
|
||||
Current Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="oldPassword"
|
||||
value={passwords.oldPassword}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter your current password"
|
||||
className="w-full p-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all dark:bg-[#3c4043] dark:text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 dark:text-google-text mb-2 font-medium">
|
||||
New Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="newPassword"
|
||||
value={passwords.newPassword}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter new password"
|
||||
className="w-full p-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all dark:bg-[#3c4043] dark:text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 dark:text-google-text mb-2 font-medium">
|
||||
Confirm New Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="confirmPassword"
|
||||
value={passwords.confirmPassword}
|
||||
onChange={handleChange}
|
||||
placeholder="Confirm new password"
|
||||
className="w-full p-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all dark:bg-[#3c4043] dark:text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="mt-2 bg-red-700 text-white font-bold py-3 px-6 rounded-lg hover:bg-red-800 transition-colors shadow-md hover:shadow-lg transform active:scale-95 duration-200"
|
||||
>
|
||||
Update Password
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
@@ -1,11 +1,16 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
"google-dark": "#202124",
|
||||
"google-gray": "#303134",
|
||||
"google-text": "#E8EAED",
|
||||
"google-text-secondary": "#9AA0A6",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Reference in New Issue
Block a user