Files
breakpilot-lehrer/studio-v2/components/korrektur/GutachtenEditor.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

195 lines
6.2 KiB
TypeScript

'use client'
import { useState, useEffect, useRef } from 'react'
interface GutachtenEditorProps {
value: string
onChange: (value: string) => void
onGenerate?: () => void
isGenerating?: boolean
placeholder?: string
className?: string
}
export function GutachtenEditor({
value,
onChange,
onGenerate,
isGenerating = false,
placeholder = 'Gutachten hier eingeben oder generieren lassen...',
className = '',
}: GutachtenEditorProps) {
const [isFocused, setIsFocused] = useState(false)
const textareaRef = useRef<HTMLTextAreaElement>(null)
// Auto-resize textarea
useEffect(() => {
const textarea = textareaRef.current
if (textarea) {
textarea.style.height = 'auto'
textarea.style.height = `${Math.max(200, textarea.scrollHeight)}px`
}
}, [value])
// Word count
const wordCount = value.trim() ? value.trim().split(/\s+/).length : 0
const charCount = value.length
return (
<div className={`space-y-3 ${className}`}>
{/* Header */}
<div className="flex items-center justify-between">
<h3 className="text-white font-semibold">Gutachten</h3>
<div className="flex items-center gap-2">
<span className="text-white/40 text-xs">
{wordCount} Woerter / {charCount} Zeichen
</span>
{onGenerate && (
<button
onClick={onGenerate}
disabled={isGenerating}
className="px-3 py-1.5 rounded-lg bg-gradient-to-r from-purple-500 to-pink-500 text-white text-sm font-medium hover:shadow-lg hover:shadow-purple-500/30 transition-all disabled:opacity-50 flex items-center gap-2"
>
{isGenerating ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
Generiere...
</>
) : (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
KI Generieren
</>
)}
</button>
)}
</div>
</div>
{/* Editor */}
<div
className={`relative rounded-2xl transition-all ${
isFocused
? 'ring-2 ring-purple-500 ring-offset-2 ring-offset-slate-900'
: ''
}`}
>
<textarea
ref={textareaRef}
value={value}
onChange={(e) => onChange(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder={placeholder}
className="w-full min-h-[200px] p-4 rounded-2xl bg-white/5 border border-white/10 text-white placeholder-white/30 resize-none focus:outline-none"
/>
{/* Loading Overlay */}
{isGenerating && (
<div className="absolute inset-0 bg-slate-900/80 backdrop-blur-sm rounded-2xl flex items-center justify-center">
<div className="text-center">
<div className="w-12 h-12 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-3" />
<p className="text-white/60 text-sm">Gutachten wird generiert...</p>
<p className="text-white/40 text-xs mt-1">Nutzt 500+ NiBiS Dokumente</p>
</div>
</div>
)}
</div>
{/* Quick Insert Buttons */}
<div className="flex flex-wrap gap-2">
<QuickInsertButton
label="Einleitung"
onClick={() => onChange(value + '\n\nEinleitung:\n')}
/>
<QuickInsertButton
label="Staerken"
onClick={() => onChange(value + '\n\nStaerken der Arbeit:\n- ')}
/>
<QuickInsertButton
label="Schwaechen"
onClick={() => onChange(value + '\n\nVerbesserungsmoeglichkeiten:\n- ')}
/>
<QuickInsertButton
label="Fazit"
onClick={() => onChange(value + '\n\nGesamteindruck:\n')}
/>
</div>
</div>
)
}
// =============================================================================
// QUICK INSERT BUTTON
// =============================================================================
interface QuickInsertButtonProps {
label: string
onClick: () => void
}
function QuickInsertButton({ label, onClick }: QuickInsertButtonProps) {
return (
<button
onClick={onClick}
className="px-3 py-1 rounded-lg bg-white/5 border border-white/10 text-white/60 text-xs hover:bg-white/10 hover:text-white transition-colors"
>
+ {label}
</button>
)
}
// =============================================================================
// GUTACHTEN PREVIEW (Read-only)
// =============================================================================
interface GutachtenPreviewProps {
value: string
className?: string
}
export function GutachtenPreview({ value, className = '' }: GutachtenPreviewProps) {
if (!value) {
return (
<div className={`p-4 rounded-2xl bg-white/5 border border-white/10 text-white/40 text-center ${className}`}>
Kein Gutachten vorhanden
</div>
)
}
// Split into paragraphs for better rendering
const paragraphs = value.split('\n\n').filter(Boolean)
return (
<div className={`p-4 rounded-2xl bg-white/5 border border-white/10 space-y-4 ${className}`}>
{paragraphs.map((paragraph, index) => {
// Check if it's a heading (ends with :)
const lines = paragraph.split('\n')
const firstLine = lines[0]
const isHeading = firstLine.endsWith(':')
if (isHeading) {
return (
<div key={index}>
<h4 className="text-white font-semibold mb-2">{firstLine}</h4>
{lines.slice(1).map((line, lineIndex) => (
<p key={lineIndex} className="text-white/70 text-sm">
{line}
</p>
))}
</div>
)
}
return (
<p key={index} className="text-white/70 text-sm whitespace-pre-wrap">
{paragraph}
</p>
)
})}
</div>
)
}