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>
210 lines
8.1 KiB
TypeScript
210 lines
8.1 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { CheckCircle, Clock, BarChart3, Plus, RefreshCw } from 'lucide-react'
|
|
import { LessonSession } from '@/lib/companion/types'
|
|
import { HomeworkSection } from './HomeworkSection'
|
|
import { ReflectionSection } from './ReflectionSection'
|
|
import {
|
|
PHASE_COLORS,
|
|
PHASE_DISPLAY_NAMES,
|
|
formatTime,
|
|
formatMinutes,
|
|
} from '@/lib/companion/constants'
|
|
|
|
interface LessonEndedViewProps {
|
|
session: LessonSession
|
|
onSaveReflection: (rating: number, notes: string, nextSteps: string) => void
|
|
onAddHomework: (title: string, dueDate: string) => void
|
|
onRemoveHomework: (id: string) => void
|
|
onStartNew: () => void
|
|
}
|
|
|
|
export function LessonEndedView({
|
|
session,
|
|
onSaveReflection,
|
|
onAddHomework,
|
|
onRemoveHomework,
|
|
onStartNew,
|
|
}: LessonEndedViewProps) {
|
|
const [activeTab, setActiveTab] = useState<'summary' | 'homework' | 'reflection'>('summary')
|
|
|
|
// Calculate analytics
|
|
const totalPlannedSeconds = session.totalPlannedDuration * 60
|
|
const totalActualSeconds = session.elapsedTime
|
|
const timeDiff = totalActualSeconds - totalPlannedSeconds
|
|
const timeDiffMinutes = Math.round(timeDiff / 60)
|
|
|
|
const startTime = new Date(session.startTime)
|
|
const endTime = session.endTime ? new Date(session.endTime) : new Date()
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Success Header */}
|
|
<div className="bg-gradient-to-r from-green-500 to-green-600 rounded-xl p-6 text-white">
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-3 bg-white/20 rounded-full">
|
|
<CheckCircle className="w-8 h-8" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-2xl font-bold">Stunde beendet!</h2>
|
|
<p className="text-green-100">
|
|
{session.className} - {session.subject}
|
|
{session.topic && ` - ${session.topic}`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tab Navigation */}
|
|
<div className="bg-white border border-slate-200 rounded-xl p-1 flex">
|
|
{[
|
|
{ id: 'summary', label: 'Zusammenfassung', icon: BarChart3 },
|
|
{ id: 'homework', label: 'Hausaufgaben', icon: Plus },
|
|
{ id: 'reflection', label: 'Reflexion', icon: RefreshCw },
|
|
].map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id as typeof activeTab)}
|
|
className={`
|
|
flex-1 flex items-center justify-center gap-2 py-3 px-4 rounded-lg
|
|
font-medium transition-all duration-200
|
|
${activeTab === tab.id
|
|
? 'bg-slate-900 text-white'
|
|
: 'text-slate-600 hover:bg-slate-100'
|
|
}
|
|
`}
|
|
>
|
|
<tab.icon className="w-4 h-4" />
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
{activeTab === 'summary' && (
|
|
<div className="space-y-6">
|
|
{/* Time Overview */}
|
|
<div className="bg-white border border-slate-200 rounded-xl p-6">
|
|
<h3 className="font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
|
<Clock className="w-5 h-5 text-slate-400" />
|
|
Zeitauswertung
|
|
</h3>
|
|
|
|
<div className="grid grid-cols-3 gap-4 mb-6">
|
|
<div className="text-center p-4 bg-slate-50 rounded-xl">
|
|
<div className="text-2xl font-bold text-slate-900">
|
|
{formatTime(totalActualSeconds)}
|
|
</div>
|
|
<div className="text-sm text-slate-500">Tatsaechlich</div>
|
|
</div>
|
|
<div className="text-center p-4 bg-slate-50 rounded-xl">
|
|
<div className="text-2xl font-bold text-slate-900">
|
|
{formatMinutes(session.totalPlannedDuration)}
|
|
</div>
|
|
<div className="text-sm text-slate-500">Geplant</div>
|
|
</div>
|
|
<div className={`text-center p-4 rounded-xl ${timeDiff > 0 ? 'bg-amber-50' : 'bg-green-50'}`}>
|
|
<div className={`text-2xl font-bold ${timeDiff > 0 ? 'text-amber-600' : 'text-green-600'}`}>
|
|
{timeDiffMinutes > 0 ? '+' : ''}{timeDiffMinutes} Min
|
|
</div>
|
|
<div className={`text-sm ${timeDiff > 0 ? 'text-amber-500' : 'text-green-500'}`}>
|
|
Differenz
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Session Times */}
|
|
<div className="flex items-center justify-between text-sm text-slate-500 border-t border-slate-100 pt-4">
|
|
<span>Start: {startTime.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}</span>
|
|
<span>Ende: {endTime.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Phase Breakdown */}
|
|
<div className="bg-white border border-slate-200 rounded-xl p-6">
|
|
<h3 className="font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
|
<BarChart3 className="w-5 h-5 text-slate-400" />
|
|
Phasen-Analyse
|
|
</h3>
|
|
|
|
<div className="space-y-4">
|
|
{session.phases.map((phase) => {
|
|
const plannedSeconds = phase.duration * 60
|
|
const actualSeconds = phase.actualTime
|
|
const diff = actualSeconds - plannedSeconds
|
|
const diffMinutes = Math.round(diff / 60)
|
|
const percentage = Math.min((actualSeconds / plannedSeconds) * 100, 150)
|
|
|
|
return (
|
|
<div key={phase.phase} className="space-y-2">
|
|
<div className="flex items-center justify-between text-sm">
|
|
<div className="flex items-center gap-2">
|
|
<div
|
|
className="w-3 h-3 rounded-full"
|
|
style={{ backgroundColor: PHASE_COLORS[phase.phase].hex }}
|
|
/>
|
|
<span className="font-medium text-slate-700">
|
|
{PHASE_DISPLAY_NAMES[phase.phase]}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-3 text-slate-500">
|
|
<span>{Math.round(actualSeconds / 60)} / {phase.duration} Min</span>
|
|
<span className={`
|
|
px-2 py-0.5 rounded text-xs font-medium
|
|
${diff > 60 ? 'bg-amber-100 text-amber-700' : diff < -60 ? 'bg-blue-100 text-blue-700' : 'bg-green-100 text-green-700'}
|
|
`}>
|
|
{diffMinutes > 0 ? '+' : ''}{diffMinutes} Min
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="h-3 bg-slate-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full rounded-full transition-all duration-500"
|
|
style={{
|
|
width: `${Math.min(percentage, 100)}%`,
|
|
backgroundColor: percentage > 100
|
|
? '#f59e0b' // amber for overtime
|
|
: PHASE_COLORS[phase.phase].hex,
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'homework' && (
|
|
<HomeworkSection
|
|
homeworkList={session.homeworkList}
|
|
onAdd={onAddHomework}
|
|
onRemove={onRemoveHomework}
|
|
/>
|
|
)}
|
|
|
|
{activeTab === 'reflection' && (
|
|
<ReflectionSection
|
|
reflection={session.reflection}
|
|
onSave={onSaveReflection}
|
|
/>
|
|
)}
|
|
|
|
{/* Start New Lesson Button */}
|
|
<div className="pt-4">
|
|
<button
|
|
onClick={onStartNew}
|
|
className="w-full py-4 px-6 bg-slate-900 text-white rounded-xl font-semibold hover:bg-slate-800 transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<RefreshCw className="w-5 h-5" />
|
|
Neue Stunde starten
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|