refactor(admin-v2): Consolidate compliance/DSGVO pages into SDK pipeline

Remove duplicate compliance and DSGVO admin pages that have been superseded
by the unified SDK pipeline. Update navigation, sidebar, roles, and module
registry to reflect the new structure. Add DSFA corpus API proxy and
source-policy components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
BreakPilot Dev
2026-02-10 23:26:05 +01:00
parent 36603259c6
commit f09e24d52c
61 changed files with 265 additions and 24517 deletions

View File

@@ -1,76 +0,0 @@
'use client'
import { Suspense } from 'react'
import { PagePurpose } from '@/components/common/PagePurpose'
import { getModuleByHref } from '@/lib/navigation'
import { CompanionDashboard } from '@/components/companion/CompanionDashboard'
import { GraduationCap } from 'lucide-react'
function LoadingFallback() {
return (
<div className="space-y-6">
{/* Header Skeleton */}
<div className="flex items-center justify-between">
<div className="h-12 w-80 bg-slate-200 rounded-xl animate-pulse" />
<div className="flex gap-2">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="h-10 w-10 bg-slate-200 rounded-lg animate-pulse" />
))}
</div>
</div>
{/* Phase Timeline Skeleton */}
<div className="bg-white border border-slate-200 rounded-xl p-6">
<div className="h-4 w-24 bg-slate-200 rounded mb-4 animate-pulse" />
<div className="flex gap-4">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="flex items-center gap-2">
<div className="w-10 h-10 bg-slate-200 rounded-full animate-pulse" />
{i < 5 && <div className="w-8 h-1 bg-slate-200 animate-pulse" />}
</div>
))}
</div>
</div>
{/* Stats Skeleton */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="bg-white border border-slate-200 rounded-xl p-4">
<div className="h-4 w-16 bg-slate-200 rounded mb-2 animate-pulse" />
<div className="h-8 w-12 bg-slate-200 rounded animate-pulse" />
</div>
))}
</div>
{/* Content Skeleton */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white border border-slate-200 rounded-xl p-6 h-64 animate-pulse" />
<div className="bg-white border border-slate-200 rounded-xl p-6 h-64 animate-pulse" />
</div>
</div>
)
}
export default function CompanionPage() {
const moduleInfo = getModuleByHref('/education/companion')
return (
<div className="space-y-6">
{/* Page Purpose Header */}
{moduleInfo && (
<PagePurpose
title={moduleInfo.module.name}
purpose={moduleInfo.module.purpose}
audience={moduleInfo.module.audience}
collapsible={true}
defaultCollapsed={true}
/>
)}
{/* Main Companion Dashboard */}
<Suspense fallback={<LoadingFallback />}>
<CompanionDashboard />
</Suspense>
</div>
)
}

View File

