Files
breakpilot-lehrer/admin-lehrer/components/companion/CompanionDashboard.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

313 lines
10 KiB
TypeScript

'use client'
import { useState, useEffect, useCallback } from 'react'
import { Settings, MessageSquare, HelpCircle, RefreshCw } from 'lucide-react'
import { CompanionMode, TeacherSettings, FeedbackType } from '@/lib/companion/types'
import { DEFAULT_TEACHER_SETTINGS, STORAGE_KEYS } from '@/lib/companion/constants'
// Components
import { ModeToggle } from './ModeToggle'
import { PhaseTimeline } from './companion-mode/PhaseTimeline'
import { StatsGrid } from './companion-mode/StatsGrid'
import { SuggestionList } from './companion-mode/SuggestionList'
import { EventsCard } from './companion-mode/EventsCard'
import { LessonContainer } from './lesson-mode/LessonContainer'
import { SettingsModal } from './modals/SettingsModal'
import { FeedbackModal } from './modals/FeedbackModal'
import { OnboardingModal } from './modals/OnboardingModal'
// Hooks
import { useCompanionData } from '@/hooks/companion/useCompanionData'
import { useLessonSession } from '@/hooks/companion/useLessonSession'
import { useKeyboardShortcuts } from '@/hooks/companion/useKeyboardShortcuts'
export function CompanionDashboard() {
// Mode state
const [mode, setMode] = useState<CompanionMode>('companion')
// Modal states
const [showSettings, setShowSettings] = useState(false)
const [showFeedback, setShowFeedback] = useState(false)
const [showOnboarding, setShowOnboarding] = useState(false)
// Settings
const [settings, setSettings] = useState<TeacherSettings>(DEFAULT_TEACHER_SETTINGS)
// Load settings from localStorage
useEffect(() => {
const stored = localStorage.getItem(STORAGE_KEYS.SETTINGS)
if (stored) {
try {
const parsed = JSON.parse(stored)
setSettings({ ...DEFAULT_TEACHER_SETTINGS, ...parsed })
} catch {
// Invalid stored settings
}
}
// Check if onboarding needed
const onboardingStored = localStorage.getItem(STORAGE_KEYS.ONBOARDING_STATE)
if (!onboardingStored) {
setShowOnboarding(true)
}
// Restore last mode
const lastMode = localStorage.getItem(STORAGE_KEYS.LAST_MODE) as CompanionMode
if (lastMode && ['companion', 'lesson', 'classic'].includes(lastMode)) {
setMode(lastMode)
}
}, [])
// Save mode to localStorage
useEffect(() => {
localStorage.setItem(STORAGE_KEYS.LAST_MODE, mode)
}, [mode])
// Companion data hook
const { data: companionData, loading: companionLoading, refresh } = useCompanionData()
// Lesson session hook
const {
session,
startLesson,
endLesson,
pauseLesson,
resumeLesson,
extendTime,
skipPhase,
saveReflection,
addHomework,
removeHomework,
isPaused,
} = useLessonSession({
onOvertimeStart: () => {
// Play sound if enabled
if (settings.soundNotifications) {
// TODO: Play notification sound
}
},
})
// Handle pause/resume toggle
const handlePauseToggle = useCallback(() => {
if (isPaused) {
resumeLesson()
} else {
pauseLesson()
}
}, [isPaused, pauseLesson, resumeLesson])
// Keyboard shortcuts
useKeyboardShortcuts({
onPauseResume: mode === 'lesson' && session ? handlePauseToggle : undefined,
onExtend: mode === 'lesson' && session && !isPaused ? () => extendTime(5) : undefined,
onNextPhase: mode === 'lesson' && session && !isPaused ? skipPhase : undefined,
onCloseModal: () => {
setShowSettings(false)
setShowFeedback(false)
setShowOnboarding(false)
},
enabled: settings.showKeyboardShortcuts,
})
// Handle settings save
const handleSaveSettings = (newSettings: TeacherSettings) => {
setSettings(newSettings)
localStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(newSettings))
}
// Handle feedback submit
const handleFeedbackSubmit = async (type: FeedbackType, title: string, description: string) => {
const response = await fetch('/api/admin/companion/feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type,
title,
description,
sessionId: session?.sessionId,
}),
})
if (!response.ok) {
throw new Error('Failed to submit feedback')
}
}
// Handle onboarding complete
const handleOnboardingComplete = (data: { state?: string; schoolType?: string }) => {
localStorage.setItem(STORAGE_KEYS.ONBOARDING_STATE, JSON.stringify({
...data,
completed: true,
completedAt: new Date().toISOString(),
}))
setShowOnboarding(false)
setSettings({ ...settings, onboardingCompleted: true })
}
// Handle lesson start
const handleStartLesson = (data: { classId: string; subject: string; topic?: string; templateId?: string }) => {
startLesson(data)
setMode('lesson')
}
return (
<div className={`min-h-[calc(100vh-200px)] ${settings.highContrastMode ? 'high-contrast' : ''}`}>
{/* Header */}
<div className="flex items-center justify-between mb-6">
<ModeToggle
currentMode={mode}
onModeChange={setMode}
disabled={!!session && session.status === 'in_progress'}
/>
<div className="flex items-center gap-2">
{/* Refresh Button */}
{mode === 'companion' && (
<button
onClick={refresh}
disabled={companionLoading}
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
title="Aktualisieren"
>
<RefreshCw className={`w-5 h-5 ${companionLoading ? 'animate-spin' : ''}`} />
</button>
)}
{/* Feedback Button */}
<button
onClick={() => setShowFeedback(true)}
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
title="Feedback"
>
<MessageSquare className="w-5 h-5" />
</button>
{/* Settings Button */}
<button
onClick={() => setShowSettings(true)}
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
title="Einstellungen"
>
<Settings className="w-5 h-5" />
</button>
{/* Help Button */}
<button
onClick={() => setShowOnboarding(true)}
className="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-colors"
title="Hilfe"
>
<HelpCircle className="w-5 h-5" />
</button>
</div>
</div>
{/* Main Content */}
{mode === 'companion' && (
<div className="space-y-6">
{/* Phase Timeline */}
<div className="bg-white border border-slate-200 rounded-xl p-6">
<h3 className="text-sm font-medium text-slate-500 mb-4">Aktuelle Phase</h3>
{companionData ? (
<PhaseTimeline
phases={companionData.phases}
currentPhaseIndex={companionData.phases.findIndex(p => p.status === 'active')}
/>
) : (
<div className="h-10 bg-slate-100 rounded animate-pulse" />
)}
</div>
{/* Stats */}
<StatsGrid
stats={companionData?.stats || { classesCount: 0, studentsCount: 0, learningUnitsCreated: 0, gradesEntered: 0 }}
loading={companionLoading}
/>
{/* Two Column Layout */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Suggestions */}
<SuggestionList
suggestions={companionData?.suggestions || []}
loading={companionLoading}
onSuggestionClick={(suggestion) => {
// Navigate to action target
window.location.href = suggestion.actionTarget
}}
/>
{/* Events */}
<EventsCard
events={companionData?.upcomingEvents || []}
loading={companionLoading}
/>
</div>
{/* Quick Start Lesson Button */}
<div className="bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl p-6 text-white">
<div className="flex items-center justify-between">
<div>
<h3 className="text-xl font-semibold mb-1">Bereit fuer die naechste Stunde?</h3>
<p className="text-blue-100">Starten Sie den Lesson-Modus fuer strukturierten Unterricht.</p>
</div>
<button
onClick={() => setMode('lesson')}
className="px-6 py-3 bg-white text-blue-600 rounded-xl font-semibold hover:bg-blue-50 transition-colors"
>
Stunde starten
</button>
</div>
</div>
</div>
)}
{mode === 'lesson' && (
<LessonContainer
session={session}
onStartLesson={handleStartLesson}
onEndLesson={endLesson}
onPauseToggle={handlePauseToggle}
onExtendTime={extendTime}
onSkipPhase={skipPhase}
onSaveReflection={saveReflection}
onAddHomework={addHomework}
onRemoveHomework={removeHomework}
/>
)}
{mode === 'classic' && (
<div className="bg-white border border-slate-200 rounded-xl p-8 text-center">
<h2 className="text-xl font-semibold text-slate-800 mb-2">Classic Mode</h2>
<p className="text-slate-600 mb-4">
Die klassische Ansicht ohne Timer und Phasenstruktur.
</p>
<p className="text-sm text-slate-500">
Dieser Modus ist fuer flexible Unterrichtsgestaltung gedacht.
</p>
</div>
)}
{/* Modals */}
<SettingsModal
isOpen={showSettings}
onClose={() => setShowSettings(false)}
settings={settings}
onSave={handleSaveSettings}
/>
<FeedbackModal
isOpen={showFeedback}
onClose={() => setShowFeedback(false)}
onSubmit={handleFeedbackSubmit}
/>
<OnboardingModal
isOpen={showOnboarding}
onClose={() => setShowOnboarding(false)}
onComplete={handleOnboardingComplete}
/>
</div>
)
}