A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
342 lines
14 KiB
Python
342 lines
14 KiB
Python
"""
|
|
School Module - Grades Page
|
|
Grades overview and entry
|
|
"""
|
|
|
|
from ..styles import SCHOOL_BASE_STYLES, GRADES_STYLES
|
|
from ..templates import ICONS, render_base_page, COMMON_SCRIPTS
|
|
|
|
|
|
def grades_page() -> str:
|
|
"""Grades overview page"""
|
|
styles = SCHOOL_BASE_STYLES + GRADES_STYLES
|
|
|
|
content = f'''
|
|
<main class="main-content">
|
|
<div class="page-header">
|
|
<div>
|
|
<h1 class="page-title">Notenspiegel</h1>
|
|
<p class="page-subtitle">Notenübersicht und -eintragung</p>
|
|
</div>
|
|
<button class="btn btn-primary" onclick="openGradeModal()">
|
|
{ICONS['plus']}
|
|
Note eintragen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Controls -->
|
|
<div class="controls">
|
|
<div class="select-wrapper">
|
|
<select id="class-select" onchange="loadGrades()">
|
|
<option value="5a">Klasse 5a</option>
|
|
<option value="5b">Klasse 5b</option>
|
|
<option value="6a">Klasse 6a</option>
|
|
</select>
|
|
</div>
|
|
<div class="select-wrapper">
|
|
<select id="subject-select" onchange="loadGrades()">
|
|
<option value="math">Mathematik</option>
|
|
<option value="german">Deutsch</option>
|
|
<option value="english">Englisch</option>
|
|
<option value="physics">Physik</option>
|
|
<option value="biology">Biologie</option>
|
|
<option value="history">Geschichte</option>
|
|
</select>
|
|
</div>
|
|
<div class="select-wrapper">
|
|
<select id="type-select" onchange="loadGrades()">
|
|
<option value="all">Alle Leistungen</option>
|
|
<option value="exam">Klassenarbeiten</option>
|
|
<option value="oral">Mündlich</option>
|
|
<option value="homework">Hausaufgaben</option>
|
|
<option value="test">Tests</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="stats-row">
|
|
<div class="stat-card">
|
|
<div class="stat-card-value" id="stat-average">2.4</div>
|
|
<div class="stat-card-label">Klassendurchschnitt</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-card-value" id="stat-best">1</div>
|
|
<div class="stat-card-label">Beste Note</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-card-value" id="stat-count">26</div>
|
|
<div class="stat-card-label">Eingetragene Noten</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Distribution Card -->
|
|
<div class="card" style="margin-bottom: 1.5rem;">
|
|
<div class="card-header">
|
|
<span class="card-title">Notenverteilung</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="distribution-bar" id="distribution-bar">
|
|
<div class="distribution-segment dist-1" style="width: 15%">4</div>
|
|
<div class="distribution-segment dist-2" style="width: 27%">7</div>
|
|
<div class="distribution-segment dist-3" style="width: 31%">8</div>
|
|
<div class="distribution-segment dist-4" style="width: 19%">5</div>
|
|
<div class="distribution-segment dist-5" style="width: 8%">2</div>
|
|
<div class="distribution-segment dist-6" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Grades Table -->
|
|
<div class="card" style="padding: 0;">
|
|
<div class="card-header" style="padding: 1rem 1.5rem; border-bottom: 1px solid var(--bp-border);">
|
|
<span class="card-title">Noten - Mathematik (Klassenarbeit 1)</span>
|
|
<button class="btn btn-secondary" onclick="exportGrades()">
|
|
{ICONS['download']}
|
|
Exportieren
|
|
</button>
|
|
</div>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Schüler</th>
|
|
<th>Note</th>
|
|
<th>Punkte</th>
|
|
<th>Datum</th>
|
|
<th>Kommentar</th>
|
|
<th>Aktion</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="grades-list">
|
|
<!-- Dynamisch geladen -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Grade Modal -->
|
|
<div class="modal-overlay" id="grade-modal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">Note eintragen</h2>
|
|
<button class="modal-close" onclick="closeGradeModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label class="form-label">Schüler</label>
|
|
<select class="form-select" id="modal-student"></select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Fach</label>
|
|
<select class="form-select" id="modal-subject">
|
|
<option value="math">Mathematik</option>
|
|
<option value="german">Deutsch</option>
|
|
<option value="english">Englisch</option>
|
|
<option value="physics">Physik</option>
|
|
<option value="biology">Biologie</option>
|
|
<option value="history">Geschichte</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Leistungsart</label>
|
|
<select class="form-select" id="modal-type">
|
|
<option value="exam">Klassenarbeit</option>
|
|
<option value="oral">Mündliche Note</option>
|
|
<option value="homework">Hausaufgabe</option>
|
|
<option value="test">Test/Quiz</option>
|
|
<option value="project">Projektarbeit</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Note</label>
|
|
<div class="grade-buttons" id="grade-buttons">
|
|
<button type="button" class="grade-btn" onclick="selectGrade(1)">1</button>
|
|
<button type="button" class="grade-btn" onclick="selectGrade(2)">2</button>
|
|
<button type="button" class="grade-btn" onclick="selectGrade(3)">3</button>
|
|
<button type="button" class="grade-btn" onclick="selectGrade(4)">4</button>
|
|
<button type="button" class="grade-btn" onclick="selectGrade(5)">5</button>
|
|
<button type="button" class="grade-btn" onclick="selectGrade(6)">6</button>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Punkte (optional)</label>
|
|
<input type="number" class="form-input" id="modal-points" placeholder="z.B. 85 von 100">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Kommentar (optional)</label>
|
|
<textarea class="form-textarea" id="modal-comment" placeholder="Anmerkungen zur Leistung..."></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">
|
|
<input type="checkbox" id="modal-notify"> Eltern benachrichtigen
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeGradeModal()">Abbrechen</button>
|
|
<button class="btn btn-primary" onclick="saveGrade()">Speichern</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast" class="toast"></div>'''
|
|
|
|
scripts = COMMON_SCRIPTS + '''
|
|
<script>
|
|
const students = [
|
|
{ id: 1, name: 'Anna Schmidt' },
|
|
{ id: 2, name: 'Ben Müller' },
|
|
{ id: 3, name: 'Clara Weber' },
|
|
{ id: 4, name: 'David Fischer' },
|
|
{ id: 5, name: 'Emma Becker' },
|
|
{ id: 6, name: 'Felix Braun' },
|
|
{ id: 7, name: 'Greta Hoffmann' },
|
|
{ id: 8, name: 'Hans Schneider' },
|
|
{ id: 9, name: 'Ida Wagner' },
|
|
{ id: 10, name: 'Jonas Koch' },
|
|
{ id: 11, name: 'Klara Bauer' },
|
|
{ id: 12, name: 'Leon Richter' },
|
|
{ id: 13, name: 'Mia Klein' },
|
|
{ id: 14, name: 'Noah Wolf' },
|
|
{ id: 15, name: 'Olivia Meier' },
|
|
{ id: 16, name: 'Paul Neumann' },
|
|
{ id: 17, name: 'Quirin Schwarz' },
|
|
{ id: 18, name: 'Rosa Zimmermann' },
|
|
{ id: 19, name: 'Samuel Krüger' },
|
|
{ id: 20, name: 'Tina Lange' },
|
|
{ id: 21, name: 'Uwe Peters' },
|
|
{ id: 22, name: 'Vera Meyer' },
|
|
{ id: 23, name: 'Wilhelm Schulz' },
|
|
{ id: 24, name: 'Xenia Huber' },
|
|
{ id: 25, name: 'Yannik Fuchs' },
|
|
{ id: 26, name: 'Zoe Berger' }
|
|
];
|
|
|
|
const sampleGrades = [
|
|
{ studentId: 1, grade: 2, points: 85, date: '2024-12-10', comment: 'Gute Arbeit' },
|
|
{ studentId: 2, grade: 3, points: 72, date: '2024-12-10', comment: '' },
|
|
{ studentId: 3, grade: 1, points: 95, date: '2024-12-10', comment: 'Sehr gut!' },
|
|
{ studentId: 4, grade: 4, points: 58, date: '2024-12-10', comment: 'Mehr üben' },
|
|
{ studentId: 5, grade: 2, points: 82, date: '2024-12-10', comment: '' },
|
|
{ studentId: 6, grade: 3, points: 70, date: '2024-12-10', comment: '' },
|
|
{ studentId: 7, grade: 2, points: 80, date: '2024-12-10', comment: '' },
|
|
{ studentId: 8, grade: 3, points: 68, date: '2024-12-10', comment: '' },
|
|
{ studentId: 9, grade: 1, points: 92, date: '2024-12-10', comment: 'Ausgezeichnet' },
|
|
{ studentId: 10, grade: 4, points: 55, date: '2024-12-10', comment: '' },
|
|
{ studentId: 11, grade: 2, points: 78, date: '2024-12-10', comment: '' },
|
|
{ studentId: 12, grade: 3, points: 65, date: '2024-12-10', comment: '' },
|
|
{ studentId: 13, grade: 2, points: 84, date: '2024-12-10', comment: '' },
|
|
{ studentId: 14, grade: 5, points: 42, date: '2024-12-10', comment: 'Nachholtermin?' },
|
|
{ studentId: 15, grade: 3, points: 71, date: '2024-12-10', comment: '' },
|
|
{ studentId: 16, grade: 2, points: 79, date: '2024-12-10', comment: '' },
|
|
{ studentId: 17, grade: 3, points: 67, date: '2024-12-10', comment: '' },
|
|
{ studentId: 18, grade: 1, points: 98, date: '2024-12-10', comment: 'Hervorragend!' },
|
|
{ studentId: 19, grade: 4, points: 52, date: '2024-12-10', comment: '' },
|
|
{ studentId: 20, grade: 3, points: 69, date: '2024-12-10', comment: '' },
|
|
{ studentId: 21, grade: 2, points: 81, date: '2024-12-10', comment: '' },
|
|
{ studentId: 22, grade: 3, points: 66, date: '2024-12-10', comment: '' },
|
|
{ studentId: 23, grade: 4, points: 54, date: '2024-12-10', comment: '' },
|
|
{ studentId: 24, grade: 1, points: 94, date: '2024-12-10', comment: 'Toll!' },
|
|
{ studentId: 25, grade: 5, points: 45, date: '2024-12-10', comment: '' },
|
|
{ studentId: 26, grade: 2, points: 83, date: '2024-12-10', comment: '' }
|
|
];
|
|
|
|
let selectedGrade = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
populateStudentSelect();
|
|
renderGradesTable();
|
|
});
|
|
|
|
function populateStudentSelect() {
|
|
const select = document.getElementById('modal-student');
|
|
select.innerHTML = students.map(s =>
|
|
`<option value="${s.id}">${s.name}</option>`
|
|
).join('');
|
|
}
|
|
|
|
function renderGradesTable() {
|
|
const tbody = document.getElementById('grades-list');
|
|
tbody.innerHTML = sampleGrades.map(grade => {
|
|
const student = students.find(s => s.id === grade.studentId);
|
|
return `
|
|
<tr>
|
|
<td>
|
|
<div class="student-info">
|
|
<div class="student-avatar">${getInitials(student.name)}</div>
|
|
<span class="student-name">${student.name}</span>
|
|
</div>
|
|
</td>
|
|
<td><span class="grade-badge grade-${grade.grade}">${grade.grade}</span></td>
|
|
<td>${grade.points}/100</td>
|
|
<td>${formatDate(grade.date)}</td>
|
|
<td>${grade.comment || '-'}</td>
|
|
<td>
|
|
<button class="btn btn-secondary" onclick="editGrade(${grade.studentId})">Bearbeiten</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function formatDate(dateStr) {
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
}
|
|
|
|
function openGradeModal() {
|
|
selectedGrade = null;
|
|
document.querySelectorAll('.grade-btn').forEach(btn => btn.classList.remove('selected'));
|
|
document.getElementById('modal-points').value = '';
|
|
document.getElementById('modal-comment').value = '';
|
|
document.getElementById('modal-notify').checked = true;
|
|
document.getElementById('grade-modal').classList.add('show');
|
|
}
|
|
|
|
function closeGradeModal() {
|
|
document.getElementById('grade-modal').classList.remove('show');
|
|
}
|
|
|
|
function selectGrade(grade) {
|
|
selectedGrade = grade;
|
|
document.querySelectorAll('.grade-btn').forEach(btn => {
|
|
btn.classList.remove('selected');
|
|
if (parseInt(btn.textContent) === grade) {
|
|
btn.classList.add('selected');
|
|
}
|
|
});
|
|
}
|
|
|
|
function editGrade(studentId) {
|
|
const grade = sampleGrades.find(g => g.studentId === studentId);
|
|
if (grade) {
|
|
document.getElementById('modal-student').value = studentId;
|
|
selectGrade(grade.grade);
|
|
document.getElementById('modal-points').value = grade.points;
|
|
document.getElementById('modal-comment').value = grade.comment;
|
|
document.getElementById('grade-modal').classList.add('show');
|
|
}
|
|
}
|
|
|
|
async function saveGrade() {
|
|
if (!selectedGrade) {
|
|
showToast('Bitte wählen Sie eine Note aus', 'error');
|
|
return;
|
|
}
|
|
|
|
closeGradeModal();
|
|
showToast('Note gespeichert!', 'success');
|
|
renderGradesTable();
|
|
}
|
|
|
|
function loadGrades() {
|
|
showToast('Noten werden geladen...');
|
|
}
|
|
|
|
function exportGrades() {
|
|
showToast('Export wird erstellt...');
|
|
}
|
|
</script>'''
|
|
|
|
return render_base_page("Notenspiegel", styles, content, scripts, "grades")
|