@@ -1,675 +0,0 @@
'use client'
import { useState, useEffect } from 'react'
import { useParams, useRouter } from 'next/navigation'
import Link from 'next/link'
// Types
interface WizardStep {
number: number
id: string
title: string
subtitle: string
description: string
icon: string
is_required: boolean
is_completed: boolean
}
interface FormData {
[key: string]: any
}
const API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8080'
// Step icons mapping
const stepIcons: Record<string, React.ReactNode> = {
'document-text': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
'academic-cap': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 14l9-5-9-5-9 5 9 5z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" />
</svg>
),
'server': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2" />
</svg>
),
'document-report': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
'currency-euro': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.121 15.536c-1.171 1.952-3.07 1.952-4.242 0-1.172-1.953-1.172-5.119 0-7.072 1.171-1.952 3.07-1.952 4.242 0M8 10.5h4m-4 3h4m9-1.5a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
'calculator': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
),
'calendar': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
),
'document-download': (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
}
// Default wizard steps
const defaultSteps: WizardStep[] = [
{ number: 1, id: 'foerderprogramm', title: 'Foerderprogramm', subtitle: 'Programm & Grunddaten', description: 'Waehlen Sie das Foerderprogramm', icon: 'document-text', is_required: true, is_completed: false },
{ number: 2, id: 'schulinformationen', title: 'Schulinformationen', subtitle: 'Schule & Traeger', description: 'Angaben zur Schule', icon: 'academic-cap', is_required: true, is_completed: false },
{ number: 3, id: 'bestandsaufnahme', title: 'IT-Bestand', subtitle: 'Aktuelle Infrastruktur', description: 'IT-Bestandsaufnahme', icon: 'server', is_required: true, is_completed: false },
{ number: 4, id: 'projektbeschreibung', title: 'Projektbeschreibung', subtitle: 'Ziele & Didaktik', description: 'Projektziele beschreiben', icon: 'document-report', is_required: true, is_completed: false },
{ number: 5, id: 'investitionen', title: 'Investitionen', subtitle: 'Kostenaufstellung', description: 'Geplante Anschaffungen', icon: 'currency-euro', is_required: true, is_completed: false },
{ number: 6, id: 'finanzierungsplan', title: 'Finanzierung', subtitle: 'Budget & Eigenanteil', description: 'Finanzierungsplan', icon: 'calculator', is_required: true, is_completed: false },
{ number: 7, id: 'zeitplan', title: 'Zeitplan', subtitle: 'Laufzeit & Meilensteine', description: 'Projektlaufzeit planen', icon: 'calendar', is_required: true, is_completed: false },
{ number: 8, id: 'abschluss', title: 'Abschluss', subtitle: 'Dokumente & Pruefung', description: 'Zusammenfassung', icon: 'document-download', is_required: true, is_completed: false },
]
export default function FoerderantragWizardPage() {
const params = useParams()
const router = useRouter()
const applicationId = params.applicationId as string
const [currentStep, setCurrentStep] = useState(1)
const [steps, setSteps] = useState<WizardStep[]>(defaultSteps)
const [formData, setFormData] = useState<FormData>({})
const [isSaving, setIsSaving] = useState(false)
const [showAssistant, setShowAssistant] = useState(false)
const [assistantMessage, setAssistantMessage] = useState('')
const [assistantHistory, setAssistantHistory] = useState<{ role: string; content: string }[]>([])
const [isDemo, setIsDemo] = useState(false)
useEffect(() => {
// Check if this is a demo application
if (applicationId.startsWith('demo-')) {
setIsDemo(true)
}
loadApplication()
}, [applicationId])
const loadApplication = async () => {
// In production, load from API
// For demo, use mock data
}
const handleFieldChange = (fieldId: string, value: any) => {
setFormData(prev => ({
...prev,
[`step_${currentStep}`]: {
...prev[`step_${currentStep}`],
[fieldId]: value,
},
}))
}
const handleSaveStep = async () => {
setIsSaving(true)
try {
// Save step data
// Update step completion status
setSteps(prev => prev.map(s =>
s.number === currentStep ? { ...s, is_completed: true } : s
))
} finally {
setIsSaving(false)
}
}
const handleNextStep = async () => {
await handleSaveStep()
if (currentStep < 8) {
setCurrentStep(prev => prev + 1)
}
}
const handlePrevStep = () => {
if (currentStep > 1) {
setCurrentStep(prev => prev - 1)
}
}
const handleAskAssistant = async () => {
if (!assistantMessage.trim()) return
const userMessage = assistantMessage
setAssistantMessage('')
setAssistantHistory(prev => [...prev, { role: 'user', content: userMessage }])
// Simulate assistant response
setTimeout(() => {
const response = getAssistantResponse(userMessage, currentStep)
setAssistantHistory(prev => [...prev, { role: 'assistant', content: response }])
}, 1000)
}
const getAssistantResponse = (question: string, step: number): string => {
// Simple response logic - in production, this calls the LLM API
if (question.toLowerCase().includes('foerderquote')) {
return 'Die Foerderquote im DigitalPakt 2.0 betraegt in der Regel 90%. Das bedeutet, dass 10% der Kosten als Eigenanteil vom Schultraeger zu tragen sind. In einigen Bundeslaendern gibt es Sonderregelungen fuer finanzschwache Kommunen.'
}
if (question.toLowerCase().includes('mep') || question.toLowerCase().includes('medienentwicklungsplan')) {
return 'Der Medienentwicklungsplan (MEP) ist ein strategisches Dokument, das die paedagogischen und technischen Ziele der Schule fuer die Digitalisierung beschreibt. In den meisten Bundeslaendern ist ein MEP Voraussetzung fuer die Foerderung.'
}
if (question.toLowerCase().includes('foerderfahig')) {
return 'Foerderfahig sind unter anderem: Netzwerkinfrastruktur, WLAN, Praesentationstechnik, Endgeraete (mit Einschraenkungen), Server und lokale KI-Systeme. Nicht foerderfahig sind: Verbrauchsmaterial, laufende Betriebskosten und Cloud-Abonnements ohne lokale Alternative.'
}
return `Ich helfe Ihnen gerne bei Schritt ${step}. Haben Sie eine konkrete Frage zu den Feldern in diesem Abschnitt? Sie koennen mich auch nach Formulierungshilfen oder Erklaerungen zu Fachbegriffen fragen.`
}
const renderStepContent = () => {
const step = steps.find(s => s.number === currentStep)
if (!step) return null
switch (currentStep) {
case 1:
return <Step1Foerderprogramm formData={formData} onChange={handleFieldChange} />
case 2:
return <Step2Schulinformationen formData={formData} onChange={handleFieldChange} />
case 3:
return <Step3Bestandsaufnahme formData={formData} onChange={handleFieldChange} />
case 4:
return <Step4Projektbeschreibung formData={formData} onChange={handleFieldChange} />
case 5:
return <Step5Investitionen formData={formData} onChange={handleFieldChange} />
case 6:
return <Step6Finanzierungsplan formData={formData} onChange={handleFieldChange} />
case 7:
return <Step7Zeitplan formData={formData} onChange={handleFieldChange} />
case 8:
return <Step8Abschluss formData={formData} onChange={handleFieldChange} />
default:
return null
}
}
return (
<div className="min-h-screen bg-slate-50">
{/* Header */}
<div className="bg-white border-b border-slate-200 sticky top-0 z-20">
<div className="px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link
href="/education/foerderantrag"
className="p-2 rounded-lg hover:bg-slate-100 transition-colors"
>
<svg className="w-5 h-5 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
<div>
<h1 className="font-semibold text-slate-900">Foerderantrag bearbeiten</h1>
<p className="text-sm text-slate-500">
Schritt {currentStep} von {steps.length}: {steps.find(s => s.number === currentStep)?.title}
</p>
</div>
</div>
<div className="flex items-center gap-3">
{isDemo && (
<span className="px-3 py-1 bg-amber-100 text-amber-700 text-sm font-medium rounded-full">
Demo-Modus
</span>
)}
<button
onClick={() => setShowAssistant(!showAssistant)}
className={`p-2 rounded-lg transition-colors ${showAssistant ? 'bg-blue-100 text-blue-600' : 'hover:bg-slate-100 text-slate-600'}`}
title="KI-Assistent"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</button>
<button
onClick={handleSaveStep}
disabled={isSaving}
className="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg font-medium hover:bg-slate-200 disabled:opacity-50 transition-colors"
>
{isSaving ? 'Speichern...' : 'Speichern'}
</button>
</div>
</div>
</div>
{/* Progress Steps */}
<div className="px-6 pb-4 overflow-x-auto">
<div className="flex gap-1 min-w-max">
{steps.map((step) => (
<button
key={step.number}
onClick={() => setCurrentStep(step.number)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-all ${
currentStep === step.number
? 'bg-blue-600 text-white'
: step.is_completed
? 'bg-green-100 text-green-700 hover:bg-green-200'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${
currentStep === step.number
? 'bg-white/20'
: step.is_completed
? 'bg-green-500 text-white'
: 'bg-slate-300 text-slate-600'
}`}>
{step.is_completed && currentStep !== step.number ? (
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
) : (
step.number
)}
</span>
<span className="hidden md:block font-medium">{step.title}</span>
</button>
))}
</div>
</div>
</div>
{/* Main Content */}
<div className="flex">
{/* Form Area */}
<div className={`flex-1 p-6 transition-all ${showAssistant ? 'pr-96' : ''}`}>
<div className="max-w-3xl mx-auto">
{/* Step Header */}
<div className="mb-8">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-lg bg-blue-100 text-blue-600 flex items-center justify-center">
{stepIcons[steps.find(s => s.number === currentStep)?.icon || 'document-text']}
</div>
<div>
<h2 className="text-xl font-semibold text-slate-900">
{steps.find(s => s.number === currentStep)?.title}
</h2>
<p className="text-sm text-slate-500">
{steps.find(s => s.number === currentStep)?.description}
</p>
</div>
</div>
</div>
{/* Step Content */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
{renderStepContent()}
</div>
{/* Navigation */}
<div className="flex items-center justify-between mt-6">
<button
onClick={handlePrevStep}
disabled={currentStep === 1}
className="px-6 py-3 text-slate-600 hover:text-slate-900 disabled:opacity-50 disabled:cursor-not-allowed font-medium flex items-center gap-2"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurueck
</button>
<button
onClick={handleNextStep}
className="px-6 py-3 bg-blue-600 text-white rounded-xl font-semibold hover:bg-blue-700 flex items-center gap-2 transition-colors"
>
{currentStep === 8 ? 'Abschliessen' : 'Weiter'}
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>
{/* Assistant Sidebar */}
{showAssistant && (
<div className="fixed right-0 top-0 h-full w-96 bg-white border-l border-slate-200 shadow-xl z-30 flex flex-col">
<div className="p-4 border-b border-slate-200 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</div>
<div>
<h3 className="font-semibold text-slate-900">KI-Assistent</h3>
<p className="text-xs text-slate-500">Ich helfe bei Fragen</p>
</div>
</div>
<button
onClick={() => setShowAssistant(false)}
className="p-2 rounded-lg hover:bg-slate-100 transition-colors"
>
<svg className="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Chat History */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{assistantHistory.length === 0 && (
<div className="text-center py-8">
<p className="text-slate-500 text-sm">
Stellen Sie mir Fragen zum aktuellen Schritt oder bitten Sie um Formulierungshilfen.
</p>
<div className="mt-4 space-y-2">
{['Was ist foerderfahig?', 'Erklaere die Foerderquote', 'Was ist ein MEP?'].map((q) => (
<button
key={q}
onClick={() => {
setAssistantMessage(q)
setTimeout(handleAskAssistant, 100)
}}
className="block w-full text-left px-3 py-2 bg-slate-100 rounded-lg text-sm text-slate-700 hover:bg-slate-200 transition-colors"
>
{q}
</button>
))}
</div>
</div>
)}
{assistantHistory.map((msg, idx) => (
<div
key={idx}
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[85%] p-3 rounded-xl text-sm ${
msg.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-slate-100 text-slate-700'
}`}
>
{msg.content}
</div>
</div>
))}
</div>
{/* Input */}
<div className="p-4 border-t border-slate-200">
<div className="flex gap-2">
<input
type="text"
value={assistantMessage}
onChange={(e) => setAssistantMessage(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAskAssistant()}
placeholder="Frage stellen..."
className="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleAskAssistant}
className="p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<svg className="w-4 h-4" 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>
</div>
</div>
</div>
)}
</div>
</div>
)
}
// Step Components (simplified for now)
function Step1Foerderprogramm({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<p className="text-slate-600">
Die Grunddaten wurden bereits beim Erstellen des Antrags festgelegt.
Sie koennen diese hier bei Bedarf anpassen.
</p>
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
<p className="text-sm text-blue-700">
Klicken Sie auf "Weiter" um mit den Schulinformationen fortzufahren.
</p>
</div>
</div>
)
}
function Step2Schulinformationen({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Schulname *</label>
<input
type="text"
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="z.B. Gymnasium am Beispielweg"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Schulnummer *</label>
<input
type="text"
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="z.B. 12345"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Anzahl Schueler</label>
<input
type="number"
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="z.B. 850"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Anzahl Lehrkraefte</label>
<input
type="number"
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="z.B. 65"
/>
</div>
</div>
</div>
)
}
function Step3Bestandsaufnahme({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<div className="flex items-center gap-3">
<input type="checkbox" id="has_wlan" className="w-4 h-4 rounded border-slate-300" />
<label htmlFor="has_wlan" className="text-sm text-slate-700">WLAN vorhanden</label>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Internet-Bandbreite</label>
<select className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>Unter 16 Mbit/s</option>
<option>16-50 Mbit/s</option>
<option>50-100 Mbit/s</option>
<option>100-250 Mbit/s</option>
<option>Ueber 250 Mbit/s</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Vorhandene Endgeraete</label>
<input
type="number"
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Anzahl"
/>
</div>
</div>
)
}
function Step4Projektbeschreibung({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Kurzbeschreibung *</label>
<textarea
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Beschreiben Sie Ihr Projekt in 2-3 Saetzen..."
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Projektziele *</label>
<textarea
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Welche konkreten Ziele verfolgen Sie?"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Paedagogisches Konzept *</label>
<textarea
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Wie wird die Technik im Unterricht eingesetzt?"
/>
</div>
</div>
)
}
function Step5Investitionen({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<p className="text-slate-600">
Listen Sie alle geplanten Investitionen auf. Der Wizard berechnet automatisch die Summen.
</p>
<div className="border border-slate-200 rounded-lg overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-slate-50">
<tr>
<th className="px-4 py-2 text-left font-medium text-slate-700">Beschreibung</th>
<th className="px-4 py-2 text-left font-medium text-slate-700">Anzahl</th>
<th className="px-4 py-2 text-left font-medium text-slate-700">Einzelpreis</th>
<th className="px-4 py-2 text-left font-medium text-slate-700">Gesamt</th>
</tr>
</thead>
<tbody>
<tr className="border-t border-slate-200">
<td className="px-4 py-2" colSpan={4}>
<button className="text-blue-600 hover:text-blue-700 font-medium text-sm flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Position hinzufuegen
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
)
}
function Step6Finanzierungsplan({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Foerderquote</label>
<div className="flex items-center gap-4">
<input
type="range"
min="50"
max="100"
defaultValue="90"
className="flex-1"
/>
<span className="text-lg font-semibold text-slate-900">90%</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-slate-50 rounded-lg">
<div className="text-sm text-slate-500">Gesamtkosten</div>
<div className="text-xl font-bold text-slate-900">0,00 EUR</div>
</div>
<div className="p-4 bg-blue-50 rounded-lg">
<div className="text-sm text-blue-600">Foerderbetrag</div>
<div className="text-xl font-bold text-blue-700">0,00 EUR</div>
</div>
</div>
</div>
)
}
function Step7Zeitplan({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Projektbeginn *</label>
<input
type="date"
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Projektende *</label>
<input
type="date"
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Meilensteine</label>
<p className="text-sm text-slate-500">Definieren Sie wichtige Projektmeilensteine</p>
</div>
</div>
)
}
function Step8Abschluss({ formData, onChange }: { formData: FormData; onChange: (id: string, value: any) => void }) {
return (
<div className="space-y-6">
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
<h3 className="font-semibold text-green-800">Zusammenfassung</h3>
<p className="text-sm text-green-700 mt-1">
Pruefen Sie alle Angaben und laden Sie ggf. zusaetzliche Dokumente hoch.
</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Datenschutzkonzept *</label>
<textarea
className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Beschreiben Sie die Massnahmen zum Datenschutz..."
/>
</div>
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg">
<h3 className="font-semibold text-amber-800">Hinweis zur Traegerpruefung</h3>
<p className="text-sm text-amber-700 mt-1">
Der generierte Antrag ist ein antragsfaehiger ENTWURF.
Die finale Pruefung und Einreichung erfolgt durch den Schultraeger.
</p>
</div>
<div className="space-y-3">
<label className="flex items-center gap-3">
<input type="checkbox" className="w-4 h-4 rounded border-slate-300" />
<span className="text-sm text-slate-700">Ich bestaetige, dass alle Angaben nach bestem Wissen gemacht wurden</span>
</label>
<label className="flex items-center gap-3">
<input type="checkbox" className="w-4 h-4 rounded border-slate-300" />
<span className="text-sm text-slate-700">Ich habe verstanden, dass der Antrag vom Schultraeger geprueft werden muss</span>
</label>
</div>
</div>
)
}

