Esprit Moyenne

Better moyenne experience

// ==UserScript==
// @name         Esprit Moyenne
// @namespace    http://tampermonkey.net/
// @version      2025-06-12
// @description  Better moyenne experience
// @author       NotSoHealthy
// @match        https://esprit-tn.com/esponline/Etudiants/Resultat2021.aspx
// @icon         https://www.google.com/s2/favicons?sz=64&domain=esprit-tn.com
// @grant        none
// @license MIT
// ==/UserScript==

(async function() {
    const style = document.createElement('style');
    style.innerHTML = `
    .row{
        margin: 0;
    }

    .table-header {
        color: white;
        background-color: #A80000;
        font-weight: bold;
    }

    .moyenne-high {
        background-color: green;
        color: white;
    }

    .moyenne-low {
        background-color: red;
        color: white;
    }

    #ContentPlaceHolder1_GridView1 {
        width: 100%;
        border-collapse: collapse;
    }

    #ContentPlaceHolder1_GridView1 th,
    #ContentPlaceHolder1_GridView1 td {
        border: 1px solid #ddd;
        padding: 8px;
    }

    #ContentPlaceHolder1_GridView1 tr:nth-child(even) {
        background-color: #f2f2f2;
    }

    #ContentPlaceHolder1_GridView1 tr:hover {
        background-color: #ddd;
    }

    #ContentPlaceHolder1_GridView1 .table-header:hover {
        background-color: #A80000;;
    }

    #downloadButton {
        margin-bottom: 10px;
        padding: 10px 20px;
        font-size: 16px;
        cursor: pointer;
        background-color: #A80000;
        color: white;
        border: none;
        border-radius: 5px;
        transition: transform 0.2s ease;
    }

    #downloadButton:hover {
        transform: scale(1.05);
        background-color: #CC0000;
    }
    `;
    document.head.appendChild(style);

    function loadScript(src) {
        return new Promise(function(resolve, reject) {
            if (document.querySelector(`script[src="${src}"]`)) {
                resolve();
                return;
            }
            const script = document.createElement('script');
            script.src = src;
            script.onload = () => resolve();
            script.onerror = () => reject(new Error(`Failed to load script ${src}`));
            document.head.appendChild(script);
        });
    }

    await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js');
    await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js');

    function getStudentDetails() {
        const nameElement = document.getElementById('Label2');
        const classElement = document.getElementById('Label3');
        const studentName = nameElement ? nameElement.textContent.trim() : 'Unknown Student';
        const studentClass = classElement ? classElement.textContent.trim() : 'Unknown Class';
        return { studentName, studentClass };
    }

    function getLogoImage() {
        const logoElement = document.querySelector('.img-responsive img');
        return logoElement ? logoElement.src : null;
    }

    function tableToJson(table) {
        const data = [];
        const headers = Array.from(table.rows[0].cells).map(cell =>
            cell.innerText.toLowerCase().replace(/ /g, '')
        );
        for (let i = 1; i < table.rows.length; i++) {
            const row = table.rows[i];
            const rowData = {};
            headers.forEach((header, index) => {
                rowData[header] = row.cells[index].innerText;
            });
            data.push(rowData);
        }
        data.forEach(item => {
            item.coef = parseFloat(item.coef.replace(',', '.'));
            item.note_exam = parseFloat(item.note_exam.replace(',', '.'));
            item.note_cc = parseFloat(item.note_cc.replace(',', '.'));
            item.note_tp = parseFloat(item.note_tp.replace(',', '.'));
        });
        return data;
    }

    const table = document.getElementById('ContentPlaceHolder1_GridView1');
    const data = tableToJson(table);

    const sumCoef = data.reduce((sum, item) => sum + item.coef, 0);

    function calculMoyenne(dataSet, totalCoef) {
        let total = 0;
        dataSet.forEach(item => {
            const { note_exam, note_cc, note_tp, coef } = item;
            let moyenne = 0;
            if (isNaN(note_tp)) {
                if (isNaN(note_cc)) {
                    moyenne = note_exam;
                } else {
                    moyenne = note_exam * 0.6 + note_cc * 0.4;
                    if (item.designation == 'Génie logiciel & atelier GL'){
                        moyenne = note_exam * 0.4 + note_cc * 0.6;
                    }
                }
            } else if (isNaN(note_cc)) {
                moyenne = note_exam * 0.8 + note_tp * 0.2;
            } else {
                moyenne = note_exam * 0.5 + note_cc * 0.3 + note_tp * 0.2;
            }
            item.moyenne = moyenne;
            total += moyenne * coef;
        });
        dataSet.push({
            designation: 'Moyenne',
            coef: totalCoef,
            nom_ens: '',
            note_cc: '',
            note_tp: '',
            note_exam: '',
            moyenne: total / totalCoef,
        });
        return dataSet;
    }

    const newData = calculMoyenne(data, sumCoef);

    function populateTable(dataSet) {
        let title = document.querySelector(".col-xs-10 > center > h1") ;
        title.innerHTML = title.innerHTML + ": " + (document.querySelectorAll("tr").length-1).toString();
        document.querySelectorAll(".row ~ br").forEach((e)=>e.remove());
        let tableContent = `
            <tr class="table-header">
                <th scope="col">DESIGNATION</th>
                <th scope="col">COEF</th>
                <th scope="col">NOM_ENS</th>
                <th scope="col">NOTE_CC</th>
                <th scope="col">NOTE_TP</th>
                <th scope="col">NOTE_EXAM</th>
                <th scope="col">Moyenne</th>
            </tr>`;
        dataSet.forEach(item => {
            const moyenneClass = !isNaN(item.moyenne)
                ? item.moyenne >= 8 ? 'moyenne-high' : 'moyenne-low'
                : '';
            tableContent += `
                <tr>
                    <td>${item.designation}</td>
                    <td>${item.coef}</td>
                    <td>${item.nom_ens}</td>
                    <td>${isNaN(item.note_cc) ? '' : item.note_cc}</td>
                    <td>${isNaN(item.note_tp) ? '' : item.note_tp}</td>
                    <td>${isNaN(item.note_exam) ? '' : item.note_exam}</td>
                    <td class="${moyenneClass}">${!isNaN(item.moyenne) ? item.moyenne.toFixed(2) : ''}</td>
                </tr>`;
        });
        const tbody = table.querySelector('tbody') || table;
        tbody.innerHTML = tableContent;
        addDownloadButton();
    }

    function addDownloadButton() {
        const downloadButton = document.createElement('button');
        downloadButton.textContent = 'Download as PDF';
        downloadButton.id = 'downloadButton';
        downloadButton.type = 'button';
        table.parentNode.insertBefore(downloadButton, table);
        downloadButton.addEventListener('click', function(event) {
            event.preventDefault();
            generatePDF();
        });
    }

    populateTable(newData);

    async function generatePDF() {
        try {
            const { studentName, studentClass } = getStudentDetails();
            const logoSrc = getLogoImage();
            const element = document.getElementById('ContentPlaceHolder1_GridView1');
            const canvas = await html2canvas(element, {
                useCORS: true,
                allowTaint: true
            });
            const imgData = canvas.toDataURL('image/png');
            const pdf = new window.jspdf.jsPDF('p', 'pt', 'a4');
            pdf.setFontSize(16);
            pdf.text(`Student Name: ${studentName}`, 20, 20);
            pdf.setTextColor(204, 0, 0);
            pdf.text(`Class: ${studentClass}`, 20, 40);
            if (logoSrc) {
                const logoImage = new Image();
                logoImage.src = logoSrc;
                pdf.addImage(logoImage, 'PNG', pdf.internal.pageSize.getWidth() - 80, 10, 60, 60);
            }
            pdf.setTextColor(0, 0, 0);
            const pdfWidth = pdf.internal.pageSize.getWidth();
            const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
            pdf.addImage(imgData, 'PNG', 0, 70, pdfWidth, pdfHeight);
            pdf.save(`${studentName}_Grades.pdf`);
        } catch (error) {
            console.error('Could not generate PDF:', error);
            alert('Could not generate PDF: ' + error.message);
        }
    }
    function newDropCheck(dataset) {
        let tableList = Array.from(document.querySelectorAll('#ContentPlaceHolder1_GridView1 tr td:nth-child(1)'));
        tableList.pop()
        let oldDrop = JSON.parse(localStorage.getItem('oldDrop')) || [];
        dataset.pop();
        let newDrop = dataset.map((e) => e.designation);
        localStorage.setItem('oldDrop', JSON.stringify(newDrop));
        let newItems = newDrop.filter(n => !oldDrop.includes(n))
        newItems.forEach((item)=>{
            tableList.forEach((element)=>{
                if (element.textContent === item){
                    element.textContent += ' 🔴'
                }
            })
        })
    }
    newDropCheck(newData);
})();