'use client' import { useState, useEffect, useCallback, useMemo } from 'react' import { useTheme } from '@/lib/ThemeContext' import { solutionsApi, subjectsApi, lessonsApi, downloadSolutionExport } from '@/lib/stundenplan/api' import type { TimetableLesson, TimetableSubject } from '@/app/stundenplan/types' interface PlanViewProps { solutionId: string } const DAYS = [ { v: 1, label: 'Mo' }, { v: 2, label: 'Di' }, { v: 3, label: 'Mi' }, { v: 4, label: 'Do' }, { v: 5, label: 'Fr' }, ] type Perspective = 'class' | 'teacher' | 'room' const PERSPECTIVE_LABEL: Record = { class: 'Klasse', teacher: 'Lehrer', room: 'Raum', } interface Resource { id: string label: string } export function PlanView({ solutionId }: PlanViewProps) { const { isDark } = useTheme() const [lessons, setLessons] = useState([]) const [subjects, setSubjects] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [perspective, setPerspective] = useState('class') const [selectedResource, setSelectedResource] = useState('') const load = useCallback(async () => { setLoading(true) setError(null) try { const [ls, sub] = await Promise.all([ solutionsApi.lessons(solutionId), subjectsApi.list(), ]) setLessons(ls || []) setSubjects(sub || []) } catch (e) { setError(e instanceof Error ? e.message : 'Laden fehlgeschlagen') } finally { setLoading(false) } }, [solutionId]) useEffect(() => { load() }, [load]) // Unique resources for the chosen perspective. const resources: Resource[] = useMemo(() => { const seen = new Map() for (const l of lessons) { let id = '' let label = '' if (perspective === 'class') { id = l.class_id label = l.class_name || id.slice(0, 8) } else if (perspective === 'teacher') { id = l.teacher_id label = l.teacher_name || id.slice(0, 8) } else if (perspective === 'room') { id = l.room_id || 'kein-raum' label = l.room_name || (l.room_id ? l.room_id.slice(0, 8) : '— kein Raum —') } if (!seen.has(id)) seen.set(id, { id, label }) } return Array.from(seen.values()).sort((a, b) => a.label.localeCompare(b.label)) }, [lessons, perspective]) // Reset selected resource when perspective changes or list refreshes. useEffect(() => { if (resources.length > 0 && !resources.some(r => r.id === selectedResource)) { setSelectedResource(resources[0].id) } }, [resources, selectedResource]) const visibleLessons = useMemo(() => { if (!selectedResource) return [] return lessons.filter(l => { if (perspective === 'class') return l.class_id === selectedResource if (perspective === 'teacher') return l.teacher_id === selectedResource return (l.room_id || 'kein-raum') === selectedResource }) }, [lessons, perspective, selectedResource]) const subjectColor = useCallback((id: string): string => { const s = subjects.find(x => x.id === id) return s?.color || (isDark ? '#475569' : '#cbd5e1') }, [subjects, isDark]) const periodIndices = useMemo(() => { const set = new Set() for (const l of lessons) set.add(l.period_index) return Array.from(set).sort((a, b) => a - b) }, [lessons]) const cellLesson = (day: number, periodIdx: number): TimetableLesson | undefined => visibleLessons.find(l => l.day_of_week === day && l.period_index === periodIdx) const togglePin = useCallback(async (lesson: TimetableLesson) => { // Optimistic update so the lock icon flips immediately even if the // server is slow. setLessons(prev => prev.map(l => l.id === lesson.id ? { ...l, pinned: !l.pinned } : l)) try { await lessonsApi.pin(lesson.id, !lesson.pinned) } catch (e) { // Revert on failure and surface the error. setLessons(prev => prev.map(l => l.id === lesson.id ? { ...l, pinned: lesson.pinned } : l)) setError(e instanceof Error ? e.message : 'Pin fehlgeschlagen') } }, []) const cardClass = isDark ? 'bg-white/10 border-white/20 text-white' : 'bg-white/80 border-black/10 text-slate-900' const selectClass = isDark ? 'bg-white/10 border-white/20 text-white' : 'bg-white border-slate-300 text-slate-900' const handleExport = (fmt: 'csv' | 'ics') => { downloadSolutionExport(solutionId, fmt).catch(e => setError(e instanceof Error ? e.message : 'Export fehlgeschlagen'), ) } return (
{(['class', 'teacher', 'room'] as Perspective[]).map(p => ( ))}
{error &&
{error}
} {loading ? (
Laedt…
) : lessons.length === 0 ? (
Keine Lessons in diesem Plan.
) : (
{DAYS.map(d => ( ))} {periodIndices.map(idx => ( {DAYS.map(d => { const lesson = cellLesson(d.v, idx) if (!lesson) { return } const color = subjectColor(lesson.subject_id) return ( ) })} ))}
Stunde{d.label}
{idx}.
{lesson.subject_name || '?'}
{perspective !== 'class' && lesson.class_name && (
{lesson.class_name}
)} {perspective !== 'teacher' && lesson.teacher_name && (
{lesson.teacher_name.split(',')[0]}
)} {perspective !== 'room' && lesson.room_name && (
{lesson.room_name}
)}
)}
) }