(() => { let gradesSettings = { hideChart: false, hideClassAverage: false }; async function loadGradesSettings() { try { const settings = await storageManager.get("pageSettings_grades", {}); gradesSettings = { hideChart: settings.hideChart || false, hideClassAverage: settings.hideClassAverage || false }; } catch (error) { console.error("Error loading grades settings:", error); } } async function transformGradesPage() { try { await loadGradesSettings(); const tanuloIdElement = document.querySelector("#TanuloId"); const tanuloId = tanuloIdElement ? tanuloIdElement.value : "772481"; const gradesData = await fetchGradesFromAPI(tanuloId); const studentAverage = calculateOverallAverage(gradesData.subjects); const classAverage = calculateOverallClassAverage(gradesData.subjects); window.currentGradesData = gradesData; const htmlString = await generatePageHTML( gradesData, studentAverage, classAverage, ); const template = document.createElement('template'); template.innerHTML = htmlString; helper.clearElement(document.body); document.body.appendChild(template.content); setupUserDropdown(); if (!gradesSettings.hideChart) { const script = document.createElement("script"); script.src = chrome.runtime.getURL("grades/chart.js"); document.head.appendChild(script); script.onload = () => { setupGradesChart(gradesData.subjects); }; } setupEventListeners(); setupGradesListScrolling(); loadingScreen.hide(); } catch (error) { console.error("Error loading grades:", error); loadingScreen.hide(); } } async function fetchGradesFromAPI(tanuloId) { try { const currentDomain = window.location.origin; let apiUrl = `${currentDomain}/api/TanuloErtekelesByTanuloApi/GetTanuloErtekelesByTanuloGridTanuloView?sort=&group=&filter=&data=%7B%22tanuloId%22%3A%22${tanuloId}%22%2C%22oktatasiNevelesiFeladatId%22%3A%227895%22%2C%22isOsztalyAtlagMegjelenik%22%3A%22True%22%7D&_=${Date.now()}`; let response = await fetch(apiUrl, { method: "GET", credentials: "include", headers: { Accept: "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest", }, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } let data = await response.json(); if (data.Data && Array.isArray(data.Data) && data.Data.length === 1 && data.Data[0].ID === 0) { console.log("Received only ID: 0, retrying with alternative endpoint..."); apiUrl = `${currentDomain}/api/TanuloErtekelesByTanuloApi/GetTanuloErtekelesByTanuloGridTanuloView?sort=&group=&filter=&data=%7B%22tanuloId%22%3A%22${tanuloId}%22%2C%22oktatasiNevelesiFeladatId%22%3A%221160%22%2C%22isOsztalyAtlagMegjelenik%22%3A%22True%22%7D&_=${Date.now()}`; response = await fetch(apiUrl, { method: "GET", credentials: "include", headers: { Accept: "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest", }, }); if (!response.ok) { throw new Error(`HTTP error on retry! status: ${response.status}`); } data = await response.json(); } return await processAPIGradesData(data); } catch (error) { console.error("Error fetching grades from API:", error); return await extractGradesDataFromDOM(); } } async function processAPIGradesData(apiData) { const subjects = []; if (!apiData.Data || !Array.isArray(apiData.Data)) { return { schoolInfo: { id: await storageManager.get("schoolCode", ""), name: await storageManager.get("schoolName", "OM azonosító - Iskola neve"), }, userData: { name: await storageManager.get("userName", "Felhasználónév"), time: document.querySelector(".usermenu_timer")?.textContent?.trim() || "45:00", }, subjects: [], }; } apiData.Data.forEach((subject) => { if ( subject.TantargyNev && subject.TantargyNev !== "Magatartás/Szorgalom" ) { const grades = []; const monthFields = [ "Szeptember", "Oktober", "November", "December", "JanuarI", "I", "JanuarII", "Februar", "Marcius", "Aprilis", "Majus", "Junius", "Julius", "Augusztus", "II", ]; monthFields.forEach((month) => { const monthData = subject[month]; if (monthData && monthData.trim() !== "") { const gradeMatches = monthData.match( /]*data-tanuloertekelesid[^>]*>([^<]+)<\/span>/g, ); if (gradeMatches) { gradeMatches.forEach((gradeHtml) => { const gradeValue = gradeHtml .match(/>([^<]+)<\/span>/)?.[1] ?.trim(); if ( gradeValue && gradeValue !== "-" && !gradeValue.includes("%") ) { const dateMatch = gradeHtml.match(/data-datum='([^']*)'/); const typeMatch = gradeHtml.match(/data-tipusmod='([^']*)'/); const themeMatch = gradeHtml.match( /data-ertekelestema='([^']*)'/, ); const weightMatch = gradeHtml.match(/data-suly='([^']*)'/); const teacherMatch = gradeHtml.match( /data-ertekelonyomtatasinev='([^']*)'/, ); const theme = themeMatch ? themeMatch[1] .replace("Téma: ", "") .replace(/&#\d+;/g, (match) => { const code = match.match(/\d+/)[0]; return String.fromCharCode(code); }) : ""; const teacher = teacherMatch ? teacherMatch[1].replace(/&#\d+;/g, (match) => { const code = match.match(/\d+/)[0]; return String.fromCharCode(code); }) : ""; const type = typeMatch ? typeMatch[1].replace(/&#\d+;/g, (match) => { const code = match.match(/\d+/)[0]; return String.fromCharCode(code); }) : ""; grades.push({ value: gradeValue, date: dateMatch ? dateMatch[1] : "", type: type, theme: theme, weight: weightMatch ? weightMatch[1] : "", teacher: teacher, isSemesterGrade: type.toLowerCase().includes("félévi") || theme.toLowerCase().includes("félévi"), isYearEndGrade: type.toLowerCase().includes("évvégi") || theme.toLowerCase().includes("évvégi") || type.toLowerCase().includes("év végi") || theme.toLowerCase().includes("év végi"), }); } }); } } }); if (grades.length > 0) { subjects.push({ name: subject.TantargyNev, grades: grades, average: subject.Atlag || 0, classAverage: subject.OsztalyAtlag || 0, }); } } }); return { schoolInfo: { id: await storageManager.get("schoolCode", ""), name: await storageManager.get("schoolName", "Iskola"), }, userData: { name: await storageManager.get("userName", "Felhasználó"), time: document.querySelector(".usermenu_timer")?.textContent?.trim() || "45:00", }, subjects: subjects, }; } async function extractGradesDataFromDOM() { const subjects = []; const rows = document.querySelectorAll( "#Osztalyzatok_7895TanuloErtekelesByTanuloGrid tbody tr", ); rows.forEach((row) => { const cells = row.querySelectorAll("td"); if (cells.length >= 17) { const subjectName = cells[2].textContent.trim(); if (subjectName && subjectName !== "Magatartás/Szorgalom") { const grades = []; const months = [ LanguageManager.t("grades.september"), LanguageManager.t("grades.october"), LanguageManager.t("grades.november"), LanguageManager.t("grades.december"), LanguageManager.t("grades.january_1"), LanguageManager.t("grades.january_2"), LanguageManager.t("grades.february"), LanguageManager.t("grades.march"), LanguageManager.t("grades.april"), LanguageManager.t("grades.may"), LanguageManager.t("grades.june_1"), LanguageManager.t("grades.june_2"), ]; months.forEach((month, index) => { const gradeElements = cells[index + 3].querySelectorAll( "span[data-tanuloertekelesid]", ); gradeElements.forEach((element) => { const gradeText = element.textContent.trim(); if (gradeText && gradeText !== "-" && !gradeText.includes("%")) { const type = element.getAttribute("data-tipusmod") || ""; const theme = element.getAttribute("data-ertekelestema") || ""; const dataType = element.getAttribute("data-tipus") || ""; grades.push({ value: gradeText, date: element.getAttribute("data-datum"), type: type, theme: theme.replace("Téma: ", ""), weight: element.getAttribute("data-suly"), teacher: element.getAttribute("data-ertekelonyomtatasinev"), isSemesterGrade: type.toLowerCase().includes("félévi") || theme.toLowerCase().includes("félévi") || dataType.toLowerCase().includes("félévi"), isYearEndGrade: type.toLowerCase().includes("évvégi") || theme.toLowerCase().includes("évvégi") || type.toLowerCase().includes("év végi") || theme.toLowerCase().includes("év végi") || dataType.toLowerCase().includes("évvégi") || dataType.toLowerCase().includes("év végi"), }); } }); }); const avgText = cells[16].textContent.trim(); const classAvgText = cells[17].textContent.trim(); const average = avgText !== "-" ? parseFloat(avgText.replace(",", ".")) : 0; const classAvg = classAvgText !== "-" ? parseFloat(classAvgText.replace(",", ".")) : 0; if (grades.length > 0) { subjects.push({ name: subjectName, grades: grades, average: average || 0, classAverage: classAvg || 0, }); } } } }); return { schoolInfo: { id: await storageManager.get("schoolCode", ""), name: await storageManager.get("schoolName", "Iskola"), }, userData: { name: await storageManager.get("userName", "Felhasználó"), time: document.querySelector(".usermenu_timer")?.textContent?.trim() || "45:00", }, subjects: subjects, }; } function calculateOverallAverage(subjects) { const validSubjects = subjects.filter((s) => s.average > 0); if (validSubjects.length === 0) return 0; return ( validSubjects.reduce((sum, s) => sum + s.average, 0) / validSubjects.length ); } function calculateOverallClassAverage(subjects) { const validSubjects = subjects.filter((s) => s.classAverage > 0); if (validSubjects.length === 0) return 0; return ( validSubjects.reduce((sum, s) => sum + s.classAverage, 0) / validSubjects.length ); } function shortenEvaluationName(name, maxLength = 30) { if (!name) return ""; if (name.length <= maxLength) return name; return name.substring(0, maxLength - 3) + "..."; } function generateGradeItem(grade) { const semesterClass = grade.isSemesterGrade ? "semester-grade" : ""; const yearEndClass = grade.isYearEndGrade ? "year-end-grade" : ""; const dateObj = new Date(grade.date); const monthNames = [ LanguageManager.t("months.january"), LanguageManager.t("months.february"), LanguageManager.t("months.march"), LanguageManager.t("months.april"), LanguageManager.t("months.may"), LanguageManager.t("months.june"), LanguageManager.t("months.july"), LanguageManager.t("months.august"), LanguageManager.t("months.september"), LanguageManager.t("months.october"), LanguageManager.t("months.november"), LanguageManager.t("months.december"), ]; const formattedDate = `${monthNames[dateObj.getMonth()]} ${dateObj.getDate()}`; const shortenedTheme = shortenEvaluationName(grade.theme); return `
${grade.value}
${shortenedTheme}
${grade.type}
${formattedDate}
`; } function calculateGradeDistribution(subjects) { const distribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; subjects.forEach((subject) => { subject.grades.forEach((grade) => { const value = parseInt(grade.value); if (value >= 1 && value <= 5 && !grade.value.includes("%")) { distribution[value]++; } }); }); return distribution; } async function generatePageHTML(data, studentAverage, classAverage) { const totalGrades = data.subjects.reduce( (sum, subject) => sum + subject.grades.length, 0, ); const gradeDistribution = calculateGradeDistribution(data.subjects); const semesterGrades = extractSemesterGrades(data.subjects); const yearEndGrades = extractYearEndGrades(data.subjects); const studentGradeLevel = Math.floor(studentAverage) || 0; const classGradeLevel = Math.floor(classAverage) || 0; schoolNameFull = `${data.schoolInfo.id} - ${data.schoolInfo.name}`; shortenedSchoolName = helper.shortenSchoolName(schoolNameFull); const showClassAverage = !gradesSettings.hideClassAverage && classAverage > 0; const showChart = !gradesSettings.hideChart; return `
${await createTemplate.header()}
${showChart ? `
${LanguageManager.t("grades.chart_title")} (${totalGrades}db)
${studentAverage > 0 ? studentAverage.toFixed(2) : "-"}
${ showClassAverage ? `
${classAverage.toFixed(2)}
` : "" }
${Object.entries(gradeDistribution) .map( ([grade, count]) => `
${grade} ${count}
`, ) .join("")}
` : `
${LanguageManager.t("grades.chart_title")} (${totalGrades}db)
${studentAverage > 0 ? studentAverage.toFixed(2) : "-"}
${ showClassAverage ? `
${classAverage.toFixed(2)}
` : "" }
${Object.entries(gradeDistribution) .map( ([grade, count]) => `
${grade} ${count}
`, ) .join("")}
`} ${ yearEndGrades.length > 0 ? `

${LanguageManager.t("grades.year_end_evaluations")}

${yearEndGrades .map( (grade) => `
${grade.value}
${grade.subject}
`, ) .join("")}
` : "" }
${generateSubjectCards(data.subjects)}
`; } function extractSemesterGrades(subjects) { const semesterGrades = []; subjects.forEach((subject) => { const semesterGrade = subject.grades.find( (grade) => grade.isSemesterGrade, ); if (semesterGrade) { semesterGrades.push({ subject: subject.name, value: semesterGrade.value, date: semesterGrade.date, }); } }); return semesterGrades; } function extractYearEndGrades(subjects) { const yearEndGrades = []; subjects.forEach((subject) => { const yearEndGrade = subject.grades.find((grade) => grade.isYearEndGrade); if (yearEndGrade) { yearEndGrades.push({ subject: subject.name, value: yearEndGrade.value, date: yearEndGrade.date, }); } }); return yearEndGrades; } function calculateGradePoints(subjects) { const allGrades = []; subjects.forEach((subject) => { subject.grades.forEach((grade) => { const date = new Date(grade.date); const value = parseInt(grade.value); const weight = parseInt(grade.weight?.match(/\d+/)?.[0] || "100") / 100; if (date && value && weight && !grade.value.includes("%")) { allGrades.push({ date, value, weight, }); } }); }); allGrades.sort((a, b) => a.date - b.date); let totalWeight = 0; let weightedSum = 0; return allGrades.map((grade) => { totalWeight += grade.weight; weightedSum += grade.value * grade.weight; return { date: grade.date.toISOString(), grade: grade.value, average: weightedSum / totalWeight, }; }); } function setupGradesChart(subjects) { const ctx = document.getElementById("gradesChart"); if (!ctx) return; const gradePoints = calculateGradePoints(subjects); const accentColor = getComputedStyle(document.documentElement) .getPropertyValue("--accent-accent") .trim(); new Chart(ctx, { type: "line", data: { labels: gradePoints.map((_, index) => ""), datasets: [ { label: "Átlag", data: gradePoints.map((p) => p.average), borderWidth: 5, borderColor: accentColor, tension: 0.5, fill: true, backgroundColor: function (context) { const chart = context.chart; const { ctx, chartArea } = chart; if (!chartArea) return null; const gradientBg = ctx.createLinearGradient( 0, chartArea.bottom, 0, chartArea.top, ); gradientBg.addColorStop(0, accentColor + "30"); gradientBg.addColorStop(1, accentColor + "10"); return gradientBg; }, pointBackgroundColor: accentColor, pointRadius: 0, pointHoverRadius: 0, }, ], }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { min: 1, max: 5, ticks: { stepSize: 1, color: accentColor, }, grid: { color: accentColor + "20", lineWidth: 1, borderDash: [5, 5], }, }, x: { display: false, }, }, plugins: { legend: { display: false, }, tooltip: { backgroundColor: getComputedStyle( document.documentElement, ).getPropertyValue("--card-card"), titleColor: getComputedStyle( document.documentElement, ).getPropertyValue("--text-primary"), bodyColor: getComputedStyle( document.documentElement, ).getPropertyValue("--text-primary"), borderColor: getComputedStyle(document.documentElement).getPropertyValue( "--text-teritary", ) + "20", borderWidth: 1, padding: 12, displayColors: false, callbacks: { title: () => "", label: (context) => `Átlag: ${context.raw.toFixed(2)}`, }, }, }, }, }); } function generateSubjectCards(subjects) { const sortedSubjects = [...subjects].sort( (a, b) => a.grades.length - b.grades.length, ); return sortedSubjects .map((subject) => { const regularGrades = subject.grades .filter((grade) => !grade.isSemesterGrade) .reverse(); const myGrade = Math.floor(subject.average) || 0; const classGrade = Math.floor(subject.classAverage) || 0; const showClassAvg = !gradesSettings.hideClassAverage && subject.classAverage > 0; return `

${subject.name}

${subject.average > 0 ? subject.average.toFixed(2) : "-"}
${ showClassAvg ? `
${subject.classAverage.toFixed(2)}
` : "" }
${regularGrades.map(generateGradeItem).join("")}
`; }) .join(""); } function setupEventListeners() { const timerEl = document.getElementById("logoutTimer"); if (timerEl) { const startTime = parseInt( timerEl.textContent?.match(/\d+/)?.[0] || "45", ); let timeLeft = startTime * 60; const updateTimer = () => { const minutes = Math.floor(timeLeft / 60); const seconds = timeLeft % 60; timerEl.textContent = `${minutes}:${seconds.toString().padStart(2, "0")}`; timeLeft--; if (timeLeft < 0) { window.location.href = chrome.runtime.getURL("logout/logout.html"); } }; setInterval(updateTimer, 1000); } } function setupGradesListScrolling() { const gradesLists = document.querySelectorAll(".grades-list"); gradesLists.forEach((list) => { const checkScrollable = () => { if (list.scrollHeight > list.clientHeight) { list.classList.add("scrollable", "has-more"); const handleScroll = () => { const isAtBottom = list.scrollTop + list.clientHeight >= list.scrollHeight - 5; if (isAtBottom) { list.classList.remove("has-more"); } else { list.classList.add("has-more"); } }; list.addEventListener("scroll", handleScroll); handleScroll(); } else { list.classList.remove("scrollable", "has-more"); } }; checkScrollable(); }); } if (window.location.href.includes("/TanuloErtekeles/Osztalyzatok")) { transformGradesPage(); } })();