Files
breakpilot-lehrer/website/app/admin/game/wizard/page.tsx
Benjamin Admin b4613e26f3 [split-required] Split 500-850 LOC files (batch 2)
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>
2026-04-25 08:24:01 +02:00

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>
)
}