backend-lehrer (10 files): - game/database.py (785 → 5), correction_api.py (683 → 4) - classroom_engine/antizipation.py (676 → 5) - llm_gateway schools/edu_search already done in prior batch klausur-service (12 files): - orientation_crop_api.py (694 → 5), pdf_export.py (677 → 4) - zeugnis_crawler.py (676 → 5), grid_editor_api.py (671 → 5) - eh_templates.py (658 → 5), mail/api.py (651 → 5) - qdrant_service.py (638 → 5), training_api.py (625 → 4) website (6 pages): - middleware (696 → 8), mail (733 → 6), consent (628 → 8) - compliance/risks (622 → 5), export (502 → 5), brandbook (629 → 7) studio-v2 (3 components): - B2BMigrationWizard (848 → 3), CleanupPanel (765 → 2) - dashboard-experimental (739 → 2) admin-lehrer (4 files): - uebersetzungen (769 → 4), manager (670 → 2) - ChunkBrowserQA (675 → 6), dsfa/page (674 → 5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
216 lines
8.6 KiB
TypeScript
216 lines
8.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback } from 'react'
|
|
import Link from 'next/link'
|
|
import {
|
|
WizardStep, TestResult, STEPS, BACKEND_URL, GAME_URL,
|
|
WizardStepper, EducationCard, TestResultCard, InteractiveDemo,
|
|
} from './_components/WizardComponents'
|
|
|
|
export default function GameWizardPage() {
|
|
const [currentStep, setCurrentStep] = useState(0)
|
|
const [steps, setSteps] = useState<WizardStep[]>(STEPS)
|
|
const [testResults, setTestResults] = useState<TestResult[]>([])
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
const currentStepData = steps[currentStep]
|
|
const isWelcome = currentStepData?.id === 'welcome'
|
|
const isSummary = currentStepData?.id === 'summary'
|
|
|
|
const runWebGLTest = useCallback(async () => {
|
|
setIsLoading(true)
|
|
const results: TestResult[] = []
|
|
try {
|
|
await fetch(GAME_URL, { mode: 'no-cors' })
|
|
results.push({ name: 'Game Server Erreichbarkeit', status: 'passed', message: 'Der Game-Server antwortet', details: GAME_URL })
|
|
} catch {
|
|
results.push({ name: 'Game Server Erreichbarkeit', status: 'failed', message: 'Game-Server nicht erreichbar. Container gestartet?', details: 'docker-compose --profile game up -d' })
|
|
}
|
|
results.push({ name: 'Iframe Embedding', status: 'passed', message: 'Iframe-Einbettung ist konfiguriert', details: '?embed=true' })
|
|
setTestResults(results)
|
|
setIsLoading(false)
|
|
const allPassed = results.every(r => r.status === 'passed')
|
|
setSteps(prev => prev.map(s => s.id === 'webgl' ? { ...s, status: allPassed ? 'completed' : 'failed' } : s))
|
|
}, [])
|
|
|
|
const runAPITest = useCallback(async () => {
|
|
setIsLoading(true)
|
|
const results: TestResult[] = []
|
|
|
|
try {
|
|
const response = await fetch(`${BACKEND_URL}/api/game/learning-level?user_id=test-user`)
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
results.push({ name: 'Learning Level Endpoint', status: 'passed', message: 'API antwortet korrekt', details: `Level: ${data.overall_level || 'Mock'}` })
|
|
} else {
|
|
results.push({ name: 'Learning Level Endpoint', status: 'failed', message: `HTTP ${response.status}` })
|
|
}
|
|
} catch {
|
|
results.push({ name: 'Learning Level Endpoint', status: 'failed', message: 'Backend nicht erreichbar', details: 'docker-compose up -d backend' })
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${BACKEND_URL}/api/game/questions?difficulty=3&count=2`)
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
results.push({ name: 'Questions Endpoint', status: 'passed', message: 'Quiz-Fragen API funktioniert', details: `${data.questions?.length || 0} Fragen` })
|
|
} else {
|
|
results.push({ name: 'Questions Endpoint', status: 'failed', message: `HTTP ${response.status}` })
|
|
}
|
|
} catch {
|
|
results.push({ name: 'Questions Endpoint', status: 'failed', message: 'Endpoint nicht erreichbar' })
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${BACKEND_URL}/api/game/leaderboard?limit=5`)
|
|
if (response.ok) {
|
|
results.push({ name: 'Leaderboard Endpoint', status: 'passed', message: 'Leaderboard API funktioniert' })
|
|
} else {
|
|
results.push({ name: 'Leaderboard Endpoint', status: 'failed', message: `HTTP ${response.status}` })
|
|
}
|
|
} catch {
|
|
results.push({ name: 'Leaderboard Endpoint', status: 'failed', message: 'Endpoint nicht erreichbar' })
|
|
}
|
|
|
|
setTestResults(results)
|
|
setIsLoading(false)
|
|
const passedCount = results.filter(r => r.status === 'passed').length
|
|
setSteps(prev => prev.map(s => s.id === 'api' ? { ...s, status: passedCount >= 2 ? 'completed' : 'failed' } : s))
|
|
}, [])
|
|
|
|
const goToNext = () => {
|
|
if (currentStep < steps.length - 1) {
|
|
setSteps(prev => prev.map((step, idx) =>
|
|
idx === currentStep && step.status === 'pending' ? { ...step, status: 'completed' } : step
|
|
))
|
|
setCurrentStep(prev => prev + 1)
|
|
setTestResults([])
|
|
}
|
|
}
|
|
|
|
const goToPrev = () => {
|
|
if (currentStep > 0) {
|
|
setCurrentStep(prev => prev - 1)
|
|
setTestResults([])
|
|
}
|
|
}
|
|
|
|
const handleStepClick = (index: number) => {
|
|
setCurrentStep(index)
|
|
setTestResults([])
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-100 py-8">
|
|
<div className="max-w-4xl mx-auto px-4">
|
|
{/* Header */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-slate-800">🎮 Breakpilot Drive - Lern-Wizard</h1>
|
|
<p className="text-slate-600 mt-1">Interaktive Tour durch alle Game-Features</p>
|
|
</div>
|
|
<Link href="/admin/game" className="text-primary-600 hover:text-primary-800 text-sm">
|
|
← Zurueck zum Dashboard
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stepper */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
|
|
<WizardStepper steps={steps} currentStep={currentStep} onStepClick={handleStepClick} />
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
|
{/* Step Header */}
|
|
<div className="flex items-center mb-6">
|
|
<span className="text-4xl mr-4">{currentStepData?.icon}</span>
|
|
<div>
|
|
<h2 className="text-xl font-bold text-slate-800">
|
|
Schritt {currentStep + 1}: {currentStepData?.name}
|
|
</h2>
|
|
<p className="text-slate-500 text-sm">{currentStep + 1} von {steps.length}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<EducationCard stepId={currentStepData?.id || ''} />
|
|
<InteractiveDemo stepId={currentStepData?.id || ''} />
|
|
|
|
{/* Test Section */}
|
|
{currentStepData?.testable && (
|
|
<div className="mb-6">
|
|
{testResults.length === 0 ? (
|
|
<div className="text-center py-6">
|
|
<button
|
|
onClick={currentStepData.id === 'webgl' ? runWebGLTest : runAPITest}
|
|
disabled={isLoading}
|
|
className={`px-6 py-3 rounded-lg font-medium transition-colors ${
|
|
isLoading ? 'bg-slate-400 cursor-not-allowed' : 'bg-green-600 text-white hover:bg-green-700'
|
|
}`}
|
|
>
|
|
{isLoading ? '⏳ Tests laufen...' : '🧪 Integration testen'}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<TestResultCard results={testResults} />
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Welcome Start Button */}
|
|
{isWelcome && (
|
|
<div className="text-center py-8">
|
|
<button onClick={goToNext} className="bg-primary-600 text-white px-8 py-3 rounded-lg font-medium hover:bg-primary-700 transition-colors">
|
|
🚀 Tour starten
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Summary Actions */}
|
|
{isSummary && (
|
|
<div className="text-center py-6 space-y-4">
|
|
<div className="flex justify-center gap-4">
|
|
<Link href="/admin/game" className="px-6 py-3 bg-primary-600 text-white rounded-lg font-medium hover:bg-primary-700 transition-colors">
|
|
📊 Zum Dashboard
|
|
</Link>
|
|
<button
|
|
onClick={() => { setCurrentStep(0); setSteps(STEPS.map(s => ({ ...s, status: 'pending' }))) }}
|
|
className="px-6 py-3 bg-slate-200 text-slate-700 rounded-lg font-medium hover:bg-slate-300 transition-colors"
|
|
>
|
|
🔄 Wizard neu starten
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Navigation */}
|
|
{!isWelcome && (
|
|
<div className="flex justify-between mt-8 pt-6 border-t">
|
|
<button
|
|
onClick={goToPrev}
|
|
disabled={currentStep === 0}
|
|
className={`px-6 py-2 rounded-lg transition-colors ${
|
|
currentStep === 0 ? 'bg-slate-200 text-slate-400 cursor-not-allowed' : 'bg-slate-200 text-slate-700 hover:bg-slate-300'
|
|
}`}
|
|
>
|
|
← Zurueck
|
|
</button>
|
|
{!isSummary && (
|
|
<button onClick={goToNext} className="bg-primary-600 text-white px-6 py-2 rounded-lg hover:bg-primary-700 transition-colors">
|
|
Weiter →
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer Info */}
|
|
<div className="text-center text-slate-500 text-sm mt-6">
|
|
Breakpilot Drive - Endless Runner Lernspiel fuer Klasse 2-6
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|