Cumulative GPA Calculator
GPA Scale Reference
| Letter | Percentage | GPA Points |
| Course Name |
Grade |
Credits |
Grade Pts |
|
${rows}
`;
}
function buildCourseRow(semId, idx, course) {
return `
|
|
|
— |
|
`;
}
function bindSemesterEvents(sem) {
const card = document.getElementById(`semester-${sem.id}`);
card.addEventListener('input', e => {
const t = e.target;
const semId = parseInt(t.dataset.sem);
const idx = parseInt(t.dataset.idx);
const field = t.dataset.field;
const s = semesters.find(s => s.id === semId);
if (s && !isNaN(idx) && field) {
s.courses[idx][field] = t.value;
refreshAll();
}
// Semester name
if (t.classList.contains('sem-name-input')) {
sem.name = t.value;
}
});
card.addEventListener('change', e => {
const t = e.target;
const semId = parseInt(t.dataset.sem);
const idx = parseInt(t.dataset.idx);
const field = t.dataset.field;
const s = semesters.find(s => s.id === semId);
if (s && !isNaN(idx) && field) {
s.courses[idx][field] = t.value;
refreshAll();
}
});
card.addEventListener('click', e => {
const btn = e.target.closest('[data-remove-course]');
if (btn) {
const semId = parseInt(btn.dataset.removeCourse);
const idx = parseInt(btn.dataset.courseIdx);
const s = semesters.find(s => s.id === semId);
if (s && s.courses.length > 1) {
s.courses.splice(idx, 1);
rebuildSemesterBody(s);
refreshAll();
}
}
const remBtn = e.target.closest('[data-remove-sem]');
if (remBtn) {
const semId = parseInt(remBtn.dataset.removeSem);
const idx = semesters.findIndex(s => s.id === semId);
if (idx !== -1) semesters.splice(idx, 1);
card.remove();
refreshAll();
}
const addCourse = e.target.closest('[data-add-course]');
if (addCourse) {
const semId = parseInt(addCourse.dataset.addCourse);
const s = semesters.find(s => s.id === semId);
if (s) {
const newIdx = s.courses.length;
s.courses.push({ name: '', grade: 'A', credits: '3' });
const tbody = document.getElementById(`tbody-${semId}`);
const tr = document.createElement('tr');
tr.id = `row-${semId}-${newIdx}`;
tr.innerHTML = buildCourseRow(semId, newIdx, s.courses[newIdx]).replace(/]*>|<\/tr>/g, '');
tbody.insertAdjacentHTML('beforeend', buildCourseRow(semId, newIdx, s.courses[newIdx]).replace(/^
]*>|<\/tr>$/g, ''));
refreshAll();
}
}
});
}
function rebuildSemesterBody(sem) {
const tbody = document.getElementById(`tbody-${sem.id}`);
tbody.innerHTML = sem.courses.map((c, i) =>
buildCourseRow(sem.id, i, c)
).join('');
}
// ── EXISTING GPA INPUTS ──────────────────────────────────────────
document.getElementById('existingGPA').addEventListener('input', refreshAll);
document.getElementById('existingCredits').addEventListener('input', refreshAll);
// ── ADD SEMESTER BUTTON ──────────────────────────────────────────
document.getElementById('addSemesterBtn').addEventListener('click', addSemester);
// ── INIT: add 1 semester by default ─────────────────────────────
addSemester();