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>
195 lines
6.2 KiB
TypeScript
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>
|
|
)
|
|
}
|