website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
211 lines
6.3 KiB
TypeScript
211 lines
6.3 KiB
TypeScript
import { useState } from 'react'
|
|
import type { Option, GFKPrinciple } from './types'
|
|
|
|
interface InputFormProps {
|
|
communicationType: string
|
|
setCommunicationType: (v: string) => void
|
|
tone: string
|
|
setTone: (v: string) => void
|
|
state: string
|
|
setState: (v: string) => void
|
|
studentName: string
|
|
setStudentName: (v: string) => void
|
|
parentName: string
|
|
setParentName: (v: string) => void
|
|
situation: string
|
|
setSituation: (v: string) => void
|
|
additionalInfo: string
|
|
setAdditionalInfo: (v: string) => void
|
|
types: Option[]
|
|
tones: Option[]
|
|
states: Option[]
|
|
gfkPrinciples: GFKPrinciple[]
|
|
error: string | null
|
|
loading: boolean
|
|
onGenerate: () => void
|
|
}
|
|
|
|
export function InputForm({
|
|
communicationType, setCommunicationType,
|
|
tone, setTone,
|
|
state, setState,
|
|
studentName, setStudentName,
|
|
parentName, setParentName,
|
|
situation, setSituation,
|
|
additionalInfo, setAdditionalInfo,
|
|
types, tones, states,
|
|
gfkPrinciples,
|
|
error, loading,
|
|
onGenerate,
|
|
}: InputFormProps) {
|
|
return (
|
|
<div className="lg:col-span-1 space-y-6">
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<h2 className="text-lg font-semibold mb-4">Nachricht erstellen</h2>
|
|
|
|
{/* Communication Type */}
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Art der Kommunikation *
|
|
</label>
|
|
<select
|
|
value={communicationType}
|
|
onChange={(e) => setCommunicationType(e.target.value)}
|
|
className="w-full border rounded-md p-2"
|
|
>
|
|
{types.map((t) => (
|
|
<option key={t.value} value={t.value}>
|
|
{t.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Tone */}
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Tonalitaet
|
|
</label>
|
|
<select
|
|
value={tone}
|
|
onChange={(e) => setTone(e.target.value)}
|
|
className="w-full border rounded-md p-2"
|
|
>
|
|
{tones.map((t) => (
|
|
<option key={t.value} value={t.value}>
|
|
{t.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* State */}
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Bundesland (fuer rechtliche Referenzen)
|
|
</label>
|
|
<select
|
|
value={state}
|
|
onChange={(e) => setState(e.target.value)}
|
|
className="w-full border rounded-md p-2"
|
|
>
|
|
{states.map((s) => (
|
|
<option key={s.value} value={s.value}>
|
|
{s.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Student Name */}
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Name des Schuelers/der Schuelerin *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={studentName}
|
|
onChange={(e) => setStudentName(e.target.value)}
|
|
placeholder="z.B. Max"
|
|
className="w-full border rounded-md p-2"
|
|
/>
|
|
</div>
|
|
|
|
{/* Parent Name */}
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Anrede der Eltern *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={parentName}
|
|
onChange={(e) => setParentName(e.target.value)}
|
|
placeholder="z.B. Frau Mueller"
|
|
className="w-full border rounded-md p-2"
|
|
/>
|
|
</div>
|
|
|
|
{/* Situation */}
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Beschreibung der Situation *
|
|
</label>
|
|
<textarea
|
|
value={situation}
|
|
onChange={(e) => setSituation(e.target.value)}
|
|
placeholder="Beschreiben Sie die Situation sachlich und konkret..."
|
|
rows={4}
|
|
className="w-full border rounded-md p-2"
|
|
/>
|
|
</div>
|
|
|
|
{/* Additional Info */}
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Zusaetzliche Informationen (optional)
|
|
</label>
|
|
<textarea
|
|
value={additionalInfo}
|
|
onChange={(e) => setAdditionalInfo(e.target.value)}
|
|
placeholder="Besondere Umstaende, gewuenschte Termine, etc."
|
|
rows={2}
|
|
className="w-full border rounded-md p-2"
|
|
/>
|
|
</div>
|
|
|
|
{/* Error */}
|
|
{error && (
|
|
<div className="mb-4 p-3 bg-red-50 text-red-700 rounded-md text-sm">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Generate Button */}
|
|
<button
|
|
onClick={onGenerate}
|
|
disabled={loading}
|
|
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
|
|
>
|
|
{loading ? 'Wird generiert...' : 'Nachricht generieren'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* GFK Info Toggle */}
|
|
<GFKInfoPanel gfkPrinciples={gfkPrinciples} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function GFKInfoPanel({ gfkPrinciples }: { gfkPrinciples: GFKPrinciple[] }) {
|
|
const [showGFKInfo, setShowGFKInfo] = useState(false)
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<button
|
|
onClick={() => setShowGFKInfo(!showGFKInfo)}
|
|
className="flex items-center justify-between w-full text-left"
|
|
>
|
|
<span className="font-semibold">Was ist GFK?</span>
|
|
<span>{showGFKInfo ? '-' : '+'}</span>
|
|
</button>
|
|
{showGFKInfo && (
|
|
<div className="mt-4 space-y-4">
|
|
<p className="text-sm text-gray-600">
|
|
Die Gewaltfreie Kommunikation (GFK) nach Marshall Rosenberg
|
|
ist ein Kommunikationsmodell, das auf vier Schritten basiert:
|
|
</p>
|
|
{gfkPrinciples.map((p, i) => (
|
|
<div key={i} className="border-l-4 border-blue-500 pl-3">
|
|
<h4 className="font-medium">{i + 1}. {p.principle}</h4>
|
|
<p className="text-sm text-gray-600">{p.description}</p>
|
|
<p className="text-xs text-gray-500 italic mt-1">
|
|
Beispiel: {p.example}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|