From d71d91f08912073ce71067a87622b3ec7ae569e6 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 21:26:26 +0530 Subject: [PATCH 01/13] updated jportal version --- jportal/src/App.jsx | 32 ++- jportal/src/components/Login.jsx | 335 +++++++++++++++++-------------- 2 files changed, 219 insertions(+), 148 deletions(-) diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx index 28b26b9..3ecb6e4 100644 --- a/jportal/src/App.jsx +++ b/jportal/src/App.jsx @@ -14,7 +14,6 @@ import { ThemeScript } from "./components/theme-script"; import { DynamicFontLoader } from "./components/DynamicFontLoader"; import { Toaster } from "./components/ui/sonner"; import "./App.css"; - import { WebPortal, LoginError } from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.23/dist/jsjiit.esm.js"; import MockWebPortal from "./components/MockWebPortal"; @@ -331,6 +330,37 @@ function App() { setIsDemoMode(true); }; + // After authentication (including auto-login), fetch and print fee summary and pending fines + useEffect(() => { + if (!isAuthenticated) return; + + const portal = isDemoMode ? mockPortal : realPortal; + + const fetchAndLogPayments = async () => { + try { + const feeSummary = await portal.get_fee_summary(); + console.log("[Portal] Fee summary:", feeSummary); + } catch (err) { + console.error("[Portal] Failed to fetch fee summary:", err); + } + + try { + const fines = await portal.get_fines_msc_charges(); + console.log("[Portal] Pending miscellaneous charges / fines:", fines); + } catch (err) { + // The API may return Failure with message "NO APPROVED REQUEST FOUND" when there are no fines + if (err && err.message && err.message.includes("NO APPROVED REQUEST FOUND")) { + console.info("[Portal] No pending fines found (NO APPROVED REQUEST FOUND)."); + } else { + console.error("[Portal] Failed to fetch pending fines:", err); + } + } + }; + + // Do not block UI - fire and forget + fetchAndLogPayments(); + }, [isAuthenticated, isDemoMode]); + if (isLoading) { return ( <> diff --git a/jportal/src/components/Login.jsx b/jportal/src/components/Login.jsx index 10e4c1c..e7e717e 100644 --- a/jportal/src/components/Login.jsx +++ b/jportal/src/components/Login.jsx @@ -3,8 +3,21 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import * as z from "zod"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { Eye, EyeOff } from "lucide-react"; @@ -13,162 +26,190 @@ import PublicHeader from "./PublicHeader"; // Define the form schema const formSchema = z.object({ - enrollmentNumber: z.string({ - required_error: "Enrollment number is required", - }), - password: z.string({ - required_error: "Password is required", - }), + enrollmentNumber: z.string({ + required_error: "Enrollment number is required", + }), + password: z.string({ + required_error: "Password is required", + }), }); export default function Login({ onLoginSuccess, onDemoLogin, w }) { - const [showPassword, setShowPassword] = useState(false); - const [loginStatus, setLoginStatus] = useState({ - isLoading: false, - credentials: null, - }); + const [showPassword, setShowPassword] = useState(false); + const [loginStatus, setLoginStatus] = useState({ + isLoading: false, + credentials: null, + }); - // Initialize form - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { - enrollmentNumber: "", - password: "", - }, - }); + // Initialize form + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + enrollmentNumber: "", + password: "", + }, + }); - // Handle demo login - const handleDemoLogin = () => { - onDemoLogin(); - }; + // Handle demo login + const handleDemoLogin = () => { + onDemoLogin(); + }; - // Handle side effects in useEffect - useEffect(() => { - if (!loginStatus.credentials) return; + // Handle side effects in useEffect + useEffect(() => { + if (!loginStatus.credentials) return; - const performLogin = async () => { - try { - await w.student_login(loginStatus.credentials.enrollmentNumber, loginStatus.credentials.password); + const performLogin = async () => { + try { + await w.student_login( + loginStatus.credentials.enrollmentNumber, + loginStatus.credentials.password + ); - // Store credentials in localStorage - localStorage.setItem("username", loginStatus.credentials.enrollmentNumber); - localStorage.setItem("password", loginStatus.credentials.password); + // Store credentials in localStorage + localStorage.setItem( + "username", + loginStatus.credentials.enrollmentNumber + ); + localStorage.setItem("password", loginStatus.credentials.password); - console.log("Login successful"); - setLoginStatus((prev) => ({ - ...prev, - isLoading: false, - credentials: null, - })); - onLoginSuccess(); - } catch (error) { - if ( - error instanceof LoginError && - error.message.includes("JIIT Web Portal server is temporarily unavailable") - ) { - console.error("JIIT Web Portal server is temporarily unavailable"); - toast.error("JIIT Web Portal server is temporarily unavailable. Please try again later."); - } else if (error instanceof LoginError && error.message.includes("Failed to fetch")) { - toast.error("Please check your internet connection. If connected, JIIT Web Portal server is unavailable."); - } else { - console.error("Login failed:", error); - toast.error("Login failed. Please check your credentials."); - } - setLoginStatus((prev) => ({ - ...prev, - isLoading: false, - credentials: null, - })); - } - }; + console.log("Login successful"); + setLoginStatus((prev) => ({ + ...prev, + isLoading: false, + credentials: null, + })); + onLoginSuccess(); + } catch (error) { + if ( + error instanceof LoginError && + error.message.includes( + "JIIT Web Portal server is temporarily unavailable" + ) + ) { + console.error("JIIT Web Portal server is temporarily unavailable"); + toast.error( + "JIIT Web Portal server is temporarily unavailable. Please try again later." + ); + } else if ( + error instanceof LoginError && + error.message.includes("Failed to fetch") + ) { + toast.error( + "Please check your internet connection. If connected, JIIT Web Portal server is unavailable." + ); + } else { + console.error("Login failed:", error); + toast.error("Login failed. Please check your credentials."); + } + setLoginStatus((prev) => ({ + ...prev, + isLoading: false, + credentials: null, + })); + } + }; - setLoginStatus((prev) => ({ ...prev, isLoading: true })); - performLogin(); - }, [loginStatus.credentials, onLoginSuccess, w]); + setLoginStatus((prev) => ({ ...prev, isLoading: true })); + performLogin(); + }, [loginStatus.credentials, onLoginSuccess, w]); - // Clean form submission - function onSubmit(values) { - setLoginStatus((prev) => ({ - ...prev, - credentials: values, - })); - } + // Clean form submission + function onSubmit(values) { + setLoginStatus((prev) => ({ + ...prev, + credentials: values, + })); + } - return ( -
- {/* Header with theme toggle */} -
- -
+ return ( +
+ {/* Header with theme toggle */} +
+ +
- - - Login - Enter your credentials to sign in - - -
- - ( - - Enrollment Number - - - - - - )} - /> - ( - - Password - -
- - -
-
- -
- )} - /> - -
-
- -
-
- Or -
-
- - - -
-
-
- ); + + + Login + Enter your credentials to sign in + + +
+ + ( + + Enrollment Number + + + + + + )} + /> + ( + + Password + +
+ + +
+
+ +
+ )} + /> + +
+
+ +
+
+ Or +
+
+ + + +
+
+
+ ); } From 2b555522be3c759bfa598231b282364caf53b241 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 21:27:16 +0530 Subject: [PATCH 02/13] added componet from the testing branch --- jportal/src/components/Fees.jsx | 476 ++++++++++++++++++++++++++++++++ 1 file changed, 476 insertions(+) create mode 100644 jportal/src/components/Fees.jsx diff --git a/jportal/src/components/Fees.jsx b/jportal/src/components/Fees.jsx new file mode 100644 index 0000000..37605ad --- /dev/null +++ b/jportal/src/components/Fees.jsx @@ -0,0 +1,476 @@ +import React, { useState, useEffect } from "react"; +import Loader from "./Loader"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"; +import { useSwipeable } from "react-swipeable"; +import TopTabsBar from "./ui/TopTabsBar"; +import { useTheme } from "./ThemeProvider"; +import { AlertCircle, CheckCircle, DollarSign, FileText, TrendingUp } from "lucide-react"; + +export default function Fees({ w, feesData, setFeesData, guest = false }) { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [activeTab, setActiveTab] = useState("fines"); + + const { theme, customThemes, selectedCustomTheme, useCardBackgrounds } = + useTheme(); + + const accentSeparator = + theme === "custom" + ? customThemes[selectedCustomTheme]?.colors["--accent-color"] || "#7ec3f0" + : theme === "white" + ? "#3182ce" + : theme === "cream" + ? "#A47551" + : theme === "amoled" + ? "#00bcd4" + : "#7ec3f0"; + + const separatorStyle = !useCardBackgrounds + ? { + borderBottom: `1px solid ${accentSeparator}66`, + margin: "2px 0", + minHeight: 0, + height: 0, + } + : {}; + + // Tab order for swiping + const tabOrder = ["fines", "summary"]; + + // Swipe handlers for tab navigation + const swipeHandlers = useSwipeable({ + onSwipedLeft: () => { + const currentIndex = tabOrder.indexOf(activeTab); + if (currentIndex < tabOrder.length - 1) { + setActiveTab(tabOrder[currentIndex + 1]); + } + }, + onSwipedRight: () => { + const currentIndex = tabOrder.indexOf(activeTab); + if (currentIndex > 0) { + setActiveTab(tabOrder[currentIndex - 1]); + } + }, + preventDefaultTouchmoveEvent: true, + trackMouse: true, + delta: 50, + swipeDuration: 500, + }); + + useEffect(() => { + const fetchFeesData = async () => { + // Return early if data is already cached + if (feesData) { + setLoading(false); + return; + } + + setLoading(true); + setError(null); + try { + const [finesData, summaryData] = await Promise.all([ + w.get_fines_msc_charges().catch((err) => { + // If the API returns "NO APPROVED REQUEST FOUND", treat it as empty array + if (err.message?.includes("NO APPROVED REQUEST FOUND")) { + return []; + } + throw err; + }), + w.get_fee_summary(), + ]); + + setFeesData({ + fines: Array.isArray(finesData) ? finesData : [], + summary: summaryData || {}, + }); + + // Debug logs + try { + console.groupCollapsed("Fees: API responses"); + console.log("finesData:", Array.isArray(finesData) ? finesData : []); + console.log("summaryData (raw):", summaryData); + console.groupEnd(); + } catch (e) { + console.warn("Failed to log fee data for debug", e); + } + } catch (error) { + console.error("Failed to fetch fees data:", error); + setError(error.message || "Failed to load fees data"); + } finally { + setLoading(false); + } + }; + + fetchFeesData(); + }, [w, feesData, setFeesData]); + + if (loading) { + return ; + } + + if (error) { + return ( +
+
+ +

