feat(companion): Migrate Companion from admin-v2 to studio-v2 as pure lesson tool
Remove Companion module entirely from admin-v2. Rebuild in studio-v2 as a focused lesson timer (no dashboard mode). Direct flow: start → active → ended. Fix timer bug where lastTickRef reset prevented countdown. Add companion link to Sidebar and i18n translations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
222
studio-v2/components/companion/CompanionDashboard.tsx
Normal file
222
studio-v2/components/companion/CompanionDashboard.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Settings, MessageSquare, HelpCircle, Timer } from 'lucide-react'
|
||||
import { TeacherSettings, FeedbackType } from '@/lib/companion/types'
|
||||
import { DEFAULT_TEACHER_SETTINGS, STORAGE_KEYS } from '@/lib/companion/constants'
|
||||
|
||||
// Components
|
||||
import { LessonStartForm } from './lesson-mode/LessonStartForm'
|
||||
import { LessonActiveView } from './lesson-mode/LessonActiveView'
|
||||
import { LessonEndedView } from './lesson-mode/LessonEndedView'
|
||||
import { SettingsModal } from './modals/SettingsModal'
|
||||
import { FeedbackModal } from './modals/FeedbackModal'
|
||||
import { OnboardingModal } from './modals/OnboardingModal'
|
||||
|
||||
// Hooks
|
||||
import { useLessonSession } from '@/hooks/companion/useLessonSession'
|
||||
import { useKeyboardShortcuts } from '@/hooks/companion/useKeyboardShortcuts'
|
||||
|
||||
export function CompanionDashboard() {
|
||||
// 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)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Lesson session hook
|
||||
const {
|
||||
session,
|
||||
startLesson,
|
||||
endLesson,
|
||||
clearSession,
|
||||
pauseLesson,
|
||||
resumeLesson,
|
||||
extendTime,
|
||||
skipPhase,
|
||||
saveReflection,
|
||||
addHomework,
|
||||
removeHomework,
|
||||
isPaused,
|
||||
} = useLessonSession({
|
||||
onOvertimeStart: () => {
|
||||
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: session ? handlePauseToggle : undefined,
|
||||
onExtend: session && !isPaused ? () => extendTime(5) : undefined,
|
||||
onNextPhase: 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/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 })
|
||||
}
|
||||
|
||||
// Determine current view based on session status
|
||||
const renderContent = () => {
|
||||
if (!session) {
|
||||
return <LessonStartForm onStart={startLesson} />
|
||||
}
|
||||
|
||||
if (session.status === 'completed') {
|
||||
return (
|
||||
<LessonEndedView
|
||||
session={session}
|
||||
onSaveReflection={saveReflection}
|
||||
onAddHomework={addHomework}
|
||||
onRemoveHomework={removeHomework}
|
||||
onStartNew={clearSession}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// in_progress or paused
|
||||
return (
|
||||
<LessonActiveView
|
||||
session={session}
|
||||
onPauseToggle={handlePauseToggle}
|
||||
onExtendTime={extendTime}
|
||||
onSkipPhase={skipPhase}
|
||||
onEndLesson={endLesson}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`min-h-[calc(100vh-200px)] ${settings.highContrastMode ? 'high-contrast' : ''}`}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-blue-100 rounded-lg">
|
||||
<Timer className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-slate-900">Unterrichtsstunde</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 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 */}
|
||||
{renderContent()}
|
||||
|
||||
{/* 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user