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:
304
backend/frontend/school/pages/timetable.py
Normal file
304
backend/frontend/school/pages/timetable.py
Normal file
@@ -0,0 +1,304 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user