This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/frontend/school/pages/timetable.py
Benjamin Admin 21a844cb8a 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>
2026-02-09 09:51:32 +01:00

305 lines
12 KiB
Python

"""
School Module - Timetable Page
Weekly timetable view
"""
from ..styles import SCHOOL_BASE_STYLES, TIMETABLE_STYLES
from ..templates import ICONS, render_base_page, COMMON_SCRIPTS
def timetable_page() -> str:
"""Timetable page"""
styles = SCHOOL_BASE_STYLES + TIMETABLE_STYLES
content = f'''
<main class="main-content">
<div class="page-header">
<div>
<h1 class="page-title">Stundenplan</h1>
<p class="page-subtitle">Wochenübersicht für Ihre Klasse</p>
</div>
<button class="btn btn-secondary" onclick="printTimetable()">
{ICONS['print']}
Drucken
</button>
</div>
<!-- Controls -->
<div class="controls">
<div class="select-wrapper">
<select id="class-select" onchange="loadTimetable()">
<option value="5a">Klasse 5a</option>
<option value="5b">Klasse 5b</option>
<option value="6a">Klasse 6a</option>
</select>
</div>
<div class="week-nav">
<button class="week-nav-btn" onclick="prevWeek()">
{ICONS['chevron_left']}
</button>
<span class="week-label" id="week-label">16. - 20. Dezember 2024</span>
<button class="week-nav-btn" onclick="nextWeek()">
{ICONS['chevron_right']}
</button>
</div>
<button class="btn btn-today" onclick="goToToday()">Heute</button>
</div>
<!-- Timetable -->
<div class="timetable-container">
<div class="timetable" id="timetable">
<!-- Header -->
<div class="timetable-header">
<div></div>
<div>Montag<br><small id="day-mon">16.12.</small></div>
<div>Dienstag<br><small id="day-tue">17.12.</small></div>
<div>Mittwoch<br><small id="day-wed">18.12.</small></div>
<div>Donnerstag<br><small id="day-thu">19.12.</small></div>
<div>Freitag<br><small id="day-fri">20.12.</small></div>
</div>
<!-- Rows will be populated by JavaScript -->
</div>
</div>
<!-- Legend -->
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background: #e0f2fe; border-left: 3px solid #0ea5e9;"></div>
<span>Mathematik</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #fef3c7; border-left: 3px solid #f59e0b;"></div>
<span>Deutsch</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #dbeafe; border-left: 3px solid #3b82f6;"></div>
<span>Englisch</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: repeating-linear-gradient(45deg, #fef3c7, #fef3c7 5px, #fde68a 5px, #fde68a 10px);"></div>
<span>Vertretung</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #fee2e2; border-left: 3px solid #ef4444;"></div>
<span>Entfall</span>
</div>
</div>
</main>
<!-- Lesson Detail Modal -->
<div class="modal-overlay" id="lesson-modal">
<div class="modal" style="max-width: 400px; padding: 1.5rem;">
<div class="modal-header" style="padding: 0; margin-bottom: 1rem; border: none;">
<h2 class="modal-title" id="modal-subject">Mathematik</h2>
<button class="modal-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-info">
<div class="modal-info-row">
<span class="modal-info-label">Lehrer</span>
<span class="modal-info-value" id="modal-teacher">Hr. Schmidt</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label">Raum</span>
<span class="modal-info-value" id="modal-room">R 204</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label">Zeit</span>
<span class="modal-info-value" id="modal-time">08:00 - 08:45</span>
</div>
<div class="modal-info-row">
<span class="modal-info-label">Status</span>
<span class="modal-info-value" id="modal-status">Regulär</span>
</div>
</div>
</div>
</div>
<div id="toast" class="toast"></div>'''
scripts = COMMON_SCRIPTS + '''
<script>
const lessonTimes = [
{ num: 1, start: '08:00', end: '08:45' },
{ num: 2, start: '08:50', end: '09:35' },
{ num: 3, start: '09:50', end: '10:35' },
{ num: 4, start: '10:40', end: '11:25' },
{ num: 5, start: '11:40', end: '12:25' },
{ num: 6, start: '12:30', end: '13:15' }
];
const timetableData = {
mon: [
{ subject: 'Mathematik', teacher: 'Hr. Schmidt', room: 'R 204', type: 'math' },
{ subject: 'Mathematik', teacher: 'Hr. Schmidt', room: 'R 204', type: 'math' },
{ subject: 'Deutsch', teacher: 'Fr. Müller', room: 'R 102', type: 'german' },
{ subject: 'Deutsch', teacher: 'Fr. Müller', room: 'R 102', type: 'german' },
{ subject: 'Englisch', teacher: 'Hr. Wagner', room: 'R 305', type: 'english' },
{ subject: 'Sport', teacher: 'Hr. Becker', room: 'Turnhalle', type: 'sport' }
],
tue: [
{ subject: 'Biologie', teacher: 'Fr. Weber', room: 'R 401', type: 'biology' },
{ subject: 'Biologie', teacher: 'Fr. Weber', room: 'R 401', type: 'biology' },
{ subject: 'Englisch', teacher: 'Hr. Wagner', room: 'R 305', type: 'english' },
{ subject: 'Mathematik', teacher: 'Hr. Schmidt', room: 'R 204', type: 'math' },
{ subject: 'Physik', teacher: 'Hr. Hoffmann', room: 'R 402', type: 'physics', substitution: true, subTeacher: 'Fr. Klein' },
null
],
wed: [
{ subject: 'Geschichte', teacher: 'Fr. Braun', room: 'R 203', type: 'history' },
{ subject: 'Geschichte', teacher: 'Fr. Braun', room: 'R 203', type: 'history' },
{ subject: 'Musik', teacher: 'Hr. Fischer', room: 'Musikraum', type: 'music' },
{ subject: 'Deutsch', teacher: 'Fr. Müller', room: 'R 102', type: 'german' },
{ subject: 'Kunst', teacher: 'Fr. Lange', room: 'Kunstraum', type: 'art' },
{ subject: 'Kunst', teacher: 'Fr. Lange', room: 'Kunstraum', type: 'art' }
],
thu: [
{ subject: 'Mathematik', teacher: 'Hr. Schmidt', room: 'R 204', type: 'math' },
{ subject: 'Physik', teacher: 'Hr. Hoffmann', room: 'R 402', type: 'physics' },
{ subject: 'Physik', teacher: 'Hr. Hoffmann', room: 'R 402', type: 'physics' },
{ subject: 'Englisch', teacher: 'Hr. Wagner', room: 'R 305', type: 'english' },
{ subject: 'Deutsch', teacher: 'Fr. Müller', room: 'R 102', type: 'german', cancelled: true },
null
],
fri: [
{ subject: 'Englisch', teacher: 'Hr. Wagner', room: 'R 305', type: 'english' },
{ subject: 'Englisch', teacher: 'Hr. Wagner', room: 'R 305', type: 'english' },
{ subject: 'Mathematik', teacher: 'Hr. Schmidt', room: 'R 204', type: 'math' },
{ subject: 'Biologie', teacher: 'Fr. Weber', room: 'R 401', type: 'biology' },
null,
null
]
};
let currentWeekStart = getMonday(new Date());
document.addEventListener('DOMContentLoaded', () => {
renderTimetable();
updateWeekLabel();
});
function getMonday(d) {
const date = new Date(d);
const day = date.getDay();
const diff = date.getDate() - day + (day === 0 ? -6 : 1);
return new Date(date.setDate(diff));
}
function updateWeekLabel() {
const start = new Date(currentWeekStart);
const end = new Date(start);
end.setDate(end.getDate() + 4);
document.getElementById('week-label').textContent =
`${start.getDate()}. - ${end.getDate()}. ${start.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })}`;
const days = ['mon', 'tue', 'wed', 'thu', 'fri'];
days.forEach((day, i) => {
const d = new Date(start);
d.setDate(d.getDate() + i);
document.getElementById(`day-${day}`).textContent =
d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
});
}
function prevWeek() {
currentWeekStart.setDate(currentWeekStart.getDate() - 7);
updateWeekLabel();
renderTimetable();
}
function nextWeek() {
currentWeekStart.setDate(currentWeekStart.getDate() + 7);
updateWeekLabel();
renderTimetable();
}
function goToToday() {
currentWeekStart = getMonday(new Date());
updateWeekLabel();
renderTimetable();
showToast('Zur aktuellen Woche gesprungen');
}
function renderTimetable() {
const container = document.getElementById('timetable');
const header = container.querySelector('.timetable-header').outerHTML;
container.innerHTML = header;
for (let i = 0; i < lessonTimes.length; i++) {
const time = lessonTimes[i];
let rowHTML = `
<div class="timetable-row">
<div class="time-cell">
<div class="lesson-num">${time.num}.</div>
<div>${time.start}</div>
<div>${time.end}</div>
</div>
`;
const days = ['mon', 'tue', 'wed', 'thu', 'fri'];
days.forEach(day => {
const lesson = timetableData[day][i];
rowHTML += `<div class="lesson-cell">`;
if (lesson) {
let cardClass = `lesson-card ${lesson.type}`;
if (lesson.substitution) cardClass += ' substitution';
if (lesson.cancelled) cardClass += ' cancelled';
rowHTML += `
<div class="${cardClass}" onclick="showLessonDetail('${lesson.subject}', '${lesson.teacher}', '${lesson.room}', '${time.start} - ${time.end}', ${lesson.substitution || false}, ${lesson.cancelled || false}, '${lesson.subTeacher || ''}')">
<div class="lesson-subject">${lesson.subject}</div>
<div class="lesson-teacher">${lesson.substitution ? lesson.subTeacher : lesson.teacher}</div>
<div class="lesson-room">${lesson.room}</div>
${lesson.substitution ? '<span class="lesson-badge badge-substitution">Vertretung</span>' : ''}
${lesson.cancelled ? '<span class="lesson-badge badge-cancelled">Entfall</span>' : ''}
</div>
`;
}
rowHTML += `</div>`;
});
rowHTML += `</div>`;
container.innerHTML += rowHTML;
}
}
function showLessonDetail(subject, teacher, room, time, isSubstitution, isCancelled, subTeacher) {
document.getElementById('modal-subject').textContent = subject;
document.getElementById('modal-teacher').textContent = isSubstitution ? `${subTeacher} (Vertretung für ${teacher})` : teacher;
document.getElementById('modal-room').textContent = room;
document.getElementById('modal-time').textContent = time;
let status = 'Regulär';
if (isCancelled) status = 'Entfällt';
else if (isSubstitution) status = 'Vertretung';
document.getElementById('modal-status').textContent = status;
document.getElementById('lesson-modal').classList.add('show');
}
function closeModal() {
document.getElementById('lesson-modal').classList.remove('show');
}
function loadTimetable() {
showToast('Stundenplan wird geladen...');
}
function printTimetable() {
window.print();
}
document.getElementById('lesson-modal').addEventListener('click', (e) => {
if (e.target.classList.contains('modal-overlay')) {
closeModal();
}
});
</script>'''
return render_base_page("Stundenplan", styles, content, scripts, "timetable")