diff --git a/app.js b/app.js index 6efb32c..219f526 100644 --- a/app.js +++ b/app.js @@ -1,15 +1,13 @@ -// Application data from the provided JSON -const subjects = { +const SUBJECT_CATALOG = { "Engineering Mathematics-III": { credits: 3, chapters: [ "Laplace Transforms", - "Fourier Series", + "Fourier Series", "Partial Differential Equations", "Z-Transforms", "Functions of Complex Variables" ], - totalChapters: 5 }, "Data Structures": { @@ -17,7 +15,7 @@ const subjects = { chapters: [ "Data, Data types, Arrays and Hash Tables", "Stacks and Queues", - "Linked Lists", + "Linked Lists", "Trees and Graphs", "Searching and Sorting" ], @@ -29,7 +27,7 @@ const subjects = { "Propositional Logic and Predicates", "Set Theory, Functions and Relations", "Combinatorics", - "Graph Theory and Trees", + "Graph Theory and Trees", "Algebraic Structures" ], totalChapters: 5 @@ -68,200 +66,187 @@ const subjects = { } }; -const gradeScale = { - "EX": {min: 91, points: 10}, - "AA`": {min: 86, points: 9}, - "AB": {min: 81, points: 8}, - "BB": {min: 76, points: 7}, - "BC": {min: 71, points: 6}, - "CC": {min: 66, points: 5}, - "CD": {min: 61, points: 4}, - "DD": {min: 56, points: 0}, - "DE": {min: 51, points: 0}, - "EE": {min: 40, points: 0}, - "EF": {min: 0, points: 0}, +const GRADE_SCALE = { + "EX": { min: 91, points: 10 }, + "AA": { min: 86, points: 9 }, + "AB": { min: 81, points: 8 }, + "BB": { min: 76, points: 7 }, + "BC": { min: 71, points: 6 }, + "CC": { min: 66, points: 5 }, + "CD": { min: 61, points: 4 }, + "DD": { min: 56, points: 0 }, + "DE": { min: 51, points: 0 }, + "EE": { min: 40, points: 0 }, + "EF": { min: 0, points: 0 } }; -// Global state let studyPlan = []; -let folderStructure = {}; -let subjectMarks = {}; -let studyProgress = {}; +let materialFolders = {}; +let subjectMarksByName = {}; +let subjectChapterProgress = {}; -// Initialize application -document.addEventListener('DOMContentLoaded', function() { - console.log('App initializing...'); +document.addEventListener('DOMContentLoaded', function () { initializeApp(); setupEventListeners(); - populateSubjectSelectors(); - initializeFolderStructure(); - calculateModeDurations(); + populateSubjectDropdowns(); + initializeMaterialFolders(); + renderModeDurations(); }); function initializeApp() { - console.log('Setting up initial data...'); - // Set default dates const today = new Date(); const startDate = new Date(today); - startDate.setDate(today.getDate() + 7); // Start next week - + startDate.setDate(today.getDate() + 7); + const examDate = new Date(today); - examDate.setDate(today.getDate() + 90); // Exam in 3 months - + examDate.setDate(today.getDate() + 90); + const startDateInput = document.getElementById('startDate'); const examDateInput = document.getElementById('examDate'); - + if (startDateInput && examDateInput) { startDateInput.value = startDate.toISOString().split('T')[0]; examDateInput.value = examDate.toISOString().split('T')[0]; } - - // Initialize subject marks - Object.keys(subjects).forEach(subject => { - subjectMarks[subject] = { - ct1: 0, ct2: 0, assignment: 0, midSem: 0, endSem: 0 + + Object.keys(SUBJECT_CATALOG).forEach(subjectName => { + subjectMarksByName[subjectName] = { + ct1: 0, + ct2: 0, + assignment: 0, + midSem: 0, + endSem: 0 }; - studyProgress[subject] = {}; - subjects[subject].chapters.forEach((chapter, index) => { - studyProgress[subject][index] = false; + subjectChapterProgress[subjectName] = {}; + SUBJECT_CATALOG[subjectName].chapters.forEach((_, index) => { + subjectChapterProgress[subjectName][index] = false; }); }); } function setupEventListeners() { - console.log('Setting up event listeners...'); - - // Tab navigation - Fixed implementation document.querySelectorAll('.nav-tab').forEach(tab => { - tab.addEventListener('click', function(e) { - e.preventDefault(); - const targetTab = this.dataset.tab; - console.log('Tab clicked:', targetTab); - switchTab(targetTab); + tab.addEventListener('click', event => { + event.preventDefault(); + switchTab(tab.dataset.tab); }); }); - - // Study plan generator + const generateBtn = document.getElementById('generatePlan'); if (generateBtn) { - generateBtn.addEventListener('click', function(e) { - e.preventDefault(); - console.log('Generate plan clicked'); - generateStudyPlan(); + generateBtn.addEventListener('click', event => { + event.preventDefault(); + handleGeneratePlanClick(); }); } - - // Folder manager + const addMaterialBtn = document.getElementById('addMaterial'); if (addMaterialBtn) { - addMaterialBtn.addEventListener('click', function(e) { - e.preventDefault(); - showAddMaterialModal(); + addMaterialBtn.addEventListener('click', event => { + event.preventDefault(); + openMaterialModal(); }); } - + const searchInput = document.getElementById('searchMaterials'); if (searchInput) { - searchInput.addEventListener('input', searchMaterials); + searchInput.addEventListener('input', filterMaterials); } - - // CGPA calculator + const calculateBtn = document.getElementById('calculateCGPA'); if (calculateBtn) { - calculateBtn.addEventListener('click', function(e) { - e.preventDefault(); - console.log('Calculate CGPA clicked'); - calculateCGPA(); + calculateBtn.addEventListener('click', event => { + event.preventDefault(); + handleCalculateCgpaClick(); }); } - + const subjectSelect = document.getElementById('subjectSelect'); if (subjectSelect) { - subjectSelect.addEventListener('change', loadSubjectMarks); - } - - // Performance modes - document.querySelectorAll('.mode-select').forEach(btn => { - btn.addEventListener('click', function(e) { - e.preventDefault(); - const mode = this.dataset.mode; - console.log('Mode selected:', mode); - selectPerformanceMode(mode); + subjectSelect.addEventListener('change', loadMarksForSelectedSubject); + } + + document.querySelectorAll('.mode-select').forEach(button => { + button.addEventListener('click', event => { + event.preventDefault(); + handleModeSelection(button.dataset.mode); }); }); - - // Modal controls + const cancelBtn = document.getElementById('cancelMaterial'); const saveBtn = document.getElementById('saveMaterial'); const closeBtn = document.querySelector('.modal-close'); - - if (cancelBtn) cancelBtn.addEventListener('click', function(e) { e.preventDefault(); hideAddMaterialModal(); }); - if (saveBtn) saveBtn.addEventListener('click', function(e) { e.preventDefault(); saveMaterial(); }); - if (closeBtn) closeBtn.addEventListener('click', function(e) { e.preventDefault(); hideAddMaterialModal(); }); - - // Material subject change + + if (cancelBtn) { + cancelBtn.addEventListener('click', event => { + event.preventDefault(); + closeMaterialModal(); + }); + } + + if (saveBtn) { + saveBtn.addEventListener('click', event => { + event.preventDefault(); + saveNewMaterial(); + }); + } + + if (closeBtn) { + closeBtn.addEventListener('click', event => { + event.preventDefault(); + closeMaterialModal(); + }); + } + const materialSubject = document.getElementById('materialSubject'); if (materialSubject) { - materialSubject.addEventListener('change', updateMaterialChapters); + materialSubject.addEventListener('change', populateMaterialChapterOptions); } } function switchTab(tabId) { - console.log('Switching to tab:', tabId); - - // Update tab buttons document.querySelectorAll('.nav-tab').forEach(tab => { tab.classList.remove('active'); }); + const activeTab = document.querySelector(`[data-tab="${tabId}"]`); if (activeTab) { activeTab.classList.add('active'); } - - // Update content - Hide all first + document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); content.style.display = 'none'; }); - - // Show selected content + const targetContent = document.getElementById(tabId); if (targetContent) { targetContent.classList.add('active'); targetContent.style.display = 'block'; - console.log('Tab switched successfully to:', tabId); - } else { - console.error('Target content not found:', tabId); } } -// SECTION 1: Study Plan Generator -function generateStudyPlan() { - console.log('Generating study plan...'); - +function handleGeneratePlanClick() { const modeSelect = document.getElementById('studyMode'); const startDateInput = document.getElementById('startDate'); const examDateInput = document.getElementById('examDate'); - + if (!modeSelect || !startDateInput || !examDateInput) { - console.error('Required elements not found'); return; } - + const mode = modeSelect.value; const startDate = new Date(startDateInput.value); const examDate = new Date(examDateInput.value); - - console.log('Mode:', mode, 'Start:', startDate, 'Exam:', examDate); - + if (!startDateInput.value || !examDateInput.value || examDate <= startDate) { alert('Please select valid start and exam dates'); return; } - - studyPlan = createStudyPlan(mode, startDate, examDate); - displayStudyPlan(); - + + studyPlan = buildStudyPlan(mode, startDate, examDate); + renderStudyPlan(); + const output = document.getElementById('studyPlanOutput'); if (output) { output.classList.remove('hidden'); @@ -269,29 +254,27 @@ function generateStudyPlan() { } } -function createStudyPlan(mode, startDate, examDate) { - console.log('Creating study plan for mode:', mode); +function buildStudyPlan(mode, startDate, examDate) { const plan = []; - const subjectList = Object.entries(subjects).sort((a, b) => b[1].credits - a[1].credits); - + const subjectList = Object.entries(SUBJECT_CATALOG).sort((a, b) => b[1].credits - a[1].credits); + if (mode === 'intense') { - // 7-day crash course const intenseDays = 7; const totalChapters = subjectList.reduce((sum, [, subject]) => sum + subject.totalChapters, 0); const chaptersPerDay = Math.ceil(totalChapters / intenseDays); - + let currentDate = new Date(examDate); currentDate.setDate(currentDate.getDate() - intenseDays); - + let chapterIndex = 0; let currentSubjectIndex = 0; - + for (let day = 0; day < intenseDays; day++) { const dayPlan = { date: new Date(currentDate), subjects: [] }; - + let chaptersToday = 0; while (chaptersToday < chaptersPerDay && currentSubjectIndex < subjectList.length) { const [subjectName, subject] = subjectList[currentSubjectIndex]; @@ -304,400 +287,414 @@ function createStudyPlan(mode, startDate, examDate) { chapterIndex++; chaptersToday++; } - + if (chapterIndex >= subject.chapters.length) { currentSubjectIndex++; chapterIndex = 0; } } - + if (dayPlan.subjects.length > 0) { plan.push(dayPlan); } - + currentDate.setDate(currentDate.getDate() + 1); } } else { - // Easy or Normal mode const chaptersPerDay = mode === 'easy' ? 1 : 2; const daysBetween = Math.ceil((examDate - startDate) / (1000 * 60 * 60 * 24)); - let currentDate = new Date(startDate); let allChapters = []; - - // Create weighted chapter list + subjectList.forEach(([subjectName, subject]) => { - subject.chapters.forEach(chapter => { + subject.chapters.forEach(chapterName => { allChapters.push({ subject: subjectName, - chapter: chapter, + chapter: chapterName, priority: subject.credits >= 3 ? 'high' : subject.credits === 2 ? 'medium' : 'low' }); }); }); - - // Distribute chapters across available days + for (let day = 0; day < daysBetween && allChapters.length > 0; day++) { const dayPlan = { date: new Date(currentDate), subjects: [] }; - + for (let i = 0; i < chaptersPerDay && allChapters.length > 0; i++) { dayPlan.subjects.push(allChapters.shift()); } - + if (dayPlan.subjects.length > 0) { plan.push(dayPlan); } - + currentDate.setDate(currentDate.getDate() + 1); } } - + return plan; } -function displayStudyPlan() { +function renderStudyPlan() { const planDetails = document.getElementById('planDetails'); if (!planDetails) return; - + planDetails.innerHTML = ''; - - studyPlan.forEach((day, index) => { + + studyPlan.forEach((day, dayIndex) => { const dayElement = document.createElement('div'); dayElement.className = 'plan-day'; - dayElement.innerHTML = ` - + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `day-${dayIndex}`; + checkbox.addEventListener('change', () => toggleDayCompletion(dayIndex)); + + const subjectsHtml = day.subjects.map(item => ` + ${escapeHtml(item.subject)} + ${escapeHtml(item.chapter)} + `).join(' • '); + + dayElement.appendChild(checkbox); + dayElement.insertAdjacentHTML('beforeend', `
-
${day.date.toLocaleDateString('en-US', { - weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' +
${day.date.toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' })}
-
- ${day.subjects.map(s => ` - ${s.subject} - ${s.chapter} - `).join(' • ')} -
+
${subjectsHtml}
- `; + `); + planDetails.appendChild(dayElement); }); - - updateProgressDisplay(); + + renderOverallProgress(); } -function updateProgress(dayIndex) { +function toggleDayCompletion(dayIndex) { const checkbox = document.getElementById(`day-${dayIndex}`); - const dayElement = checkbox.closest('.plan-day'); - + const dayElement = checkbox && checkbox.closest('.plan-day'); + if (!dayElement) return; + if (checkbox.checked) { dayElement.classList.add('completed'); } else { dayElement.classList.remove('completed'); } - - updateProgressDisplay(); + + renderOverallProgress(); } -function updateProgressDisplay() { +function renderOverallProgress() { const completedDays = document.querySelectorAll('.plan-day input:checked').length; const totalDays = studyPlan.length; const progress = totalDays > 0 ? (completedDays / totalDays) * 100 : 0; - + const progressFill = document.getElementById('overallProgress'); const progressText = document.getElementById('progressText'); - + if (progressFill) progressFill.style.width = `${progress}%`; if (progressText) progressText.textContent = `${Math.round(progress)}% Complete`; } -// SECTION 2: Folder Manager -function initializeFolderStructure() { - folderStructure = {}; - Object.keys(subjects).forEach(subjectName => { - folderStructure[subjectName] = { +function initializeMaterialFolders() { + materialFolders = {}; + + Object.keys(SUBJECT_CATALOG).forEach(subjectName => { + materialFolders[subjectName] = { chapters: {}, materials: [] }; - - subjects[subjectName].chapters.forEach((chapter, index) => { - folderStructure[subjectName].chapters[index] = { - name: chapter, + + SUBJECT_CATALOG[subjectName].chapters.forEach((chapterName, index) => { + materialFolders[subjectName].chapters[index] = { + name: chapterName, materials: [] }; }); }); - - renderFolderStructure(); + + renderMaterialFolders(); } -function renderFolderStructure() { +function renderMaterialFolders() { const container = document.getElementById('folderStructure'); if (!container) return; - + container.innerHTML = ''; - - Object.entries(folderStructure).forEach(([subjectName, subjectData]) => { + + Object.entries(materialFolders).forEach(([subjectName, subjectData]) => { + const totalMaterials = subjectData.materials.length + + Object.values(subjectData.chapters).reduce((sum, chapter) => sum + chapter.materials.length, 0); + const subjectFolder = document.createElement('div'); subjectFolder.className = 'folder-item'; - - const totalMaterials = subjectData.materials.length + - Object.values(subjectData.chapters).reduce((sum, chapter) => sum + chapter.materials.length, 0); - - subjectFolder.innerHTML = ` -
- 📁 - ${subjectName} - (${totalMaterials} materials) -
-
- ${Object.entries(subjectData.chapters).map(([chapterIndex, chapter]) => ` -
-
- 📄 - ${chapter.name} - (${chapter.materials.length} materials) -
-
- ${chapter.materials.map((material, materialIndex) => ` -
- ${material.type} - ${material.name} -
- -
-
- `).join('')} -
-
- `).join('')} - ${subjectData.materials.map((material, materialIndex) => ` -
- ${material.type} - ${material.name} -
- -
-
- `).join('')} -
+ + const subjectHeader = document.createElement('div'); + subjectHeader.className = 'folder-header'; + subjectHeader.innerHTML = ` + 📁 + ${escapeHtml(subjectName)} + (${totalMaterials} materials) `; - + + const subjectChildrenId = `folder-${sanitizeHtmlId(subjectName)}`; + const subjectChildren = document.createElement('div'); + subjectChildren.className = 'folder-children'; + subjectChildren.id = subjectChildrenId; + + subjectHeader.addEventListener('click', () => toggleSubjectFolder(subjectName)); + + Object.entries(subjectData.chapters).forEach(([chapterIndex, chapter]) => { + const chapterFolder = document.createElement('div'); + chapterFolder.className = 'folder-item'; + + const chapterHeader = document.createElement('div'); + chapterHeader.className = 'folder-header'; + chapterHeader.innerHTML = ` + 📄 + ${escapeHtml(chapter.name)} + (${chapter.materials.length} materials) + `; + + const chapterChildrenId = `chapter-${sanitizeHtmlId(subjectName)}-${chapterIndex}`; + const chapterChildren = document.createElement('div'); + chapterChildren.className = 'folder-children'; + chapterChildren.id = chapterChildrenId; + + chapterHeader.addEventListener('click', () => toggleChapterFolder(subjectName, chapterIndex)); + + chapter.materials.forEach((material, materialIndex) => { + chapterChildren.appendChild(createMaterialElement(subjectName, chapterIndex, materialIndex, material)); + }); + + chapterFolder.appendChild(chapterHeader); + chapterFolder.appendChild(chapterChildren); + subjectChildren.appendChild(chapterFolder); + }); + + subjectData.materials.forEach((material, materialIndex) => { + subjectChildren.appendChild(createMaterialElement(subjectName, null, materialIndex, material)); + }); + + subjectFolder.appendChild(subjectHeader); + subjectFolder.appendChild(subjectChildren); container.appendChild(subjectFolder); }); } -function toggleFolder(subjectName) { - const folder = document.getElementById(`folder-${subjectName}`); - if (folder) { - folder.style.display = folder.style.display === 'none' ? 'block' : 'none'; - } +function createMaterialElement(subjectName, chapterIndex, materialIndex, material) { + const element = document.createElement('div'); + element.className = 'material-item'; + element.innerHTML = ` + ${escapeHtml(material.type)} + ${escapeHtml(material.name)} +
+ +
+ `; + + const removeButton = element.querySelector('.remove-material-btn'); + removeButton.addEventListener('click', () => deleteMaterial(subjectName, chapterIndex, materialIndex)); + + return element; } -function toggleChapter(subjectName, chapterIndex) { - const chapter = document.getElementById(`chapter-${subjectName}-${chapterIndex}`); - if (chapter) { - chapter.style.display = chapter.style.display === 'none' ? 'block' : 'none'; - } +function toggleSubjectFolder(subjectName) { + const folder = document.getElementById(`folder-${sanitizeHtmlId(subjectName)}`); + if (!folder) return; + + folder.style.display = folder.style.display === 'none' ? 'block' : 'none'; +} + +function toggleChapterFolder(subjectName, chapterIndex) { + const chapter = document.getElementById(`chapter-${sanitizeHtmlId(subjectName)}-${chapterIndex}`); + if (!chapter) return; + + chapter.style.display = chapter.style.display === 'none' ? 'block' : 'none'; } -function showAddMaterialModal() { +function openMaterialModal() { const modal = document.getElementById('addMaterialModal'); if (modal) { modal.classList.remove('hidden'); } } -function hideAddMaterialModal() { +function closeMaterialModal() { const modal = document.getElementById('addMaterialModal'); if (modal) { modal.classList.add('hidden'); } - // Clear form + const materialName = document.getElementById('materialName'); const materialType = document.getElementById('materialType'); if (materialName) materialName.value = ''; if (materialType) materialType.value = 'pdf'; } -function updateMaterialChapters() { +function populateMaterialChapterOptions() { const subjectSelect = document.getElementById('materialSubject'); const chapterSelect = document.getElementById('materialChapter'); - + if (!subjectSelect || !chapterSelect) return; - + const selectedSubject = subjectSelect.value; - chapterSelect.innerHTML = ''; - - if (selectedSubject && subjects[selectedSubject]) { - subjects[selectedSubject].chapters.forEach((chapter, index) => { + + if (selectedSubject && SUBJECT_CATALOG[selectedSubject]) { + SUBJECT_CATALOG[selectedSubject].chapters.forEach((chapterName, index) => { const option = document.createElement('option'); option.value = index; - option.textContent = chapter; + option.textContent = chapterName; chapterSelect.appendChild(option); }); } } -function saveMaterial() { - const subjectName = document.getElementById('materialSubject').value; - const chapterIndex = document.getElementById('materialChapter').value; - const materialName = document.getElementById('materialName').value; - const materialType = document.getElementById('materialType').value; - +function saveNewMaterial() { + const subjectSelect = document.getElementById('materialSubject'); + const chapterSelect = document.getElementById('materialChapter'); + const nameInput = document.getElementById('materialName'); + const typeInput = document.getElementById('materialType'); + + const subjectName = subjectSelect ? subjectSelect.value : ''; + const chapterIndex = chapterSelect ? chapterSelect.value : ''; + const materialName = nameInput ? nameInput.value.trim() : ''; + const materialType = typeInput ? typeInput.value : 'pdf'; + if (!subjectName || !materialName) { alert('Please fill in all required fields'); return; } - + const material = { name: materialName, type: materialType, dateAdded: new Date().toLocaleDateString() }; - + if (chapterIndex !== '') { - folderStructure[subjectName].chapters[chapterIndex].materials.push(material); + materialFolders[subjectName].chapters[chapterIndex].materials.push(material); } else { - folderStructure[subjectName].materials.push(material); + materialFolders[subjectName].materials.push(material); } - - renderFolderStructure(); - hideAddMaterialModal(); + + renderMaterialFolders(); + closeMaterialModal(); } -function removeMaterial(subjectName, chapterIndex, materialIndex) { - if (chapterIndex !== null) { - folderStructure[subjectName].chapters[chapterIndex].materials.splice(materialIndex, 1); +function deleteMaterial(subjectName, chapterIndex, materialIndex) { + if (chapterIndex !== null && chapterIndex !== undefined) { + materialFolders[subjectName].chapters[chapterIndex].materials.splice(materialIndex, 1); } else { - folderStructure[subjectName].materials.splice(materialIndex, 1); + materialFolders[subjectName].materials.splice(materialIndex, 1); } - - renderFolderStructure(); + + renderMaterialFolders(); } -function searchMaterials() { - const searchTerm = document.getElementById('searchMaterials').value.toLowerCase(); - const materials = document.querySelectorAll('.material-item'); - - materials.forEach(material => { - const materialName = material.querySelector('.material-name'); - if (materialName && materialName.textContent.toLowerCase().includes(searchTerm)) { - material.style.display = 'flex'; - } else { - material.style.display = 'none'; - } +function filterMaterials() { + const searchInput = document.getElementById('searchMaterials'); + const searchTerm = (searchInput && searchInput.value || '').toLowerCase(); + + document.querySelectorAll('.material-item').forEach(item => { + const nameElement = item.querySelector('.material-name'); + const materialName = nameElement ? nameElement.textContent.toLowerCase() : ''; + + item.style.display = materialName.includes(searchTerm) ? 'flex' : 'none'; }); } -// SECTION 3: CGPA Calculator -function populateSubjectSelectors() { +function populateSubjectDropdowns() { const subjectSelect = document.getElementById('subjectSelect'); const materialSubjectSelect = document.getElementById('materialSubject'); - + if (subjectSelect) { - Object.keys(subjects).forEach(subject => { + Object.keys(SUBJECT_CATALOG).forEach(subjectName => { const option = document.createElement('option'); - option.value = subject; - option.textContent = subject; + option.value = subjectName; + option.textContent = subjectName; subjectSelect.appendChild(option); }); - - // Load first subject by default - if (Object.keys(subjects).length > 0) { - subjectSelect.value = Object.keys(subjects)[0]; - loadSubjectMarks(); + + const firstSubject = Object.keys(SUBJECT_CATALOG)[0]; + if (firstSubject) { + subjectSelect.value = firstSubject; + loadMarksForSelectedSubject(); } } - + if (materialSubjectSelect) { - Object.keys(subjects).forEach(subject => { + Object.keys(SUBJECT_CATALOG).forEach(subjectName => { const option = document.createElement('option'); - option.value = subject; - option.textContent = subject; + option.value = subjectName; + option.textContent = subjectName; materialSubjectSelect.appendChild(option); }); } } -function loadSubjectMarks() { +function loadMarksForSelectedSubject() { const subjectSelect = document.getElementById('subjectSelect'); if (!subjectSelect) return; - - const subject = subjectSelect.value; - const marks = subjectMarks[subject]; - - if (marks) { - const ct1 = document.getElementById('ct1'); - const ct2 = document.getElementById('ct2'); - const assignment = document.getElementById('assignment'); - const midSem = document.getElementById('midSem'); - const endSem = document.getElementById('endSem'); - - if (ct1) ct1.value = marks.ct1; - if (ct2) ct2.value = marks.ct2; - if (assignment) assignment.value = marks.assignment; - if (midSem) midSem.value = marks.midSem; - if (endSem) endSem.value = marks.endSem; - } + + const marks = subjectMarksByName[subjectSelect.value]; + if (!marks) return; + + const fieldMap = { + ct1: 'ct1', + ct2: 'ct2', + assignment: 'assignment', + midSem: 'midSem', + endSem: 'endSem' + }; + + Object.entries(fieldMap).forEach(([key, id]) => { + const input = document.getElementById(id); + if (input) input.value = marks[key]; + }); } -function calculateCGPA() { +function handleCalculateCgpaClick() { const subjectSelect = document.getElementById('subjectSelect'); if (!subjectSelect) return; - + const subject = subjectSelect.value; - const ct1 = parseFloat(document.getElementById('ct1').value) || 0; - const ct2 = parseFloat(document.getElementById('ct2').value) || 0; - const assignment = parseFloat(document.getElementById('assignment').value) || 0; - const midSem = parseFloat(document.getElementById('midSem').value) || 0; - const endSem = parseFloat(document.getElementById('endSem').value) || 0; - - // Save marks - subjectMarks[subject] = { ct1, ct2, assignment, midSem, endSem }; - - // Calculate best 2 of 3 from CT1, CT2, Assignment - const caScores = [ct1, ct2, assignment].sort((a, b) => b - a); - const caTotal = caScores[0] + caScores[1]; // Best 2 scores - - const totalScore = caTotal + midSem + endSem; - const percentage = (totalScore / 100) * 100; - - // Determine grade - let grade = 'F'; - let gradePoints = 0; - - for (const [gradeName, gradeData] of Object.entries(gradeScale)) { - if (percentage >= gradeData.min) { - grade = gradeName; - gradePoints = gradeData.points; - break; - } - } - - // Calculate required marks for 9.0 CGPA (A grade = 90%) - const requiredTotal = 90; // 90% for A grade (9 points) - const currentPartial = caTotal + midSem; - const requiredEndSem = Math.max(0, requiredTotal - currentPartial); - - // Calculate overall CGPA - const overallCGPA = calculateOverallCGPA(); - - // Display results + const marks = { + ct1: parseFloat(document.getElementById('ct1').value) || 0, + ct2: parseFloat(document.getElementById('ct2').value) || 0, + assignment: parseFloat(document.getElementById('assignment').value) || 0, + midSem: parseFloat(document.getElementById('midSem').value) || 0, + endSem: parseFloat(document.getElementById('endSem').value) || 0 + }; + + subjectMarksByName[subject] = marks; + + const { grade, points } = computeGradeFromMarks(marks); + const requiredEndSem = computeRequiredEndSemForGrade(marks, 'AA'); + const overallCgpa = computeOverallCgpa(); + const totalScore = computeTotalScoreFromMarks(marks); + const currentScore = document.getElementById('currentScore'); const currentGrade = document.getElementById('currentGrade'); const requirement = document.getElementById('requirement'); - const overallCGPAElement = document.getElementById('overallCGPA'); - + const overallCgpaElement = document.getElementById('overallCGPA'); + if (currentScore) currentScore.textContent = `${Math.round(totalScore)}/100`; - if (currentGrade) currentGrade.textContent = `${grade} (${gradePoints} points)`; + if (currentGrade) currentGrade.textContent = `${grade} (${points} points)`; if (requirement) { - requirement.textContent = requiredEndSem <= 60 ? `${Math.round(requiredEndSem)}/60 in End Sem` : 'Target not achievable'; + requirement.textContent = requiredEndSem <= 60 + ? `${Math.round(requiredEndSem)}/60 in End Sem` + : 'Target not achievable'; } - if (overallCGPAElement) overallCGPAElement.textContent = overallCGPA.toFixed(2); - + if (overallCgpaElement) overallCgpaElement.textContent = overallCgpa.toFixed(2); + const results = document.getElementById('cgpaResults'); if (results) { results.classList.remove('hidden'); @@ -705,68 +702,84 @@ function calculateCGPA() { } } -function calculateOverallCGPA() { +function computeTotalScoreFromMarks(marks) { + const caScores = [marks.ct1, marks.ct2, marks.assignment].sort((a, b) => b - a); + const caTotal = caScores[0] + caScores[1]; + return caTotal + marks.midSem + marks.endSem; +} + +function computeGradeFromMarks(marks) { + const totalScore = computeTotalScoreFromMarks(marks); + const percentage = (totalScore / 100) * 100; + + let grade = 'F'; + let points = 0; + + for (const [gradeName, gradeData] of Object.entries(GRADE_SCALE)) { + if (percentage >= gradeData.min) { + grade = gradeName; + points = gradeData.points; + break; + } + } + + return { grade, points }; +} + +function computeRequiredEndSemForGrade(marks, gradeName) { + const targetTotal = GRADE_SCALE[gradeName].min; + const caScores = [marks.ct1, marks.ct2, marks.assignment].sort((a, b) => b - a); + const caTotal = caScores[0] + caScores[1]; + const currentPartial = caTotal + marks.midSem; + + return Math.max(0, targetTotal - currentPartial); +} + +function computeOverallCgpa() { let totalCredits = 0; let weightedPoints = 0; - - Object.entries(subjects).forEach(([subjectName, subjectData]) => { - const marks = subjectMarks[subjectName]; + + Object.entries(SUBJECT_CATALOG).forEach(([subjectName, subjectData]) => { + const marks = subjectMarksByName[subjectName]; if (marks) { - const caScores = [marks.ct1, marks.ct2, marks.assignment].sort((a, b) => b - a); - const caTotal = caScores[0] + caScores[1]; - const totalScore = caTotal + marks.midSem + marks.endSem; - const percentage = (totalScore / 100) * 100; - - let gradePoints = 0; - for (const [, gradeData] of Object.entries(gradeScale)) { - if (percentage >= gradeData.min) { - gradePoints = gradeData.points; - break; - } - } - + const { points } = computeGradeFromMarks(marks); totalCredits += subjectData.credits; - weightedPoints += gradePoints * subjectData.credits; + weightedPoints += points * subjectData.credits; } }); - + return totalCredits > 0 ? weightedPoints / totalCredits : 0; } -// SECTION 4: Performance Modes -function calculateModeDurations() { - const totalChapters = Object.values(subjects).reduce((sum, subject) => sum + subject.totalChapters, 0); - +function renderModeDurations() { + const totalChapters = Object.values(SUBJECT_CATALOG).reduce((sum, subject) => sum + subject.totalChapters, 0); + const easyDuration = document.getElementById('easyDuration'); const normalDuration = document.getElementById('normalDuration'); const intenseChapters = document.getElementById('intenseChapters'); - + if (easyDuration) easyDuration.textContent = `${totalChapters} days`; if (normalDuration) normalDuration.textContent = `${Math.ceil(totalChapters / 2)} days`; if (intenseChapters) intenseChapters.textContent = `${Math.ceil(totalChapters / 7)}`; } -function selectPerformanceMode(mode) { - console.log('Selecting performance mode:', mode); - - // Update UI +function handleModeSelection(mode) { document.querySelectorAll('.mode-card').forEach(card => { card.classList.remove('selected'); }); + const selectedCard = document.querySelector(`[data-mode="${mode}"]`); if (selectedCard) { selectedCard.classList.add('selected'); } - - // Update study mode selector in Section 1 + const studyModeSelect = document.getElementById('studyMode'); if (studyModeSelect) { studyModeSelect.value = mode; } - - // Show mode details - showModeBreakdown(mode); - + + renderModeBreakdown(mode); + const details = document.getElementById('selectedModeDetails'); if (details) { details.classList.remove('hidden'); @@ -774,18 +787,18 @@ function selectPerformanceMode(mode) { } } -function showModeBreakdown(mode) { +function renderModeBreakdown(mode) { const breakdown = document.getElementById('modeBreakdown'); if (!breakdown) return; - - const subjectList = Object.entries(subjects).sort((a, b) => b[1].credits - a[1].credits); - + + const subjectList = Object.entries(SUBJECT_CATALOG).sort((a, b) => b[1].credits - a[1].credits); + breakdown.innerHTML = ''; - + subjectList.forEach(([subjectName, subjectData]) => { const item = document.createElement('div'); item.className = 'breakdown-item'; - + let allocation = ''; if (mode === 'easy') { allocation = `${subjectData.totalChapters} days (1 chapter/day)`; @@ -795,18 +808,33 @@ function showModeBreakdown(mode) { const priority = subjectData.credits >= 3 ? 'High Priority' : 'Medium Priority'; allocation = `${priority} - ${subjectData.totalChapters} chapters`; } - + item.innerHTML = ` - ${subjectName} (${subjectData.credits} credits) + ${escapeHtml(subjectName)} (${subjectData.credits} credits) ${allocation} `; - + breakdown.appendChild(item); }); } -// Make functions globally available for onclick handlers -window.toggleFolder = toggleFolder; -window.toggleChapter = toggleChapter; -window.removeMaterial = removeMaterial; -window.updateProgress = updateProgress; +function escapeHtml(text) { + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function sanitizeHtmlId(value) { + return String(value) + .replace(/[^a-zA-Z0-9-]+/g, '-') + .replace(/^-+|-+$/g, '') + .toLowerCase(); +} + +window.toggleFolder = toggleSubjectFolder; +window.toggleChapter = toggleChapterFolder; +window.removeMaterial = deleteMaterial; +window.updateProgress = toggleDayCompletion; \ No newline at end of file