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>
777 lines
31 KiB
TypeScript
777 lines
31 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useRef } from 'react'
|
|
|
|
// ============================================================================
|
|
// TYPES
|
|
// ============================================================================
|
|
|
|
interface SearchResult {
|
|
id: string
|
|
title: string
|
|
bundesland: string
|
|
bundesland_name: string
|
|
doc_type: string
|
|
snippet: string
|
|
relevance_score: number
|
|
url: string
|
|
last_updated: string
|
|
}
|
|
|
|
interface Message {
|
|
id: string
|
|
role: 'user' | 'assistant' | 'system'
|
|
content: string
|
|
sources?: SearchResult[]
|
|
timestamp: Date
|
|
}
|
|
|
|
interface UserPreferences {
|
|
bundesland: string | null
|
|
schulform: string | null
|
|
hasSeenWizard: boolean
|
|
favorites: string[]
|
|
recentSearches: string[]
|
|
}
|
|
|
|
// ============================================================================
|
|
// CONSTANTS
|
|
// ============================================================================
|
|
|
|
const BUNDESLAENDER = [
|
|
{ code: 'bw', name: 'Baden-Württemberg', emoji: '🏰' },
|
|
{ code: 'by', name: 'Bayern', emoji: '🦁' },
|
|
{ code: 'be', name: 'Berlin', emoji: '🐻' },
|
|
{ code: 'bb', name: 'Brandenburg', emoji: '🦅' },
|
|
{ code: 'hb', name: 'Bremen', emoji: '🔑' },
|
|
{ code: 'hh', name: 'Hamburg', emoji: '⚓' },
|
|
{ code: 'he', name: 'Hessen', emoji: '🦁' },
|
|
{ code: 'mv', name: 'Mecklenburg-Vorpommern', emoji: '🐂' },
|
|
{ code: 'ni', name: 'Niedersachsen', emoji: '🐴' },
|
|
{ code: 'nw', name: 'Nordrhein-Westfalen', emoji: '🏛️' },
|
|
{ code: 'rp', name: 'Rheinland-Pfalz', emoji: '🍇' },
|
|
{ code: 'sl', name: 'Saarland', emoji: '⚒️' },
|
|
{ code: 'sn', name: 'Sachsen', emoji: '⚔️' },
|
|
{ code: 'st', name: 'Sachsen-Anhalt', emoji: '🏰' },
|
|
{ code: 'sh', name: 'Schleswig-Holstein', emoji: '🌊' },
|
|
{ code: 'th', name: 'Thüringen', emoji: '🌲' },
|
|
]
|
|
|
|
const SCHULFORMEN = [
|
|
{ id: 'grundschule', name: 'Grundschule', icon: '🎒' },
|
|
{ id: 'hauptschule', name: 'Hauptschule', icon: '📚' },
|
|
{ id: 'realschule', name: 'Realschule', icon: '📖' },
|
|
{ id: 'gymnasium', name: 'Gymnasium', icon: '🎓' },
|
|
{ id: 'gesamtschule', name: 'Gesamtschule', icon: '🏫' },
|
|
{ id: 'foerderschule', name: 'Förderschule', icon: '💚' },
|
|
{ id: 'berufsschule', name: 'Berufsschule', icon: '🔧' },
|
|
]
|
|
|
|
const COMMON_QUESTIONS = [
|
|
'Wie formuliere ich eine Bemerkung zur Arbeits- und Sozialverhalten?',
|
|
'Welche Noten dürfen im Zeugnis stehen?',
|
|
'Wann sind Zeugniskonferenzen durchzuführen?',
|
|
'Wie gehe ich mit Fehlzeiten um?',
|
|
'Welche Unterschriften sind erforderlich?',
|
|
'Wie werden Versetzungsentscheidungen dokumentiert?',
|
|
]
|
|
|
|
// ============================================================================
|
|
// ONBOARDING WIZARD COMPONENT
|
|
// ============================================================================
|
|
|
|
function OnboardingWizard({ onComplete }: { onComplete: (prefs: Partial<UserPreferences>) => void }) {
|
|
const [step, setStep] = useState(0)
|
|
const [bundesland, setBundesland] = useState<string | null>(null)
|
|
const [schulform, setSchulform] = useState<string | null>(null)
|
|
|
|
const steps = [
|
|
{
|
|
title: 'Willkommen beim Zeugnis-Assistenten',
|
|
subtitle: 'Ihr intelligenter Helfer für alle Fragen rund um Zeugnisse',
|
|
},
|
|
{
|
|
title: 'In welchem Bundesland unterrichten Sie?',
|
|
subtitle: 'Wir zeigen Ihnen die passenden Verordnungen',
|
|
},
|
|
{
|
|
title: 'An welcher Schulform?',
|
|
subtitle: 'So können wir die Informationen noch besser anpassen',
|
|
},
|
|
{
|
|
title: 'Alles eingerichtet!',
|
|
subtitle: 'Sie können jetzt loslegen',
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gradient-to-br from-blue-600 to-purple-700">
|
|
<div className="bg-white dark:bg-gray-800 rounded-3xl shadow-2xl w-full max-w-2xl overflow-hidden">
|
|
{/* Progress */}
|
|
<div className="h-2 bg-gray-100 dark:bg-gray-700">
|
|
<div
|
|
className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all duration-500"
|
|
style={{ width: `${((step + 1) / steps.length) * 100}%` }}
|
|
/>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-8">
|
|
{/* Step 0: Welcome */}
|
|
{step === 0 && (
|
|
<div className="text-center py-8">
|
|
<div className="w-24 h-24 mx-auto mb-6 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center">
|
|
<span className="text-5xl">📋</span>
|
|
</div>
|
|
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-3">
|
|
{steps[0].title}
|
|
</h2>
|
|
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8">
|
|
{steps[0].subtitle}
|
|
</p>
|
|
<div className="grid grid-cols-3 gap-4 text-center mb-8">
|
|
<div className="p-4">
|
|
<span className="text-3xl">🔍</span>
|
|
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
|
Schnelle Suche in Verordnungen
|
|
</p>
|
|
</div>
|
|
<div className="p-4">
|
|
<span className="text-3xl">💬</span>
|
|
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
|
KI-gestützte Antworten
|
|
</p>
|
|
</div>
|
|
<div className="p-4">
|
|
<span className="text-3xl">📚</span>
|
|
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
|
Alle 16 Bundesländer
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 1: Bundesland */}
|
|
{step === 1 && (
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2 text-center">
|
|
{steps[1].title}
|
|
</h2>
|
|
<p className="text-gray-600 dark:text-gray-400 mb-6 text-center">
|
|
{steps[1].subtitle}
|
|
</p>
|
|
<div className="grid grid-cols-4 gap-3 max-h-80 overflow-y-auto">
|
|
{BUNDESLAENDER.map((bl) => (
|
|
<button
|
|
key={bl.code}
|
|
onClick={() => setBundesland(bl.code)}
|
|
className={`p-4 rounded-xl border-2 transition-all text-center hover:scale-105 ${
|
|
bundesland === bl.code
|
|
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30 shadow-lg'
|
|
: 'border-gray-200 dark:border-gray-700 hover:border-blue-300'
|
|
}`}
|
|
>
|
|
<span className="text-2xl">{bl.emoji}</span>
|
|
<p className="mt-2 text-sm font-medium text-gray-900 dark:text-white">
|
|
{bl.name}
|
|
</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 2: Schulform */}
|
|
{step === 2 && (
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2 text-center">
|
|
{steps[2].title}
|
|
</h2>
|
|
<p className="text-gray-600 dark:text-gray-400 mb-6 text-center">
|
|
{steps[2].subtitle}
|
|
</p>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{SCHULFORMEN.map((sf) => (
|
|
<button
|
|
key={sf.id}
|
|
onClick={() => setSchulform(sf.id)}
|
|
className={`p-6 rounded-xl border-2 transition-all hover:scale-105 ${
|
|
schulform === sf.id
|
|
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30 shadow-lg'
|
|
: 'border-gray-200 dark:border-gray-700 hover:border-blue-300'
|
|
}`}
|
|
>
|
|
<span className="text-3xl">{sf.icon}</span>
|
|
<p className="mt-3 text-lg font-medium text-gray-900 dark:text-white">
|
|
{sf.name}
|
|
</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 3: Complete */}
|
|
{step === 3 && (
|
|
<div className="text-center py-8">
|
|
<div className="w-24 h-24 mx-auto mb-6 bg-gradient-to-br from-green-400 to-emerald-600 rounded-full flex items-center justify-center">
|
|
<span className="text-5xl">✓</span>
|
|
</div>
|
|
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-3">
|
|
{steps[3].title}
|
|
</h2>
|
|
<p className="text-lg text-gray-600 dark:text-gray-400 mb-6">
|
|
{steps[3].subtitle}
|
|
</p>
|
|
<div className="bg-gray-50 dark:bg-gray-900 rounded-xl p-6 text-left max-w-md mx-auto">
|
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
|
Ihre Einstellungen:
|
|
</p>
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-xl">
|
|
{BUNDESLAENDER.find(b => b.code === bundesland)?.emoji}
|
|
</span>
|
|
<span className="font-medium text-gray-900 dark:text-white">
|
|
{BUNDESLAENDER.find(b => b.code === bundesland)?.name}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-xl">
|
|
{SCHULFORMEN.find(s => s.id === schulform)?.icon}
|
|
</span>
|
|
<span className="font-medium text-gray-900 dark:text-white">
|
|
{SCHULFORMEN.find(s => s.id === schulform)?.name}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
<div className="px-8 py-6 bg-gray-50 dark:bg-gray-900 flex justify-between">
|
|
<button
|
|
onClick={() => step > 0 && setStep(step - 1)}
|
|
className={`px-6 py-2 text-gray-600 dark:text-gray-400 ${step === 0 ? 'invisible' : ''}`}
|
|
>
|
|
Zurück
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
if (step < 3) {
|
|
setStep(step + 1)
|
|
} else {
|
|
onComplete({
|
|
bundesland,
|
|
schulform,
|
|
hasSeenWizard: true,
|
|
})
|
|
}
|
|
}}
|
|
disabled={step === 1 && !bundesland || step === 2 && !schulform}
|
|
className="px-8 py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-medium rounded-xl shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{step === 3 ? 'Loslegen' : 'Weiter'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// CHAT INTERFACE COMPONENT
|
|
// ============================================================================
|
|
|
|
function ChatInterface({ messages, onSendMessage, isLoading, bundesland }: {
|
|
messages: Message[]
|
|
onSendMessage: (message: string) => void
|
|
isLoading: boolean
|
|
bundesland: string | null
|
|
}) {
|
|
const [input, setInput] = useState('')
|
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
const inputRef = useRef<HTMLTextAreaElement>(null)
|
|
|
|
useEffect(() => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
}, [messages])
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (input.trim() && !isLoading) {
|
|
onSendMessage(input.trim())
|
|
setInput('')
|
|
}
|
|
}
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault()
|
|
handleSubmit(e)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col h-full">
|
|
{/* Messages */}
|
|
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
|
{messages.length === 0 ? (
|
|
<div className="text-center py-12">
|
|
<div className="w-20 h-20 mx-auto mb-6 bg-gradient-to-br from-blue-100 to-purple-100 dark:from-blue-900/30 dark:to-purple-900/30 rounded-full flex items-center justify-center">
|
|
<span className="text-4xl">💬</span>
|
|
</div>
|
|
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
|
Stellen Sie eine Frage
|
|
</h3>
|
|
<p className="text-gray-500 dark:text-gray-400 mb-6 max-w-md mx-auto">
|
|
Der Zeugnis-Assistent beantwortet Ihre Fragen basierend auf den
|
|
offiziellen Verordnungen {bundesland ? `für ${BUNDESLAENDER.find(b => b.code === bundesland)?.name}` : ''}.
|
|
</p>
|
|
<div className="flex flex-wrap justify-center gap-2">
|
|
{COMMON_QUESTIONS.slice(0, 3).map((q, i) => (
|
|
<button
|
|
key={i}
|
|
onClick={() => onSendMessage(q)}
|
|
className="px-4 py-2 text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition"
|
|
>
|
|
{q}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
messages.map((message) => (
|
|
<div
|
|
key={message.id}
|
|
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
|
>
|
|
<div
|
|
className={`max-w-[80%] rounded-2xl p-4 ${
|
|
message.role === 'user'
|
|
? 'bg-blue-600 text-white'
|
|
: 'bg-white dark:bg-gray-800 shadow-lg border border-gray-200 dark:border-gray-700'
|
|
}`}
|
|
>
|
|
<p className={`${message.role === 'user' ? 'text-white' : 'text-gray-900 dark:text-white'}`}>
|
|
{message.content}
|
|
</p>
|
|
{message.sources && message.sources.length > 0 && (
|
|
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">
|
|
Quellen:
|
|
</p>
|
|
<div className="space-y-2">
|
|
{message.sources.map((source) => (
|
|
<a
|
|
key={source.id}
|
|
href={source.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="block p-2 bg-gray-50 dark:bg-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
|
>
|
|
<p className="text-sm font-medium text-blue-600 dark:text-blue-400">
|
|
{source.title}
|
|
</p>
|
|
<p className="text-xs text-gray-500">
|
|
{source.bundesland_name} • {source.doc_type}
|
|
</p>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
{isLoading && (
|
|
<div className="flex justify-start">
|
|
<div className="bg-white dark:bg-gray-800 rounded-2xl p-4 shadow-lg border border-gray-200 dark:border-gray-700">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" />
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }} />
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div ref={messagesEndRef} />
|
|
</div>
|
|
|
|
{/* Input */}
|
|
<div className="p-4 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
|
<form onSubmit={handleSubmit} className="flex gap-3">
|
|
<textarea
|
|
ref={inputRef}
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder="Stellen Sie Ihre Frage..."
|
|
rows={1}
|
|
className="flex-1 px-4 py-3 bg-gray-100 dark:bg-gray-900 border-0 rounded-xl resize-none focus:ring-2 focus:ring-blue-500 text-gray-900 dark:text-white placeholder-gray-500"
|
|
/>
|
|
<button
|
|
type="submit"
|
|
disabled={!input.trim() || isLoading}
|
|
className="px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// SEARCH RESULTS COMPONENT
|
|
// ============================================================================
|
|
|
|
function SearchResults({ results, onSelect }: {
|
|
results: SearchResult[]
|
|
onSelect: (result: SearchResult) => void
|
|
}) {
|
|
if (results.length === 0) {
|
|
return (
|
|
<div className="text-center py-12">
|
|
<span className="text-4xl">🔍</span>
|
|
<p className="mt-4 text-gray-500 dark:text-gray-400">
|
|
Keine Ergebnisse gefunden
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{results.map((result) => (
|
|
<button
|
|
key={result.id}
|
|
onClick={() => onSelect(result)}
|
|
className="w-full text-left p-4 bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-md hover:border-blue-300 transition"
|
|
>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<h4 className="font-medium text-gray-900 dark:text-white mb-1">
|
|
{result.title}
|
|
</h4>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
|
{result.bundesland_name} • {result.doc_type}
|
|
</p>
|
|
<p className="text-sm text-gray-600 dark:text-gray-300 line-clamp-2">
|
|
{result.snippet}
|
|
</p>
|
|
</div>
|
|
<div className="ml-4 flex items-center gap-2">
|
|
<span className={`px-2 py-1 text-xs rounded-full ${
|
|
result.relevance_score > 0.8
|
|
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
|
: result.relevance_score > 0.5
|
|
? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'
|
|
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'
|
|
}`}>
|
|
{Math.round(result.relevance_score * 100)}%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// ============================================================================
|
|
// MAIN PAGE
|
|
// ============================================================================
|
|
|
|
export default function ZeugnissePage() {
|
|
const [preferences, setPreferences] = useState<UserPreferences>({
|
|
bundesland: null,
|
|
schulform: null,
|
|
hasSeenWizard: false,
|
|
favorites: [],
|
|
recentSearches: [],
|
|
})
|
|
const [showWizard, setShowWizard] = useState(true)
|
|
const [activeTab, setActiveTab] = useState<'chat' | 'search' | 'documents'>('chat')
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [searchResults, setSearchResults] = useState<SearchResult[]>([])
|
|
const [messages, setMessages] = useState<Message[]>([])
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
// Load preferences from localStorage
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem('zeugnis-preferences')
|
|
if (saved) {
|
|
const parsed = JSON.parse(saved)
|
|
setPreferences(parsed)
|
|
setShowWizard(!parsed.hasSeenWizard)
|
|
}
|
|
}, [])
|
|
|
|
// Save preferences
|
|
const savePreferences = (newPrefs: Partial<UserPreferences>) => {
|
|
const updated = { ...preferences, ...newPrefs }
|
|
setPreferences(updated)
|
|
localStorage.setItem('zeugnis-preferences', JSON.stringify(updated))
|
|
}
|
|
|
|
// Handle wizard completion
|
|
const handleWizardComplete = (prefs: Partial<UserPreferences>) => {
|
|
savePreferences(prefs)
|
|
setShowWizard(false)
|
|
}
|
|
|
|
// Handle chat message
|
|
const handleSendMessage = async (content: string) => {
|
|
const userMessage: Message = {
|
|
id: Date.now().toString(),
|
|
role: 'user',
|
|
content,
|
|
timestamp: new Date(),
|
|
}
|
|
setMessages(prev => [...prev, userMessage])
|
|
setIsLoading(true)
|
|
|
|
// Simulate API call
|
|
setTimeout(() => {
|
|
const assistantMessage: Message = {
|
|
id: (Date.now() + 1).toString(),
|
|
role: 'assistant',
|
|
content: `Basierend auf der Zeugnisverordnung ${preferences.bundesland ? `für ${BUNDESLAENDER.find(b => b.code === preferences.bundesland)?.name}` : ''} kann ich Ihnen folgende Auskunft geben:\n\n${content.includes('Bemerkung') ? 'Bemerkungen zum Arbeits- und Sozialverhalten sollten wertschätzend formuliert werden und konkrete Beobachtungen enthalten. Sie müssen den Vorgaben der jeweiligen Schulordnung entsprechen.' : content.includes('Noten') ? 'Im Zeugnis werden die Noten gemäß der geltenden Notenverordnung eingetragen. Die Notenskala reicht von 1 (sehr gut) bis 6 (ungenügend).' : 'Ich habe in den Verordnungen relevante Informationen zu Ihrer Frage gefunden. Bitte beachten Sie die verlinkten Quellen für weitere Details.'}`,
|
|
sources: [
|
|
{
|
|
id: '1',
|
|
title: 'Zeugnisverordnung',
|
|
bundesland: preferences.bundesland || 'ni',
|
|
bundesland_name: BUNDESLAENDER.find(b => b.code === (preferences.bundesland || 'ni'))?.name || 'Niedersachsen',
|
|
doc_type: 'Verordnung',
|
|
snippet: 'Relevanter Auszug aus der Verordnung...',
|
|
relevance_score: 0.92,
|
|
url: '#',
|
|
last_updated: '2024-01-15',
|
|
},
|
|
],
|
|
timestamp: new Date(),
|
|
}
|
|
setMessages(prev => [...prev, assistantMessage])
|
|
setIsLoading(false)
|
|
}, 1500)
|
|
}
|
|
|
|
// Handle search
|
|
const handleSearch = (query: string) => {
|
|
setSearchQuery(query)
|
|
if (!query.trim()) {
|
|
setSearchResults([])
|
|
return
|
|
}
|
|
|
|
// Simulate search results
|
|
setSearchResults([
|
|
{
|
|
id: '1',
|
|
title: 'Zeugnisverordnung - Bemerkungen',
|
|
bundesland: preferences.bundesland || 'ni',
|
|
bundesland_name: BUNDESLAENDER.find(b => b.code === (preferences.bundesland || 'ni'))?.name || 'Niedersachsen',
|
|
doc_type: 'Verordnung',
|
|
snippet: 'Die Bemerkungen im Zeugnis sollen das Arbeits- und Sozialverhalten beschreiben...',
|
|
relevance_score: 0.95,
|
|
url: '#',
|
|
last_updated: '2024-01-15',
|
|
},
|
|
{
|
|
id: '2',
|
|
title: 'Ergänzende Bestimmungen für Zeugnisse',
|
|
bundesland: preferences.bundesland || 'ni',
|
|
bundesland_name: BUNDESLAENDER.find(b => b.code === (preferences.bundesland || 'ni'))?.name || 'Niedersachsen',
|
|
doc_type: 'Erlass',
|
|
snippet: 'Für die Erstellung von Zeugnissen gelten folgende ergänzende Bestimmungen...',
|
|
relevance_score: 0.87,
|
|
url: '#',
|
|
last_updated: '2024-02-20',
|
|
},
|
|
])
|
|
|
|
// Save to recent searches
|
|
savePreferences({
|
|
recentSearches: [query, ...preferences.recentSearches.filter(s => s !== query).slice(0, 9)],
|
|
})
|
|
}
|
|
|
|
if (showWizard) {
|
|
return <OnboardingWizard onComplete={handleWizardComplete} />
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
|
|
{/* Header */}
|
|
<header className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
|
|
<div className="max-w-7xl mx-auto px-6 py-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-xl flex items-center justify-center">
|
|
<span className="text-xl">📋</span>
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
|
|
Zeugnis-Assistent
|
|
</h1>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
{BUNDESLAENDER.find(b => b.code === preferences.bundesland)?.name || 'Alle Bundesländer'}
|
|
{preferences.schulform && ` • ${SCHULFORMEN.find(s => s.id === preferences.schulform)?.name}`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<button
|
|
onClick={() => setShowWizard(true)}
|
|
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition"
|
|
title="Einstellungen"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="flex gap-1 mt-4 -mb-px">
|
|
{[
|
|
{ id: 'chat' as const, label: 'Assistent', icon: '💬' },
|
|
{ id: 'search' as const, label: 'Suche', icon: '🔍' },
|
|
{ id: 'documents' as const, label: 'Dokumente', icon: '📄' },
|
|
].map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={`px-6 py-3 text-sm font-medium rounded-t-lg transition ${
|
|
activeTab === tab.id
|
|
? 'bg-gray-100 dark:bg-gray-700 text-blue-600 dark:text-blue-400 border-b-2 border-blue-600'
|
|
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
|
|
}`}
|
|
>
|
|
<span className="mr-2">{tab.icon}</span>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Content */}
|
|
<main className="max-w-7xl mx-auto p-6">
|
|
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-700 overflow-hidden min-h-[600px]">
|
|
{activeTab === 'chat' && (
|
|
<ChatInterface
|
|
messages={messages}
|
|
onSendMessage={handleSendMessage}
|
|
isLoading={isLoading}
|
|
bundesland={preferences.bundesland}
|
|
/>
|
|
)}
|
|
|
|
{activeTab === 'search' && (
|
|
<div className="p-6">
|
|
{/* Search Input */}
|
|
<div className="relative mb-6">
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => handleSearch(e.target.value)}
|
|
placeholder="Suchen Sie in den Verordnungen..."
|
|
className="w-full px-12 py-4 bg-gray-100 dark:bg-gray-900 border-0 rounded-xl text-lg focus:ring-2 focus:ring-blue-500"
|
|
/>
|
|
<svg
|
|
className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
</svg>
|
|
</div>
|
|
|
|
{/* Recent Searches */}
|
|
{!searchQuery && preferences.recentSearches.length > 0 && (
|
|
<div className="mb-6">
|
|
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-3">
|
|
Letzte Suchen
|
|
</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
{preferences.recentSearches.map((search, i) => (
|
|
<button
|
|
key={i}
|
|
onClick={() => handleSearch(search)}
|
|
className="px-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition"
|
|
>
|
|
{search}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Common Questions */}
|
|
{!searchQuery && (
|
|
<div className="mb-6">
|
|
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-3">
|
|
Häufige Fragen
|
|
</h3>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{COMMON_QUESTIONS.map((q, i) => (
|
|
<button
|
|
key={i}
|
|
onClick={() => handleSearch(q)}
|
|
className="p-4 text-left bg-gray-50 dark:bg-gray-900 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
|
>
|
|
<p className="text-sm text-gray-700 dark:text-gray-300">{q}</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Results */}
|
|
{searchQuery && (
|
|
<SearchResults
|
|
results={searchResults}
|
|
onSelect={(result) => {
|
|
handleSendMessage(`Erkläre mir: ${result.title}`)
|
|
setActiveTab('chat')
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'documents' && (
|
|
<div className="p-6">
|
|
<div className="text-center py-12">
|
|
<span className="text-6xl">📚</span>
|
|
<h3 className="mt-4 text-xl font-semibold text-gray-900 dark:text-white">
|
|
Dokumente-Browser
|
|
</h3>
|
|
<p className="mt-2 text-gray-500 dark:text-gray-400 max-w-md mx-auto">
|
|
Durchsuchen Sie alle verfügbaren Verordnungen und Handreichungen für Ihr Bundesland.
|
|
</p>
|
|
<button className="mt-6 px-6 py-3 bg-blue-600 text-white font-medium rounded-xl hover:bg-blue-700 transition">
|
|
Dokumente durchsuchen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|