diff --git a/jportal/src/App.jsx b/jportal/src/App.jsx
index 28b26b9..a532fec 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";
@@ -15,7 +21,10 @@ 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";
@@ -30,6 +39,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);
@@ -50,9 +60,11 @@ function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) {
const [selectedSubjectsSem, setSelectedSubjectsSem] = useState(null);
const [attendanceDailyDate, setAttendanceDailyDate] = useState(new Date());
- const [isAttendanceCalendarOpen, setIsAttendanceCalendarOpen] = useState(false);
+ const [isAttendanceCalendarOpen, setIsAttendanceCalendarOpen] =
+ useState(false);
const [isAttendanceTrackerOpen, setIsAttendanceTrackerOpen] = useState(false);
- const [attendanceSubjectCacheStatus, setAttendanceSubjectCacheStatus] = useState({});
+ const [attendanceSubjectCacheStatus, setAttendanceSubjectCacheStatus] =
+ useState({});
// Add attendance goal state
const [attendanceGoal, setAttendanceGoal] = useState(() => {
@@ -104,29 +116,16 @@ function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) {
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);
- }, []);
+ // Add new state for fees
+ const [feesData, setFeesData] = useState(null);
return (
-
+
} />
@@ -245,7 +244,16 @@ function AuthenticatedApp({ w, setIsAuthenticated, setIsDemoMode }) {
/>
}
/>
- } />
+
+ }
+ />
@@ -271,7 +279,13 @@ function LoginWrapper({ onLoginSuccess, onDemoLogin, w }) {
}, 100);
};
- return ;
+ return (
+
+ );
}
function App() {
@@ -299,10 +313,17 @@ function App() {
} catch (error) {
if (
error instanceof LoginError &&
- error.message.includes("JIIT Web Portal server is temporarily unavailable")
+ 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("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."
);
@@ -377,7 +398,11 @@ function App() {
path="*"
element={
<>
- {error && {error}
}
+ {error && (
+
+ {error}
+
+ )}
{
- 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
-
-
-
-
-
-
-
- );
+
+
+ Login
+ Enter your credentials to sign in
+
+
+
+
+
+
+
+ );
}
diff --git a/jportal/src/components/Profile.jsx b/jportal/src/components/Profile.jsx
index 5aaa136..73bdf40 100644
--- a/jportal/src/components/Profile.jsx
+++ b/jportal/src/components/Profile.jsx
@@ -1,289 +1,584 @@
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 ? (
-

- ) : (
-
- {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 && }
-
- ))}
-
-
-
-
-
-
- );
+ const [loading, setLoading] = useState(true);
+ 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
+ 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]);
+
+ // 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 (
+
+ 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 ? (
+

+ ) : (
+
+ {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 && (
+
+ )}
+
+
+ ))}
+
+
+ )}
+ >
+ )}
+
+
+
+
+
+ );
}
// 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"}
+
+
+ );
+}