{error}

+
+
+ ); + } + + const finesArray = feesData?.fines || []; + const summaryData = feesData?.summary || {}; + const feeHeads = summaryData.feeHeads || []; + const studentInfo = summaryData.studentInfo?.[0] || {}; + const advanceAmount = summaryData.advanceamount?.[0]?.amount || 0; + + // Helper function to format currency + const formatCurrency = (amount) => { + if (amount === null || amount === undefined) return "N/A"; + const num = parseFloat(amount); + if (isNaN(num)) return "N/A"; + return `₹${num.toLocaleString("en-IN", { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}`; + }; + + // Calculate total fines + const totalFines = finesArray.reduce((sum, fine) => { + return sum + (parseFloat(fine.charge) || parseFloat(fine.feeamounttobepaid) || 0); + }, 0); + + // Calculate summary totals from feeHeads + const totalFeeAmount = feeHeads.reduce((sum, head) => sum + (parseFloat(head.feeamount) || 0), 0); + const totalReceived = feeHeads.reduce((sum, head) => sum + (parseFloat(head.receiveamount) || 0), 0); + const totalDue = feeHeads.reduce((sum, head) => sum + (parseFloat(head.dueamount) || 0), 0); + const totalRefund = feeHeads.reduce((sum, head) => sum + (parseFloat(head.refundamount) || 0), 0); + + return ( +
+ {guest && ( +
+ Guest Demo: Viewing Sample Data +
+ )} + +
+ + {/* Sidebar Tabs for large screens */} +
+ + + + Pending Fines + + + + Fee Summary + + +
+ + {/* Main Content */} +
+ {/* TabsList for mobile only */} +
+ + + Pending Fines + + + Fee Summary + + +
+ + {/* Pending Fines Tab */} + +
+ {finesArray.length > 0 ? ( + <> + {/* Total Fines Summary Card */} +
+
+
+

Total Fines Pending

+

+ {formatCurrency(totalFines)} +

+
+ +
+
+ + {/* Individual Fines */} + {finesArray.map((fine, index) => ( +
+
+
+

+ {fine.servicename || "Miscellaneous Charge"} +

+

+ {fine.remarksbyauthority || "No remarks"} +

+
+ + {formatCurrency(fine.charge || fine.feeamounttobepaid)} + +
+ +
+ {fine.servicecode && ( +
+ Service +

+ {fine.servicecode} +

+
+ )} + {fine.requestno && ( +
+ Request No +

+ {fine.requestno} +

+
+ )} + {fine.quantity && ( +
+ Quantity +

+ {fine.quantity} +

+
+ )} +
+ + {fine.remarksbystudents && ( +
+ + Student Remarks + +

+ {fine.remarksbystudents} +

+
+ )} +
+ ))} + + ) : ( +
+ +

+ No Pending Fines +

+

+ You don't have any pending fines or miscellaneous charges. +

+
+ )} +
+
+ + {/* Fee Summary Tab */} + +
+ {/* Overall Summary Cards */} +
+ {/* Total Fee Card */} +
+
+
+

Total Fee

+

+ {formatCurrency(totalFeeAmount)} +

+
+ +
+
+ + {/* Total Received Card */} +
+
+
+

Total Received

+

+ {formatCurrency(totalReceived)} +

+
+ +
+
+ + {/* Total Due Card */} +
+
+
+

Total Due

+

+ {formatCurrency(totalDue)} +

+
+ +
+
+ + {/* Advance/Refund Card */} + {(advanceAmount > 0 || totalRefund > 0) && ( +
+
+
+

+ {advanceAmount > 0 ? "Advance" : "Refund"} +

+

+ {formatCurrency(advanceAmount > 0 ? advanceAmount : totalRefund)} +

+
+ +
+
+ )} +
+ + {/* Fee Heads by Semester/Event */} + {feeHeads.length > 0 && ( +
+

+ Fee Breakdown by Semester +

+ +
+ {feeHeads.map((head, idx) => { + const academicYear = head.academicyear || "N/A"; + const semester = head.stynumber ? `Semester ${head.stynumber}` : "N/A"; + const feeAmount = parseFloat(head.feeamount) || 0; + const received = parseFloat(head.receiveamount) || 0; + const due = parseFloat(head.dueamount) || 0; + const refund = parseFloat(head.refundamount) || 0; + + return ( +
+
+
+

+ {semester} ({academicYear}) +

+

+ Event: {head.eventid} +

+
+ + {head.stytypedesc || "Regular"} + +
+ +
+
+ Fee Amount +

+ {formatCurrency(feeAmount)} +

+
+
+ Received +

+ {formatCurrency(received)} +

+
+
+ Due +

+ {formatCurrency(due)} +

+
+ {refund > 0 && ( +
+ Refund +

+ {formatCurrency(refund)} +

+
+ )} +
+ + {/* Waiver, Transfer details if present */} + {(parseFloat(head.waiveramount) > 0 || + parseFloat(head.transferinamount) > 0 || + parseFloat(head.transferoutamount) > 0) && ( +
+ {parseFloat(head.waiveramount) > 0 && ( +
+ Waived +

+ {formatCurrency(head.waiveramount)} +

+
+ )} + {parseFloat(head.transferinamount) > 0 && ( +
+ Transfer In +

+ {formatCurrency(head.transferinamount)} +

+
+ )} + {parseFloat(head.transferoutamount) > 0 && ( +
+ Transfer Out +

+ {formatCurrency(head.transferoutamount)} +

+
+ )} +
+ )} +
+ ); + })} +
+
+ )} + +
+
+
+
+
+
+ ); +} From 39039412dd2ffcd82658fbef64f33f768e12a638 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 21:36:28 +0530 Subject: [PATCH 03/13] updated to use the new themeing system --- jportal/src/components/Fees.jsx | 148 +++++++++++++++----------------- 1 file changed, 67 insertions(+), 81 deletions(-) diff --git a/jportal/src/components/Fees.jsx b/jportal/src/components/Fees.jsx index 37605ad..204522b 100644 --- a/jportal/src/components/Fees.jsx +++ b/jportal/src/components/Fees.jsx @@ -3,7 +3,7 @@ import Loader from "./Loader"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"; import { useSwipeable } from "react-swipeable"; import TopTabsBar from "./ui/TopTabsBar"; -import { useTheme } from "./ThemeProvider"; +import { useThemeStore } from "@/stores/theme-store"; import { AlertCircle, CheckCircle, DollarSign, FileText, TrendingUp } from "lucide-react"; export default function Fees({ w, feesData, setFeesData, guest = false }) { @@ -11,28 +11,14 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState("fines"); - const { theme, customThemes, selectedCustomTheme, useCardBackgrounds } = - useTheme(); + // Subscribe to theme store to ensure re-renders on theme changes + const themeState = useThemeStore((state) => state.themeState); - const accentSeparator = - theme === "custom" - ? customThemes[selectedCustomTheme]?.colors["--accent-color"] || "#7ec3f0" - : theme === "white" - ? "#3182ce" - : theme === "cream" - ? "#A47551" - : theme === "amoled" - ? "#00bcd4" - : "#7ec3f0"; - - const separatorStyle = !useCardBackgrounds - ? { - borderBottom: `1px solid ${accentSeparator}66`, - margin: "2px 0", - minHeight: 0, - height: 0, - } - : {}; + // Force re-render when theme changes + const [, setForceUpdate] = useState({}); + useEffect(() => { + setForceUpdate({}); + }, [themeState]); // Tab order for swiping const tabOrder = ["fines", "summary"]; @@ -112,8 +98,8 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { return (
- -

{error}

+ +

