Genesis What If?

What if you got a 45/50 on that next test? What would your grade be? This extension answers those questions without you having to manually calculate the answer. All grades are editable, and the buttons are self-explanatory. (New Jersey)

// ==UserScript==
// @name         Genesis What If?
// @namespace    http://tampermonkey.net/
// @version      1.35
// @description  What if you got a 45/50 on that next test? What would your grade be? This extension answers those questions without you having to manually calculate the answer. All grades are editable, and the buttons are self-explanatory. (New Jersey)
// @author       You
// @license      MIT
// @match        https://parents.c3.genesisedu.net/*/parents?tab1=studentdata&tab2=gradebook&tab3=coursesummary*
// @icon         https://parents.robbinsville.k12.nj.us//genesis/images/newIcon2.ico
// @grant        none
// ==/UserScript==

(function() {
const fractionPattern = /(\d+(\.\d+)?) \/ (\d+)/;
var numerator = 0;
var denominator = 0;
// const headings = document.querySelectorAll(".listheading")
let specialnumber = -2

var listHeadings = document.querySelectorAll('.listheading');
var listRowEvens = document.querySelectorAll('.listroweven');
var listRowOdds = document.querySelectorAll('.listrowodd');
function HowManyCategories() {
    var secondListHeading = listHeadings[1];
    var thirdListHeading = listHeadings[2];
    var elementsBetweenHeadings = 0;

    // Iterate through the elements between the second and third list headings
    for (let i = 0; i < listRowEvens.length; i++) {
      let element = listRowEvens[i];

      if (element.compareDocumentPosition(secondListHeading) & Node.DOCUMENT_POSITION_PRECEDING &&
          element.compareDocumentPosition(thirdListHeading) & Node.DOCUMENT_POSITION_FOLLOWING) {
        elementsBetweenHeadings++;
      }
    }

    for (let i = 0; i < listRowOdds.length; i++) {
      let element = listRowOdds[i];

      if (element.compareDocumentPosition(secondListHeading) & Node.DOCUMENT_POSITION_PRECEDING &&
          element.compareDocumentPosition(thirdListHeading) & Node.DOCUMENT_POSITION_FOLLOWING) {
        elementsBetweenHeadings++;
      }
    }

    return elementsBetweenHeadings;
  }
var categoryWeightage = {}
var categoryGrades = {}
function AssignWeightage(row){
    let categoryName = row.querySelector('td.cellLeft b').textContent;
  let weightString = row.querySelector('td.cellRight:nth-child(2)').textContent;
  let weight = parseFloat(weightString.replace('%', '')) / 100; // Convert to decimal
    categoryGrades[categoryName] = [0,0]
      categoryWeightage[categoryName] = weight;
    //console.log(categoryGrades)
}
if (listHeadings.length >= 3) {
    var weightage = true;
    var numCategories = HowManyCategories()
    specialnumber -= numCategories
    //console.log(numCategories)
    let rows = document.querySelectorAll('.listroweven, .listrowodd');
    let filteredRows = Array.from(rows).slice(0, -2)
    let catArray = Array.from(filteredRows).slice(filteredRows.length - numCategories, filteredRows.length)
    catArray.forEach(row => AssignWeightage(row))
    //console.log(categoryWeightage)

}

function IdentifyGrade(){
    newGrade.textContent = " "
    newGrade.textContent = "|| " + percentage + "% " + percentageToLetterGrade(percentage)
    originalGrade.insertAdjacentElement('afterend', newGrade)
}


function EditableGrade(row){
    let cells = row.querySelectorAll('.cellLeft')
    let cell = cells[1]

    if(weightage){
        var categoryName2 = cells[0].querySelectorAll('div')[2].textContent.trim()
        //console.log(categoryName)
        var cellLeft = cells[0];

        var existingDiv = cellLeft.querySelector('div[style="font-size: 8pt;font-style: italic;"]');

        // Create a select element
        var selectElement = document.createElement('select');

        // Populate the select element with options from the dictionary
        for (var category in categoryWeightage) {
            var optionElement = document.createElement('option');
            optionElement.value = category;
            optionElement.text = category;
            if (category === existingDiv.textContent.trim()) {
                optionElement.selected = true;
            }
            selectElement.appendChild(optionElement);
        }

        // Add an event listener to the select element
        selectElement.addEventListener('change', function() {
            var selectedCategory = selectElement.options[selectElement.selectedIndex].text;
            var selectedValue = categoryWeightage[selectedCategory];

            // You can do something with the selected value if needed

            // Update the content of the element based on the selected category
            cellLeft.querySelector('input[type="hidden"]').value = selectedCategory;


        });

        // Replace the existing div with the select element
        existingDiv.replaceWith(selectElement);
    }

    let childNodes = cell.childNodes;

    var divElements = cell.getElementsByTagName('div');
    var secondToLastDiv = divElements[divElements.length - 2];
    if (secondToLastDiv !== undefined) {
        var spanElement = secondToLastDiv.querySelector('span');
    }
    if (spanElement && (spanElement.textContent.trim() === 'Absent' || spanElement.textContent.trim() === 'Missing')) {
    return;
    }


    let innerGrade = "";
    for (let i = 0; i < childNodes.length; i++) {
        if (childNodes[i].nodeType === Node.TEXT_NODE && childNodes[i].textContent.indexOf('/') !== -1) { //current issue tha
            innerGrade += childNodes[i].textContent;
            childNodes[i].textContent = " ";
            let spanElement = document.createElement('span');
            spanElement.textContent = innerGrade;
            spanElement.setAttribute('contentEditable', true)
            cell.replaceChild(spanElement, childNodes[i])
            break;
        }
    }
    innerGrade = innerGrade.trim().replace(/\s+/g, " ")
    const [num, denom] = innerGrade.split('/').map(num => parseFloat(num.trim()));
    if(!weightage){
        numerator += num;
        denominator += denom;
    } else{
        categoryGrades[categoryName2][0] += num
        categoryGrades[categoryName2][1] += denom
    }
    //console.log(innerGrade, num, denom)
}


function SelectGrade(row){
    let cells = row.querySelectorAll('.cellLeft')
    let cell = cells[1]

    if(weightage){
        //var categoryName2 = cells[0].querySelectorAll('div')[2].textContent.trim()
        var selectElement = row.querySelector('.cellLeft select');
        var categoryName2 = selectElement.value;
        //console.log(categoryName2);
    }

    var divElements = cell.getElementsByTagName('div');
    var secondToLastDiv = divElements[divElements.length - 2];
    if (secondToLastDiv !== undefined) {
        var spanElement = secondToLastDiv.querySelector('span');
    }
    if (spanElement && (spanElement.textContent.trim() === 'Absent' || spanElement.textContent.trim() === 'Missing')) {
    return;
    }
    //let cell = row.querySelectorAll('.cellLeft')[1]
    const spanElements = cell.querySelectorAll('span')
      let innerGrade = cell.querySelectorAll('span')[spanElements.length - 1].textContent.trim().replace(/\s+/g, " ")
    const [num, denom] = innerGrade.split('/').map(num => parseFloat(num.trim()));
    var percen = 0;
    if(denom != 0){
      percen = ((num / denom) * 100).toFixed(2);
    } else{
      percen = "NaN"
    }

    // Select the bottom div element
    const bottomDivElement = cell.querySelector('div[style="font-weight: bold;"]');

    // Set the percentage as its content
    //console.log(percen)
    if(percen != "NaN"){
    bottomDivElement.textContent = percen + '%';
    }
    if(!weightage){
        numerator += num;
        denominator += denom;
    }else{
        categoryGrades[categoryName2][0] += num
        categoryGrades[categoryName2][1] += denom
    }
    //console.log(innerGrade, num, denom)

}

function SelectAssignments(){
    let rows = document.querySelectorAll('.listroweven, .listrowodd')
    let rowArray = Array.from(rows).slice(0, specialnumber)
    //console.log(rowArray)
    rowArray.forEach(row => EditableGrade(row))
}

function Refresh(){
    numerator = 0;
    denominator = 0;
    for (let category in categoryGrades) {
    if (categoryGrades.hasOwnProperty(category)) {
        categoryGrades[category] = categoryGrades[category].map(value => 0); // Set all values to zero
        }
    }
    let rows = document.querySelectorAll('.listroweven, .listrowodd');
    let filteredRows = Array.from(rows).slice(0, -2)
    let catArray = Array.from(filteredRows).slice(filteredRows.length - numCategories, filteredRows.length)


    let rowArray = Array.from(rows).slice(0, specialnumber)
    //console.log(rowArray)
    rowArray.forEach(row => SelectGrade(row))
    let percentage = 0.000
    if(!weightage){
        percentage = ((numerator/denominator) * 100 )
    } else {
        for (const category in categoryGrades) {
        const categoryPercentage = (categoryGrades[category][0] / categoryGrades[category][1]) * 100;
        percentage += categoryPercentage * categoryWeightage[category];
        }
        catArray.forEach(row => changeCategoryPercentage(row))
    }

    percentage = percentage.toFixed(2)
    newGrade.textContent = "|| " + percentage + "% " + percentageToLetterGrade(percentage)
    console.log(categoryGrades)



}

function changeCategoryPercentage(row){
    let categoryName = row.querySelector('td.cellLeft b').textContent;
    let weightString = row.querySelector('td.cellRight:nth-child(3)').textContent;
    let category = row.querySelector('td.cellLeft').textContent
    let categoryPercentage = (categoryGrades[category][0] / categoryGrades[category][1]) * 100;
    row.querySelector('td.cellRight:nth-child(3)').textContent = categoryPercentage.toFixed(1) + " %";
}

function addRow(){ //CHANGE THIS SO IT DOES NOT RELY ON CLONING, SO IT WORKS WITH JUST ONE ROW
    let row = document.querySelectorAll('.listroweven, .listrowodd')
    let rowArray = Array.from(row).slice(0, specialnumber)
    //const numberOfRows = rowArray.length

    let lastRow = rowArray[rowArray.length - 1];
    let secondToLastRow = rowArray[rowArray.length - 2];

    let clonedRow = secondToLastRow.cloneNode(true);
    clonedRow.querySelector('b').textContent = "Genesis What If"
    clonedRow.querySelector('b').style.color = "Green"

    clonedRow.querySelectorAll('div')[5].textContent = 'N/A'
    clonedRow.querySelectorAll('div')[5].setAttribute('contentEditable', 'true')

    lastRow.parentNode.insertBefore(clonedRow, lastRow.nextSibling);
}


function percentageToLetterGrade(numberGrade){
        let letter;
      if (numberGrade >= 96.5) {
        letter = 'A+';
      } else if (numberGrade >= 92.5) {
        letter = 'A';
        return letter;
      } else if (numberGrade >= 89.5) {
        letter = 'A-';
      } else if (numberGrade >= 86.5) {
        letter = 'B+';
      } else if (numberGrade >= 82.5) {
        letter = 'B';
      } else if (numberGrade >= 79.5) {
        letter = 'B-';
      } else if (numberGrade >= 76.5) {
        letter = 'C+';
      } else if (numberGrade >= 72.5) {
        letter = 'C';
      } else if (numberGrade >= 69.5) {
        letter = 'C-';
      } else if (numberGrade >= 66.5) {
        letter = 'D+';
      } else if (numberGrade >= 59.5) {
        letter = 'D-';
      } else {
        letter = 'F';
      }
      return letter;
    };

function AddButtons(){
    const headerT = document.querySelectorAll('.listheading')[0];
    const parentDiv = headerT//.parentNode
    let refreshButton = document.createElement("button")
    refreshButton.id="refreshButton"
    refreshButton.innerHTML = "Refresh"
    refreshButton.onclick = () => Refresh();
    refreshButton.title = "The keyboard shortcut for refreshButton is shift + R";

    let addRowButton = document.createElement("button")
    addRowButton.innerHTML = "Add Row"
    addRowButton.onclick = () => addRow();
    addRowButton.title = "The keyboard shortcut for addRowButton is shift + T";

    parentDiv.insertAdjacentElement('beforebegin', refreshButton)
    parentDiv.insertAdjacentElement('beforebegin', addRowButton)
}



SelectAssignments();

let originalGrade = document.querySelectorAll('b')[0]
    //console.log(originalGrade.textContent.trim().replace(/\s+/g, ' '))
let percentage = 0.000
if(!weightage){
    percentage = ((numerator/denominator) * 100 )
} else {
    for (const category in categoryGrades) {
    const categoryPercentage = (categoryGrades[category][0] / categoryGrades[category][1]) * 100;
    percentage += categoryPercentage * categoryWeightage[category];
    }
}
percentage = percentage.toFixed(2)
let newGrade = document.createElement('b')
IdentifyGrade();
AddButtons();

// console.log(categoryGrades)

    function doc_keyUp(e) {

    // this would test for whichever key is 40 (down arrow) and the ctrl key at the same time
    if (e.shiftKey && e.key === 'R') {
        // call your function to do the thing
        Refresh();
    }
}
    function doc_keyUp2(e) {

    if (e.shiftKey && e.key === 'R') {

        Refresh();
    }

    if (e.shiftKey && e.key === 'T') {

        addRow();
    }
}

// Add event listeners for keyup event
document.addEventListener('keyup', doc_keyUp2, false);

// register the handler
document.addEventListener('keyup', doc_keyUp, false);



})();