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>
934 lines
23 KiB
Python
934 lines
23 KiB
Python
"""
|
||
BreakPilot Studio - Notenbuch (Gradebook) Modul
|
||
|
||
Funktionen:
|
||
- Notenübersicht pro Klasse/Schüler
|
||
- Noteneingabe für verschiedene Prüfungsarten
|
||
- Durchschnittsberechnung
|
||
- Trend-Analyse
|
||
- Export nach Excel/PDF
|
||
- Integration mit Zeugnissen
|
||
"""
|
||
|
||
|
||
class GradebookModule:
|
||
"""Modul fuer digitale Notenverwaltung."""
|
||
|
||
@staticmethod
|
||
def get_css() -> str:
|
||
"""CSS fuer das Gradebook-Modul."""
|
||
return """
|
||
/* =============================================
|
||
GRADEBOOK MODULE - Notenbuch
|
||
============================================= */
|
||
|
||
/* Panel Layout */
|
||
.panel-gradebook {
|
||
display: none;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background: var(--bp-bg);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.panel-gradebook.active {
|
||
display: flex;
|
||
}
|
||
|
||
/* Header */
|
||
.gradebook-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20px 32px;
|
||
border-bottom: 1px solid var(--bp-border);
|
||
background: var(--bp-surface);
|
||
}
|
||
|
||
.gradebook-title-section h1 {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: var(--bp-text);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.gradebook-subtitle {
|
||
font-size: 14px;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
.gradebook-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
/* Filter Bar */
|
||
.gradebook-filters {
|
||
display: flex;
|
||
gap: 16px;
|
||
padding: 16px 32px;
|
||
background: var(--bp-surface-elevated);
|
||
border-bottom: 1px solid var(--bp-border);
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.filter-group label {
|
||
font-size: 12px;
|
||
color: var(--bp-text-muted);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.filter-group select,
|
||
.filter-group input {
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 8px;
|
||
background: var(--bp-surface);
|
||
color: var(--bp-text);
|
||
font-size: 13px;
|
||
min-width: 150px;
|
||
}
|
||
|
||
.filter-group select:focus,
|
||
.filter-group input:focus {
|
||
outline: none;
|
||
border-color: var(--bp-primary);
|
||
}
|
||
|
||
/* Content Layout */
|
||
.gradebook-content {
|
||
flex: 1;
|
||
overflow: auto;
|
||
padding: 24px 32px;
|
||
}
|
||
|
||
/* Grade Table */
|
||
.gradebook-table-container {
|
||
background: var(--bp-surface);
|
||
border-radius: 12px;
|
||
border: 1px solid var(--bp-border);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.gradebook-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.gradebook-table thead {
|
||
background: var(--bp-surface-elevated);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
|
||
.gradebook-table th {
|
||
padding: 12px 16px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: var(--bp-text);
|
||
border-bottom: 2px solid var(--bp-border);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.gradebook-table th.grade-column {
|
||
text-align: center;
|
||
min-width: 80px;
|
||
}
|
||
|
||
.gradebook-table th.average-column {
|
||
text-align: center;
|
||
background: var(--bp-primary-soft);
|
||
}
|
||
|
||
.gradebook-table tbody tr {
|
||
border-bottom: 1px solid var(--bp-border);
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.gradebook-table tbody tr:hover {
|
||
background: var(--bp-surface-elevated);
|
||
}
|
||
|
||
.gradebook-table td {
|
||
padding: 12px 16px;
|
||
color: var(--bp-text);
|
||
}
|
||
|
||
.gradebook-table td.grade-cell {
|
||
text-align: center;
|
||
}
|
||
|
||
.gradebook-table td.average-cell {
|
||
text-align: center;
|
||
font-weight: 600;
|
||
background: var(--bp-primary-soft);
|
||
}
|
||
|
||
/* Student Name Column */
|
||
.student-name {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.student-avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
background: var(--bp-primary);
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.student-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.student-info .name {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.student-info .class {
|
||
font-size: 11px;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
/* Grade Input */
|
||
.grade-input {
|
||
width: 50px;
|
||
padding: 6px 8px;
|
||
border: 1px solid transparent;
|
||
border-radius: 6px;
|
||
background: var(--bp-surface);
|
||
color: var(--bp-text);
|
||
text-align: center;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.grade-input:hover {
|
||
border-color: var(--bp-border);
|
||
}
|
||
|
||
.grade-input:focus {
|
||
outline: none;
|
||
border-color: var(--bp-primary);
|
||
background: var(--bp-surface-elevated);
|
||
}
|
||
|
||
/* Grade Colors */
|
||
.grade-1 { color: #22c55e; }
|
||
.grade-2 { color: #84cc16; }
|
||
.grade-3 { color: #eab308; }
|
||
.grade-4 { color: #f97316; }
|
||
.grade-5 { color: #ef4444; }
|
||
.grade-6 { color: #dc2626; }
|
||
|
||
.grade-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.grade-badge.grade-1 { background: rgba(34, 197, 94, 0.15); }
|
||
.grade-badge.grade-2 { background: rgba(132, 204, 22, 0.15); }
|
||
.grade-badge.grade-3 { background: rgba(234, 179, 8, 0.15); }
|
||
.grade-badge.grade-4 { background: rgba(249, 115, 22, 0.15); }
|
||
.grade-badge.grade-5 { background: rgba(239, 68, 68, 0.15); }
|
||
.grade-badge.grade-6 { background: rgba(220, 38, 38, 0.15); }
|
||
|
||
/* Trend Indicator */
|
||
.trend-indicator {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 12px;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.trend-up { color: var(--bp-success); }
|
||
.trend-down { color: var(--bp-danger); }
|
||
.trend-stable { color: var(--bp-text-muted); }
|
||
|
||
/* Statistics Panel */
|
||
.gradebook-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--bp-surface);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.stat-card .stat-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.stat-card .stat-icon.blue {
|
||
background: rgba(59, 130, 246, 0.15);
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.stat-card .stat-icon.green {
|
||
background: rgba(34, 197, 94, 0.15);
|
||
color: #22c55e;
|
||
}
|
||
|
||
.stat-card .stat-icon.yellow {
|
||
background: rgba(234, 179, 8, 0.15);
|
||
color: #eab308;
|
||
}
|
||
|
||
.stat-card .stat-icon.red {
|
||
background: rgba(239, 68, 68, 0.15);
|
||
color: #ef4444;
|
||
}
|
||
|
||
.stat-card .stat-value {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: var(--bp-text);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.stat-card .stat-label {
|
||
font-size: 13px;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
/* Add Grade Modal */
|
||
.add-grade-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.add-grade-modal.active {
|
||
display: flex;
|
||
}
|
||
|
||
.add-grade-content {
|
||
background: var(--bp-surface);
|
||
border-radius: 16px;
|
||
padding: 24px;
|
||
width: 100%;
|
||
max-width: 500px;
|
||
max-height: 90vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.add-grade-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.add-grade-header h2 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.add-grade-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
}
|
||
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.form-group label {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group select,
|
||
.form-group textarea {
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 8px;
|
||
background: var(--bp-surface-elevated);
|
||
color: var(--bp-text);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-group input:focus,
|
||
.form-group select:focus,
|
||
.form-group textarea:focus {
|
||
outline: none;
|
||
border-color: var(--bp-primary);
|
||
}
|
||
|
||
.form-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
/* Exam Types */
|
||
.exam-type-selector {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.exam-type-btn {
|
||
padding: 8px 16px;
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 20px;
|
||
background: var(--bp-surface);
|
||
color: var(--bp-text-muted);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.exam-type-btn:hover {
|
||
border-color: var(--bp-primary);
|
||
color: var(--bp-text);
|
||
}
|
||
|
||
.exam-type-btn.active {
|
||
background: var(--bp-primary);
|
||
border-color: var(--bp-primary);
|
||
color: white;
|
||
}
|
||
|
||
/* Chart Container */
|
||
.gradebook-chart {
|
||
background: var(--bp-surface);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.gradebook-chart h3 {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.chart-placeholder {
|
||
height: 200px;
|
||
background: var(--bp-surface-elevated);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
/* Responsive */
|
||
@media (max-width: 768px) {
|
||
.gradebook-filters {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.filter-group {
|
||
width: 100%;
|
||
}
|
||
|
||
.filter-group select {
|
||
width: 100%;
|
||
}
|
||
|
||
.form-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.gradebook-stats {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
/* Print Styles */
|
||
@media print {
|
||
.gradebook-header,
|
||
.gradebook-filters,
|
||
.gradebook-actions,
|
||
.add-grade-modal {
|
||
display: none !important;
|
||
}
|
||
|
||
.gradebook-content {
|
||
padding: 0;
|
||
}
|
||
|
||
.gradebook-table {
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
"""
|
||
|
||
@staticmethod
|
||
def get_html() -> str:
|
||
"""HTML fuer das Gradebook-Panel."""
|
||
return """
|
||
<!-- Gradebook Panel -->
|
||
<div id="panel-gradebook" class="panel-gradebook">
|
||
<!-- Header -->
|
||
<div class="gradebook-header">
|
||
<div class="gradebook-title-section">
|
||
<h1>Notenbuch</h1>
|
||
<div class="gradebook-subtitle">Notenübersicht und -verwaltung</div>
|
||
</div>
|
||
<div class="gradebook-actions">
|
||
<button class="btn btn-secondary" onclick="exportGradebook()">
|
||
<span class="icon">📥</span> Exportieren
|
||
</button>
|
||
<button class="btn btn-primary" onclick="openAddGradeModal()">
|
||
<span class="icon">➕</span> Note eintragen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filters -->
|
||
<div class="gradebook-filters">
|
||
<div class="filter-group">
|
||
<label>Schuljahr</label>
|
||
<select id="gradebook-year" onchange="loadGradebook()">
|
||
<option value="2024/2025">2024/2025</option>
|
||
<option value="2023/2024">2023/2024</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Klasse</label>
|
||
<select id="gradebook-class" onchange="loadGradebook()">
|
||
<option value="">Alle Klassen</option>
|
||
<option value="5a">5a</option>
|
||
<option value="5b">5b</option>
|
||
<option value="6a">6a</option>
|
||
<option value="6b">6b</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Fach</label>
|
||
<select id="gradebook-subject" onchange="loadGradebook()">
|
||
<option value="">Alle Fächer</option>
|
||
<option value="deutsch">Deutsch</option>
|
||
<option value="mathematik">Mathematik</option>
|
||
<option value="englisch">Englisch</option>
|
||
<option value="biologie">Biologie</option>
|
||
<option value="geschichte">Geschichte</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Halbjahr</label>
|
||
<select id="gradebook-semester" onchange="loadGradebook()">
|
||
<option value="1">1. Halbjahr</option>
|
||
<option value="2">2. Halbjahr</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Content -->
|
||
<div class="gradebook-content">
|
||
<!-- Statistics -->
|
||
<div class="gradebook-stats">
|
||
<div class="stat-card">
|
||
<div class="stat-icon blue">📊</div>
|
||
<div class="stat-value" id="stat-average">2.4</div>
|
||
<div class="stat-label">Klassendurchschnitt</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon green">✓</div>
|
||
<div class="stat-value" id="stat-passed">24</div>
|
||
<div class="stat-label">Schüler bestanden</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon yellow">📝</div>
|
||
<div class="stat-value" id="stat-exams">8</div>
|
||
<div class="stat-label">Leistungsnachweise</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon red">⚠️</div>
|
||
<div class="stat-value" id="stat-warning">3</div>
|
||
<div class="stat-label">Gefährdete Schüler</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Grade Distribution Chart -->
|
||
<div class="gradebook-chart">
|
||
<h3>Notenverteilung</h3>
|
||
<div class="chart-placeholder" id="grade-distribution-chart">
|
||
Hier wird das Notenverteilungsdiagramm angezeigt
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Grade Table -->
|
||
<div class="gradebook-table-container">
|
||
<table class="gradebook-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Schüler/in</th>
|
||
<th class="grade-column">Klausur 1</th>
|
||
<th class="grade-column">Test 1</th>
|
||
<th class="grade-column">Klausur 2</th>
|
||
<th class="grade-column">Mündlich</th>
|
||
<th class="grade-column">Test 2</th>
|
||
<th class="grade-column average-column">Schnitt</th>
|
||
<th class="grade-column">Trend</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="gradebook-tbody">
|
||
<!-- Dynamisch generiert -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Grade Modal -->
|
||
<div class="add-grade-modal" id="add-grade-modal">
|
||
<div class="add-grade-content">
|
||
<div class="add-grade-header">
|
||
<h2>Note eintragen</h2>
|
||
<button class="btn-icon" onclick="closeAddGradeModal()">✕</button>
|
||
</div>
|
||
<form class="add-grade-form" onsubmit="saveGrade(event)">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Schüler/in</label>
|
||
<select id="grade-student" required>
|
||
<option value="">Auswählen...</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Fach</label>
|
||
<select id="grade-subject" required>
|
||
<option value="">Auswählen...</option>
|
||
<option value="deutsch">Deutsch</option>
|
||
<option value="mathematik">Mathematik</option>
|
||
<option value="englisch">Englisch</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Art des Leistungsnachweises</label>
|
||
<div class="exam-type-selector">
|
||
<button type="button" class="exam-type-btn active" data-type="klausur">Klausur</button>
|
||
<button type="button" class="exam-type-btn" data-type="test">Test</button>
|
||
<button type="button" class="exam-type-btn" data-type="muendlich">Mündlich</button>
|
||
<button type="button" class="exam-type-btn" data-type="hausaufgabe">Hausaufgabe</button>
|
||
<button type="button" class="exam-type-btn" data-type="referat">Referat</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Note</label>
|
||
<select id="grade-value" required>
|
||
<option value="">Auswählen...</option>
|
||
<option value="1">1 (sehr gut)</option>
|
||
<option value="2">2 (gut)</option>
|
||
<option value="3">3 (befriedigend)</option>
|
||
<option value="4">4 (ausreichend)</option>
|
||
<option value="5">5 (mangelhaft)</option>
|
||
<option value="6">6 (ungenügend)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Datum</label>
|
||
<input type="date" id="grade-date" required>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Gewichtung (%)</label>
|
||
<input type="number" id="grade-weight" min="0" max="100" value="100" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Bemerkung (optional)</label>
|
||
<textarea id="grade-comment" rows="2" placeholder="z.B. Nachschreibeklausur"></textarea>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="button" class="btn btn-secondary" onclick="closeAddGradeModal()">Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
"""
|
||
|
||
@staticmethod
|
||
def get_js() -> str:
|
||
"""JavaScript fuer das Gradebook-Modul."""
|
||
return """
|
||
// =============================================
|
||
// GRADEBOOK MODULE - JavaScript
|
||
// =============================================
|
||
|
||
// Sample data (in production: from API)
|
||
const gradebookData = {
|
||
students: [
|
||
{ id: 1, name: 'Anna Beispiel', class: '5a', grades: { k1: 2, t1: 1, k2: 2, m: 2, t2: 2 }, avg: 1.8 },
|
||
{ id: 2, name: 'Ben Schmidt', class: '5a', grades: { k1: 3, t1: 3, k2: 3, m: 2, t2: 3 }, avg: 2.8 },
|
||
{ id: 3, name: 'Clara Weber', class: '5a', grades: { k1: 1, t1: 2, k2: 1, m: 1, t2: 1 }, avg: 1.2 },
|
||
{ id: 4, name: 'David Müller', class: '5a', grades: { k1: 4, t1: 4, k2: 5, m: 3, t2: 4 }, avg: 4.0 },
|
||
{ id: 5, name: 'Emma Fischer', class: '5a', grades: { k1: 2, t1: 2, k2: 2, m: 2, t2: 2 }, avg: 2.0 },
|
||
{ id: 6, name: 'Felix Wagner', class: '5a', grades: { k1: 3, t1: 2, k2: 3, m: 3, t2: 2 }, avg: 2.6 },
|
||
{ id: 7, name: 'Greta Hoffmann', class: '5a', grades: { k1: 1, t1: 1, k2: 1, m: 2, t2: 1 }, avg: 1.2 },
|
||
{ id: 8, name: 'Hans Bauer', class: '5a', grades: { k1: 5, t1: 4, k2: 5, m: 4, t2: 5 }, avg: 4.6 },
|
||
]
|
||
};
|
||
|
||
// Initialize Gradebook
|
||
function initGradebook() {
|
||
loadGradebook();
|
||
initExamTypeSelector();
|
||
setTodayDate();
|
||
populateStudentSelect();
|
||
}
|
||
|
||
// Load Gradebook Data
|
||
function loadGradebook() {
|
||
const tbody = document.getElementById('gradebook-tbody');
|
||
if (!tbody) return;
|
||
|
||
tbody.innerHTML = '';
|
||
|
||
gradebookData.students.forEach(student => {
|
||
const row = createStudentRow(student);
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
updateStatistics();
|
||
}
|
||
|
||
// Create Student Row
|
||
function createStudentRow(student) {
|
||
const row = document.createElement('tr');
|
||
|
||
// Get initials
|
||
const initials = student.name.split(' ').map(n => n[0]).join('');
|
||
|
||
// Calculate trend
|
||
const grades = Object.values(student.grades);
|
||
const recent = grades.slice(-2);
|
||
const trend = recent.length >= 2
|
||
? (recent[1] < recent[0] ? 'up' : recent[1] > recent[0] ? 'down' : 'stable')
|
||
: 'stable';
|
||
|
||
const trendIcon = trend === 'up' ? '↑' : trend === 'down' ? '↓' : '→';
|
||
const trendClass = trend === 'up' ? 'trend-up' : trend === 'down' ? 'trend-down' : 'trend-stable';
|
||
|
||
row.innerHTML = `
|
||
<td>
|
||
<div class="student-name">
|
||
<div class="student-avatar">${initials}</div>
|
||
<div class="student-info">
|
||
<span class="name">${student.name}</span>
|
||
<span class="class">${student.class}</span>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td class="grade-cell">
|
||
<span class="grade-badge grade-${student.grades.k1}">${student.grades.k1}</span>
|
||
</td>
|
||
<td class="grade-cell">
|
||
<span class="grade-badge grade-${student.grades.t1}">${student.grades.t1}</span>
|
||
</td>
|
||
<td class="grade-cell">
|
||
<span class="grade-badge grade-${student.grades.k2}">${student.grades.k2}</span>
|
||
</td>
|
||
<td class="grade-cell">
|
||
<span class="grade-badge grade-${student.grades.m}">${student.grades.m}</span>
|
||
</td>
|
||
<td class="grade-cell">
|
||
<span class="grade-badge grade-${student.grades.t2}">${student.grades.t2}</span>
|
||
</td>
|
||
<td class="average-cell">
|
||
<span class="grade-${Math.round(student.avg)}">${student.avg.toFixed(1)}</span>
|
||
</td>
|
||
<td class="grade-cell">
|
||
<span class="trend-indicator ${trendClass}">${trendIcon}</span>
|
||
</td>
|
||
`;
|
||
|
||
return row;
|
||
}
|
||
|
||
// Update Statistics
|
||
function updateStatistics() {
|
||
const students = gradebookData.students;
|
||
const averages = students.map(s => s.avg);
|
||
const classAvg = (averages.reduce((a, b) => a + b, 0) / averages.length).toFixed(1);
|
||
const passed = students.filter(s => s.avg <= 4.0).length;
|
||
const warning = students.filter(s => s.avg > 4.0).length;
|
||
|
||
document.getElementById('stat-average').textContent = classAvg;
|
||
document.getElementById('stat-passed').textContent = passed;
|
||
document.getElementById('stat-warning').textContent = warning;
|
||
}
|
||
|
||
// Exam Type Selector
|
||
function initExamTypeSelector() {
|
||
const buttons = document.querySelectorAll('.exam-type-btn');
|
||
buttons.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
buttons.forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
});
|
||
});
|
||
}
|
||
|
||
// Set Today's Date
|
||
function setTodayDate() {
|
||
const dateInput = document.getElementById('grade-date');
|
||
if (dateInput) {
|
||
dateInput.value = new Date().toISOString().split('T')[0];
|
||
}
|
||
}
|
||
|
||
// Populate Student Select
|
||
function populateStudentSelect() {
|
||
const select = document.getElementById('grade-student');
|
||
if (!select) return;
|
||
|
||
select.innerHTML = '<option value="">Auswählen...</option>';
|
||
gradebookData.students.forEach(student => {
|
||
const option = document.createElement('option');
|
||
option.value = student.id;
|
||
option.textContent = `${student.name} (${student.class})`;
|
||
select.appendChild(option);
|
||
});
|
||
}
|
||
|
||
// Open Add Grade Modal
|
||
function openAddGradeModal() {
|
||
const modal = document.getElementById('add-grade-modal');
|
||
if (modal) {
|
||
modal.classList.add('active');
|
||
setTodayDate();
|
||
}
|
||
}
|
||
|
||
// Close Add Grade Modal
|
||
function closeAddGradeModal() {
|
||
const modal = document.getElementById('add-grade-modal');
|
||
if (modal) {
|
||
modal.classList.remove('active');
|
||
}
|
||
}
|
||
|
||
// Save Grade
|
||
function saveGrade(event) {
|
||
event.preventDefault();
|
||
|
||
const studentId = document.getElementById('grade-student').value;
|
||
const subject = document.getElementById('grade-subject').value;
|
||
const grade = document.getElementById('grade-value').value;
|
||
const date = document.getElementById('grade-date').value;
|
||
const weight = document.getElementById('grade-weight').value;
|
||
const comment = document.getElementById('grade-comment').value;
|
||
|
||
const examType = document.querySelector('.exam-type-btn.active')?.dataset.type || 'klausur';
|
||
|
||
// In production: API call
|
||
console.log('Saving grade:', {
|
||
studentId, subject, grade, date, weight, comment, examType
|
||
});
|
||
|
||
// Show success message
|
||
alert('Note wurde gespeichert!');
|
||
closeAddGradeModal();
|
||
|
||
// Reload table
|
||
loadGradebook();
|
||
}
|
||
|
||
// Export Gradebook
|
||
function exportGradebook() {
|
||
const format = prompt('Export-Format wählen (excel/pdf/csv):', 'excel');
|
||
|
||
if (format) {
|
||
// In production: API call to generate export
|
||
console.log('Exporting gradebook as:', format);
|
||
alert(`Export als ${format.toUpperCase()} wird erstellt...`);
|
||
}
|
||
}
|
||
|
||
// Initialize on panel activation
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// Check if gradebook panel is active
|
||
const panel = document.getElementById('panel-gradebook');
|
||
if (panel && panel.classList.contains('active')) {
|
||
initGradebook();
|
||
}
|
||
});
|
||
|
||
// Also initialize when panel becomes active
|
||
const observer = new MutationObserver((mutations) => {
|
||
mutations.forEach((mutation) => {
|
||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||
const panel = document.getElementById('panel-gradebook');
|
||
if (panel && panel.classList.contains('active')) {
|
||
initGradebook();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
const gradebookPanel = document.getElementById('panel-gradebook');
|
||
if (gradebookPanel) {
|
||
observer.observe(gradebookPanel, { attributes: true });
|
||
}
|
||
"""
|