{error}

); @@ -150,7 +136,7 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { return (
{guest && ( -
+
Guest Demo: Viewing Sample Data
)} @@ -169,14 +155,14 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { > Pending Fines Fee Summary @@ -197,13 +183,13 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { > Pending Fines Fee Summary @@ -216,11 +202,11 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { {finesArray.length > 0 ? ( <> {/* Total Fines Summary Card */} -
+
-

Total Fines Pending

-

+

Total Fines Pending

+

{formatCurrency(totalFines)}

@@ -232,14 +218,14 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { {finesArray.map((fine, index) => (
-

+

{fine.servicename || "Miscellaneous Charge"}

-

+

{fine.remarksbyauthority || "No remarks"}

@@ -251,24 +237,24 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) {
{fine.servicecode && (
- Service -

+ Service +

{fine.servicecode}

)} {fine.requestno && (
- Request No -

+ Request No +

{fine.requestno}

)} {fine.quantity && (
- Quantity -

+ Quantity +

{fine.quantity}

@@ -276,11 +262,11 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) {
{fine.remarksbystudents && ( -
- +
+ Student Remarks -

+

{fine.remarksbystudents}

@@ -289,12 +275,12 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { ))} ) : ( -
- -

+
+ +

No Pending Fines

-

+

You don't have any pending fines or miscellaneous charges.

@@ -308,11 +294,11 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { {/* Overall Summary Cards */}
{/* Total Fee Card */} -
+
-

Total Fee

-

+

Total Fee

+

{formatCurrency(totalFeeAmount)}

@@ -321,37 +307,37 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) {
{/* Total Received Card */} -
+
-

Total Received

-

+

Total Received

+

{formatCurrency(totalReceived)}

- +
{/* Total Due Card */} -
+
-

Total Due

-

+

Total Due

+

{formatCurrency(totalDue)}

- +
{/* Advance/Refund Card */} {(advanceAmount > 0 || totalRefund > 0) && ( -
+
-

+

{advanceAmount > 0 ? "Advance" : "Refund"}

@@ -366,8 +352,8 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { {/* Fee Heads by Semester/Event */} {feeHeads.length > 0 && ( -

-

+
+

Fee Breakdown by Semester

@@ -383,44 +369,44 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { return (
-

+

{semester} ({academicYear})

-

+

Event: {head.eventid}

- + {head.stytypedesc || "Regular"}
- Fee Amount -

+ Fee Amount +

{formatCurrency(feeAmount)}

- Received -

+ Received +

{formatCurrency(received)}

- Due -

+ Due +

{formatCurrency(due)}

{refund > 0 && (
- Refund + Refund

{formatCurrency(refund)}

@@ -432,27 +418,27 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { {(parseFloat(head.waiveramount) > 0 || parseFloat(head.transferinamount) > 0 || parseFloat(head.transferoutamount) > 0) && ( -
+
{parseFloat(head.waiveramount) > 0 && (
- Waived -

+ Waived +

{formatCurrency(head.waiveramount)}

)} {parseFloat(head.transferinamount) > 0 && (
- Transfer In -

+ Transfer In +

{formatCurrency(head.transferinamount)}

)} {parseFloat(head.transferoutamount) > 0 && (
- Transfer Out -

+ Transfer Out +

{formatCurrency(head.transferoutamount)}

From 2459a9c74bf95a5b6545ef6dbcaa1407648f768e Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 21:38:41 +0530 Subject: [PATCH 04/13] removed loader --- jportal/src/components/Fees.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jportal/src/components/Fees.jsx b/jportal/src/components/Fees.jsx index 204522b..e0db380 100644 --- a/jportal/src/components/Fees.jsx +++ b/jportal/src/components/Fees.jsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from "react"; -import Loader from "./Loader"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"; import { useSwipeable } from "react-swipeable"; import TopTabsBar from "./ui/TopTabsBar"; @@ -91,7 +90,7 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { }, [w, feesData, setFeesData]); if (loading) { - return ; + return

Loading fees data...

; } if (error) { From dc479a003013c90516887a094b2bdbc4aa4a344e Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 21:40:42 +0530 Subject: [PATCH 05/13] removed react-swipeables --- jportal/src/components/Fees.jsx | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/jportal/src/components/Fees.jsx b/jportal/src/components/Fees.jsx index e0db380..8985977 100644 --- a/jportal/src/components/Fees.jsx +++ b/jportal/src/components/Fees.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from "react"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"; -import { useSwipeable } from "react-swipeable"; import TopTabsBar from "./ui/TopTabsBar"; import { useThemeStore } from "@/stores/theme-store"; import { AlertCircle, CheckCircle, DollarSign, FileText, TrendingUp } from "lucide-react"; @@ -18,29 +17,7 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) { useEffect(() => { setForceUpdate({}); }, [themeState]); - - // Tab order for swiping - const tabOrder = ["fines", "summary"]; - - // Swipe handlers for tab navigation - const swipeHandlers = useSwipeable({ - onSwipedLeft: () => { - const currentIndex = tabOrder.indexOf(activeTab); - if (currentIndex < tabOrder.length - 1) { - setActiveTab(tabOrder[currentIndex + 1]); - } - }, - onSwipedRight: () => { - const currentIndex = tabOrder.indexOf(activeTab); - if (currentIndex > 0) { - setActiveTab(tabOrder[currentIndex - 1]); - } - }, - preventDefaultTouchmoveEvent: true, - trackMouse: true, - delta: 50, - swipeDuration: 500, - }); + // Theme re-render helper (swipe removed) useEffect(() => { const fetchFeesData = async () => { @@ -170,10 +147,7 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) {
{/* Main Content */} -
+
{/* TabsList for mobile only */}
Date: Sun, 9 Nov 2025 21:45:06 +0530 Subject: [PATCH 06/13] added consistent tabs --- jportal/src/components/Fees.jsx | 73 +++++++++------------------------ 1 file changed, 20 insertions(+), 53 deletions(-) diff --git a/jportal/src/components/Fees.jsx b/jportal/src/components/Fees.jsx index 8985977..9b29790 100644 --- a/jportal/src/components/Fees.jsx +++ b/jportal/src/components/Fees.jsx @@ -1,8 +1,7 @@ import React, { useState, useEffect } from "react"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"; -import TopTabsBar from "./ui/TopTabsBar"; import { useThemeStore } from "@/stores/theme-store"; -import { AlertCircle, CheckCircle, DollarSign, FileText, TrendingUp } from "lucide-react"; +import { AlertCircle, CheckCircle, DollarSign, TrendingUp } from "lucide-react"; export default function Fees({ w, feesData, setFeesData, guest = false }) { const [loading, setLoading] = useState(true); @@ -117,60 +116,29 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) {
)} -
+
- {/* Sidebar Tabs for large screens */} -
- + - - - Pending Fines - - - - Fee Summary - - -
- - {/* Main Content */} -
- {/* TabsList for mobile only */} -
- - - Pending Fines - - - Fee Summary - - -
+ Pending Fines + + + Fee Summary + + - {/* Pending Fines Tab */} - + {/* Pending Fines Tab */} +
{finesArray.length > 0 ? ( <> @@ -261,8 +229,8 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) {
- {/* Fee Summary Tab */} - + {/* Fee Summary Tab */} +
{/* Overall Summary Cards */}
@@ -427,7 +395,6 @@ export default function Fees({ w, feesData, setFeesData, guest = false }) {
-
From dcee33312ecef5434dd951e88d20e91d60f7f87b Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 21:45:13 +0530 Subject: [PATCH 07/13] added a fees route --- jportal/src/App.jsx | 843 +++++++++++++++++++++++--------------------- 1 file changed, 441 insertions(+), 402 deletions(-) diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx index 3ecb6e4..1e02619 100644 --- a/jportal/src/App.jsx +++ b/jportal/src/App.jsx @@ -1,5 +1,11 @@ import { useState, useEffect, useRef } from "react"; -import { HashRouter as Router, Routes, Route, Navigate, useNavigate } from "react-router-dom"; +import { + HashRouter as Router, + Routes, + Route, + Navigate, + useNavigate, +} from "react-router-dom"; import Header from "./components/Header"; import Navbar from "./components/Navbar"; import Login from "./components/Login"; @@ -8,13 +14,17 @@ import Grades from "./components/Grades"; import Exams from "./components/Exams"; import Subjects from "./components/Subjects"; import Profile from "./components/Profile"; +import Fees from "./components/Fees"; import Cloudflare from "@/components/Cloudflare"; import { ThemeProvider } from "./components/theme-provider"; import { ThemeScript } from "./components/theme-script"; import { DynamicFontLoader } from "./components/DynamicFontLoader"; import { Toaster } from "./components/ui/sonner"; import "./App.css"; -import { WebPortal, LoginError } from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.23/dist/jsjiit.esm.js"; +import { + WebPortal, + LoginError, +} from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.23/dist/jsjiit.esm.js"; import MockWebPortal from "./components/MockWebPortal"; import { TriangleAlert } from "lucide-react"; @@ -29,412 +39,441 @@ const mockPortal = new MockWebPortal(); // Create a wrapper component to use the useNavigate hook function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) { - const [activeAttendanceTab, setActiveAttendanceTab] = useState("overview"); - const [attendanceData, setAttendanceData] = useState({}); - const [attendanceSemestersData, setAttendanceSemestersData] = useState(null); - - const [subjectData, setSubjectData] = useState({}); - const [subjectSemestersData, setSubjectSemestersData] = useState(null); - const [subjectChoices, setSubjectChoices] = useState({}); - const [activeSubjectsTab, setActiveSubjectsTab] = useState("registered"); - const [subjectsLoading, setSubjectsLoading] = useState(true); - const [subjectsDataLoading, setSubjectsDataLoading] = useState(true); - const [subjectChoicesLoading, setSubjectChoicesLoading] = useState(false); - - const [gradesData, setGradesData] = useState({}); - const [gradesSemesterData, setGradesSemesterData] = useState(null); - - const [selectedAttendanceSem, setSelectedAttendanceSem] = useState(null); - const [selectedGradesSem, setSelectedGradesSem] = useState(null); - const [selectedSubjectsSem, setSelectedSubjectsSem] = useState(null); - - const [attendanceDailyDate, setAttendanceDailyDate] = useState(new Date()); - const [isAttendanceCalendarOpen, setIsAttendanceCalendarOpen] = useState(false); - const [isAttendanceTrackerOpen, setIsAttendanceTrackerOpen] = useState(false); - const [attendanceSubjectCacheStatus, setAttendanceSubjectCacheStatus] = useState({}); - - // Add attendance goal state - const [attendanceGoal, setAttendanceGoal] = useState(() => { - const savedGoal = localStorage.getItem("attendanceGoal"); - return savedGoal ? parseInt(savedGoal) : 75; // Default to 75% if not set - }); - - // Add effect to save goal to localStorage when it changes - useEffect(() => { - localStorage.setItem("attendanceGoal", attendanceGoal.toString()); - }, [attendanceGoal]); - - // Add new profile data state - const [profileData, setProfileData] = useState(null); - - // Add new state for grades component - const [activeGradesTab, setActiveGradesTab] = useState("overview"); - const [gradeCardSemesters, setGradeCardSemesters] = useState([]); - const [selectedGradeCardSem, setSelectedGradeCardSem] = useState(null); - const [gradeCard, setGradeCard] = useState(null); - - // Add new state for storing grade cards - const [gradeCards, setGradeCards] = useState({}); - - // Add new states for subject attendance - const [subjectAttendanceData, setSubjectAttendanceData] = useState({}); - const [selectedSubject, setSelectedSubject] = useState(null); - - // Add new state for exams - const [examSchedule, setExamSchedule] = useState({}); - const [examSemesters, setExamSemesters] = useState([]); - const [selectedExamSem, setSelectedExamSem] = useState(null); - const [selectedExamEvent, setSelectedExamEvent] = useState(null); - - // Add new state for marks - const [marksSemesters, setMarksSemesters] = useState([]); - const [selectedMarksSem, setSelectedMarksSem] = useState(null); - const [marksSemesterData, setMarksSemesterData] = useState(null); - const [marksData, setMarksData] = useState({}); - - // Add these new states lifted from Grades.jsx - const [gradesLoading, setGradesLoading] = useState(true); - const [gradesError, setGradesError] = useState(null); - const [gradeCardLoading, setGradeCardLoading] = useState(false); - const [isDownloadDialogOpen, setIsDownloadDialogOpen] = useState(false); - const [marksLoading, setMarksLoading] = useState(false); - - // Add these new states lifted from Attendance.jsx - const [isAttendanceMetaLoading, setIsAttendanceMetaLoading] = useState(true); - const [isAttendanceDataLoading, setIsAttendanceDataLoading] = useState(true); - - // Ref to measure header height - const headerRef = useRef(null); - - // Measure header height and set CSS variable - useEffect(() => { - const updateHeaderHeight = () => { - if (headerRef.current) { - const height = headerRef.current.offsetHeight; - document.documentElement.style.setProperty('--header-height', `${height}px`); - } - }; - - // Update on mount and when window resizes - updateHeaderHeight(); - window.addEventListener('resize', updateHeaderHeight); - - return () => window.removeEventListener('resize', updateHeaderHeight); - }, []); - - return ( -
-
-
-
- - } /> - } /> - - } - /> - - } - /> - - } - /> - - } - /> - } /> - - -
- ); + const [activeAttendanceTab, setActiveAttendanceTab] = useState("overview"); + const [attendanceData, setAttendanceData] = useState({}); + const [attendanceSemestersData, setAttendanceSemestersData] = useState(null); + + const [subjectData, setSubjectData] = useState({}); + const [subjectSemestersData, setSubjectSemestersData] = useState(null); + const [subjectChoices, setSubjectChoices] = useState({}); + const [activeSubjectsTab, setActiveSubjectsTab] = useState("registered"); + const [subjectsLoading, setSubjectsLoading] = useState(true); + const [subjectsDataLoading, setSubjectsDataLoading] = useState(true); + const [subjectChoicesLoading, setSubjectChoicesLoading] = useState(false); + + const [gradesData, setGradesData] = useState({}); + const [gradesSemesterData, setGradesSemesterData] = useState(null); + + const [selectedAttendanceSem, setSelectedAttendanceSem] = useState(null); + const [selectedGradesSem, setSelectedGradesSem] = useState(null); + const [selectedSubjectsSem, setSelectedSubjectsSem] = useState(null); + + const [attendanceDailyDate, setAttendanceDailyDate] = useState(new Date()); + const [isAttendanceCalendarOpen, setIsAttendanceCalendarOpen] = + useState(false); + const [isAttendanceTrackerOpen, setIsAttendanceTrackerOpen] = useState(false); + const [attendanceSubjectCacheStatus, setAttendanceSubjectCacheStatus] = + useState({}); + + // Add attendance goal state + const [attendanceGoal, setAttendanceGoal] = useState(() => { + const savedGoal = localStorage.getItem("attendanceGoal"); + return savedGoal ? parseInt(savedGoal) : 75; // Default to 75% if not set + }); + + // Add effect to save goal to localStorage when it changes + useEffect(() => { + localStorage.setItem("attendanceGoal", attendanceGoal.toString()); + }, [attendanceGoal]); + + // Add new profile data state + const [profileData, setProfileData] = useState(null); + + // Add new state for grades component + const [activeGradesTab, setActiveGradesTab] = useState("overview"); + const [gradeCardSemesters, setGradeCardSemesters] = useState([]); + const [selectedGradeCardSem, setSelectedGradeCardSem] = useState(null); + const [gradeCard, setGradeCard] = useState(null); + + // Add new state for storing grade cards + const [gradeCards, setGradeCards] = useState({}); + + // Add new states for subject attendance + const [subjectAttendanceData, setSubjectAttendanceData] = useState({}); + const [selectedSubject, setSelectedSubject] = useState(null); + + // Add new state for exams + const [examSchedule, setExamSchedule] = useState({}); + const [examSemesters, setExamSemesters] = useState([]); + const [selectedExamSem, setSelectedExamSem] = useState(null); + const [selectedExamEvent, setSelectedExamEvent] = useState(null); + + // Add new state for marks + const [marksSemesters, setMarksSemesters] = useState([]); + const [selectedMarksSem, setSelectedMarksSem] = useState(null); + const [marksSemesterData, setMarksSemesterData] = useState(null); + const [marksData, setMarksData] = useState({}); + + // Add these new states lifted from Grades.jsx + const [gradesLoading, setGradesLoading] = useState(true); + const [gradesError, setGradesError] = useState(null); + const [gradeCardLoading, setGradeCardLoading] = useState(false); + const [isDownloadDialogOpen, setIsDownloadDialogOpen] = useState(false); + const [marksLoading, setMarksLoading] = useState(false); + + // Add these new states lifted from Attendance.jsx + const [isAttendanceMetaLoading, setIsAttendanceMetaLoading] = useState(true); + const [isAttendanceDataLoading, setIsAttendanceDataLoading] = useState(true); + + return ( +
+
+
+
+ + } /> + } /> + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + + +
+ ); } function LoginWrapper({ onLoginSuccess, onDemoLogin, w }) { - const navigate = useNavigate(); - - const handleLoginSuccess = () => { - onLoginSuccess(); - // Add a small delay to ensure state updates before navigation - setTimeout(() => { - navigate("/attendance"); - }, 100); - }; - - const handleDemoLogin = () => { - onDemoLogin(); - // Add a small delay to ensure state updates before navigation - setTimeout(() => { - navigate("/attendance"); - }, 100); - }; - - return ; + const navigate = useNavigate(); + + const handleLoginSuccess = () => { + onLoginSuccess(); + // Add a small delay to ensure state updates before navigation + setTimeout(() => { + navigate("/attendance"); + }, 100); + }; + + const handleDemoLogin = () => { + onDemoLogin(); + // Add a small delay to ensure state updates before navigation + setTimeout(() => { + navigate("/attendance"); + }, 100); + }; + + return ( + + ); } function App() { - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [isDemoMode, setIsDemoMode] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - // Determine which portal to use based on demo mode - const activePortal = isDemoMode ? mockPortal : realPortal; - - useEffect(() => { - const username = localStorage.getItem("username"); - const password = localStorage.getItem("password"); - - const performLogin = async () => { - try { - if (username && password) { - await realPortal.student_login(username, password); - if (realPortal.session) { - setIsAuthenticated(true); - setIsDemoMode(false); - } - } - } catch (error) { - if ( - error instanceof LoginError && - error.message.includes("JIIT Web Portal server is temporarily unavailable") - ) { - setError("JIIT Web Portal server is temporarily unavailable. Please try again later."); - } else if (error instanceof LoginError && error.message.includes("Failed to fetch")) { - setError( - "Please check your internet connection. If connected, JIIT Web Portal server is temporarily unavailable." - ); - } else { - console.error("Auto-login failed:", error); - setError("Auto-login failed. Please login again."); - } - localStorage.removeItem("username"); - localStorage.removeItem("password"); - setIsAuthenticated(false); - } finally { - setIsLoading(false); - } - }; - - performLogin(); - }, []); - - const handleRealLogin = () => { - setIsAuthenticated(true); - setIsDemoMode(false); - }; - - const handleDemoLogin = () => { - setIsAuthenticated(true); - setIsDemoMode(true); - }; - - // After authentication (including auto-login), fetch and print fee summary and pending fines - useEffect(() => { - if (!isAuthenticated) return; - - const portal = isDemoMode ? mockPortal : realPortal; - - const fetchAndLogPayments = async () => { - try { - const feeSummary = await portal.get_fee_summary(); - console.log("[Portal] Fee summary:", feeSummary); - } catch (err) { - console.error("[Portal] Failed to fetch fee summary:", err); - } - - try { - const fines = await portal.get_fines_msc_charges(); - console.log("[Portal] Pending miscellaneous charges / fines:", fines); - } catch (err) { - // The API may return Failure with message "NO APPROVED REQUEST FOUND" when there are no fines - if (err && err.message && err.message.includes("NO APPROVED REQUEST FOUND")) { - console.info("[Portal] No pending fines found (NO APPROVED REQUEST FOUND)."); - } else { - console.error("[Portal] Failed to fetch pending fines:", err); - } - } - }; - - // Do not block UI - fire and forget - fetchAndLogPayments(); - }, [isAuthenticated, isDemoMode]); - - if (isLoading) { - return ( - <> - - - -
- Signing in... -
-
- - ); - } - - return ( - <> - - - - - , - }} - toastOptions={{ - style: { - background: "var(--popover)", - color: "var(--popover-foreground)", - border: "1px solid var(--border)", - boxShadow: "var(--shadow-lg)", - }, - }} - /> - -
- - {/* Public route - accessible without authentication */} - } /> - - {/* Protected routes - require authentication */} - {!isAuthenticated ? ( - - {error &&
{error}
} - - - } - /> - ) : ( - - } - /> - )} -
-
-
-
-
- - ); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [isDemoMode, setIsDemoMode] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Determine which portal to use based on demo mode + const activePortal = isDemoMode ? mockPortal : realPortal; + + useEffect(() => { + const username = localStorage.getItem("username"); + const password = localStorage.getItem("password"); + + const performLogin = async () => { + try { + if (username && password) { + await realPortal.student_login(username, password); + if (realPortal.session) { + setIsAuthenticated(true); + setIsDemoMode(false); + } + } + } catch (error) { + if ( + error instanceof LoginError && + error.message.includes( + "JIIT Web Portal server is temporarily unavailable" + ) + ) { + setError( + "JIIT Web Portal server is temporarily unavailable. Please try again later." + ); + } else if ( + error instanceof LoginError && + error.message.includes("Failed to fetch") + ) { + setError( + "Please check your internet connection. If connected, JIIT Web Portal server is temporarily unavailable." + ); + } else { + console.error("Auto-login failed:", error); + setError("Auto-login failed. Please login again."); + } + localStorage.removeItem("username"); + localStorage.removeItem("password"); + setIsAuthenticated(false); + } finally { + setIsLoading(false); + } + }; + + performLogin(); + }, []); + + const handleRealLogin = () => { + setIsAuthenticated(true); + setIsDemoMode(false); + }; + + const handleDemoLogin = () => { + setIsAuthenticated(true); + setIsDemoMode(true); + }; + + // After authentication (including auto-login), fetch and print fee summary and pending fines + useEffect(() => { + if (!isAuthenticated) return; + + const portal = isDemoMode ? mockPortal : realPortal; + + const fetchAndLogPayments = async () => { + try { + const feeSummary = await portal.get_fee_summary(); + console.log("[Portal] Fee summary:", feeSummary); + } catch (err) { + console.error("[Portal] Failed to fetch fee summary:", err); + } + + try { + const fines = await portal.get_fines_msc_charges(); + console.log("[Portal] Pending miscellaneous charges / fines:", fines); + } catch (err) { + // The API may return Failure with message "NO APPROVED REQUEST FOUND" when there are no fines + if ( + err && + err.message && + err.message.includes("NO APPROVED REQUEST FOUND") + ) { + console.info( + "[Portal] No pending fines found (NO APPROVED REQUEST FOUND)." + ); + } else { + console.error("[Portal] Failed to fetch pending fines:", err); + } + } + }; + + // Do not block UI - fire and forget + fetchAndLogPayments(); + }, [isAuthenticated, isDemoMode]); + + if (isLoading) { + return ( + <> + + + +
+ Signing in... +
+
+ + ); + } + + return ( + <> + + + + + , + }} + toastOptions={{ + style: { + background: "var(--popover)", + color: "var(--popover-foreground)", + border: "1px solid var(--border)", + boxShadow: "var(--shadow-lg)", + }, + }} + /> + +
+ + {/* Public route - accessible without authentication */} + } /> + + {/* Protected routes - require authentication */} + {!isAuthenticated ? ( + + {error && ( +
+ {error} +
+ )} + + + } + /> + ) : ( + + } + /> + )} +
+
+
+
+
+ + ); } export default App; From a0acb8524215e88936b17405dd4cbfc4d463ea98 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Tue, 18 Nov 2025 21:43:17 +0530 Subject: [PATCH 08/13] updaed 0.23 to avoid conflict --- jportal/src/App.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx index 1e02619..033c8ba 100644 --- a/jportal/src/App.jsx +++ b/jportal/src/App.jsx @@ -21,10 +21,11 @@ import { ThemeScript } from "./components/theme-script"; import { DynamicFontLoader } from "./components/DynamicFontLoader"; import { Toaster } from "./components/ui/sonner"; import "./App.css"; + import { WebPortal, LoginError, -} from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.23/dist/jsjiit.esm.js"; +} from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.22/dist/jsjiit.esm.js"; import MockWebPortal from "./components/MockWebPortal"; import { TriangleAlert } from "lucide-react"; From 4d090302274b49b165fbc62cfb95c446f1a56a7b Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Mon, 22 Dec 2025 15:22:37 +0530 Subject: [PATCH 09/13] basic profile page changes --- jportal/src/App.jsx | 2 +- jportal/src/components/Profile.jsx | 824 +++++++++++++++++++---------- 2 files changed, 544 insertions(+), 282 deletions(-) diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx index 033c8ba..24b96e9 100644 --- a/jportal/src/App.jsx +++ b/jportal/src/App.jsx @@ -25,7 +25,7 @@ import "./App.css"; import { WebPortal, LoginError, -} from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.22/dist/jsjiit.esm.js"; +} from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.23/dist/jsjiit.esm.js"; import MockWebPortal from "./components/MockWebPortal"; import { TriangleAlert } from "lucide-react"; diff --git a/jportal/src/components/Profile.jsx b/jportal/src/components/Profile.jsx index 5aaa136..481578e 100644 --- a/jportal/src/components/Profile.jsx +++ b/jportal/src/components/Profile.jsx @@ -1,289 +1,551 @@ import React, { useState, useEffect } from "react"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { Image, UserRound, GraduationCap, IdCard, Mail, BookOpen, Users, Calendar } from "lucide-react"; - +import { + Image, + UserRound, + GraduationCap, + IdCard, + Mail, + BookOpen, + Users, + Calendar, + CheckCircle, +} from "lucide-react"; export default function Profile({ w, profileData, setProfileData }) { - const [loading, setLoading] = useState(true); - const [activeTab, setActiveTab] = useState("personal"); - const [showProfilePhoto, setShowProfilePhoto] = useState(false); - - useEffect(() => { - const fetchProfileData = async () => { - // Return early if data is already cached - if (profileData) { - setLoading(false); - return; - } - - setLoading(true); - try { - const data = await w.get_personal_info(); - setProfileData(data); - } catch (error) { - console.error("Failed to fetch profile data:", error); - } finally { - setLoading(false); - } - }; - - fetchProfileData(); - }, [w, profileData, setProfileData]); - - if (loading) { - return ( -
- Loading profile... -
- ); - } - - const info = profileData?.generalinformation || {}; - const qualifications = profileData?.qualification || []; - - // Prepare profile/avatar image (mobile header) - const photoData = profileData?.["photo&signature"]?.photo; - const hasProfilePhoto = Boolean(photoData); - const profileImg = hasProfilePhoto - ? `data:image/jpeg;base64,${photoData}` - : null; - - // Helper function to get initials from name - const getInitials = (name) => { - if (!name) return "U"; - const nameParts = name.trim().split(/\s+/); - if (nameParts.length === 1) { - // Single name - take first 2 letters - return nameParts[0].substring(0, 2).toUpperCase(); - } - // Multiple names - take first letter of first and last name - const firstName = nameParts[0]; - const lastName = nameParts[nameParts.length - 1]; - return (firstName.charAt(0) + lastName.charAt(0)).toUpperCase(); - }; - - const initials = getInitials(info.studentname); - - return ( -
- {/* Profile Header Card */} -
- {hasProfilePhoto && ( - - )} - -
- {/* Avatar */} -
- {showProfilePhoto && hasProfilePhoto ? ( - Profile - ) : ( -
- {initials} -
- )} -
- - {/* Info */} -
-

- {info.studentname || "N/A"} -

- -
-
- - {info.programcode || "N/A"} - - {info.registrationno || "N/A"} -
- -
- - {info.studentemailid || "N/A"} -
-
-
-
- - {/* Bottom Row: Semester, Section, Batch */} -
-
- - Sem: - {info.semester || "N/A"} -
- -
- - Sec: - {info.sectioncode || "N/A"} -
- -
- - Batch: - {info.batch || "N/A"} -
-
-
- - {/* Tabs Bar (mobile) */} - - - - Personal - - - Academic - - - Contact - - - Education - - - - {/* Personal Information */} - -
-

Personal Information

-
- - - - - -
-
-
- - {/* Academic Information */} - -
-

Academic Information

-
- - - - - - - - -
-
-
- - {/* Contact + Family + Address */} - -
-

Contact Information

-
- - - - -
-
- -
-

Family Information

-
- - - - - -
-
- -
-

Current Address

-
- - - - - -
-
- -
-

Permanent Address

-
- - - - - -
-
-
- - {/* Educational Qualifications */} - -
-

Educational Qualifications

- {qualifications.map((qual, index) => ( -
- - - - - - - {qual.grade && } -
- ))} -
-
-
- -
- Made with Big 🍆 Energy by{" "} - - Yash Malik - -
-
- ); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState("personal"); + const [showProfilePhoto, setShowProfilePhoto] = useState(false); + + useEffect(() => { + const fetchProfileData = async () => { + // Return early if data is already cached + if (profileData) { + setLoading(false); + return; + } + + setLoading(true); + try { + const data = await w.get_personal_info(); + setProfileData(data); + } catch (error) { + console.error("Failed to fetch profile data:", error); + } finally { + setLoading(false); + } + }; + + fetchProfileData(); + }, [w, profileData, setProfileData]); + + if (loading) { + return ( +
+ Loading profile... +
+ ); + } + + const info = profileData?.generalinformation || {}; + const qualifications = profileData?.qualification || []; + + // Prepare profile/avatar image (mobile header) + const photoData = profileData?.["photo&signature"]?.photo; + const hasProfilePhoto = Boolean(photoData); + const profileImg = hasProfilePhoto + ? `data:image/jpeg;base64,${photoData}` + : null; + + // Helper function to get initials from name + const getInitials = (name) => { + if (!name) return "U"; + const nameParts = name.trim().split(/\s+/); + if (nameParts.length === 1) { + // Single name - take first 2 letters + return nameParts[0].substring(0, 2).toUpperCase(); + } + // Multiple names - take first letter of first and last name + const firstName = nameParts[0]; + const lastName = nameParts[nameParts.length - 1]; + return (firstName.charAt(0) + lastName.charAt(0)).toUpperCase(); + }; + + const initials = getInitials(info.studentname); + + // Fees Helpers + const formatCurrency = (amount) => { + if (amount === null || amount === undefined) return "N/A"; + const num = parseFloat(amount); + if (isNaN(num)) return "N/A"; + return `₹${num.toLocaleString("en-IN", { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}`; + }; + + const finesArray = feesData.fines || []; + const summaryData = feesData.summary || {}; + const feeHeads = summaryData.feeHeads || []; + + const totalFines = finesArray.reduce((sum, fine) => { + return ( + sum + (parseFloat(fine.charge) || parseFloat(fine.feeamounttobepaid) || 0) + ); + }, 0); + + const totalFeeAmount = feeHeads.reduce( + (sum, head) => sum + (parseFloat(head.feeamount) || 0), + 0 + ); + const totalReceived = feeHeads.reduce( + (sum, head) => sum + (parseFloat(head.receiveamount) || 0), + 0 + ); + const totalDue = feeHeads.reduce( + (sum, head) => sum + (parseFloat(head.dueamount) || 0), + 0 + ); + const totalRefund = feeHeads.reduce( + (sum, head) => sum + (parseFloat(head.refundamount) || 0), + 0 + ); + const advanceAmount = summaryData.advanceamount?.[0]?.amount || 0; + + return ( +
+ {/* Profile Header Card */} +
+ {hasProfilePhoto && ( + + )} + +
+ {/* Avatar */} +
+ {showProfilePhoto && hasProfilePhoto ? ( + Profile + ) : ( +
+ {initials} +
+ )} +
+ + {/* Info */} +
+

+ {info.studentname || "N/A"} +

+ +
+
+ + {info.programcode || "N/A"} + + + {info.registrationno || "N/A"} + +
+ +
+ + + {info.studentemailid || "N/A"} + +
+
+
+
+ + {/* Bottom Row: Semester, Section, Batch */} +
+
+ + Sem: + + {info.semester || "N/A"} + +
+ +
+ + Sec: + + {info.sectioncode || "N/A"} + +
+ +
+ + Batch: + + {info.batch || "N/A"} + +
+
+
+ + {/* Tabs Bar (mobile) */} + + + + Personal + + + Academic + + + Contact + + + Education + + + Fines + + + Fees + + + + {/* Personal Information */} + +
+

Personal Information

+
+ + + + + +
+
+
+ + {/* Academic Information */} + +
+

Academic Information

+
+ + + + + + + + +
+
+
+ + {/* Contact + Family + Address */} + +
+

Contact Information

+
+ + + + +
+
+ +
+

Family Information

+
+ + + + + +
+
+ +
+

Current Address

+
+ + + + + +
+
+ +
+

Permanent Address

+
+ + + + + +
+
+
+ + {/* Educational Qualifications */} + +
+

+ Educational Qualifications +

+ {qualifications.map((qual, index) => ( +
+ + + + + + + {qual.grade && } +
+ ))} +
+
+ + {/* Pending Fines */} + + {feesLoading ? ( +
Loading fines data...
+ ) : finesArray.length > 0 ? ( + <> +
+
+ Total Pending + + {formatCurrency(totalFines)} + +
+
+ {finesArray.map((fine, index) => ( +
+

+ {fine.servicename || "Charge"} +

+
+ + + {fine.quantity && ( + + )} + {fine.remarksbystudents && ( + + )} +
+
+ ))} + + ) : ( +
+ +

No Pending Fines

+

+ You are all caught up! +

+
+ )} +
+ + {/* Fees Summary */} + + {feesLoading ? ( +
Loading fees data...
+ ) : ( + <> +
+
+
+ Total Fee +
+
+ {formatCurrency(totalFeeAmount)} +
+
+
+
+ Total Received +
+
+ {formatCurrency(totalReceived)} +
+
+
+
+ Total Due +
+
+ {formatCurrency(totalDue)} +
+
+ {(advanceAmount > 0 || totalRefund > 0) && ( +
+
+ {advanceAmount > 0 ? "Advance" : "Refund"} +
+
+ {formatCurrency( + advanceAmount > 0 ? advanceAmount : totalRefund + )} +
+
+ )} +
+ + {feeHeads.length > 0 && ( +
+

+ Fee Breakdown +

+
+ {feeHeads.map((head, idx) => ( +
+
+
+
+ {head.stynumber + ? `Semester ${head.stynumber}` + : "N/A"} +
+
+ {head.academicyear} +
+
+
+ {head.stytypedesc || "Regular"} +
+
+
+ + + + {parseFloat(head.refundamount) > 0 && ( + + )} +
+
+ ))} +
+
+ )} + + )} +
+
+ +
+ Made with Big 🍆 Energy by{" "} + + Yash Malik + +
+
+ ); } // Helper component for consistent info display function InfoRow({ label, value }) { - return ( -
- {label}: - {value || "N/A"} -
- ); -} \ No newline at end of file + return ( +
+ + {label}: + + + {value || "N/A"} + +
+ ); +} From d9bbc66bad36412ab5305145d32c3dc864f337fe Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Mon, 22 Dec 2025 15:32:23 +0530 Subject: [PATCH 10/13] removed debug logs --- jportal/src/App.jsx | 50 +++++---------------------------------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx index 24b96e9..5d514a5 100644 --- a/jportal/src/App.jsx +++ b/jportal/src/App.jsx @@ -40,6 +40,7 @@ const mockPortal = new MockWebPortal(); // Create a wrapper component to use the useNavigate hook function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) { + const headerRef = useRef(null); const [activeAttendanceTab, setActiveAttendanceTab] = useState("overview"); const [attendanceData, setAttendanceData] = useState({}); const [attendanceSemestersData, setAttendanceSemestersData] = useState(null); @@ -116,6 +117,9 @@ function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) { const [isAttendanceMetaLoading, setIsAttendanceMetaLoading] = useState(true); const [isAttendanceDataLoading, setIsAttendanceDataLoading] = useState(true); + // Add new state for fees + const [feesData, setFeesData] = useState(null); + return (
@@ -253,14 +257,7 @@ function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) { /> - } + element={} /> @@ -360,43 +357,6 @@ function App() { setIsDemoMode(true); }; - // After authentication (including auto-login), fetch and print fee summary and pending fines - useEffect(() => { - if (!isAuthenticated) return; - - const portal = isDemoMode ? mockPortal : realPortal; - - const fetchAndLogPayments = async () => { - try { - const feeSummary = await portal.get_fee_summary(); - console.log("[Portal] Fee summary:", feeSummary); - } catch (err) { - console.error("[Portal] Failed to fetch fee summary:", err); - } - - try { - const fines = await portal.get_fines_msc_charges(); - console.log("[Portal] Pending miscellaneous charges / fines:", fines); - } catch (err) { - // The API may return Failure with message "NO APPROVED REQUEST FOUND" when there are no fines - if ( - err && - err.message && - err.message.includes("NO APPROVED REQUEST FOUND") - ) { - console.info( - "[Portal] No pending fines found (NO APPROVED REQUEST FOUND)." - ); - } else { - console.error("[Portal] Failed to fetch pending fines:", err); - } - } - }; - - // Do not block UI - fire and forget - fetchAndLogPayments(); - }, [isAuthenticated, isDemoMode]); - if (isLoading) { return ( <> From efb0118aa3584c859f0b8b78d8f02ab9d1c2d527 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Mon, 22 Dec 2025 15:33:19 +0530 Subject: [PATCH 11/13] added fines and fees section in profile --- jportal/src/components/Profile.jsx | 43 ++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/jportal/src/components/Profile.jsx b/jportal/src/components/Profile.jsx index 481578e..73bdf40 100644 --- a/jportal/src/components/Profile.jsx +++ b/jportal/src/components/Profile.jsx @@ -17,6 +17,10 @@ export default function Profile({ w, profileData, setProfileData }) { const [activeTab, setActiveTab] = useState("personal"); const [showProfilePhoto, setShowProfilePhoto] = useState(false); + // Fees and Fines state + const [feesData, setFeesData] = useState({ fines: [], summary: {} }); + const [feesLoading, setFeesLoading] = useState(true); + useEffect(() => { const fetchProfileData = async () => { // Return early if data is already cached @@ -39,6 +43,35 @@ export default function Profile({ w, profileData, setProfileData }) { fetchProfileData(); }, [w, profileData, setProfileData]); + // Fetch fees data separately + useEffect(() => { + const fetchFeesData = async () => { + setFeesLoading(true); + try { + const [finesData, summaryData] = await Promise.all([ + w.get_fines_msc_charges().catch((err) => { + if (err.message?.includes("NO APPROVED REQUEST FOUND")) { + return []; + } + throw err; + }), + w.get_fee_summary(), + ]); + + setFeesData({ + fines: Array.isArray(finesData) ? finesData : [], + summary: summaryData || {}, + }); + } catch (error) { + console.error("Failed to fetch fees data for profile:", error); + } finally { + setFeesLoading(false); + } + }; + + fetchFeesData(); + }, [w]); + if (loading) { return (
@@ -374,7 +407,7 @@ export default function Profile({ w, profileData, setProfileData }) {
Loading fines data...
) : finesArray.length > 0 ? ( <> -
+
Total Pending @@ -432,7 +465,7 @@ export default function Profile({ w, profileData, setProfileData }) { ) : ( <>
-
+
Total Fee
@@ -440,7 +473,7 @@ export default function Profile({ w, profileData, setProfileData }) { {formatCurrency(totalFeeAmount)}
-
+
Total Received
@@ -448,7 +481,7 @@ export default function Profile({ w, profileData, setProfileData }) { {formatCurrency(totalReceived)}
-
+
Total Due
@@ -457,7 +490,7 @@ export default function Profile({ w, profileData, setProfileData }) {
{(advanceAmount > 0 || totalRefund > 0) && ( -
+
{advanceAmount > 0 ? "Advance" : "Refund"}
From a75346a3dc2aa2a484ebc6099fbcd5f73bc35c5e Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Mon, 22 Dec 2025 15:39:00 +0530 Subject: [PATCH 12/13] purged seperate fees route --- jportal/src/App.jsx | 5 - jportal/src/components/Fees.jsx | 402 -------------------------------- 2 files changed, 407 deletions(-) delete mode 100644 jportal/src/components/Fees.jsx diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx index 5d514a5..dfb871c 100644 --- a/jportal/src/App.jsx +++ b/jportal/src/App.jsx @@ -14,7 +14,6 @@ import Grades from "./components/Grades"; import Exams from "./components/Exams"; import Subjects from "./components/Subjects"; import Profile from "./components/Profile"; -import Fees from "./components/Fees"; import Cloudflare from "@/components/Cloudflare"; import { ThemeProvider } from "./components/theme-provider"; import { ThemeScript } from "./components/theme-script"; @@ -255,10 +254,6 @@ function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) { /> } /> - } - />
diff --git a/jportal/src/components/Fees.jsx b/jportal/src/components/Fees.jsx deleted file mode 100644 index 9b29790..0000000 --- a/jportal/src/components/Fees.jsx +++ /dev/null @@ -1,402 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"; -import { useThemeStore } from "@/stores/theme-store"; -import { AlertCircle, CheckCircle, DollarSign, TrendingUp } from "lucide-react"; - -export default function Fees({ w, feesData, setFeesData, guest = false }) { - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [activeTab, setActiveTab] = useState("fines"); - - // Subscribe to theme store to ensure re-renders on theme changes - const themeState = useThemeStore((state) => state.themeState); - - // Force re-render when theme changes - const [, setForceUpdate] = useState({}); - useEffect(() => { - setForceUpdate({}); - }, [themeState]); - // Theme re-render helper (swipe removed) - - useEffect(() => { - const fetchFeesData = async () => { - // Return early if data is already cached - if (feesData) { - setLoading(false); - return; - } - - setLoading(true); - setError(null); - try { - const [finesData, summaryData] = await Promise.all([ - w.get_fines_msc_charges().catch((err) => { - // If the API returns "NO APPROVED REQUEST FOUND", treat it as empty array - if (err.message?.includes("NO APPROVED REQUEST FOUND")) { - return []; - } - throw err; - }), - w.get_fee_summary(), - ]); - - setFeesData({ - fines: Array.isArray(finesData) ? finesData : [], - summary: summaryData || {}, - }); - - // Debug logs - try { - console.groupCollapsed("Fees: API responses"); - console.log("finesData:", Array.isArray(finesData) ? finesData : []); - console.log("summaryData (raw):", summaryData); - console.groupEnd(); - } catch (e) { - console.warn("Failed to log fee data for debug", e); - } - } catch (error) { - console.error("Failed to fetch fees data:", error); - setError(error.message || "Failed to load fees data"); - } finally { - setLoading(false); - } - }; - - fetchFeesData(); - }, [w, feesData, setFeesData]); - - if (loading) { - return

Loading fees data...

; - } - - if (error) { - return ( -
-
- -

{error}

-
-
- ); - } - - const finesArray = feesData?.fines || []; - const summaryData = feesData?.summary || {}; - const feeHeads = summaryData.feeHeads || []; - const studentInfo = summaryData.studentInfo?.[0] || {}; - const advanceAmount = summaryData.advanceamount?.[0]?.amount || 0; - - // Helper function to format currency - const formatCurrency = (amount) => { - if (amount === null || amount === undefined) return "N/A"; - const num = parseFloat(amount); - if (isNaN(num)) return "N/A"; - return `₹${num.toLocaleString("en-IN", { - minimumFractionDigits: 0, - maximumFractionDigits: 2, - })}`; - }; - - // Calculate total fines - const totalFines = finesArray.reduce((sum, fine) => { - return sum + (parseFloat(fine.charge) || parseFloat(fine.feeamounttobepaid) || 0); - }, 0); - - // Calculate summary totals from feeHeads - const totalFeeAmount = feeHeads.reduce((sum, head) => sum + (parseFloat(head.feeamount) || 0), 0); - const totalReceived = feeHeads.reduce((sum, head) => sum + (parseFloat(head.receiveamount) || 0), 0); - const totalDue = feeHeads.reduce((sum, head) => sum + (parseFloat(head.dueamount) || 0), 0); - const totalRefund = feeHeads.reduce((sum, head) => sum + (parseFloat(head.refundamount) || 0), 0); - - return ( -
- {guest && ( -
- Guest Demo: Viewing Sample Data -
- )} - -
- - - - Pending Fines - - - Fee Summary - - - - {/* Pending Fines Tab */} - -
- {finesArray.length > 0 ? ( - <> - {/* Total Fines Summary Card */} -
-
-
-

Total Fines Pending

-

- {formatCurrency(totalFines)} -

-
- -
-
- - {/* Individual Fines */} - {finesArray.map((fine, index) => ( -
-
-
-

- {fine.servicename || "Miscellaneous Charge"} -

-

- {fine.remarksbyauthority || "No remarks"} -

-
- - {formatCurrency(fine.charge || fine.feeamounttobepaid)} - -
- -
- {fine.servicecode && ( -
- Service -

- {fine.servicecode} -

-
- )} - {fine.requestno && ( -
- Request No -

- {fine.requestno} -

-
- )} - {fine.quantity && ( -
- Quantity -

- {fine.quantity} -

-
- )} -
- - {fine.remarksbystudents && ( -
- - Student Remarks - -

- {fine.remarksbystudents} -

-
- )} -
- ))} - - ) : ( -
- -

- No Pending Fines -

-

- You don't have any pending fines or miscellaneous charges. -

-
- )} -
-
- - {/* Fee Summary Tab */} - -
- {/* Overall Summary Cards */} -
- {/* Total Fee Card */} -
-
-
-

Total Fee

-

- {formatCurrency(totalFeeAmount)} -

-
- -
-
- - {/* Total Received Card */} -
-
-
-

Total Received

-

- {formatCurrency(totalReceived)} -

-
- -
-
- - {/* Total Due Card */} -
-
-
-

Total Due

-

- {formatCurrency(totalDue)} -

-
- -
-
- - {/* Advance/Refund Card */} - {(advanceAmount > 0 || totalRefund > 0) && ( -
-
-
-

- {advanceAmount > 0 ? "Advance" : "Refund"} -

-

- {formatCurrency(advanceAmount > 0 ? advanceAmount : totalRefund)} -

-
- -
-
- )} -
- - {/* Fee Heads by Semester/Event */} - {feeHeads.length > 0 && ( -
-

- Fee Breakdown by Semester -

- -
- {feeHeads.map((head, idx) => { - const academicYear = head.academicyear || "N/A"; - const semester = head.stynumber ? `Semester ${head.stynumber}` : "N/A"; - const feeAmount = parseFloat(head.feeamount) || 0; - const received = parseFloat(head.receiveamount) || 0; - const due = parseFloat(head.dueamount) || 0; - const refund = parseFloat(head.refundamount) || 0; - - return ( -
-
-
-

- {semester} ({academicYear}) -

-

- Event: {head.eventid} -

-
- - {head.stytypedesc || "Regular"} - -
- -
-
- Fee Amount -

- {formatCurrency(feeAmount)} -

-
-
- Received -

- {formatCurrency(received)} -

-
-
- Due -

- {formatCurrency(due)} -

-
- {refund > 0 && ( -
- Refund -

- {formatCurrency(refund)} -

-
- )} -
- - {/* Waiver, Transfer details if present */} - {(parseFloat(head.waiveramount) > 0 || - parseFloat(head.transferinamount) > 0 || - parseFloat(head.transferoutamount) > 0) && ( -
- {parseFloat(head.waiveramount) > 0 && ( -
- Waived -

- {formatCurrency(head.waiveramount)} -

-
- )} - {parseFloat(head.transferinamount) > 0 && ( -
- Transfer In -

- {formatCurrency(head.transferinamount)} -

-
- )} - {parseFloat(head.transferoutamount) > 0 && ( -
- Transfer Out -

- {formatCurrency(head.transferoutamount)} -

-
- )} -
- )} -
- ); - })} -
-
- )} - -
-
-
-
-
- ); -} From 32c6a28581ae40bec3f51750965e1f96788c948c Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Mon, 22 Dec 2025 16:04:01 +0530 Subject: [PATCH 13/13] formating fix --- jportal/src/App.jsx | 784 ++++++++++++++++++++++---------------------- 1 file changed, 392 insertions(+), 392 deletions(-) diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx index dfb871c..a532fec 100644 --- a/jportal/src/App.jsx +++ b/jportal/src/App.jsx @@ -1,10 +1,10 @@ import { useState, useEffect, useRef } from "react"; import { - HashRouter as Router, - Routes, - Route, - Navigate, - useNavigate, + HashRouter as Router, + Routes, + Route, + Navigate, + useNavigate, } from "react-router-dom"; import Header from "./components/Header"; import Navbar from "./components/Navbar"; @@ -22,8 +22,8 @@ import { Toaster } from "./components/ui/sonner"; import "./App.css"; import { - WebPortal, - LoginError, + WebPortal, + LoginError, } from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.23/dist/jsjiit.esm.js"; import MockWebPortal from "./components/MockWebPortal"; @@ -39,397 +39,397 @@ const mockPortal = new MockWebPortal(); // Create a wrapper component to use the useNavigate hook function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) { - const headerRef = useRef(null); - const [activeAttendanceTab, setActiveAttendanceTab] = useState("overview"); - const [attendanceData, setAttendanceData] = useState({}); - const [attendanceSemestersData, setAttendanceSemestersData] = useState(null); - - const [subjectData, setSubjectData] = useState({}); - const [subjectSemestersData, setSubjectSemestersData] = useState(null); - const [subjectChoices, setSubjectChoices] = useState({}); - const [activeSubjectsTab, setActiveSubjectsTab] = useState("registered"); - const [subjectsLoading, setSubjectsLoading] = useState(true); - const [subjectsDataLoading, setSubjectsDataLoading] = useState(true); - const [subjectChoicesLoading, setSubjectChoicesLoading] = useState(false); - - const [gradesData, setGradesData] = useState({}); - const [gradesSemesterData, setGradesSemesterData] = useState(null); - - const [selectedAttendanceSem, setSelectedAttendanceSem] = useState(null); - const [selectedGradesSem, setSelectedGradesSem] = useState(null); - const [selectedSubjectsSem, setSelectedSubjectsSem] = useState(null); - - const [attendanceDailyDate, setAttendanceDailyDate] = useState(new Date()); - const [isAttendanceCalendarOpen, setIsAttendanceCalendarOpen] = - useState(false); - const [isAttendanceTrackerOpen, setIsAttendanceTrackerOpen] = useState(false); - const [attendanceSubjectCacheStatus, setAttendanceSubjectCacheStatus] = - useState({}); - - // Add attendance goal state - const [attendanceGoal, setAttendanceGoal] = useState(() => { - const savedGoal = localStorage.getItem("attendanceGoal"); - return savedGoal ? parseInt(savedGoal) : 75; // Default to 75% if not set - }); - - // Add effect to save goal to localStorage when it changes - useEffect(() => { - localStorage.setItem("attendanceGoal", attendanceGoal.toString()); - }, [attendanceGoal]); - - // Add new profile data state - const [profileData, setProfileData] = useState(null); - - // Add new state for grades component - const [activeGradesTab, setActiveGradesTab] = useState("overview"); - const [gradeCardSemesters, setGradeCardSemesters] = useState([]); - const [selectedGradeCardSem, setSelectedGradeCardSem] = useState(null); - const [gradeCard, setGradeCard] = useState(null); - - // Add new state for storing grade cards - const [gradeCards, setGradeCards] = useState({}); - - // Add new states for subject attendance - const [subjectAttendanceData, setSubjectAttendanceData] = useState({}); - const [selectedSubject, setSelectedSubject] = useState(null); - - // Add new state for exams - const [examSchedule, setExamSchedule] = useState({}); - const [examSemesters, setExamSemesters] = useState([]); - const [selectedExamSem, setSelectedExamSem] = useState(null); - const [selectedExamEvent, setSelectedExamEvent] = useState(null); - - // Add new state for marks - const [marksSemesters, setMarksSemesters] = useState([]); - const [selectedMarksSem, setSelectedMarksSem] = useState(null); - const [marksSemesterData, setMarksSemesterData] = useState(null); - const [marksData, setMarksData] = useState({}); - - // Add these new states lifted from Grades.jsx - const [gradesLoading, setGradesLoading] = useState(true); - const [gradesError, setGradesError] = useState(null); - const [gradeCardLoading, setGradeCardLoading] = useState(false); - const [isDownloadDialogOpen, setIsDownloadDialogOpen] = useState(false); - const [marksLoading, setMarksLoading] = useState(false); - - // Add these new states lifted from Attendance.jsx - const [isAttendanceMetaLoading, setIsAttendanceMetaLoading] = useState(true); - const [isAttendanceDataLoading, setIsAttendanceDataLoading] = useState(true); - - // Add new state for fees - const [feesData, setFeesData] = useState(null); - - return ( -
-
-
-
- - } /> - } /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - -
- ); + const headerRef = useRef(null); + const [activeAttendanceTab, setActiveAttendanceTab] = useState("overview"); + const [attendanceData, setAttendanceData] = useState({}); + const [attendanceSemestersData, setAttendanceSemestersData] = useState(null); + + const [subjectData, setSubjectData] = useState({}); + const [subjectSemestersData, setSubjectSemestersData] = useState(null); + const [subjectChoices, setSubjectChoices] = useState({}); + const [activeSubjectsTab, setActiveSubjectsTab] = useState("registered"); + const [subjectsLoading, setSubjectsLoading] = useState(true); + const [subjectsDataLoading, setSubjectsDataLoading] = useState(true); + const [subjectChoicesLoading, setSubjectChoicesLoading] = useState(false); + + const [gradesData, setGradesData] = useState({}); + const [gradesSemesterData, setGradesSemesterData] = useState(null); + + const [selectedAttendanceSem, setSelectedAttendanceSem] = useState(null); + const [selectedGradesSem, setSelectedGradesSem] = useState(null); + const [selectedSubjectsSem, setSelectedSubjectsSem] = useState(null); + + const [attendanceDailyDate, setAttendanceDailyDate] = useState(new Date()); + const [isAttendanceCalendarOpen, setIsAttendanceCalendarOpen] = + useState(false); + const [isAttendanceTrackerOpen, setIsAttendanceTrackerOpen] = useState(false); + const [attendanceSubjectCacheStatus, setAttendanceSubjectCacheStatus] = + useState({}); + + // Add attendance goal state + const [attendanceGoal, setAttendanceGoal] = useState(() => { + const savedGoal = localStorage.getItem("attendanceGoal"); + return savedGoal ? parseInt(savedGoal) : 75; // Default to 75% if not set + }); + + // Add effect to save goal to localStorage when it changes + useEffect(() => { + localStorage.setItem("attendanceGoal", attendanceGoal.toString()); + }, [attendanceGoal]); + + // Add new profile data state + const [profileData, setProfileData] = useState(null); + + // Add new state for grades component + const [activeGradesTab, setActiveGradesTab] = useState("overview"); + const [gradeCardSemesters, setGradeCardSemesters] = useState([]); + const [selectedGradeCardSem, setSelectedGradeCardSem] = useState(null); + const [gradeCard, setGradeCard] = useState(null); + + // Add new state for storing grade cards + const [gradeCards, setGradeCards] = useState({}); + + // Add new states for subject attendance + const [subjectAttendanceData, setSubjectAttendanceData] = useState({}); + const [selectedSubject, setSelectedSubject] = useState(null); + + // Add new state for exams + const [examSchedule, setExamSchedule] = useState({}); + const [examSemesters, setExamSemesters] = useState([]); + const [selectedExamSem, setSelectedExamSem] = useState(null); + const [selectedExamEvent, setSelectedExamEvent] = useState(null); + + // Add new state for marks + const [marksSemesters, setMarksSemesters] = useState([]); + const [selectedMarksSem, setSelectedMarksSem] = useState(null); + const [marksSemesterData, setMarksSemesterData] = useState(null); + const [marksData, setMarksData] = useState({}); + + // Add these new states lifted from Grades.jsx + const [gradesLoading, setGradesLoading] = useState(true); + const [gradesError, setGradesError] = useState(null); + const [gradeCardLoading, setGradeCardLoading] = useState(false); + const [isDownloadDialogOpen, setIsDownloadDialogOpen] = useState(false); + const [marksLoading, setMarksLoading] = useState(false); + + // Add these new states lifted from Attendance.jsx + const [isAttendanceMetaLoading, setIsAttendanceMetaLoading] = useState(true); + const [isAttendanceDataLoading, setIsAttendanceDataLoading] = useState(true); + + // Add new state for fees + const [feesData, setFeesData] = useState(null); + + return ( +
+
+
+
+ + } /> + } /> + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + + +
+ ); } function LoginWrapper({ onLoginSuccess, onDemoLogin, w }) { - const navigate = useNavigate(); - - const handleLoginSuccess = () => { - onLoginSuccess(); - // Add a small delay to ensure state updates before navigation - setTimeout(() => { - navigate("/attendance"); - }, 100); - }; - - const handleDemoLogin = () => { - onDemoLogin(); - // Add a small delay to ensure state updates before navigation - setTimeout(() => { - navigate("/attendance"); - }, 100); - }; - - return ( - - ); + const navigate = useNavigate(); + + const handleLoginSuccess = () => { + onLoginSuccess(); + // Add a small delay to ensure state updates before navigation + setTimeout(() => { + navigate("/attendance"); + }, 100); + }; + + const handleDemoLogin = () => { + onDemoLogin(); + // Add a small delay to ensure state updates before navigation + setTimeout(() => { + navigate("/attendance"); + }, 100); + }; + + return ( + + ); } function App() { - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [isDemoMode, setIsDemoMode] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - // Determine which portal to use based on demo mode - const activePortal = isDemoMode ? mockPortal : realPortal; - - useEffect(() => { - const username = localStorage.getItem("username"); - const password = localStorage.getItem("password"); - - const performLogin = async () => { - try { - if (username && password) { - await realPortal.student_login(username, password); - if (realPortal.session) { - setIsAuthenticated(true); - setIsDemoMode(false); - } - } - } catch (error) { - if ( - error instanceof LoginError && - error.message.includes( - "JIIT Web Portal server is temporarily unavailable" - ) - ) { - setError( - "JIIT Web Portal server is temporarily unavailable. Please try again later." - ); - } else if ( - error instanceof LoginError && - error.message.includes("Failed to fetch") - ) { - setError( - "Please check your internet connection. If connected, JIIT Web Portal server is temporarily unavailable." - ); - } else { - console.error("Auto-login failed:", error); - setError("Auto-login failed. Please login again."); - } - localStorage.removeItem("username"); - localStorage.removeItem("password"); - setIsAuthenticated(false); - } finally { - setIsLoading(false); - } - }; - - performLogin(); - }, []); - - const handleRealLogin = () => { - setIsAuthenticated(true); - setIsDemoMode(false); - }; - - const handleDemoLogin = () => { - setIsAuthenticated(true); - setIsDemoMode(true); - }; - - if (isLoading) { - return ( - <> - - - -
- Signing in... -
-
- - ); - } - - return ( - <> - - - - - , - }} - toastOptions={{ - style: { - background: "var(--popover)", - color: "var(--popover-foreground)", - border: "1px solid var(--border)", - boxShadow: "var(--shadow-lg)", - }, - }} - /> - -
- - {/* Public route - accessible without authentication */} - } /> - - {/* Protected routes - require authentication */} - {!isAuthenticated ? ( - - {error && ( -
- {error} -
- )} - - - } - /> - ) : ( - - } - /> - )} -
-
-
-
-
- - ); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [isDemoMode, setIsDemoMode] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Determine which portal to use based on demo mode + const activePortal = isDemoMode ? mockPortal : realPortal; + + useEffect(() => { + const username = localStorage.getItem("username"); + const password = localStorage.getItem("password"); + + const performLogin = async () => { + try { + if (username && password) { + await realPortal.student_login(username, password); + if (realPortal.session) { + setIsAuthenticated(true); + setIsDemoMode(false); + } + } + } catch (error) { + if ( + error instanceof LoginError && + error.message.includes( + "JIIT Web Portal server is temporarily unavailable" + ) + ) { + setError( + "JIIT Web Portal server is temporarily unavailable. Please try again later." + ); + } else if ( + error instanceof LoginError && + error.message.includes("Failed to fetch") + ) { + setError( + "Please check your internet connection. If connected, JIIT Web Portal server is temporarily unavailable." + ); + } else { + console.error("Auto-login failed:", error); + setError("Auto-login failed. Please login again."); + } + localStorage.removeItem("username"); + localStorage.removeItem("password"); + setIsAuthenticated(false); + } finally { + setIsLoading(false); + } + }; + + performLogin(); + }, []); + + const handleRealLogin = () => { + setIsAuthenticated(true); + setIsDemoMode(false); + }; + + const handleDemoLogin = () => { + setIsAuthenticated(true); + setIsDemoMode(true); + }; + + if (isLoading) { + return ( + <> + + + +
+ Signing in... +
+
+ + ); + } + + return ( + <> + + + + + , + }} + toastOptions={{ + style: { + background: "var(--popover)", + color: "var(--popover-foreground)", + border: "1px solid var(--border)", + boxShadow: "var(--shadow-lg)", + }, + }} + /> + +
+ + {/* Public route - accessible without authentication */} + } /> + + {/* Protected routes - require authentication */} + {!isAuthenticated ? ( + + {error && ( +
+ {error} +
+ )} + + + } + /> + ) : ( + + } + /> + )} +
+
+
+
+
+ + ); } export default App;