Files
breakpilot-lehrer/studio-v2/components/OnboardingWizard.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
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>
2026-02-11 23:47:26 +01:00

514 lines
17 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { useLanguage } from '@/lib/LanguageContext'
import { useTheme } from '@/lib/ThemeContext'
import { GermanyMap, bundeslaender } from './GermanyMap'
import { CityMap } from './CityMap'
import { SchoolSearch } from './SchoolSearch'
import { BPIcon } from './Logo'
interface OnboardingWizardProps {
onComplete: (data: OnboardingData) => void
}
export interface OnboardingData {
bundesland: string
bundeslandName: string
city: string
cityLat?: number
cityLng?: number
schoolName: string
schoolType: string
}
// Schulformen mit Icons und Beschreibungen
const schulformen = [
// Allgemeinbildende Schulen
{
id: 'gymnasium',
name: 'Gymnasium',
icon: '🎓',
description: 'Allgemeinbildend bis Abitur',
category: 'allgemein'
},
{
id: 'gesamtschule',
name: 'Gesamtschule',
icon: '🏫',
description: 'Integriert/kooperativ',
category: 'allgemein'
},
{
id: 'realschule',
name: 'Realschule',
icon: '📚',
description: 'Mittlerer Abschluss',
category: 'allgemein'
},
{
id: 'hauptschule',
name: 'Hauptschule',
icon: '📖',
description: 'Erster Abschluss',
category: 'allgemein'
},
{
id: 'mittelschule',
name: 'Mittelschule',
icon: '📝',
description: 'Bayern/Sachsen',
category: 'allgemein'
},
{
id: 'oberschule',
name: 'Oberschule',
icon: '🏛️',
description: 'Sachsen/Brandenburg',
category: 'allgemein'
},
{
id: 'stadtteilschule',
name: 'Stadtteilschule',
icon: '🌆',
description: 'Hamburg',
category: 'allgemein'
},
{
id: 'gemeinschaftsschule',
name: 'Gemeinschaftsschule',
icon: '🤲',
description: 'BW/SH/TH/SL/BE',
category: 'allgemein'
},
// Berufliche Schulen
{
id: 'berufsschule',
name: 'Berufsschule',
icon: '🔧',
description: 'Duale Ausbildung',
category: 'beruflich'
},
{
id: 'berufliches_gymnasium',
name: 'Berufl. Gymnasium',
icon: '💼',
description: 'Fachgebundenes Abitur',
category: 'beruflich'
},
{
id: 'fachoberschule',
name: 'Fachoberschule',
icon: '📊',
description: 'Fachhochschulreife',
category: 'beruflich'
},
{
id: 'berufsfachschule',
name: 'Berufsfachschule',
icon: '🛠️',
description: 'Vollzeitberufliche Bildung',
category: 'beruflich'
},
// Sonder- und Förderschulen
{
id: 'foerderschule',
name: 'Förderschule',
icon: '🤝',
description: 'Sonderpädagogisch',
category: 'foerder'
},
{
id: 'foerderzentrum',
name: 'Förderzentrum',
icon: '💚',
description: 'Inklusiv/integriert',
category: 'foerder'
},
// Privatschulen & Besondere Formen
{
id: 'privatschule',
name: 'Privatschule',
icon: '🏰',
description: 'Freier Träger',
category: 'privat'
},
{
id: 'internat',
name: 'Internat',
icon: '🛏️',
description: 'Mit Unterbringung',
category: 'privat'
},
{
id: 'waldorfschule',
name: 'Waldorfschule',
icon: '🌿',
description: 'Anthroposophisch',
category: 'alternativ'
},
{
id: 'montessori',
name: 'Montessori-Schule',
icon: '🧒',
description: 'Montessori-Pädagogik',
category: 'alternativ'
},
// Grundschulen
{
id: 'grundschule',
name: 'Grundschule',
icon: '🏠',
description: 'Klasse 1-4',
category: 'grund'
},
// Internationale
{
id: 'internationale_schule',
name: 'Internationale Schule',
icon: '🌍',
description: 'IB/Cambridge',
category: 'international'
},
{
id: 'europaeische_schule',
name: 'Europäische Schule',
icon: '🇪🇺',
description: 'EU-Curriculum',
category: 'international'
},
]
// Kategorien für die Anzeige
const schulformKategorien = [
{ id: 'allgemein', name: 'Allgemeinbildend', icon: '📚' },
{ id: 'beruflich', name: 'Berufsbildend', icon: '💼' },
{ id: 'foerder', name: 'Förderschulen', icon: '💚' },
{ id: 'privat', name: 'Privat & Internat', icon: '🏰' },
{ id: 'alternativ', name: 'Alternative Pädagogik', icon: '🌿' },
{ id: 'grund', name: 'Primarstufe', icon: '🏠' },
{ id: 'international', name: 'International', icon: '🌍' },
]
export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
const { t } = useLanguage()
const { isDark } = useTheme()
const [step, setStep] = useState(1)
const [data, setData] = useState<Partial<OnboardingData>>({})
const [citySearch, setCitySearch] = useState('')
const [schoolSearch, setSchoolSearch] = useState('')
const totalSteps = 4
const handleNext = () => {
if (step < totalSteps) {
setStep(step + 1)
} else {
// Abschluss
onComplete(data as OnboardingData)
}
}
const handleBack = () => {
if (step > 1) {
setStep(step - 1)
}
}
const canProceed = () => {
switch (step) {
case 1:
return !!data.bundesland
case 2:
return !!data.city && data.city.trim().length > 0
case 3:
return !!data.schoolName && data.schoolName.trim().length > 0
case 4:
return !!data.schoolType
default:
return false
}
}
return (
<div className={`min-h-screen flex flex-col ${
isDark
? 'bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-800'
: 'bg-gradient-to-br from-slate-100 via-blue-50 to-indigo-100'
}`}>
{/* Animated Background */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className={`absolute -top-40 -right-40 w-80 h-80 rounded-full mix-blend-multiply filter blur-3xl animate-pulse ${
isDark ? 'bg-purple-500 opacity-50' : 'bg-purple-300 opacity-30'
}`} />
<div className={`absolute -bottom-40 -left-40 w-80 h-80 rounded-full mix-blend-multiply filter blur-3xl animate-pulse ${
isDark ? 'bg-blue-500 opacity-50' : 'bg-blue-300 opacity-30'
}`} style={{ animationDelay: '1s' }} />
</div>
<div className="relative z-10 flex-1 flex flex-col items-center justify-center p-8">
{/* Logo & Willkommen */}
<div className="text-center mb-8">
<div className="flex items-center justify-center gap-3 mb-4">
<BPIcon variant="cupertino" size={56} />
<div className="text-left">
<h1 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
BreakPilot Studio
</h1>
<p className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
Willkommen! Lassen Sie uns loslegen.
</p>
</div>
</div>
</div>
{/* Progress Bar */}
<div className="w-full max-w-2xl mb-8">
<div className="flex items-center justify-between mb-2">
{[1, 2, 3, 4].map((s) => (
<div
key={s}
className={`w-10 h-10 rounded-full flex items-center justify-center font-medium transition-all ${
s === step
? 'bg-gradient-to-br from-blue-500 to-purple-500 text-white scale-110 shadow-lg'
: s < step
? isDark
? 'bg-green-500/30 text-green-300'
: 'bg-green-100 text-green-700'
: isDark
? 'bg-white/10 text-white/40'
: 'bg-slate-200 text-slate-400'
}`}
>
{s < step ? '✓' : s}
</div>
))}
</div>
<div className={`h-2 rounded-full overflow-hidden ${isDark ? 'bg-white/10' : 'bg-slate-200'}`}>
<div
className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all duration-500"
style={{ width: `${((step - 1) / (totalSteps - 1)) * 100}%` }}
/>
</div>
</div>
{/* Main Card */}
<div className={`w-full max-w-2xl backdrop-blur-xl border rounded-3xl p-8 ${
isDark
? 'bg-white/10 border-white/20'
: 'bg-white/80 border-black/10 shadow-xl'
}`}>
{/* Step 1: Bundesland */}
{step === 1 && (
<div className="text-center">
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
In welchem Bundesland unterrichten Sie?
</h2>
<p className={`mb-6 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Klicken Sie auf Ihr Bundesland in der Karte
</p>
<GermanyMap
selectedState={data.bundesland || null}
suggestedState="HH"
onSelectState={(stateId) => setData({
...data,
bundesland: stateId,
bundeslandName: bundeslaender[stateId as keyof typeof bundeslaender]
})}
className="mx-auto max-w-md"
/>
</div>
)}
{/* Step 2: Stadt */}
{step === 2 && (
<div className="text-center">
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
In welcher Stadt arbeiten Sie?
</h2>
<p className={`mb-4 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Suchen Sie Ihre Stadt oder klicken Sie auf die Karte
</p>
{/* Info Box - Bundesland */}
<div className={`inline-flex items-center gap-2 px-4 py-2 rounded-full mb-4 ${
isDark ? 'bg-white/10' : 'bg-slate-100'
}`}>
<span className="text-lg">📍</span>
<span className={`text-sm font-medium ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
{data.bundeslandName}
</span>
</div>
{/* CityMap Komponente */}
<CityMap
bundesland={data.bundesland || 'HH'}
bundeslandName={data.bundeslandName || 'Hamburg'}
selectedCity={data.city || ''}
onSelectCity={(city, lat, lng) => setData({
...data,
city,
cityLat: lat,
cityLng: lng
})}
className="max-w-lg mx-auto"
/>
</div>
)}
{/* Step 3: Schule */}
{step === 3 && (
<div className="text-center">
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
Wie heißt Ihre Schule?
</h2>
<p className={`mb-4 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Suchen Sie Ihre Schule oder geben Sie den Namen ein
</p>
{/* SchoolSearch Komponente mit Autocomplete */}
<SchoolSearch
city={data.city || ''}
bundesland={data.bundesland || 'HH'}
bundeslandName={data.bundeslandName || 'Hamburg'}
selectedSchool={data.schoolName || ''}
onSelectSchool={(schoolName, schoolId) => setData({
...data,
schoolName
})}
className="max-w-lg mx-auto"
/>
</div>
)}
{/* Step 4: Schulform */}
{step === 4 && (
<div className="text-center">
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
Welche Schulform ist es?
</h2>
<p className={`mb-6 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Wählen Sie die passende Schulform
</p>
{/* Scrollbarer Bereich mit Kategorien */}
<div className="max-h-[400px] overflow-y-auto pr-2 space-y-6">
{schulformKategorien.map((kategorie) => {
const formenInKategorie = schulformen.filter(f => f.category === kategorie.id)
if (formenInKategorie.length === 0) return null
return (
<div key={kategorie.id}>
{/* Kategorie-Header */}
<div className={`flex items-center gap-2 mb-3 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
<span>{kategorie.icon}</span>
<span className="text-sm font-medium">{kategorie.name}</span>
<div className={`flex-1 h-px ${isDark ? 'bg-white/10' : 'bg-slate-200'}`} />
</div>
{/* Schulformen in dieser Kategorie */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{formenInKategorie.map((form) => {
const isSelected = data.schoolType === form.id
return (
<button
key={form.id}
onClick={() => setData({ ...data, schoolType: form.id })}
className={`p-3 rounded-xl border-2 transition-all hover:scale-105 text-left ${
isSelected
? 'border-blue-500 bg-blue-500/20 shadow-lg'
: isDark
? 'border-white/10 bg-white/5 hover:bg-white/10 hover:border-white/20'
: 'border-slate-200 bg-white hover:bg-slate-50 hover:border-slate-300'
}`}
>
<div className="flex items-center gap-2">
<span className="text-xl">{form.icon}</span>
<div className="flex-1 min-w-0">
<p className={`font-medium text-sm truncate ${
isSelected
? isDark ? 'text-blue-300' : 'text-blue-700'
: isDark ? 'text-white' : 'text-slate-900'
}`}>
{form.name}
</p>
<p className={`text-xs truncate ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
{form.description}
</p>
</div>
</div>
</button>
)
})}
</div>
</div>
)
})}
</div>
{/* Summary */}
{data.schoolType && (
<div className={`mt-6 p-4 rounded-xl ${
isDark ? 'bg-white/5' : 'bg-slate-50'
}`}>
<p className={`text-sm ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
<strong>{data.schoolName}</strong>
</p>
<p className={`text-xs ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
{schulformen.find(f => f.id === data.schoolType)?.name} in {data.city}, {data.bundeslandName}
</p>
</div>
)}
</div>
)}
</div>
{/* Navigation Buttons */}
<div className="flex items-center gap-4 mt-8">
{step > 1 && (
<button
onClick={handleBack}
className={`px-6 py-3 rounded-xl font-medium transition-all ${
isDark
? 'bg-white/10 text-white hover:bg-white/20'
: 'bg-slate-200 text-slate-700 hover:bg-slate-300'
}`}
>
Zurück
</button>
)}
<button
onClick={handleNext}
disabled={!canProceed()}
className={`px-8 py-3 rounded-xl font-medium transition-all ${
canProceed()
? 'bg-gradient-to-r from-blue-500 to-purple-500 text-white hover:shadow-xl hover:shadow-purple-500/30 hover:scale-105'
: isDark
? 'bg-white/10 text-white/30 cursor-not-allowed'
: 'bg-slate-200 text-slate-400 cursor-not-allowed'
}`}
>
{step === totalSteps ? 'Los geht\'s! →' : 'Weiter →'}
</button>
</div>
{/* Skip Option */}
<button
onClick={() => onComplete({
bundesland: data.bundesland || 'NI',
bundeslandName: data.bundeslandName || 'Niedersachsen',
city: data.city || 'Unbekannt',
schoolName: data.schoolName || 'Meine Schule',
schoolType: data.schoolType || 'gymnasium'
})}
className={`mt-4 text-sm ${isDark ? 'text-white/40 hover:text-white/60' : 'text-slate-400 hover:text-slate-600'}`}
>
Überspringen (später einrichten)
</button>
</div>
</div>
)
}