From db792107129cdb2039aed67b3aea28e769c8d41d Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 19:41:27 +0530 Subject: [PATCH 1/7] added taeget gpa modal --- jportal/src/components/TargetCPA.jsx | 922 +++++++++++++++++++++++++++ 1 file changed, 922 insertions(+) create mode 100644 jportal/src/components/TargetCPA.jsx diff --git a/jportal/src/components/TargetCPA.jsx b/jportal/src/components/TargetCPA.jsx new file mode 100644 index 0000000..e6ee22a --- /dev/null +++ b/jportal/src/components/TargetCPA.jsx @@ -0,0 +1,922 @@ +import { useState, useEffect } from "react" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { Calculator, Loader2 } from "lucide-react" +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + TextField as MuiTextField, + Button as MuiButton, + Select as MuiSelect, + MenuItem as MuiMenuItem, + FormControl, + InputLabel, +} from "@mui/material" +import { useTheme } from "./ThemeProvider" +import { formatDecimal, getGpaDecimal } from "../lib/utils" +import fakedata from "../../fakedata.json" + +export default function CGPATargetCalculator({ + w, + semesterData: sd = [], + guest = false, +}) { + const { useMaterialUI, useCardBackgrounds } = useTheme() + const [isOpen, setIsOpen] = useState(false); + const [activeTab, setActiveTab] = useState("sgpa"); + + const [subjectSemesters, setSubjectSemesters] = useState([]); + const [selectedSemester, setSelectedSemester] = useState(null); + const [subjectData, setSubjectData] = useState({}); + const [semesterCreditsMap, setSemesterCreditsMap] = useState({}); + const [isLoadingSemesters, setIsLoadingSemesters] = useState(false); + const [isLoadingSubjects, setIsLoadingSubjects] = useState(false); + + // Target GPA states + const [targetCGPA, setTargetCGPA] = useState(""); + const [targetSemester, setTargetSemester] = useState(null); + const [requiredSGPA, setRequiredSGPA] = useState(null); + const [targetError, setTargetError] = useState(""); + + const formatGPA = (value) => formatDecimal(value, getGpaDecimal()); + + const [sgpaSubjects, setSgpaSubjects] = useState([]); + + useEffect(() => { + if (!selectedSemester) return; + const semesterId = selectedSemester.registration_id; + if (!semesterId) return; + if (subjectData[semesterId]) return; + if (guest || w) { + fetchSubjectsForSemester(selectedSemester, { updateSgpaSubjects: true }); + } + }, [selectedSemester, subjectData, guest, w]); + + useEffect(() => { + if (!isOpen || subjectSemesters.length > 0) return; + if (guest || w) { + fetchSubjectSemesters(); + } + }, [isOpen, subjectSemesters.length, guest, w]); + + const fetchSubjectSemesters = async () => { + setIsLoadingSemesters(true); + try { + let semesters = []; + if (guest) { + // Guest mode: use fakedata + semesters = fakedata.semesters || []; + } else if (w) { + semesters = await w.get_registered_semesters(); + } + + semesters = Array.isArray(semesters) ? semesters : []; + setSubjectSemesters(semesters); + + if (semesters && semesters.length > 0) { + const defaultSemester = semesters[0]; + setSelectedSemester(prev => prev || defaultSemester); + setTargetSemester(prev => prev || defaultSemester); + } else { + setSelectedSemester(null); + setTargetSemester(null); + } + } catch (error) { + console.error('Failed to fetch semesters:', error); + } finally { + setIsLoadingSemesters(false); + } + }; + + const fetchSubjectsForSemester = async (semester, { updateSgpaSubjects = true } = {}) => { + if (!semester) return; + setIsLoadingSubjects(true); + try { + const semesterId = semester.registration_id; + + if (guest) { + // Guest mode: use fakedata snapshot + const guestData = fakedata.subjects?.subjectData?.[semesterId] || null; + const guestSubjects = guestData?.subjects || []; + + setSubjectData((prev) => ({ + ...prev, + [semesterId]: guestData || guestSubjects, + })); + + if (guestSubjects.length > 0) { + const processedSubjects = processSubjectsForSGPA(guestSubjects); + const totalCredits = processedSubjects.reduce((sum, subject) => sum + (subject.credits || 0), 0); + + setSemesterCreditsMap((prev) => ({ + ...prev, + [semesterId]: guestData?.total_credits || totalCredits, + })); + + if (updateSgpaSubjects) { + setSgpaSubjects(processedSubjects); + } + } else { + setSemesterCreditsMap((prev) => ({ + ...prev, + [semesterId]: 0, + })); + if (updateSgpaSubjects) { + setSgpaSubjects([]); + } + } + } else if (w) { + const subjectsResponse = await w.get_registered_subjects_and_faculties(semester); + + setSubjectData((prev) => ({ + ...prev, + [semesterId]: subjectsResponse, + })); + + const subjectList = Array.isArray(subjectsResponse?.subjects) + ? subjectsResponse.subjects + : []; + + if (subjectList.length > 0) { + const processedSubjects = processSubjectsForSGPA(subjectList); + const totalCredits = processedSubjects.reduce((sum, subject) => sum + (subject.credits || 0), 0); + const derivedCredits = !isNaN(parseFloat(subjectsResponse?.total_credits)) + ? parseFloat(subjectsResponse.total_credits) + : totalCredits; + + setSemesterCreditsMap((prev) => ({ + ...prev, + [semesterId]: derivedCredits, + })); + + if (updateSgpaSubjects) { + setSgpaSubjects(processedSubjects); + } + } else { + const derivedCredits = !isNaN(parseFloat(subjectsResponse?.total_credits)) + ? parseFloat(subjectsResponse.total_credits) + : 0; + + setSemesterCreditsMap((prev) => ({ + ...prev, + [semesterId]: derivedCredits, + })); + + if (updateSgpaSubjects) { + setSgpaSubjects([]); + } + } + } + } catch (error) { + console.error('Failed to fetch subjects:', error); + } finally { + setIsLoadingSubjects(false); + } + }; + + const processSubjectsForSGPA = (subjects) => { + const groupedSubjects = subjects.reduce((acc, subject) => { + const baseCode = subject.subject_code; + if (!acc[baseCode] && subject.audtsubject !== "Y") { + acc[baseCode] = { + name: subject.subject_desc, + code: baseCode, + credits: parseInt(subject.credits) || 0, + grade: "A", + gradePoints: 9 + }; + } + return acc; + }, {}); + + return Object.values(groupedSubjects); + }; + + const gradePointMap = { + "A+": 10, "A": 9, "B+": 8, "B": 7, "C+": 6, "C": 5, "D": 4, "F": 0 + }; + + const gradeOptions = ["A+", "A", "B+", "B", "C+", "C", "D", "F"]; + + const handleGradeChange = (index, grade) => { + setSgpaSubjects(prev => prev.map((subject, i) => + i === index + ? { ...subject, grade, gradePoints: gradePointMap[grade] || 0 } + : subject + )); + }; + + const calculateSGPA = () => { + let totalPoints = 0; + let totalCredits = 0; + + sgpaSubjects.forEach(subject => { + if (subject.grade && subject.credits > 0) { + totalPoints += subject.gradePoints * subject.credits; + totalCredits += subject.credits; + } + }); + + if (totalCredits === 0) return "-"; + return formatGPA(totalPoints / totalCredits); + }; + + const handleSemesterChange = (semesterId) => { + const semester = subjectSemesters.find(sem => sem.registration_id === semesterId); + setSelectedSemester(semester); + + if (semester) { + fetchSubjectsForSemester(semester, { updateSgpaSubjects: true }); + } + }; + + const handleTargetSemesterChange = (semesterId) => { + const semester = subjectSemesters.find((sem) => sem.registration_id === semesterId) || null; + setTargetSemester(semester); + setRequiredSGPA(null); + setTargetError(""); + + if (!semester) return; + + const hasCachedCredits = Object.prototype.hasOwnProperty.call( + semesterCreditsMap, + semester.registration_id, + ); + + if (!hasCachedCredits) { + fetchSubjectsForSemester(semester, { updateSgpaSubjects: false }); + } + }; + + const isSameSemester = (gradeSemester, semester) => { + if (!gradeSemester || !semester) return false; + + if ( + gradeSemester.registration_id && + semester.registration_id && + gradeSemester.registration_id === semester.registration_id + ) { + return true; + } + + if ( + gradeSemester.registration_code && + semester.registration_code && + gradeSemester.registration_code === semester.registration_code + ) { + return true; + } + + if ( + gradeSemester.stynumber !== undefined && + semester.stynumber !== undefined && + String(gradeSemester.stynumber) === String(semester.stynumber) + ) { + return true; + } + + if ( + gradeSemester.coursename && + semester.coursename && + gradeSemester.coursename === semester.coursename + ) { + return true; + } + + return false; + }; + + const findGradeDataForSemester = (semester) => { + if (!semester || !Array.isArray(sd)) return null; + return sd.find((gradeSemester) => isSameSemester(gradeSemester, semester)) || null; + }; + + const getSemesterCredits = (semester) => { + if (!semester) return 0; + + const cached = semesterCreditsMap[semester.registration_id]; + const cachedValue = parseFloat(cached); + if (!isNaN(cachedValue) && cachedValue > 0) { + return cachedValue; + } + + const directCredits = parseFloat(semester.totalcoursecredit); + if (!isNaN(directCredits) && directCredits > 0) { + return directCredits; + } + + const gradeData = findGradeDataForSemester(semester); + const gradeCredits = parseFloat(gradeData?.totalcoursecredit); + if (!isNaN(gradeCredits) && gradeCredits > 0) { + return gradeCredits; + } + + const storedSubjects = subjectData[semester.registration_id]; + const rawSubjects = Array.isArray(storedSubjects?.subjects) + ? storedSubjects.subjects + : Array.isArray(storedSubjects) + ? storedSubjects + : []; + + if (rawSubjects.length > 0) { + const processedSubjects = processSubjectsForSGPA(rawSubjects); + const derivedCredits = processedSubjects.reduce( + (sum, subject) => sum + (subject.credits || 0), + 0, + ); + return derivedCredits; + } + + const fallbackCredits = parseFloat(storedSubjects?.total_credits); + if (!isNaN(fallbackCredits) && fallbackCredits > 0) { + return fallbackCredits; + } + + return 0; + }; + + const calculateProjectedCGPA = () => { + const currentSgpa = parseFloat(calculateSGPA()); + if (isNaN(currentSgpa) || currentSgpa === 0) return "-"; + + let currentCredits = 0; + sgpaSubjects.forEach(subject => { + if (subject.credits > 0) { + currentCredits += subject.credits; + } + }); + + if (currentCredits === 0) return "-"; + + let previousGradePoints = 0; + let previousCredits = 0; + + if (sd && Array.isArray(sd)) { + sd.forEach(sem => { + const sgpa = parseFloat(sem.sgpa); + const credits = parseFloat(sem.totalcoursecredit); + if (!isNaN(sgpa) && !isNaN(credits)) { + previousGradePoints += sgpa * credits; + previousCredits += credits; + } + }); + } + + const totalGradePoints = previousGradePoints + (currentSgpa * currentCredits); + const totalCredits = previousCredits + currentCredits; + + if (totalCredits === 0) return "-"; + return formatGPA(totalGradePoints / totalCredits); + }; + + const calculateRequiredSGPA = () => { + setTargetError(""); + setRequiredSGPA(null); + + if (!targetCGPA || isNaN(targetCGPA)) { + setTargetError("Please enter a valid target CGPA"); + return; + } + + const target = parseFloat(targetCGPA); + if (target < 0 || target > 10) { + setTargetError("Target CGPA must be between 0 and 10"); + return; + } + + if (!targetSemester) { + setTargetError("Please select a semester"); + return; + } + + const currentSemCredits = getSemesterCredits(targetSemester); + + if (!currentSemCredits || currentSemCredits <= 0) { + setTargetError("Unable to determine credits for the selected semester. Try loading it once in the SGPA tab."); + return; + } + + // Calculate previous semesters totals + let previousGradePoints = 0; + let previousCredits = 0; + + if (Array.isArray(sd) && sd.length > 0) { + sd.forEach((sem) => { + const sgpa = parseFloat(sem.sgpa); + const credits = parseFloat(sem.totalcoursecredit); + if (!isNaN(sgpa) && !isNaN(credits)) { + previousGradePoints += sgpa * credits; + previousCredits += credits; + } + }); + } + + // Required SGPA = (targetCGPA * (totalCredits + currentCredits) - previousGradePoints) / currentCredits + const totalCreditsAfter = previousCredits + currentSemCredits; + const required = (target * totalCreditsAfter - previousGradePoints) / currentSemCredits; + + setRequiredSGPA(required); + }; + + return ( + <> + + + + + + + + GPA Calculator + + + + + + + SGPA + + + Target GPA + + + + +
+ {useMaterialUI ? ( + + + Select Semester + + handleSemesterChange(e.target.value)} + displayEmpty + variant="outlined" + fullWidth + sx={{ + background: "var(--card-bg)", + color: "var(--text-color)", + borderRadius: "var(--radius)", + "& .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--border-color)", + }, + "&:hover .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--accent-color)", + }, + "&.Mui-focused .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--accent-color)", + }, + }} + > + + {isLoadingSemesters ? "Loading..." : "Choose semester"} + + {subjectSemesters.map((semester) => ( + + {semester.registration_code} + + ))} + + + ) : ( + + )} + + {isLoadingSemesters && ( +
+ + Loading semesters... +
+ )} +
+ + {selectedSemester && ( + <> + {isLoadingSubjects ? ( +
+ + Loading subjects... +
+ ) : sgpaSubjects.length > 0 ? ( + <> +
+ {sgpaSubjects.map((subject, index) => ( +
+
+
+ {subject.name} +
+
+ {subject.code} + {subject.credits} credits +
+
+
+ + {useMaterialUI ? ( + + handleGradeChange(index, e.target.value)} + sx={{ + background: "var(--card-bg)", + color: "var(--text-color)", + borderRadius: "var(--radius)", + fontSize: { xs: "0.75rem", sm: "0.875rem" }, + "& .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--border-color)", + }, + "&:hover .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--accent-color)", + }, + "&.Mui-focused .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--accent-color)", + }, + }} + > + {gradeOptions.map(grade => ( + + {grade} + + ))} + + + ) : ( + + )} +
+
+ ))} +
+
+
+ Calculated SGPA + + {calculateSGPA()} + +
+
+ Projected CGPA + + {calculateProjectedCGPA()} + +
+
+ + ) : ( +
+

+ No subjects found for this semester +

+
+ )} + + )} +
+ + +
+
+

