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>
361 lines
15 KiB
TypeScript
361 lines
15 KiB
TypeScript
'use client'
|
|
|
|
// =============================================================================
|
|
// Types
|
|
// =============================================================================
|
|
|
|
export type StepStatus = 'pending' | 'active' | 'completed' | 'failed'
|
|
|
|
export interface WizardStep {
|
|
id: string
|
|
name: string
|
|
icon: string
|
|
status: StepStatus
|
|
testable?: boolean
|
|
}
|
|
|
|
export interface TestResult {
|
|
name: string
|
|
status: 'passed' | 'failed' | 'pending'
|
|
message: string
|
|
details?: string
|
|
}
|
|
|
|
// =============================================================================
|
|
// Constants
|
|
// =============================================================================
|
|
|
|
export const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000'
|
|
export const GAME_URL = process.env.NEXT_PUBLIC_GAME_URL || 'http://localhost:3001'
|
|
|
|
export const STEPS: WizardStep[] = [
|
|
{ id: 'welcome', name: 'Willkommen', icon: '🎮', status: 'pending' },
|
|
{ id: 'overview', name: 'Dashboard Uebersicht', icon: '📊', status: 'pending' },
|
|
{ id: 'stats', name: 'Statistiken', icon: '📈', status: 'pending' },
|
|
{ id: 'leaderboard', name: 'Leaderboard', icon: '🏆', status: 'pending' },
|
|
{ id: 'webgl', name: 'WebGL Embedding', icon: '🎯', status: 'pending', testable: true },
|
|
{ id: 'api', name: 'API Integration', icon: '🔌', status: 'pending', testable: true },
|
|
{ id: 'quiz', name: 'Quiz-System', icon: '❓', status: 'pending' },
|
|
{ id: 'learning', name: 'Lernniveau', icon: '📚', status: 'pending' },
|
|
{ id: 'summary', name: 'Zusammenfassung', icon: '✅', status: 'pending' },
|
|
]
|
|
|
|
export const EDUCATION_CONTENT: Record<string, { title: string; content: string[]; tips?: string[] }> = {
|
|
'welcome': {
|
|
title: 'Willkommen bei Breakpilot Drive!',
|
|
content: [
|
|
'Breakpilot Drive ist ein **Endless Runner Lernspiel** fuer Schueler der Klassen 2-6.',
|
|
'Das Spiel kombiniert Spielspass mit Lernen:',
|
|
'• Fahre so weit wie moeglich',
|
|
'• Beantworte Quiz-Fragen um Punkte zu sammeln',
|
|
'• Das System passt sich automatisch an dein Lernniveau an',
|
|
'In diesem Wizard lernst du alle Admin-Features kennen und testest die Integration.',
|
|
],
|
|
tips: [
|
|
'Das Spiel laeuft auf Port 3001 als Unity WebGL Build',
|
|
'Die API-Endpoints sind unter /api/game/* verfuegbar',
|
|
],
|
|
},
|
|
'overview': {
|
|
title: 'Das Game Dashboard',
|
|
content: [
|
|
'Das Dashboard unter **/admin/game** bietet drei Hauptbereiche:',
|
|
'**1. Uebersicht-Tab:**',
|
|
'• Statistik-Karten mit KPIs (Spieler, Sessions, Genauigkeit)',
|
|
'• Top 5 Leaderboard',
|
|
'• Schnellzugriff-Buttons',
|
|
'**2. Spielen-Tab:**',
|
|
'• Embedded WebGL Game im iframe',
|
|
'• Direkt im Admin-Panel spielbar',
|
|
'**3. Statistiken-Tab:**',
|
|
'• Genauigkeit nach Fach',
|
|
'• Aktivitaets-Feed',
|
|
'• Lernniveau-Verteilung',
|
|
],
|
|
tips: [
|
|
'Die Tabs sind oben im Dashboard als Buttons sichtbar',
|
|
'Klicke auf "Aktualisieren" um die neuesten Daten zu laden',
|
|
],
|
|
},
|
|
'stats': {
|
|
title: 'Statistiken verstehen',
|
|
content: [
|
|
'Die Statistik-Karten zeigen wichtige KPIs:',
|
|
'**Aktive Spieler heute:** Anzahl der Spieler mit mindestens einer Session heute',
|
|
'**Spielsessions:** Gesamtzahl aller abgeschlossenen Spielsessions',
|
|
'**Quiz-Fragen beantwortet:** Kumulative Anzahl beantworteter Fragen',
|
|
'**Durchschnittliche Genauigkeit:** Prozent der richtig beantworteten Fragen',
|
|
'**Gesamte Spielzeit:** Summe aller Spielzeiten in Stunden',
|
|
'Trends zeigen die Veraenderung zur Vorwoche.',
|
|
],
|
|
tips: ['Gruene Trends = Verbesserung', 'Rote Trends = Bereich mit Aufmerksamkeitsbedarf'],
|
|
},
|
|
'leaderboard': {
|
|
title: 'Leaderboard & Gamification',
|
|
content: [
|
|
'Das Leaderboard motiviert Schueler durch:',
|
|
'**Ranking:** Top 5 Spieler nach Gesamtpunktzahl',
|
|
'**Goldene Medaille:** Platz 1 ist besonders hervorgehoben',
|
|
'**Genauigkeit:** Zeigt wie viele Fragen richtig beantwortet wurden',
|
|
'Spaeter kommen hinzu:',
|
|
'• Klassen-Ranglisten',
|
|
'• Wochen/Monats-Ranglisten',
|
|
'• Achievements & Badges',
|
|
],
|
|
tips: ['Leaderboards koennen pro Klasse oder schulweit sein', 'Datenschutz: Nur Vornamen + erster Buchstabe des Nachnamens werden gezeigt'],
|
|
},
|
|
'webgl': {
|
|
title: 'WebGL Embedding',
|
|
content: [
|
|
'Das Spiel wird als **Unity WebGL Build** eingebettet:',
|
|
'**Technologie:**', '• Unity 6 (Version 6000.0)', '• Universal Render Pipeline (URP)', '• WebAssembly (WASM) fuer Performance',
|
|
'**Embedding:**', '• Das Spiel laeuft in einem iframe auf Port 3001', '• Parameter wie ?embed=true optimieren fuer Einbettung', '• Fullscreen und Gamepad werden unterstuetzt',
|
|
'**Wichtig:** Der Game-Container muss laufen:', '`docker-compose --profile game up -d`',
|
|
],
|
|
tips: ['Bei Ladefehlern: Container-Status pruefen', 'CORS muss korrekt konfiguriert sein'],
|
|
},
|
|
'api': {
|
|
title: 'API Integration',
|
|
content: [
|
|
'Die Game API stellt folgende Endpoints bereit:',
|
|
'**GET /api/game/learning-level**', '→ Aktuelles Lernniveau des Spielers',
|
|
'**GET /api/game/questions?difficulty=3&count=5**', '→ Quiz-Fragen basierend auf Schwierigkeit',
|
|
'**POST /api/game/session**', '→ Spielsession speichern (Score, Zeit, Antworten)',
|
|
'**GET /api/game/achievements**', '→ Freigeschaltete Achievements',
|
|
'**GET /api/game/leaderboard?limit=10**', '→ Top-Spieler Rangliste',
|
|
],
|
|
tips: ['Alle Endpoints erfordern JWT-Token in Produktion', 'Im Dev-Modus ist Auth optional'],
|
|
},
|
|
'quiz': {
|
|
title: 'Das Quiz-System',
|
|
content: [
|
|
'Quiz-Fragen erscheinen waehrend des Spiels:',
|
|
'**Quick-Modus (5 Sekunden):**', '• 2-3 Antwortmoeglichkeiten', '• Wird durch visuelle Trigger ausgeloest (Bruecke, Baum)', '• Schnelle Punkte bei richtiger Antwort',
|
|
'**Pause-Modus (unbegrenzt):**', '• 4 Antwortmoeglichkeiten', '• Spieler kann nachdenken', '• Mehr Punkte moeglich',
|
|
'**Faecher:** Mathematik, Deutsch, Englisch', '**LLM-Generierung:** Fragen werden dynamisch erstellt',
|
|
],
|
|
tips: ['Fragen werden im Valkey-Cache gespeichert', 'Schwierigkeit passt sich automatisch an'],
|
|
},
|
|
'learning': {
|
|
title: 'Adaptives Lernniveau',
|
|
content: [
|
|
'Das System passt sich automatisch an:',
|
|
'**5 Lernstufen:**', '• Level 1: Klasse 2-3 (Beginner)', '• Level 2: Klasse 3-4 (Elementary)', '• Level 3: Klasse 4-5 (Intermediate)', '• Level 4: Klasse 5-6 (Advanced)', '• Level 5: Klasse 6+ (Expert)',
|
|
'**Anpassung:**', '• ≥80% richtig ueber 10 Fragen → Level Up', '• <40% richtig ueber 5 Fragen → Level Down', '• Schwache Faecher werden identifiziert',
|
|
'**State Engine:** Nutzt die bestehende Breakpilot State Machine',
|
|
],
|
|
tips: ['Eltern sehen das Niveau ihrer Kinder im Dashboard', 'Lehrer sehen Klassen-Durchschnitte'],
|
|
},
|
|
'summary': {
|
|
title: 'Zusammenfassung',
|
|
content: [
|
|
'Du hast alle Breakpilot Drive Features kennengelernt:',
|
|
'✅ Dashboard mit Uebersicht, Spielen, Statistiken', '✅ Statistik-Karten und KPIs', '✅ Leaderboard & Gamification',
|
|
'✅ WebGL Embedding', '✅ API Integration', '✅ Quiz-System mit Quick/Pause-Modus', '✅ Adaptives Lernniveau',
|
|
'**Naechste Schritte:**', '• Teste das Dashboard unter /admin/game', '• Starte den Game-Container', '• Spiele eine Runde im "Spielen"-Tab',
|
|
],
|
|
tips: ['Bei Fragen: Siehe docs/breakpilot-drive/README.md', 'API-Doku: docs/breakpilot-drive/architecture.md'],
|
|
},
|
|
}
|
|
|
|
// =============================================================================
|
|
// WizardStepper
|
|
// =============================================================================
|
|
|
|
export function WizardStepper({
|
|
steps, currentStep, onStepClick
|
|
}: {
|
|
steps: WizardStep[]
|
|
currentStep: number
|
|
onStepClick: (index: number) => void
|
|
}) {
|
|
return (
|
|
<div className="flex items-center justify-between mb-8 overflow-x-auto pb-4">
|
|
{steps.map((step, index) => (
|
|
<div key={step.id} className="flex items-center">
|
|
<button
|
|
onClick={() => onStepClick(index)}
|
|
className={`flex flex-col items-center min-w-[80px] p-2 rounded-lg transition-colors ${
|
|
index === currentStep
|
|
? 'bg-primary-100 text-primary-700'
|
|
: step.status === 'completed'
|
|
? 'bg-green-100 text-green-700 cursor-pointer hover:bg-green-200'
|
|
: step.status === 'failed'
|
|
? 'bg-red-100 text-red-700 cursor-pointer hover:bg-red-200'
|
|
: 'text-slate-400 hover:bg-slate-100'
|
|
}`}
|
|
>
|
|
<span className="text-2xl mb-1">{step.icon}</span>
|
|
<span className="text-xs font-medium text-center">{step.name}</span>
|
|
{step.status === 'completed' && <span className="text-xs text-green-600">✓</span>}
|
|
{step.status === 'failed' && <span className="text-xs text-red-600">✗</span>}
|
|
</button>
|
|
{index < steps.length - 1 && (
|
|
<div className={`h-0.5 w-8 mx-1 ${index < currentStep ? 'bg-green-400' : 'bg-slate-200'}`} />
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// EducationCard
|
|
// =============================================================================
|
|
|
|
export function EducationCard({ stepId }: { stepId: string }) {
|
|
const content = EDUCATION_CONTENT[stepId]
|
|
if (!content) return null
|
|
|
|
return (
|
|
<div className="bg-primary-50 border border-primary-200 rounded-lg p-6 mb-6">
|
|
<h3 className="text-lg font-semibold text-primary-800 mb-4 flex items-center">
|
|
<span className="mr-2">📖</span>
|
|
{content.title}
|
|
</h3>
|
|
<div className="space-y-2 text-primary-900">
|
|
{content.content.map((line, index) => (
|
|
<p
|
|
key={index}
|
|
className={`${line.startsWith('•') ? 'ml-4' : ''} ${line.startsWith('**') ? 'font-semibold mt-3' : ''}`}
|
|
dangerouslySetInnerHTML={{
|
|
__html: line
|
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/`(.*?)`/g, '<code class="bg-primary-100 px-1 rounded">$1</code>')
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
{content.tips && content.tips.length > 0 && (
|
|
<div className="mt-4 pt-4 border-t border-primary-200">
|
|
<p className="text-sm font-semibold text-primary-700 mb-2">💡 Tipps:</p>
|
|
{content.tips.map((tip, index) => (
|
|
<p key={index} className="text-sm text-primary-700 ml-4">• {tip}</p>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// TestResultCard
|
|
// =============================================================================
|
|
|
|
export function TestResultCard({ results }: { results: TestResult[] }) {
|
|
return (
|
|
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4">
|
|
<h4 className="font-semibold text-slate-800 mb-3">Test-Ergebnisse</h4>
|
|
<div className="space-y-2">
|
|
{results.map((result, index) => (
|
|
<div
|
|
key={index}
|
|
className={`flex items-center justify-between p-3 rounded-lg ${
|
|
result.status === 'passed' ? 'bg-green-50' :
|
|
result.status === 'failed' ? 'bg-red-50' : 'bg-slate-50'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<span className={`text-lg ${
|
|
result.status === 'passed' ? 'text-green-600' :
|
|
result.status === 'failed' ? 'text-red-600' : 'text-slate-400'
|
|
}`}>
|
|
{result.status === 'passed' ? '✓' : result.status === 'failed' ? '✗' : '○'}
|
|
</span>
|
|
<div>
|
|
<p className="font-medium text-slate-800">{result.name}</p>
|
|
<p className="text-sm text-slate-600">{result.message}</p>
|
|
</div>
|
|
</div>
|
|
{result.details && (
|
|
<code className="text-xs bg-slate-100 px-2 py-1 rounded">{result.details}</code>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// InteractiveDemo
|
|
// =============================================================================
|
|
|
|
export function InteractiveDemo({ stepId }: { stepId: string }) {
|
|
if (stepId === 'stats') {
|
|
return (
|
|
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4">
|
|
<h4 className="font-semibold text-slate-800 mb-3">Live-Vorschau: Statistik-Karten</h4>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
{[
|
|
{ label: 'Aktive Spieler', value: '42', icon: '👥', color: 'blue' },
|
|
{ label: 'Genauigkeit', value: '78%', icon: '✓', color: 'green' },
|
|
{ label: 'Spielzeit', value: '156h', icon: '⏱️', color: 'purple' },
|
|
].map((stat) => (
|
|
<div key={stat.label} className={`bg-${stat.color}-50 rounded-lg p-4 text-center`}>
|
|
<span className="text-2xl">{stat.icon}</span>
|
|
<p className="text-2xl font-bold mt-1">{stat.value}</p>
|
|
<p className="text-sm text-slate-600">{stat.label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (stepId === 'leaderboard') {
|
|
return (
|
|
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4">
|
|
<h4 className="font-semibold text-slate-800 mb-3">Live-Vorschau: Leaderboard</h4>
|
|
<div className="space-y-2">
|
|
{[
|
|
{ rank: 1, name: 'Max M.', score: 25000, color: 'yellow' },
|
|
{ rank: 2, name: 'Lisa K.', score: 23500, color: 'slate' },
|
|
{ rank: 3, name: 'Tim S.', score: 21000, color: 'orange' },
|
|
].map((entry) => (
|
|
<div key={entry.rank} className="flex items-center justify-between py-2 border-b border-slate-100">
|
|
<div className="flex items-center gap-3">
|
|
<span className={`w-8 h-8 rounded-full bg-${entry.color}-100 flex items-center justify-center font-bold`}>
|
|
{entry.rank}
|
|
</span>
|
|
<span className="font-medium">{entry.name}</span>
|
|
</div>
|
|
<span className="text-slate-600">{entry.score.toLocaleString()} Punkte</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (stepId === 'learning') {
|
|
return (
|
|
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4">
|
|
<h4 className="font-semibold text-slate-800 mb-3">Live-Vorschau: Lernniveau</h4>
|
|
<div className="space-y-3">
|
|
{[
|
|
{ subject: 'Mathematik', level: 3.2, color: 'blue' },
|
|
{ subject: 'Deutsch', level: 2.8, color: 'green' },
|
|
{ subject: 'Englisch', level: 3.5, color: 'purple' },
|
|
].map((item) => (
|
|
<div key={item.subject}>
|
|
<div className="flex justify-between text-sm mb-1">
|
|
<span>{item.subject}</span>
|
|
<span className="font-medium">Level {item.level.toFixed(1)}</span>
|
|
</div>
|
|
<div className="h-3 bg-slate-100 rounded-full overflow-hidden">
|
|
<div
|
|
className={`h-full bg-${item.color}-500 rounded-full`}
|
|
style={{ width: `${(item.level / 5) * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return null
|
|
}
|