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>
313 lines
10 KiB
TypeScript
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>
|
|
)
|
|
}
|