[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>
This commit is contained in:
Benjamin Admin
2026-04-25 08:24:01 +02:00
parent 34da9f4cda
commit b4613e26f3
118 changed files with 15258 additions and 14680 deletions

View File

@@ -0,0 +1,189 @@
'use client'
import type {
WebsiteContent,
HeroContent,
FeatureContent,
} from '@/lib/content-types'
// Shared styles
export const inputCls = 'w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition-colors'
export const labelCls = 'block text-xs font-medium text-slate-600 mb-1'
export interface EditorProps {
content: WebsiteContent
setContent: React.Dispatch<React.SetStateAction<WebsiteContent | null>>
}
// =============================================================================
// Hero Editor
// =============================================================================
export function HeroEditor({ content, setContent }: EditorProps) {
function update(field: keyof HeroContent, value: string) {
setContent(c => c ? { ...c, hero: { ...c.hero, [field]: value } } : c)
}
return (
<div className="grid gap-3">
<div><label className={labelCls}>Badge</label><input className={inputCls} value={content.hero.badge} onChange={e => update('badge', e.target.value)} /></div>
<div><label className={labelCls}>Titel</label><input className={inputCls} value={content.hero.title} onChange={e => update('title', e.target.value)} /></div>
<div className="grid grid-cols-2 gap-3">
<div><label className={labelCls}>Highlight 1</label><input className={inputCls} value={content.hero.titleHighlight1} onChange={e => update('titleHighlight1', e.target.value)} /></div>
<div><label className={labelCls}>Highlight 2</label><input className={inputCls} value={content.hero.titleHighlight2} onChange={e => update('titleHighlight2', e.target.value)} /></div>
</div>
<div><label className={labelCls}>Untertitel</label><textarea className={inputCls} rows={2} value={content.hero.subtitle} onChange={e => update('subtitle', e.target.value)} /></div>
<div className="grid grid-cols-3 gap-3">
<div><label className={labelCls}>CTA Primaer</label><input className={inputCls} value={content.hero.ctaPrimary} onChange={e => update('ctaPrimary', e.target.value)} /></div>
<div><label className={labelCls}>CTA Sekundaer</label><input className={inputCls} value={content.hero.ctaSecondary} onChange={e => update('ctaSecondary', e.target.value)} /></div>
<div><label className={labelCls}>CTA Hinweis</label><input className={inputCls} value={content.hero.ctaHint} onChange={e => update('ctaHint', e.target.value)} /></div>
</div>
</div>
)
}
// =============================================================================
// Features Editor
// =============================================================================
export function FeaturesEditor({ content, setContent }: EditorProps) {
function update(index: number, field: keyof FeatureContent, value: string) {
setContent(c => {
if (!c) return c
const features = [...c.features]
features[index] = { ...features[index], [field]: value }
return { ...c, features }
})
}
return (
<div className="space-y-3">
{content.features.map((feature, i) => (
<div key={feature.id} className="bg-slate-50 rounded-lg p-3 space-y-2">
<div className="grid grid-cols-6 gap-2">
<div><label className={labelCls}>Icon</label><input className={`${inputCls} text-center text-lg`} value={feature.icon} onChange={e => update(i, 'icon', e.target.value)} /></div>
<div className="col-span-5"><label className={labelCls}>Titel</label><input className={inputCls} value={feature.title} onChange={e => update(i, 'title', e.target.value)} /></div>
</div>
<div><label className={labelCls}>Beschreibung</label><textarea className={inputCls} rows={2} value={feature.description} onChange={e => update(i, 'description', e.target.value)} /></div>
</div>
))}
</div>
)
}
// =============================================================================
// FAQ Editor
// =============================================================================
export function FAQEditor({ content, setContent }: EditorProps) {
function updateItem(index: number, field: 'question' | 'answer', value: string) {
setContent(c => {
if (!c) return c
const faq = [...c.faq]
if (field === 'answer') { faq[index] = { ...faq[index], answer: value.split('\n') } }
else { faq[index] = { ...faq[index], question: value } }
return { ...c, faq }
})
}
function addItem() { setContent(c => c ? { ...c, faq: [...c.faq, { question: 'Neue Frage?', answer: ['Antwort hier...'] }] } : c) }
function removeItem(index: number) { setContent(c => c ? { ...c, faq: c.faq.filter((_, i) => i !== index) } : c) }
return (
<div className="space-y-3">
{content.faq.map((item, i) => (
<div key={i} className="bg-slate-50 rounded-lg p-3 space-y-2 relative group">
<button onClick={() => removeItem(i)} className="absolute top-2 right-2 p-1 text-red-400 hover:text-red-600 opacity-0 group-hover:opacity-100 transition-opacity" title="Entfernen">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
</button>
<div><label className={labelCls}>Frage {i + 1}</label><input className={inputCls} value={item.question} onChange={e => updateItem(i, 'question', e.target.value)} /></div>
<div><label className={labelCls}>Antwort</label><textarea className={`${inputCls} font-mono`} rows={3} value={item.answer.join('\n')} onChange={e => updateItem(i, 'answer', e.target.value)} /></div>
</div>
))}
<button onClick={addItem} className="w-full py-2 border-2 border-dashed border-slate-300 rounded-lg text-sm text-slate-500 hover:border-sky-400 hover:text-sky-600 transition-colors">
+ Frage hinzufuegen
</button>
</div>
)
}
// =============================================================================
// Pricing Editor
// =============================================================================
export function PricingEditor({ content, setContent }: EditorProps) {
function update(index: number, field: string, value: string | number | boolean) {
setContent(c => {
if (!c) return c
const pricing = [...c.pricing]
if (field === 'price') { pricing[index] = { ...pricing[index], price: Number(value) } }
else if (field === 'popular') { pricing[index] = { ...pricing[index], popular: Boolean(value) } }
else if (field.startsWith('features.')) {
const sub = field.replace('features.', '')
if (sub === 'included' && typeof value === 'string') { pricing[index] = { ...pricing[index], features: { ...pricing[index].features, included: value.split('\n') } } }
else { pricing[index] = { ...pricing[index], features: { ...pricing[index].features, [sub]: value } } }
} else { pricing[index] = { ...pricing[index], [field]: value } }
return { ...c, pricing }
})
}
return (
<div className="space-y-4">
{content.pricing.map((plan, i) => (
<div key={plan.id} className="bg-slate-50 rounded-lg p-3 space-y-2">
<div className="flex items-center gap-2 mb-1">
<span className="text-sm font-semibold text-slate-800">{plan.name}</span>
{plan.popular && <span className="text-xs bg-sky-100 text-sky-700 px-1.5 py-0.5 rounded">Beliebt</span>}
</div>
<div className="grid grid-cols-4 gap-2">
<div><label className={labelCls}>Name</label><input className={inputCls} value={plan.name} onChange={e => update(i, 'name', e.target.value)} /></div>
<div><label className={labelCls}>Preis (EUR)</label><input className={inputCls} type="number" step="0.01" value={plan.price} onChange={e => update(i, 'price', e.target.value)} /></div>
<div><label className={labelCls}>Intervall</label><input className={inputCls} value={plan.interval} onChange={e => update(i, 'interval', e.target.value)} /></div>
<div className="flex items-end pb-1"><label className="flex items-center gap-2 cursor-pointer"><input type="checkbox" checked={plan.popular || false} onChange={e => update(i, 'popular', e.target.checked)} className="w-4 h-4 text-sky-600 rounded" /><span className="text-xs text-slate-600">Beliebt</span></label></div>
</div>
<div><label className={labelCls}>Beschreibung</label><input className={inputCls} value={plan.description} onChange={e => update(i, 'description', e.target.value)} /></div>
<div className="grid grid-cols-2 gap-2">
<div><label className={labelCls}>Aufgaben</label><input className={inputCls} value={plan.features.tasks} onChange={e => update(i, 'features.tasks', e.target.value)} /></div>
<div><label className={labelCls}>Aufgaben-Beschreibung</label><input className={inputCls} value={plan.features.taskDescription} onChange={e => update(i, 'features.taskDescription', e.target.value)} /></div>
</div>
<div><label className={labelCls}>Features (eine pro Zeile)</label><textarea className={`${inputCls} font-mono`} rows={3} value={plan.features.included.join('\n')} onChange={e => update(i, 'features.included', e.target.value)} /></div>
</div>
))}
</div>
)
}
// =============================================================================
// Trust Editor
// =============================================================================
export function TrustEditor({ content, setContent }: EditorProps) {
function update(key: 'item1' | 'item2' | 'item3', field: 'value' | 'label', val: string) {
setContent(c => c ? { ...c, trust: { ...c.trust, [key]: { ...c.trust[key], [field]: val } } } : c)
}
return (
<div className="grid grid-cols-3 gap-3">
{(['item1', 'item2', 'item3'] as const).map((key, i) => (
<div key={key} className="bg-slate-50 rounded-lg p-3 space-y-2">
<div><label className={labelCls}>Wert {i + 1}</label><input className={inputCls} value={content.trust[key].value} onChange={e => update(key, 'value', e.target.value)} /></div>
<div><label className={labelCls}>Label {i + 1}</label><input className={inputCls} value={content.trust[key].label} onChange={e => update(key, 'label', e.target.value)} /></div>
</div>
))}
</div>
)
}
// =============================================================================
// Testimonial Editor
// =============================================================================
export function TestimonialEditor({ content, setContent }: EditorProps) {
function update(field: 'quote' | 'author' | 'role', value: string) {
setContent(c => c ? { ...c, testimonial: { ...c.testimonial, [field]: value } } : c)
}
return (
<div className="space-y-3">
<div><label className={labelCls}>Zitat</label><textarea className={inputCls} rows={3} value={content.testimonial.quote} onChange={e => update('quote', e.target.value)} /></div>
<div className="grid grid-cols-2 gap-3">
<div><label className={labelCls}>Autor</label><input className={inputCls} value={content.testimonial.author} onChange={e => update('author', e.target.value)} /></div>
<div><label className={labelCls}>Rolle</label><input className={inputCls} value={content.testimonial.role} onChange={e => update('role', e.target.value)} /></div>
</div>
</div>
)
}