+ Enter your desired final CGPA, and we'll calculate the SGPA you need to achieve in the selected semester. +

+ +
+
+ + {useMaterialUI ? ( + + handleTargetSemesterChange(e.target.value)} + displayEmpty + sx={{ + background: "var(--card-bg)", + color: "var(--text-color)", + borderRadius: "var(--radius)", + "& .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--border-color)", + }, + "&:hover .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--accent-color)", + }, + "&.Mui-focused .MuiOutlinedInput-notchedOutline": { + borderColor: "var(--accent-color)", + }, + }} + > + + + {isLoadingSemesters ? "Loading semesters..." : "Select a semester"} + + + {subjectSemesters.map((sem) => { + const credits = getSemesterCredits(sem); + return ( + + {sem.registration_code || sem.coursename || sem.stynumber} + {credits > 0 && ( + + ({credits} credits) + + )} + + ); + })} + + + ) : ( + + )} +
+ +
+ + {useMaterialUI ? ( + setTargetCGPA(e.target.value)} + placeholder="Enter target CGPA (e.g., 9.0)" + fullWidth + sx={{ + background: "var(--card-bg)", + borderRadius: "var(--radius)", + "& .MuiOutlinedInput-root": { + color: "var(--text-color)", + "& fieldset": { + borderColor: "var(--border-color)", + }, + "&:hover fieldset": { + borderColor: "var(--accent-color)", + }, + "&.Mui-focused fieldset": { + borderColor: "var(--accent-color)", + }, + }, + }} + /> + ) : ( + setTargetCGPA(e.target.value)} + placeholder="Enter target CGPA (e.g., 9.0)" + className="bg-[var(--card-bg)] text-[var(--text-color)] border-[var(--border-color)] rounded-[var(--radius)]" + /> + )} +
+ + {useMaterialUI ? ( + + Calculate Required SGPA + + ) : ( + + )} +
+ + {targetError && ( +
+ {targetError} +
+ )} + + {requiredSGPA !== null && !targetError && ( +
+
+
+

+ {targetSemester?.registration_code || targetSemester?.coursename || targetSemester?.stynumber || "Selected Semester"} +

+

+ Required SGPA +

+ {requiredSGPA > 10 ? ( +
+

+ Not Achievable +

+

+ Target CGPA of {formatGPA(parseFloat(targetCGPA))} cannot be achieved this semester. + The required SGPA ({formatGPA(requiredSGPA)}) exceeds the maximum possible (10.0). +

+
+ ) : requiredSGPA < 0 ? ( +
+

+ Already Achieved! +

+

+ Your current CGPA already meets or exceeds your target of {formatGPA(parseFloat(targetCGPA))}. +

+
+ ) : ( +
+

+ {formatGPA(requiredSGPA)} +

+

+ Achieve an SGPA of {formatGPA(requiredSGPA)} this semester to reach your target CGPA of {formatGPA(parseFloat(targetCGPA))}. +

+
+ )} +
+
+ + {sd && sd.length > 0 && ( +
+

+ Current Stats: + • Previous semesters: {sd.length} + • Current CGPA: { + formatGPA( + sd.reduce((sum, sem) => sum + (parseFloat(sem.sgpa) * parseFloat(sem.totalcoursecredit)), 0) / + sd.reduce((sum, sem) => sum + parseFloat(sem.totalcoursecredit), 0) + ) + } +

+
+ )} +
+ )} +
+
+
+
+
+
+ + ); +} From be35cb162b9b85b3e21038064b7d83907fc9f219 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 19:45:02 +0530 Subject: [PATCH 2/7] remoed mui --- jportal/src/components/TargetCPA.jsx | 358 ++++++--------------------- 1 file changed, 80 insertions(+), 278 deletions(-) diff --git a/jportal/src/components/TargetCPA.jsx b/jportal/src/components/TargetCPA.jsx index e6ee22a..ae2184b 100644 --- a/jportal/src/components/TargetCPA.jsx +++ b/jportal/src/components/TargetCPA.jsx @@ -10,14 +10,6 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" -import { - TextField as MuiTextField, - Button as MuiButton, - Select as MuiSelect, - MenuItem as MuiMenuItem, - FormControl, - InputLabel, -} from "@mui/material" import { useTheme } from "./ThemeProvider" import { formatDecimal, getGpaDecimal } from "../lib/utils" import fakedata from "../../fakedata.json" @@ -27,7 +19,7 @@ export default function CGPATargetCalculator({ semesterData: sd = [], guest = false, }) { - const { useMaterialUI, useCardBackgrounds } = useTheme() + const { useCardBackgrounds } = useTheme() const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState("sgpa"); @@ -470,67 +462,21 @@ export default function CGPATargetCalculator({
- {useMaterialUI ? ( - - - Select Semester - - handleSemesterChange(e.target.value)} - displayEmpty - variant="outlined" - fullWidth - sx={{ - background: "var(--card-bg)", - color: "var(--text-color)", - borderRadius: "var(--radius)", - "& .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--border-color)", - }, - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--accent-color)", - }, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--accent-color)", - }, - }} - > - - {isLoadingSemesters ? "Loading..." : "Choose semester"} - - {subjectSemesters.map((semester) => ( - - {semester.registration_code} - - ))} - - - ) : ( - - )} + {isLoadingSemesters && (
@@ -567,51 +513,21 @@ export default function CGPATargetCalculator({
- {useMaterialUI ? ( - - handleGradeChange(index, e.target.value)} - sx={{ - background: "var(--card-bg)", - color: "var(--text-color)", - borderRadius: "var(--radius)", - fontSize: { xs: "0.75rem", sm: "0.875rem" }, - "& .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--border-color)", - }, - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--accent-color)", - }, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--accent-color)", - }, - }} - > - {gradeOptions.map(grade => ( - - {grade} - - ))} - - - ) : ( - - )} +
))} @@ -666,177 +582,63 @@ export default function CGPATargetCalculator({ - {useMaterialUI ? ( - - handleTargetSemesterChange(e.target.value)} - displayEmpty - sx={{ - background: "var(--card-bg)", - color: "var(--text-color)", - borderRadius: "var(--radius)", - "& .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--border-color)", - }, - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--accent-color)", - }, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: "var(--accent-color)", - }, - }} - > - - - {isLoadingSemesters ? "Loading semesters..." : "Select a semester"} - - - {subjectSemesters.map((sem) => { - const credits = getSemesterCredits(sem); - return ( - - {sem.registration_code || sem.coursename || sem.stynumber} - {credits > 0 && ( - - ({credits} credits) - - )} - - ); - })} - - - ) : ( - - )} +
- {useMaterialUI ? ( - setTargetCGPA(e.target.value)} - placeholder="Enter target CGPA (e.g., 9.0)" - fullWidth - sx={{ - background: "var(--card-bg)", - borderRadius: "var(--radius)", - "& .MuiOutlinedInput-root": { - color: "var(--text-color)", - "& fieldset": { - borderColor: "var(--border-color)", - }, - "&:hover fieldset": { - borderColor: "var(--accent-color)", - }, - "&.Mui-focused fieldset": { - borderColor: "var(--accent-color)", - }, - }, - }} - /> - ) : ( - setTargetCGPA(e.target.value)} - placeholder="Enter target CGPA (e.g., 9.0)" - className="bg-[var(--card-bg)] text-[var(--text-color)] border-[var(--border-color)] rounded-[var(--radius)]" - /> - )} + setTargetCGPA(e.target.value)} + placeholder="Enter target CGPA (e.g., 9.0)" + className="bg-[var(--card-bg)] text-[var(--text-color)] border-[var(--border-color)] rounded-[var(--radius)]" + />
- {useMaterialUI ? ( - - Calculate Required SGPA - - ) : ( - - )} + {targetError && ( From 227a6d2347cf852f7638cc957fa26366ec420a7e Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 19:58:01 +0530 Subject: [PATCH 3/7] add utility functions for decimal formatting and GPA calculations -- abstraction for the general settings --- jportal/src/lib/utils.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/jportal/src/lib/utils.js b/jportal/src/lib/utils.js index b20bf01..ead7941 100644 --- a/jportal/src/lib/utils.js +++ b/jportal/src/lib/utils.js @@ -4,3 +4,26 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs) { return twMerge(clsx(inputs)); } + +export function getDecimalPlaces() { + return 2; +} + +export function formatDecimal(value, places = 2) { + if (typeof value === "number") { + return value.toFixed(places); + } + return value; +} + +export function getAttendanceDecimal() { + return 2; +} + +export function getGpaDecimal() { + return 2; +} + +export function getTargetGpaDecimal() { + return 2; +} From 763902d45ba7e54fd86f0fe0638320fe2086b233 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 19:58:28 +0530 Subject: [PATCH 4/7] integrated new theming system --- jportal/src/components/TargetCPA.jsx | 178 ++++++++------------------- 1 file changed, 51 insertions(+), 127 deletions(-) diff --git a/jportal/src/components/TargetCPA.jsx b/jportal/src/components/TargetCPA.jsx index ae2184b..31ed227 100644 --- a/jportal/src/components/TargetCPA.jsx +++ b/jportal/src/components/TargetCPA.jsx @@ -10,16 +10,13 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" -import { useTheme } from "./ThemeProvider" import { formatDecimal, getGpaDecimal } from "../lib/utils" -import fakedata from "../../fakedata.json" export default function CGPATargetCalculator({ w, semesterData: sd = [], guest = false, }) { - const { useCardBackgrounds } = useTheme() const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState("sgpa"); @@ -61,15 +58,10 @@ export default function CGPATargetCalculator({ setIsLoadingSemesters(true); try { let semesters = []; - if (guest) { - // Guest mode: use fakedata - semesters = fakedata.semesters || []; - } else if (w) { - semesters = await w.get_registered_semesters(); - } + semesters = await w.get_registered_semesters(); - semesters = Array.isArray(semesters) ? semesters : []; - setSubjectSemesters(semesters); + semesters = Array.isArray(semesters) ? semesters : []; + setSubjectSemesters(semesters); if (semesters && semesters.length > 0) { const defaultSemester = semesters[0]; @@ -92,38 +84,6 @@ export default function CGPATargetCalculator({ try { const semesterId = semester.registration_id; - if (guest) { - // Guest mode: use fakedata snapshot - const guestData = fakedata.subjects?.subjectData?.[semesterId] || null; - const guestSubjects = guestData?.subjects || []; - - setSubjectData((prev) => ({ - ...prev, - [semesterId]: guestData || guestSubjects, - })); - - if (guestSubjects.length > 0) { - const processedSubjects = processSubjectsForSGPA(guestSubjects); - const totalCredits = processedSubjects.reduce((sum, subject) => sum + (subject.credits || 0), 0); - - setSemesterCreditsMap((prev) => ({ - ...prev, - [semesterId]: guestData?.total_credits || totalCredits, - })); - - if (updateSgpaSubjects) { - setSgpaSubjects(processedSubjects); - } - } else { - setSemesterCreditsMap((prev) => ({ - ...prev, - [semesterId]: 0, - })); - if (updateSgpaSubjects) { - setSgpaSubjects([]); - } - } - } else if (w) { const subjectsResponse = await w.get_registered_subjects_and_faculties(semester); setSubjectData((prev) => ({ @@ -164,7 +124,6 @@ export default function CGPATargetCalculator({ setSgpaSubjects([]); } } - } } catch (error) { console.error('Failed to fetch subjects:', error); } finally { @@ -420,13 +379,7 @@ export default function CGPATargetCalculator({ <> - + - + GPA Calculator - + SGPA Target GPA @@ -463,10 +416,10 @@ export default function CGPATargetCalculator({
handleGradeChange(index, grade)} > - + - + {gradeOptions.map(grade => ( {grade} @@ -533,23 +482,15 @@ export default function CGPATargetCalculator({ ))}
-
- Calculated SGPA - +
+ Calculated SGPA + {calculateSGPA()}
-
- Projected CGPA - +
+ Projected CGPA + {calculateProjectedCGPA()}
@@ -557,7 +498,7 @@ export default function CGPATargetCalculator({ ) : (
-

+

No subjects found for this semester

@@ -568,39 +509,34 @@ export default function CGPATargetCalculator({
-
-

+

+

Enter your desired final CGPA, and we'll calculate the SGPA you need to achieve in the selected semester.

-
{targetError && ( -
+
{targetError}
)} {requiredSGPA !== null && !targetError && (
-
+
-

+

{targetSemester?.registration_code || targetSemester?.coursename || targetSemester?.stynumber || "Selected Semester"}

-

+

Required SGPA

{requiredSGPA > 10 ? (
-

+

Not Achievable

-

+

Target CGPA of {formatGPA(parseFloat(targetCGPA))} cannot be achieved this semester. The required SGPA ({formatGPA(requiredSGPA)}) exceeds the maximum possible (10.0).

) : requiredSGPA < 0 ? (
-

+

Already Achieved!

-

+

Your current CGPA already meets or exceeds your target of {formatGPA(parseFloat(targetCGPA))}.

) : (
-

+

{formatGPA(requiredSGPA)}

-

+

Achieve an SGPA of {formatGPA(requiredSGPA)} this semester to reach your target CGPA of {formatGPA(parseFloat(targetCGPA))}.

@@ -694,12 +622,8 @@ export default function CGPATargetCalculator({
{sd && sd.length > 0 && ( -
-

+

+

Current Stats: • Previous semesters: {sd.length} • Current CGPA: { From 381aa7f27cc693041fc8385cf79e165f0247ecb1 Mon Sep 17 00:00:00 2001 From: Tashif Ahmad Khan Date: Sun, 9 Nov 2025 19:58:48 +0530 Subject: [PATCH 5/7] integrated in the grades page --- jportal/src/components/Grades.jsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/jportal/src/components/Grades.jsx b/jportal/src/components/Grades.jsx index bc42f1a..cbc6a4a 100644 --- a/jportal/src/components/Grades.jsx +++ b/jportal/src/components/Grades.jsx @@ -4,9 +4,10 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import GradeCard from "./GradeCard"; import { Button } from "@/components/ui/button"; -import { Download } from "lucide-react"; +import { CalculatorIcon, Download } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import MarksCard from "./MarksCard"; +import CGPATargetCalculator from "./TargetCPA"; import { generate_local_name, API } from "https://cdn.jsdelivr.net/npm/jsjiit@0.0.16/dist/jsjiit.esm.js"; import MockWebPortal from "./MockWebPortal"; @@ -392,7 +393,12 @@ export default function Grades({

)} -
+
+