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>
305 lines
12 KiB
Python
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()">×</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")
|