486 lines
15 KiB
JavaScript
486 lines
15 KiB
JavaScript
/**
|
||
* Notenrechner DOM-Version
|
||
* Moderne JavaScript DOM-Manipulation mit addEventListener
|
||
* David & Karo - EIS Projekt
|
||
*/
|
||
|
||
// Notenmaßstab definieren
|
||
const NOTENMASSTAB = {
|
||
'sehr gut': { min: 90, max: 100, color: '#ff1493' },
|
||
'gut': { min: 75, max: 89, color: '#ff69b4' },
|
||
'befriedigend': { min: 60, max: 74, color: '#ffa500' },
|
||
'ausreichend': { min: 50, max: 59, color: '#ff8c00' },
|
||
'nicht bestanden': { min: 0, max: 49, color: '#dc143c' }
|
||
};
|
||
|
||
/**
|
||
* Berechnet die Note basierend auf der Punktzahl
|
||
* @param {number} punkte - Erreichte Punktzahl
|
||
* @param {number} maxPunkte - Maximale Punktzahl (default: 100)
|
||
* @returns {object} - Enthält Note, Prozentage und Details
|
||
*/
|
||
function berechneNote(punkte, maxPunkte = 100) {
|
||
// Eingabe validieren
|
||
punkte = parseFloat(punkte);
|
||
maxPunkte = parseFloat(maxPunkte);
|
||
|
||
if (isNaN(punkte) || isNaN(maxPunkte)) {
|
||
return {
|
||
note: 'Fehler',
|
||
prozent: 0,
|
||
valid: false,
|
||
nachricht: 'Bitte geben Sie Zahlen ein!'
|
||
};
|
||
}
|
||
|
||
if (maxPunkte <= 0) {
|
||
return {
|
||
note: 'Fehler',
|
||
prozent: 0,
|
||
valid: false,
|
||
nachricht: 'Maximale Punktzahl muss größer als 0 sein!'
|
||
};
|
||
}
|
||
|
||
if (punkte < 0 || punkte > maxPunkte) {
|
||
return {
|
||
note: 'Fehler',
|
||
prozent: 0,
|
||
valid: false,
|
||
nachricht: `Punkte müssen zwischen 0 und ${maxPunkte} liegen!`
|
||
};
|
||
}
|
||
|
||
// Prozentage berechnen
|
||
const prozent = Math.round((punkte / maxPunkte) * 100);
|
||
|
||
// Note basierend auf Prozentage bestimmen
|
||
let note = 'nicht bestanden';
|
||
let noteColor = '#dc143c';
|
||
|
||
for (const [noteText, range] of Object.entries(NOTENMASSTAB)) {
|
||
if (prozent >= range.min && prozent <= range.max) {
|
||
note = noteText;
|
||
noteColor = range.color;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return {
|
||
note: note,
|
||
prozent: prozent,
|
||
punkte: punkte,
|
||
maxPunkte: maxPunkte,
|
||
valid: true,
|
||
color: noteColor,
|
||
nachricht: `${punkte} von ${maxPunkte} Punkten (${prozent}%) = ${note}`
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Validiert die Eingabe
|
||
*/
|
||
function validateInput(value, maxValue = 100) {
|
||
if (value === '' || value === null) {
|
||
return { valid: false, error: 'Bitte geben Sie eine Punktzahl ein!' };
|
||
}
|
||
|
||
const num = parseFloat(value);
|
||
|
||
if (isNaN(num)) {
|
||
return { valid: false, error: 'Bitte geben Sie eine Zahl ein!' };
|
||
}
|
||
|
||
if (num < 0 || num > maxValue) {
|
||
return { valid: false, error: `Punktzahl muss zwischen 0 und ${maxValue} liegen!` };
|
||
}
|
||
|
||
return { valid: true };
|
||
}
|
||
|
||
// Warte bis DOM geladen ist
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Simple Notenrechner
|
||
const simplePointsInput = document.getElementById('simple-points');
|
||
const simpleMaxInput = document.getElementById('simple-max');
|
||
const simpleCalculateBtn = document.getElementById('simple-calculate-btn');
|
||
const simpleResult = document.getElementById('simple-result');
|
||
const simpleProgressBar = document.getElementById('simple-progress-bar');
|
||
|
||
// Multiple Notenrechner
|
||
const addGradeBtn = document.getElementById('add-grade-btn');
|
||
const multipleCalculateBtn = document.getElementById('multiple-calculate-btn');
|
||
const multipleResetBtn = document.getElementById('multiple-reset-btn');
|
||
const gradesContainer = document.getElementById('grades-container');
|
||
const multipleResult = document.getElementById('multiple-result');
|
||
const multipleAverage = document.getElementById('multiple-average');
|
||
|
||
// Simple Grade Calculator Event Listener
|
||
if (simpleCalculateBtn && simplePointsInput) {
|
||
simpleCalculateBtn.addEventListener('click', function() {
|
||
calculateSimpleGrade();
|
||
});
|
||
|
||
// Enter-Taste auch zum Berechnen verwenden
|
||
simplePointsInput.addEventListener('keypress', function(event) {
|
||
if (event.key === 'Enter') {
|
||
calculateSimpleGrade();
|
||
}
|
||
});
|
||
|
||
// Live-Berechnung während des Tippens
|
||
simplePointsInput.addEventListener('input', function() {
|
||
updateSimpleGradeLive();
|
||
});
|
||
|
||
simpleMaxInput.addEventListener('input', function() {
|
||
updateSimpleGradeLive();
|
||
});
|
||
}
|
||
|
||
// Multiple Grade Calculator Event Listener
|
||
if (addGradeBtn) {
|
||
addGradeBtn.addEventListener('click', function() {
|
||
addGradeInput();
|
||
});
|
||
}
|
||
|
||
if (multipleCalculateBtn) {
|
||
multipleCalculateBtn.addEventListener('click', function() {
|
||
calculateMultipleGrades();
|
||
});
|
||
}
|
||
|
||
if (multipleResetBtn) {
|
||
multipleResetBtn.addEventListener('click', function() {
|
||
resetMultipleCalculator();
|
||
});
|
||
}
|
||
|
||
// Initiale Grade hinzufügen
|
||
if (gradesContainer) {
|
||
addGradeInput();
|
||
}
|
||
|
||
/**
|
||
* Berechnet einfache Note und zeigt Ergebnis
|
||
*/
|
||
function calculateSimpleGrade() {
|
||
const points = simplePointsInput.value;
|
||
const max = simpleMaxInput.value || 100;
|
||
|
||
// Validierung
|
||
const validation = validateInput(points, max);
|
||
if (!validation.valid) {
|
||
showSimpleError(validation.error);
|
||
return;
|
||
}
|
||
|
||
// Berechnung
|
||
const result = berechneNote(parseFloat(points), parseFloat(max));
|
||
|
||
if (!result.valid) {
|
||
showSimpleError(result.nachricht);
|
||
return;
|
||
}
|
||
|
||
// Erfolgreiche Anzeige
|
||
showSimpleSuccess(result);
|
||
}
|
||
|
||
/**
|
||
* Live-Aktualisierung während des Tippens
|
||
*/
|
||
function updateSimpleGradeLive() {
|
||
const points = simplePointsInput.value;
|
||
const max = simpleMaxInput.value || 100;
|
||
|
||
if (points === '') {
|
||
simpleResult.innerHTML = '';
|
||
simpleProgressBar.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const validation = validateInput(points, max);
|
||
if (!validation.valid) {
|
||
simpleResult.innerHTML = `<p class="error-text">⚠️ ${validation.error}</p>`;
|
||
simpleProgressBar.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const result = berechneNote(parseFloat(points), parseFloat(max));
|
||
if (result.valid) {
|
||
simpleResult.innerHTML = `
|
||
<div class="grade-result-inline">
|
||
<span class="grade-note" style="color: ${result.color};">${result.note}</span>
|
||
<span class="grade-percent">${result.prozent}%</span>
|
||
</div>
|
||
`;
|
||
updateProgressBar(result.prozent, result.color);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Fehleranzeige mit rotem Hintergrund
|
||
*/
|
||
function showSimpleError(errorMessage) {
|
||
simplePointsInput.style.borderColor = 'red';
|
||
simplePointsInput.style.backgroundColor = 'rgba(220, 20, 60, 0.1)';
|
||
simpleResult.innerHTML = `<p class="error-text">❌ ${errorMessage}</p>`;
|
||
simpleProgressBar.style.display = 'none';
|
||
|
||
// Reset nach 2 Sekunden
|
||
setTimeout(() => {
|
||
simplePointsInput.style.borderColor = '';
|
||
simplePointsInput.style.backgroundColor = '';
|
||
}, 2000);
|
||
}
|
||
|
||
/**
|
||
* Erfolgreiche Anzeige mit Progressbar
|
||
*/
|
||
function showSimpleSuccess(result) {
|
||
simplePointsInput.style.borderColor = 'green';
|
||
simplePointsInput.style.backgroundColor = 'rgba(34, 197, 94, 0.1)';
|
||
|
||
simpleResult.innerHTML = `
|
||
<div class="grade-result-detailed">
|
||
<div class="result-header">
|
||
<h4>📊 Ergebnis:</h4>
|
||
<span class="grade-note-large" style="color: ${result.color};">${result.note}</span>
|
||
</div>
|
||
<div class="result-details">
|
||
<p><strong>Punkte:</strong> ${result.punkte} / ${result.maxPunkte}</p>
|
||
<p><strong>Prozentage:</strong> ${result.prozent}%</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
updateProgressBar(result.prozent, result.color);
|
||
|
||
// Reset nach 3 Sekunden
|
||
setTimeout(() => {
|
||
simplePointsInput.style.borderColor = '';
|
||
simplePointsInput.style.backgroundColor = '';
|
||
}, 3000);
|
||
}
|
||
|
||
/**
|
||
* Aktualisiert die Progressbar mit farbiger Visualisierung
|
||
*/
|
||
function updateProgressBar(percent, color) {
|
||
simpleProgressBar.style.display = 'block';
|
||
|
||
// Bestimme die Hintergrundfarbe basierend auf der Note
|
||
let bgColor = color;
|
||
let bgOpacity = 0.2;
|
||
|
||
simpleProgressBar.innerHTML = `
|
||
<div class="progress-bar-container-enhanced">
|
||
<div class="progress-bar-background" style="background-color: rgba(${hexToRgb(color)}, ${bgOpacity});">
|
||
<div class="progress-bar-fill" style="width: ${percent}%; background-color: ${color}; box-shadow: 0 0 15px ${color};">
|
||
<span class="progress-bar-text">${percent}%</span>
|
||
</div>
|
||
</div>
|
||
<div class="progress-bar-labels">
|
||
<span class="label-0">0%</span>
|
||
<span class="label-25">25%</span>
|
||
<span class="label-50">50%</span>
|
||
<span class="label-75">75%</span>
|
||
<span class="label-100">100%</span>
|
||
</div>
|
||
</div>
|
||
<p class="progress-text">
|
||
<span class="progress-emoji">📊</span>
|
||
Fortschritt: <strong>${percent}%</strong> –
|
||
<span style="color: ${color}; font-weight: bold;">Sehr Gut Bereich</span>
|
||
</p>
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* Konvertiert Hex-Farben zu RGB für rgba()
|
||
*/
|
||
function hexToRgb(hex) {
|
||
// Entfernt das # Zeichen
|
||
hex = hex.replace('#', '');
|
||
|
||
// Konvertiert zu RGB
|
||
const r = parseInt(hex.substring(0, 2), 16);
|
||
const g = parseInt(hex.substring(2, 4), 16);
|
||
const b = parseInt(hex.substring(4, 6), 16);
|
||
|
||
return `${r}, ${g}, ${b}`;
|
||
}
|
||
|
||
/**
|
||
* Neue Grade-Input-Zeile hinzufügen (Multiple)
|
||
*/
|
||
function addGradeInput() {
|
||
if (!gradesContainer) return;
|
||
|
||
const gradeId = Date.now();
|
||
const gradeDiv = document.createElement('div');
|
||
gradeDiv.className = 'grade-input-group-dom';
|
||
gradeDiv.id = `grade-${gradeId}`;
|
||
|
||
gradeDiv.innerHTML = `
|
||
<div class="grade-input-wrapper-dom">
|
||
<input
|
||
type="number"
|
||
class="grade-points-dom"
|
||
placeholder="Punkte"
|
||
min="0"
|
||
step="0.5"
|
||
>
|
||
<span class="grade-separator">/</span>
|
||
<input
|
||
type="number"
|
||
class="grade-max-dom"
|
||
placeholder="Max"
|
||
value="100"
|
||
min="1"
|
||
step="1"
|
||
>
|
||
<div class="grade-result-dom">
|
||
<span class="grade-note-dom">-</span>
|
||
<span class="grade-percent-dom">0%</span>
|
||
</div>
|
||
<button class="grade-remove-btn-dom" type="button">✕</button>
|
||
</div>
|
||
`;
|
||
|
||
gradesContainer.appendChild(gradeDiv);
|
||
|
||
// Event Listener für neue Eingabe
|
||
const pointsInput = gradeDiv.querySelector('.grade-points-dom');
|
||
const maxInput = gradeDiv.querySelector('.grade-max-dom');
|
||
const removeBtn = gradeDiv.querySelector('.grade-remove-btn-dom');
|
||
const noteSpan = gradeDiv.querySelector('.grade-note-dom');
|
||
const percentSpan = gradeDiv.querySelector('.grade-percent-dom');
|
||
|
||
function updateGradeLive() {
|
||
const points = parseFloat(pointsInput.value) || 0;
|
||
const max = parseFloat(maxInput.value) || 100;
|
||
|
||
if (max > 0 && points >= 0 && points <= max) {
|
||
const result = berechneNote(points, max);
|
||
if (result.valid) {
|
||
noteSpan.textContent = result.note;
|
||
percentSpan.textContent = result.prozent + '%';
|
||
noteSpan.style.color = result.color;
|
||
}
|
||
} else {
|
||
noteSpan.textContent = '-';
|
||
percentSpan.textContent = '0%';
|
||
}
|
||
}
|
||
|
||
pointsInput.addEventListener('input', updateGradeLive);
|
||
maxInput.addEventListener('input', updateGradeLive);
|
||
|
||
removeBtn.addEventListener('click', function() {
|
||
gradeDiv.remove();
|
||
});
|
||
|
||
pointsInput.focus();
|
||
}
|
||
|
||
/**
|
||
* Berechnet mehrere Noten
|
||
*/
|
||
function calculateMultipleGrades() {
|
||
const gradeInputs = gradesContainer.querySelectorAll('.grade-input-group-dom');
|
||
const noten = [];
|
||
|
||
gradeInputs.forEach(input => {
|
||
const points = parseFloat(input.querySelector('.grade-points-dom').value);
|
||
const max = parseFloat(input.querySelector('.grade-max-dom').value);
|
||
|
||
if (!isNaN(points) && !isNaN(max) && max > 0) {
|
||
const result = berechneNote(points, max);
|
||
if (result.valid) {
|
||
noten.push(result);
|
||
}
|
||
}
|
||
});
|
||
|
||
if (noten.length === 0) {
|
||
multipleResult.innerHTML = `<p class="error-text">⚠️ Bitte füllen Sie mindestens eine Note aus!</p>`;
|
||
multipleAverage.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
// Einzelnoten anzeigen mit Progressbars
|
||
let resultHTML = '<div class="results-list"><h4>📊 Ihre Noten:</h4>';
|
||
noten.forEach((note, index) => {
|
||
resultHTML += `
|
||
<div class="result-item-dom">
|
||
<span class="result-number">#${index + 1}</span>
|
||
<div class="result-details-with-bar">
|
||
<div class="result-top">
|
||
<span class="result-details">${note.punkte} / ${note.maxPunkte}</span>
|
||
<span class="result-grade-dom" style="color: ${note.color};">${note.note}</span>
|
||
</div>
|
||
<div class="mini-progress-bar">
|
||
<div class="mini-progress-bar-bg">
|
||
<div class="mini-progress-bar-fill" style="width: ${note.prozent}%; background-color: ${note.color}; box-shadow: 0 0 8px ${note.color};"></div>
|
||
</div>
|
||
<span class="mini-progress-percent">${note.prozent}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
resultHTML += '</div>';
|
||
multipleResult.innerHTML = resultHTML;
|
||
|
||
// Durchschnitt berechnen
|
||
if (noten.length > 1) {
|
||
const durchschnittProzent = Math.round(
|
||
noten.reduce((sum, n) => sum + n.prozent, 0) / noten.length
|
||
);
|
||
|
||
let durchschnittNote = 'nicht bestanden';
|
||
let durchschnittColor = '#dc143c';
|
||
for (const [noteText, range] of Object.entries(NOTENMASSTAB)) {
|
||
if (durchschnittProzent >= range.min && durchschnittProzent <= range.max) {
|
||
durchschnittNote = noteText;
|
||
durchschnittColor = range.color;
|
||
break;
|
||
}
|
||
}
|
||
|
||
multipleAverage.innerHTML = `
|
||
<div class="average-box-dom">
|
||
<h4>📈 Durchschnitt:</h4>
|
||
<div class="average-display-dom">
|
||
<div class="average-percent-dom" style="border: 3px solid ${durchschnittColor}; box-shadow: 0 0 20px ${durchschnittColor};">${durchschnittProzent}%</div>
|
||
<div class="average-note-dom" style="color: ${durchschnittColor};">${durchschnittNote}</div>
|
||
</div>
|
||
<div class="average-progress-bar">
|
||
<div class="average-progress-bg">
|
||
<div class="average-progress-fill" style="width: ${durchschnittProzent}%; background-color: ${durchschnittColor}; box-shadow: 0 0 15px ${durchschnittColor};"></div>
|
||
</div>
|
||
</div>
|
||
<p class="average-details-dom">Von ${noten.length} Note(n) – Durchschnitt: <strong>${durchschnittProzent}%</strong></p>
|
||
</div>
|
||
`;
|
||
|
||
// Console Ausgabe
|
||
console.log(`Durchschnitt: ${durchschnittProzent}% = ${durchschnittNote}`);
|
||
} else {
|
||
multipleAverage.innerHTML = '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Setzt Multiple Grade Calculator zurück
|
||
*/
|
||
function resetMultipleCalculator() {
|
||
gradesContainer.innerHTML = '';
|
||
multipleResult.innerHTML = '';
|
||
multipleAverage.innerHTML = '';
|
||
addGradeInput();
|
||
}
|
||
});
|