fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
368
admin-v2/app/(admin)/education/foerderantrag/new/page.tsx
Normal file
368
admin-v2/app/(admin)/education/foerderantrag/new/page.tsx
Normal file
@@ -0,0 +1,368 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user