fix: Restore all files lost during destructive rebase
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>
This commit is contained in:
249
backend/frontend/school/pages/attendance.py
Normal file
249
backend/frontend/school/pages/attendance.py
Normal file
@@ -0,0 +1,249 @@
|
||||
"""
|
||||
School Module - Attendance Page
|
||||
Attendance tracking for students
|
||||
"""
|
||||
|
||||
from ..styles import SCHOOL_BASE_STYLES, ATTENDANCE_STYLES
|
||||
from ..templates import ICONS, render_base_page, COMMON_SCRIPTS
|
||||
|
||||
|
||||
def attendance_page() -> str:
|
||||
"""Attendance tracking page"""
|
||||
styles = SCHOOL_BASE_STYLES + ATTENDANCE_STYLES
|
||||
|
||||
content = f'''
|
||||
<main class="main-content">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Anwesenheit</h1>
|
||||
<p class="page-subtitle">Erfassen Sie die Anwesenheit Ihrer Schüler</p>
|
||||
</div>
|
||||
<button class="btn btn-success" onclick="saveAttendance()">
|
||||
{ICONS['check']}
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls">
|
||||
<div class="select-wrapper">
|
||||
<select id="class-select" onchange="loadClass()">
|
||||
<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="lesson-select">
|
||||
<option value="1">1. Stunde (08:00 - 08:45)</option>
|
||||
<option value="2">2. Stunde (08:50 - 09:35)</option>
|
||||
<option value="3">3. Stunde (09:50 - 10:35)</option>
|
||||
<option value="4">4. Stunde (10:40 - 11:25)</option>
|
||||
<option value="5">5. Stunde (11:40 - 12:25)</option>
|
||||
<option value="6">6. Stunde (12:30 - 13:15)</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="date" id="date-select" onchange="loadAttendance()">
|
||||
</div>
|
||||
|
||||
<!-- Stats Bar -->
|
||||
<div class="stats-bar">
|
||||
<div class="stat-item">
|
||||
<span class="stat-dot present"></span>
|
||||
<span class="stat-value" id="count-present">24</span>
|
||||
<span class="stat-label">Anwesend</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-dot absent"></span>
|
||||
<span class="stat-value" id="count-absent">1</span>
|
||||
<span class="stat-label">Abwesend</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-dot late"></span>
|
||||
<span class="stat-value" id="count-late">1</span>
|
||||
<span class="stat-label">Verspätet</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-dot excused"></span>
|
||||
<span class="stat-value" id="count-excused">0</span>
|
||||
<span class="stat-label">Entschuldigt</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendance 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">Schülerliste</span>
|
||||
<button class="btn btn-secondary" onclick="markAllPresent()">Alle anwesend</button>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Schüler</th>
|
||||
<th>Status</th>
|
||||
<th>Anmerkung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="student-list">
|
||||
<!-- Dynamisch geladen -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="toast" class="toast"></div>'''
|
||||
|
||||
scripts = COMMON_SCRIPTS + '''
|
||||
<script>
|
||||
// Sample student data
|
||||
const students = [
|
||||
{ id: 1, name: 'Anna Schmidt', number: 1 },
|
||||
{ id: 2, name: 'Ben Müller', number: 2 },
|
||||
{ id: 3, name: 'Clara Weber', number: 3 },
|
||||
{ id: 4, name: 'David Fischer', number: 4 },
|
||||
{ id: 5, name: 'Emma Becker', number: 5 },
|
||||
{ id: 6, name: 'Felix Braun', number: 6 },
|
||||
{ id: 7, name: 'Greta Hoffmann', number: 7 },
|
||||
{ id: 8, name: 'Hans Schneider', number: 8 },
|
||||
{ id: 9, name: 'Ida Wagner', number: 9 },
|
||||
{ id: 10, name: 'Jonas Koch', number: 10 },
|
||||
{ id: 11, name: 'Klara Bauer', number: 11 },
|
||||
{ id: 12, name: 'Leon Richter', number: 12 },
|
||||
{ id: 13, name: 'Mia Klein', number: 13 },
|
||||
{ id: 14, name: 'Noah Wolf', number: 14 },
|
||||
{ id: 15, name: 'Olivia Meier', number: 15 },
|
||||
{ id: 16, name: 'Paul Neumann', number: 16 },
|
||||
{ id: 17, name: 'Quirin Schwarz', number: 17 },
|
||||
{ id: 18, name: 'Rosa Zimmermann', number: 18 },
|
||||
{ id: 19, name: 'Samuel Krüger', number: 19 },
|
||||
{ id: 20, name: 'Tina Lange', number: 20 },
|
||||
{ id: 21, name: 'Uwe Peters', number: 21 },
|
||||
{ id: 22, name: 'Vera Meyer', number: 22 },
|
||||
{ id: 23, name: 'Wilhelm Schulz', number: 23 },
|
||||
{ id: 24, name: 'Xenia Huber', number: 24 },
|
||||
{ id: 25, name: 'Yannik Fuchs', number: 25 },
|
||||
{ id: 26, name: 'Zoe Berger', number: 26 }
|
||||
];
|
||||
|
||||
// Attendance state
|
||||
let attendance = {};
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('date-select').value = today;
|
||||
|
||||
students.forEach(s => {
|
||||
attendance[s.id] = { status: 'present', notes: '' };
|
||||
});
|
||||
|
||||
attendance[1] = { status: 'absent', notes: '' };
|
||||
attendance[3] = { status: 'late', notes: '10 Minuten' };
|
||||
|
||||
renderStudentList();
|
||||
updateStats();
|
||||
});
|
||||
|
||||
function renderStudentList() {
|
||||
const tbody = document.getElementById('student-list');
|
||||
tbody.innerHTML = students.map(student => {
|
||||
const att = attendance[student.id] || { status: 'present', notes: '' };
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
<div class="student-info">
|
||||
<div class="student-avatar">${getInitials(student.name)}</div>
|
||||
<div>
|
||||
<div class="student-name">${student.name}</div>
|
||||
<div class="student-number">Nr. ${student.number}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="status-toggle">
|
||||
<button class="status-btn present ${att.status === 'present' ? 'active' : ''}"
|
||||
onclick="setStatus(${student.id}, 'present')">Anwesend</button>
|
||||
<button class="status-btn absent ${att.status === 'absent' ? 'active' : ''}"
|
||||
onclick="setStatus(${student.id}, 'absent')">Abwesend</button>
|
||||
<button class="status-btn late ${att.status === 'late' ? 'active' : ''}"
|
||||
onclick="setStatus(${student.id}, 'late')">Verspätet</button>
|
||||
<button class="status-btn excused ${att.status === 'excused' ? 'active' : ''}"
|
||||
onclick="setStatus(${student.id}, 'excused')">Entschuldigt</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="notes-input" placeholder="Anmerkung..."
|
||||
value="${att.notes}"
|
||||
onchange="setNotes(${student.id}, this.value)">
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function setStatus(studentId, status) {
|
||||
attendance[studentId] = {
|
||||
...attendance[studentId],
|
||||
status: status
|
||||
};
|
||||
renderStudentList();
|
||||
updateStats();
|
||||
}
|
||||
|
||||
function setNotes(studentId, notes) {
|
||||
attendance[studentId] = {
|
||||
...attendance[studentId],
|
||||
notes: notes
|
||||
};
|
||||
}
|
||||
|
||||
function markAllPresent() {
|
||||
students.forEach(s => {
|
||||
attendance[s.id] = { status: 'present', notes: '' };
|
||||
});
|
||||
renderStudentList();
|
||||
updateStats();
|
||||
}
|
||||
|
||||
function updateStats() {
|
||||
const counts = { present: 0, absent: 0, late: 0, excused: 0 };
|
||||
Object.values(attendance).forEach(a => {
|
||||
counts[a.status]++;
|
||||
});
|
||||
|
||||
document.getElementById('count-present').textContent = counts.present;
|
||||
document.getElementById('count-absent').textContent = counts.absent;
|
||||
document.getElementById('count-late').textContent = counts.late;
|
||||
document.getElementById('count-excused').textContent = counts.excused;
|
||||
}
|
||||
|
||||
function loadClass() {
|
||||
showToast('Klasse wird geladen...');
|
||||
}
|
||||
|
||||
function loadAttendance() {
|
||||
showToast('Anwesenheit wird geladen...');
|
||||
}
|
||||
|
||||
async function saveAttendance() {
|
||||
const classId = document.getElementById('class-select').value;
|
||||
const lessonId = document.getElementById('lesson-select').value;
|
||||
const date = document.getElementById('date-select').value;
|
||||
|
||||
const records = Object.entries(attendance).map(([studentId, att]) => ({
|
||||
student_id: studentId,
|
||||
status: att.status,
|
||||
notes: att.notes,
|
||||
lesson_number: parseInt(lessonId),
|
||||
date: date
|
||||
}));
|
||||
|
||||
try {
|
||||
showToast('Anwesenheit gespeichert!', 'success');
|
||||
} catch (error) {
|
||||
showToast('Fehler beim Speichern', 'error');
|
||||
}
|
||||
}
|
||||
</script>'''
|
||||
|
||||
return render_base_page("Anwesenheit", styles, content, scripts, "attendance")
|
||||
Reference in New Issue
Block a user