View File

@@ -1,368 +0,0 @@
'use client'
import { useState, useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import Link from 'next/link'
// Types
type FundingProgram = 'DIGITALPAKT_1' | 'DIGITALPAKT_2' | 'LANDESFOERDERUNG' | 'SCHULTRAEGER'
type FederalState = 'NI' | 'NRW' | 'BAY' | 'BW' | 'HE' | 'SN' | 'TH' | 'SA' | 'BB' | 'MV' | 'SH' | 'HH' | 'HB' | 'BE' | 'SL' | 'RP'
interface FormData {
title: string
funding_program: FundingProgram
federal_state: FederalState
preset_id: string
}
const API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8080'
const fundingPrograms = [
{ value: 'DIGITALPAKT_2', label: 'DigitalPakt 2.0', description: 'Foerderung digitaler Bildungsinfrastruktur (2025-2030)' },
{ value: 'DIGITALPAKT_1', label: 'DigitalPakt 1.0 (Restmittel)', description: 'Restmittel aus der ersten Phase' },
{ value: 'LANDESFOERDERUNG', label: 'Landesfoerderung', description: 'Landesspezifische Foerderprogramme' },
{ value: 'SCHULTRAEGER', label: 'Schultraegerfoerderung', description: 'Foerderung durch Schultraeger' },
]
const federalStates = [
{ value: 'NI', label: 'Niedersachsen', flag: 'NI' },
{ value: 'NRW', label: 'Nordrhein-Westfalen', flag: 'NRW' },
{ value: 'BAY', label: 'Bayern', flag: 'BAY' },
{ value: 'BW', label: 'Baden-Wuerttemberg', flag: 'BW' },
{ value: 'HE', label: 'Hessen', flag: 'HE' },
{ value: 'SN', label: 'Sachsen', flag: 'SN' },
{ value: 'TH', label: 'Thueringen', flag: 'TH' },
{ value: 'SA', label: 'Sachsen-Anhalt', flag: 'SA' },
{ value: 'BB', label: 'Brandenburg', flag: 'BB' },
{ value: 'MV', label: 'Mecklenburg-Vorpommern', flag: 'MV' },
{ value: 'SH', label: 'Schleswig-Holstein', flag: 'SH' },
{ value: 'HH', label: 'Hamburg', flag: 'HH' },
{ value: 'HB', label: 'Bremen', flag: 'HB' },
{ value: 'BE', label: 'Berlin', flag: 'BE' },
{ value: 'SL', label: 'Saarland', flag: 'SL' },
{ value: 'RP', label: 'Rheinland-Pfalz', flag: 'RP' },
]
const presets = [
{
id: 'breakpilot_basic',
name: 'BreakPilot Basis',
description: 'Lokale KI-Arbeitsstation fuer eine Schule',
budget: '~18.500 EUR',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
),
color: 'blue',
},
{
id: 'breakpilot_cluster',
name: 'BreakPilot Schulverbund',
description: 'Zentrale KI-Infrastruktur fuer mehrere Schulen',
budget: '~68.500 EUR',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
),
color: 'purple',
},
{
id: '',
name: 'Individuell',
description: 'Leerer Wizard fuer eigene Projekte',
budget: 'Flexibel',
icon: (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
),
color: 'slate',
},
]
export default function NewFoerderantragPage() {
const router = useRouter()
const searchParams = useSearchParams()
const [formData, setFormData] = useState<FormData>({
title: '',
funding_program: 'DIGITALPAKT_2',
federal_state: 'NI',
preset_id: searchParams.get('preset') || '',
})
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
// Set preset from URL params
useEffect(() => {
const preset = searchParams.get('preset')
if (preset) {
setFormData(prev => ({ ...prev, preset_id: preset }))
// Auto-generate title based on preset
const presetInfo = presets.find(p => p.id === preset)
if (presetInfo && presetInfo.id) {
setFormData(prev => ({
...prev,
preset_id: preset,
title: `${presetInfo.name} - ${new Date().toLocaleDateString('de-DE')}`,
}))
}
}
}, [searchParams])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
if (!formData.title.trim()) {
setError('Bitte geben Sie einen Projekttitel ein')
return
}
setIsSubmitting(true)
try {
// In production, this would call the API
// const response = await fetch(`${API_BASE}/sdk/v1/funding/applications`, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(formData),
// })
// const data = await response.json()
// router.push(`/education/foerderantrag/${data.id}`)
// For now, redirect to mock ID
const mockId = 'demo-' + Date.now()
router.push(`/education/foerderantrag/${mockId}`)
} catch (err) {
setError('Fehler beim Erstellen des Antrags')
} finally {
setIsSubmitting(false)
}
}
const getPresetColorClasses = (color: string, isSelected: boolean) => {
const colors: Record<string, { border: string; bg: string; ring: string }> = {
blue: {
border: isSelected ? 'border-blue-500' : 'border-slate-200',
bg: isSelected ? 'bg-blue-50' : 'bg-white',
ring: 'ring-blue-500',
},
purple: {
border: isSelected ? 'border-purple-500' : 'border-slate-200',
bg: isSelected ? 'bg-purple-50' : 'bg-white',
ring: 'ring-purple-500',
},
slate: {
border: isSelected ? 'border-slate-500' : 'border-slate-200',
bg: isSelected ? 'bg-slate-50' : 'bg-white',
ring: 'ring-slate-500',
},
}
return colors[color] || colors.slate
}
return (
<div className="max-w-4xl mx-auto">
{/* Back Link */}
<Link
href="/education/foerderantrag"
className="inline-flex items-center gap-2 text-slate-600 hover:text-slate-900 mb-6"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurueck zur Uebersicht
</Link>
{/* Header */}
<div className="mb-8">
<h1 className="text-2xl font-bold text-slate-900">Neuen Foerderantrag starten</h1>
<p className="mt-2 text-slate-600">
Waehlen Sie das Foerderprogramm und Ihr Bundesland. Der Wizard fuehrt Sie durch alle weiteren Schritte.
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-8">
{/* Preset Selection */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-3">
Schnellstart mit Preset (optional)
</label>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{presets.map((preset) => {
const isSelected = formData.preset_id === preset.id
const colors = getPresetColorClasses(preset.color, isSelected)
return (
<button
key={preset.id || 'custom'}
type="button"
onClick={() => setFormData(prev => ({
...prev,
preset_id: preset.id,
title: preset.id ? `${preset.name} - ${new Date().toLocaleDateString('de-DE')}` : prev.title,
}))}
className={`relative p-4 rounded-xl border-2 text-left transition-all ${colors.border} ${colors.bg} ${isSelected ? 'ring-2 ' + colors.ring : ''}`}
>
{isSelected && (
<div className="absolute top-2 right-2">
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
)}
<div className={`w-12 h-12 rounded-lg bg-${preset.color}-100 text-${preset.color}-600 flex items-center justify-center mb-3`}>
{preset.icon}
</div>
<h3 className="font-semibold text-slate-900">{preset.name}</h3>
<p className="text-sm text-slate-500 mt-1">{preset.description}</p>
<p className="text-sm font-medium text-slate-700 mt-2">{preset.budget}</p>
</button>
)
})}
</div>
</div>
{/* Funding Program */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-3">
Foerderprogramm *
</label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{fundingPrograms.map((program) => (
<label
key={program.value}
className={`relative flex items-start p-4 rounded-xl border-2 cursor-pointer transition-all ${
formData.funding_program === program.value
? 'border-blue-500 bg-blue-50 ring-2 ring-blue-500'
: 'border-slate-200 bg-white hover:border-slate-300'
}`}
>
<input
type="radio"
name="funding_program"
value={program.value}
checked={formData.funding_program === program.value}
onChange={(e) => setFormData(prev => ({ ...prev, funding_program: e.target.value as FundingProgram }))}
className="sr-only"
/>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-medium text-slate-900">{program.label}</span>
{formData.funding_program === program.value && (
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
)}
</div>
<p className="text-sm text-slate-500 mt-1">{program.description}</p>
</div>
</label>
))}
</div>
</div>
{/* Federal State */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-3">
Bundesland *
</label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{federalStates.map((state) => (
<button
key={state.value}
type="button"
onClick={() => setFormData(prev => ({ ...prev, federal_state: state.value as FederalState }))}
className={`px-4 py-3 rounded-lg border-2 text-sm font-medium transition-all ${
formData.federal_state === state.value
? 'border-blue-500 bg-blue-50 text-blue-700'
: 'border-slate-200 bg-white text-slate-700 hover:border-slate-300'
}`}
>
{state.label}
</button>
))}
</div>
<p className="mt-2 text-sm text-slate-500">
{formData.federal_state === 'NI' && 'Niedersachsen ist der Pilot-Standort mit optimaler Unterstuetzung.'}
</p>
</div>
{/* Project Title */}
<div>
<label htmlFor="title" className="block text-sm font-medium text-slate-700 mb-2">
Projekttitel *
</label>
<input
type="text"
id="title"
value={formData.title}
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
placeholder="z.B. Digitale Lernumgebung fuer differenzierten Unterricht"
className="w-full px-4 py-3 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
maxLength={200}
/>
<p className="mt-2 text-sm text-slate-500">
Ein aussagekraeftiger Titel fuer Ihr Foerderprojekt (max. 200 Zeichen)
</p>
</div>
{/* Error */}
{error && (
<div className="p-4 bg-red-50 border border-red-200 rounded-xl text-red-700">
{error}
</div>
)}
{/* Actions */}
<div className="flex items-center justify-between pt-4 border-t border-slate-200">
<Link
href="/education/foerderantrag"
className="px-6 py-3 text-slate-600 hover:text-slate-900 font-medium"
>
Abbrechen
</Link>
<button
type="submit"
disabled={isSubmitting}
className="px-8 py-3 bg-blue-600 text-white rounded-xl font-semibold hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 transition-colors"
>
{isSubmitting ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
Wird erstellt...
</>
) : (
<>
Wizard starten
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
</>
)}
</button>
</div>
</form>
{/* Help Box */}
<div className="mt-8 bg-amber-50 border border-amber-200 rounded-xl p-6">
<div className="flex gap-4">
<div className="flex-shrink-0">
<svg className="w-6 h-6 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</div>
<div>
<h3 className="font-semibold text-amber-800">KI-Assistent verfuegbar</h3>
<p className="mt-1 text-sm text-amber-700">
Im Wizard steht Ihnen ein KI-Assistent zur Seite, der bei Fragen hilft,
Formulierungen vorschlaegt und Sie durch den Antragsprozess fuehrt.
</p>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,365 +0,0 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { PagePurpose } from '@/components/common/PagePurpose'
// Types
interface FundingApplication {
id: string
application_number: string
title: string
funding_program: string
status: string
current_step: number
total_steps: number
requested_amount: number
school_profile?: {
name: string
federal_state: string
}
created_at: string
updated_at: string
}
interface Statistics {
total_applications: number
draft_count: number
submitted_count: number
approved_count: number
total_requested: number
total_approved: number
}
const API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8080'
// Status badge colors
const statusColors: Record<string, { bg: string; text: string; label: string }> = {
DRAFT: { bg: 'bg-slate-100', text: 'text-slate-700', label: 'Entwurf' },
IN_PROGRESS: { bg: 'bg-blue-100', text: 'text-blue-700', label: 'In Bearbeitung' },
REVIEW: { bg: 'bg-amber-100', text: 'text-amber-700', label: 'Pruefung' },
SUBMITTED: { bg: 'bg-purple-100', text: 'text-purple-700', label: 'Eingereicht' },
APPROVED: { bg: 'bg-green-100', text: 'text-green-700', label: 'Genehmigt' },
REJECTED: { bg: 'bg-red-100', text: 'text-red-700', label: 'Abgelehnt' },
}
const programLabels: Record<string, string> = {
DIGITALPAKT_1: 'DigitalPakt 1.0',
DIGITALPAKT_2: 'DigitalPakt 2.0',
LANDESFOERDERUNG: 'Landesfoerderung',
SCHULTRAEGER: 'Schultraeger',
}
export default function FoerderantragPage() {
const [applications, setApplications] = useState<FundingApplication[]>([])
const [statistics, setStatistics] = useState<Statistics | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
try {
setLoading(true)
// In production, these would be real API calls
// For now, we use mock data
setApplications([])
setStatistics({
total_applications: 0,
draft_count: 0,
submitted_count: 0,
approved_count: 0,
total_requested: 0,
total_approved: 0,
})
} catch (err) {
setError('Fehler beim Laden der Daten')
} finally {
setLoading(false)
}
}
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(amount)
}
const formatDate = (dateStr: string) => {
return new Date(dateStr).toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
return (
<div className="space-y-8">
{/* Page Purpose */}
<PagePurpose
title="Foerderantrag-Wizard"
purpose="Erstellen Sie antragsfaehige Foerderantraege fuer Schulen. Der Wizard fuehrt Sie Schritt fuer Schritt durch den Prozess und generiert alle erforderlichen Dokumente."
audience={['Schulleitung', 'IT-Beauftragte', 'Schultraeger']}
architecture={{
services: ['ai-compliance-sdk (Go)', 'LLM-Service (32B)'],
databases: ['PostgreSQL'],
}}
collapsible={true}
defaultCollapsed={true}
/>
{/* Hero Section */}
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-blue-600 via-blue-700 to-indigo-800 p-8 text-white">
<div className="absolute inset-0 bg-[url('/grid-pattern.svg')] opacity-10" />
<div className="relative z-10">
<div className="flex items-start justify-between">
<div>
<h1 className="text-3xl font-bold">Foerderantrag-Wizard</h1>
<p className="mt-2 text-blue-100 max-w-2xl">
Erstellen Sie vollstaendige Foerderantraege fuer DigitalPakt 2.0 und Landesfoerderungen.
Der Wizard fuehrt Sie durch alle 8 Schritte und generiert antragsfaehige Dokumente.
</p>
<div className="mt-6 flex gap-4">
<Link
href="/education/foerderantrag/new"
className="inline-flex items-center gap-2 px-6 py-3 bg-white text-blue-700 rounded-xl font-semibold hover:bg-blue-50 transition-colors shadow-lg"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Neuen Antrag starten
</Link>
</div>
</div>
<div className="hidden lg:block">
<svg className="w-32 h-32 text-blue-300 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
</div>
</div>
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-slate-200 p-5 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-blue-100 flex items-center justify-center">
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<div className="text-2xl font-bold text-slate-900">{statistics?.total_applications || 0}</div>
<div className="text-sm text-slate-500">Antraege gesamt</div>
</div>
</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-5 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-amber-100 flex items-center justify-center">
<svg className="w-6 h-6 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</div>
<div>
<div className="text-2xl font-bold text-slate-900">{statistics?.draft_count || 0}</div>
<div className="text-sm text-slate-500">Entwuerfe</div>
</div>
</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-5 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-purple-100 flex items-center justify-center">
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
</div>
<div>
<div className="text-2xl font-bold text-slate-900">{statistics?.submitted_count || 0}</div>
<div className="text-sm text-slate-500">Eingereicht</div>
</div>
</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-5 hover:shadow-md transition-shadow">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-green-100 flex items-center justify-center">
<svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<div className="text-2xl font-bold text-slate-900">{formatCurrency(statistics?.total_requested || 0)}</div>
<div className="text-sm text-slate-500">Beantragt</div>
</div>
</div>
</div>
</div>
{/* Quick Start Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Link
href="/education/foerderantrag/new?preset=breakpilot_basic"
className="group bg-white rounded-xl border-2 border-slate-200 p-6 hover:border-blue-400 hover:shadow-lg transition-all"
>
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 flex items-center justify-center mb-4">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
<h3 className="font-semibold text-lg text-slate-900 group-hover:text-blue-600 transition-colors">
BreakPilot Basis
</h3>
<p className="text-sm text-slate-500 mt-1">
Lokale KI-Arbeitsstation fuer eine Schule. Vorausgefuellte Kostenplanung und Datenschutzkonzept.
</p>
<div className="mt-4 text-sm font-medium text-blue-600">
~18.500 EUR Foerdervolumen
</div>
</Link>
<Link
href="/education/foerderantrag/new?preset=breakpilot_cluster"
className="group bg-white rounded-xl border-2 border-slate-200 p-6 hover:border-blue-400 hover:shadow-lg transition-all"
>
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-500 to-pink-600 flex items-center justify-center mb-4">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
</div>
<h3 className="font-semibold text-lg text-slate-900 group-hover:text-purple-600 transition-colors">
BreakPilot Schulverbund
</h3>
<p className="text-sm text-slate-500 mt-1">
Zentrale KI-Infrastruktur fuer mehrere Schulen eines Traegers.
</p>
<div className="mt-4 text-sm font-medium text-purple-600">
~68.500 EUR Foerdervolumen
</div>
</Link>
<Link
href="/education/foerderantrag/new"
className="group bg-white rounded-xl border-2 border-slate-200 p-6 hover:border-slate-400 hover:shadow-lg transition-all"
>
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-slate-500 to-slate-700 flex items-center justify-center mb-4">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
</div>
<h3 className="font-semibold text-lg text-slate-900 group-hover:text-slate-700 transition-colors">
Individueller Antrag
</h3>
<p className="text-sm text-slate-500 mt-1">
Leerer Wizard fuer individuelle Projekte. Volle Flexibilitaet bei der Planung.
</p>
<div className="mt-4 text-sm font-medium text-slate-600">
Beliebiges Foerdervolumen
</div>
</Link>
</div>
{/* Applications List */}
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-6 py-4 border-b border-slate-200 flex items-center justify-between">
<h2 className="font-semibold text-lg text-slate-900">Meine Antraege</h2>
<div className="flex items-center gap-2">
<select className="px-3 py-1.5 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">Alle Status</option>
<option value="DRAFT">Entwurf</option>
<option value="SUBMITTED">Eingereicht</option>
<option value="APPROVED">Genehmigt</option>
</select>
</div>
</div>
{loading ? (
<div className="p-12 text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-4 text-slate-500">Lade Antraege...</p>
</div>
) : applications.length === 0 ? (
<div className="p-12 text-center">
<svg className="w-16 h-16 text-slate-300 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 className="mt-4 text-lg font-medium text-slate-900">Noch keine Antraege</h3>
<p className="mt-2 text-slate-500">
Starten Sie jetzt Ihren ersten Foerderantrag mit dem Wizard.
</p>
<Link
href="/education/foerderantrag/new"
className="mt-6 inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Ersten Antrag erstellen
</Link>
</div>
) : (
<div className="divide-y divide-slate-100">
{applications.map((app) => {
const status = statusColors[app.status] || statusColors.DRAFT
return (
<Link
key={app.id}
href={`/education/foerderantrag/${app.id}`}
className="flex items-center gap-4 p-4 hover:bg-slate-50 transition-colors"
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3">
<h3 className="font-medium text-slate-900 truncate">{app.title}</h3>
<span className={`px-2 py-0.5 text-xs font-medium rounded-full ${status.bg} ${status.text}`}>
{status.label}
</span>
</div>
<div className="flex items-center gap-4 mt-1 text-sm text-slate-500">
<span>{app.application_number}</span>
<span>{programLabels[app.funding_program] || app.funding_program}</span>
{app.school_profile?.name && (
<span>{app.school_profile.name}</span>
)}
</div>
</div>
<div className="text-right">
<div className="font-medium text-slate-900">{formatCurrency(app.requested_amount)}</div>
<div className="text-sm text-slate-500">Schritt {app.current_step}/{app.total_steps}</div>
</div>
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
)
})}
</div>
)}
</div>
{/* Info Box */}
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6">
<div className="flex gap-4">
<div className="flex-shrink-0">
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<h3 className="font-semibold text-blue-800">Wichtiger Hinweis</h3>
<p className="mt-1 text-sm text-blue-700">
Der Wizard erstellt einen <strong>antragsfaehigen Entwurf</strong>. Die finale Pruefung und
Einreichung erfolgt durch den Schultraeger. Alle generierten Dokumente (Antragsschreiben,
Kostenplan, Datenschutzkonzept) koennen als ZIP heruntergeladen werden.
</p>
</div>
</div>
</div>
</div>
)
}