refactor: Consolidate standalone services into admin-v2, add new SDK modules
Remove standalone services (ai-compliance-sdk root, developer-portal, dsms-gateway, dsms-node, night-scheduler) and legacy compliance/dsgvo pages. Add new SDK pipeline modules (academy, document-crawler, dsb-portal, incidents, whistleblower, reporting, sso, multi-tenant, industry-templates). Add drafting engine, legal corpus files (AT/CH/DE), pitch-deck, blog and Förderantrag pages. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
540
website/app/foerderantrag/[applicationId]/page.tsx
Normal file
540
website/app/foerderantrag/[applicationId]/page.tsx
Normal file
@@ -0,0 +1,540 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { useLanguage } from '@/lib/LanguageContext'
|
||||
|
||||
interface WizardStep {
|
||||
number: number
|
||||
id: string
|
||||
titleKey: string
|
||||
subtitleKey: string
|
||||
descKey: string
|
||||
icon: string
|
||||
is_required: boolean
|
||||
is_completed: boolean
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
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>
|
||||
),
|
||||
}
|
||||
|
||||
export default function FoerderantragWizardPage() {
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const { t, isRTL } = useLanguage()
|
||||
const applicationId = params.applicationId as string
|
||||
|
||||
const defaultSteps: WizardStep[] = [
|
||||
{ number: 1, id: 'foerderprogramm', titleKey: 'fa_step1_title', subtitleKey: 'fa_step1_subtitle', descKey: 'fa_step1_desc', icon: 'document-text', is_required: true, is_completed: false },
|
||||
{ number: 2, id: 'schulinformationen', titleKey: 'fa_step2_title', subtitleKey: 'fa_step2_subtitle', descKey: 'fa_step2_desc', icon: 'academic-cap', is_required: true, is_completed: false },
|
||||
{ number: 3, id: 'bestandsaufnahme', titleKey: 'fa_step3_title', subtitleKey: 'fa_step3_subtitle', descKey: 'fa_step3_desc', icon: 'server', is_required: true, is_completed: false },
|
||||
{ number: 4, id: 'projektbeschreibung', titleKey: 'fa_step4_title', subtitleKey: 'fa_step4_subtitle', descKey: 'fa_step4_desc', icon: 'document-report', is_required: true, is_completed: false },
|
||||
{ number: 5, id: 'investitionen', titleKey: 'fa_step5_title', subtitleKey: 'fa_step5_subtitle', descKey: 'fa_step5_desc', icon: 'currency-euro', is_required: true, is_completed: false },
|
||||
{ number: 6, id: 'finanzierungsplan', titleKey: 'fa_step6_title', subtitleKey: 'fa_step6_subtitle', descKey: 'fa_step6_desc', icon: 'calculator', is_required: true, is_completed: false },
|
||||
{ number: 7, id: 'zeitplan', titleKey: 'fa_step7_title', subtitleKey: 'fa_step7_subtitle', descKey: 'fa_step7_desc', icon: 'calendar', is_required: true, is_completed: false },
|
||||
{ number: 8, id: 'abschluss', titleKey: 'fa_step8_title', subtitleKey: 'fa_step8_subtitle', descKey: 'fa_step8_desc', icon: 'document-download', is_required: true, is_completed: false },
|
||||
]
|
||||
|
||||
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(() => {
|
||||
if (applicationId.startsWith('demo-')) {
|
||||
setIsDemo(true)
|
||||
}
|
||||
}, [applicationId])
|
||||
|
||||
const handleFieldChange = (fieldId: string, value: any) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[`step_${currentStep}`]: {
|
||||
...prev[`step_${currentStep}`],
|
||||
[fieldId]: value,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSaveStep = async () => {
|
||||
setIsSaving(true)
|
||||
try {
|
||||
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 }])
|
||||
|
||||
setTimeout(() => {
|
||||
const response = `${t('fa_assistant_title')}: ${userMessage}`
|
||||
setAssistantHistory(prev => [...prev, { role: 'assistant', content: response }])
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const currentStepData = steps.find(s => s.number === currentStep)
|
||||
|
||||
const renderStepContent = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return <Step1 t={t} />
|
||||
case 2:
|
||||
return <Step2 t={t} />
|
||||
case 3:
|
||||
return <Step3 t={t} />
|
||||
case 4:
|
||||
return <Step4 t={t} />
|
||||
case 5:
|
||||
return <Step5 t={t} />
|
||||
case 6:
|
||||
return <Step6 t={t} />
|
||||
case 7:
|
||||
return <Step7 t={t} />
|
||||
case 8:
|
||||
return <Step8 t={t} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen bg-slate-50 ${isRTL ? 'rtl' : ''}`}>
|
||||
{/* 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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<div className={`flex items-center gap-4 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<Link
|
||||
href="/foerderantrag"
|
||||
className="p-2 rounded-lg hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
<svg className={`w-5 h-5 text-slate-600 ${isRTL ? 'rotate-180' : ''}`} 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">{t('fa_wizard_header')}</h1>
|
||||
<p className="text-sm text-slate-500">
|
||||
{t('fa_step1_title').split('')[0] && `${currentStep} ${t('fa_wizard_step_of')} ${steps.length}: ${currentStepData ? t(currentStepData.titleKey) : ''}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex items-center gap-3 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
{isDemo && (
|
||||
<span className="px-3 py-1 bg-amber-100 text-amber-700 text-sm font-medium rounded-full">
|
||||
{t('fa_demo_mode')}
|
||||
</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={t('fa_assistant_title')}
|
||||
>
|
||||
<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 ? t('fa_wizard_saving') : t('fa_wizard_save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Steps */}
|
||||
<div className="px-6 pb-4 overflow-x-auto">
|
||||
<div className={`flex gap-1 min-w-max ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
{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">{t(step.titleKey)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex">
|
||||
{/* Form Area */}
|
||||
<div className={`flex-1 p-6 transition-all ${showAssistant ? (isRTL ? 'pl-96' : 'pr-96') : ''}`}>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{/* Step Header */}
|
||||
<div className="mb-8">
|
||||
<div className={`flex items-center gap-3 mb-2 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<div className="w-10 h-10 rounded-lg bg-blue-100 text-blue-600 flex items-center justify-center">
|
||||
{stepIcons[currentStepData?.icon || 'document-text']}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-slate-900">
|
||||
{currentStepData ? t(currentStepData.titleKey) : ''}
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
{currentStepData ? t(currentStepData.descKey) : ''}
|
||||
</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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<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 ${isRTL ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
<svg className={`w-4 h-4 ${isRTL ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
{t('fa_wizard_prev')}
|
||||
</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 ${isRTL ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
{currentStep === 8 ? t('fa_wizard_finish') : t('fa_wizard_next')}
|
||||
<svg className={`w-4 h-4 ${isRTL ? 'rotate-180' : ''}`} 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 ${isRTL ? 'left-0' : 'right-0'} top-0 h-full w-96 bg-white border-${isRTL ? 'r' : '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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<div className={`flex items-center gap-2 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<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">{t('fa_assistant_title')}</h3>
|
||||
</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">
|
||||
{t('fa_assistant_placeholder')}
|
||||
</p>
|
||||
</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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<input
|
||||
type="text"
|
||||
value={assistantMessage}
|
||||
onChange={(e) => setAssistantMessage(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleAskAssistant()}
|
||||
placeholder={t('fa_assistant_placeholder')}
|
||||
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
|
||||
function Step1({ t }: { t: (key: string) => string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<p className="text-slate-600">{t('fa_step1_desc')}</p>
|
||||
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<p className="text-sm text-blue-700">{t('fa_wizard_next')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Step2({ t }: { t: (key: string) => string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step2_title')} *</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" />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step2_subtitle')}</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" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step2_subtitle')}</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" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Step3({ t }: { t: (key: string) => string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step3_desc')}</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>16 Mbit/s</option>
|
||||
<option>16-50 Mbit/s</option>
|
||||
<option>50-100 Mbit/s</option>
|
||||
<option>100-250 Mbit/s</option>
|
||||
<option>250+ Mbit/s</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Step4({ t }: { t: (key: string) => string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step4_desc')} *</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} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step4_subtitle')} *</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} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Step5({ t }: { t: (key: string) => string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<p className="text-slate-600">{t('fa_step5_desc')}</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">{t('fa_step5_subtitle')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="border-t border-slate-200">
|
||||
<td className="px-4 py-2">
|
||||
<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>
|
||||
+
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Step6({ t }: { t: (key: string) => string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step6_desc')}</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">{t('fa_step6_subtitle')}</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">{t('fa_step6_title')}</div>
|
||||
<div className="text-xl font-bold text-blue-700">0,00 EUR</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Step7({ t }: { t: (key: string) => string }) {
|
||||
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">{t('fa_step7_subtitle')} *</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">{t('fa_step7_subtitle')} *</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>
|
||||
)
|
||||
}
|
||||
|
||||
function Step8({ t }: { t: (key: string) => string }) {
|
||||
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">{t('fa_step8_title')}</h3>
|
||||
<p className="text-sm text-green-700 mt-1">{t('fa_step8_desc')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">{t('fa_step8_subtitle')} *</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} />
|
||||
</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">{t('fa_info_text')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
312
website/app/foerderantrag/new/page.tsx
Normal file
312
website/app/foerderantrag/new/page.tsx
Normal file
@@ -0,0 +1,312 @@
|
||||
'use client'
|
||||
|
||||
import { Suspense, useState, useEffect } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import Header from '@/components/Header'
|
||||
import Footer from '@/components/Footer'
|
||||
import { useLanguage } from '@/lib/LanguageContext'
|
||||
|
||||
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 federalStates: { value: string; label: string }[] = [
|
||||
{ value: 'NI', label: 'Niedersachsen' },
|
||||
{ value: 'NRW', label: 'Nordrhein-Westfalen' },
|
||||
{ value: 'BAY', label: 'Bayern' },
|
||||
{ value: 'BW', label: 'Baden-Wuerttemberg' },
|
||||
{ value: 'HE', label: 'Hessen' },
|
||||
{ value: 'SN', label: 'Sachsen' },
|
||||
{ value: 'TH', label: 'Thueringen' },
|
||||
{ value: 'SA', label: 'Sachsen-Anhalt' },
|
||||
{ value: 'BB', label: 'Brandenburg' },
|
||||
{ value: 'MV', label: 'Mecklenburg-Vorpommern' },
|
||||
{ value: 'SH', label: 'Schleswig-Holstein' },
|
||||
{ value: 'HH', label: 'Hamburg' },
|
||||
{ value: 'HB', label: 'Bremen' },
|
||||
{ value: 'BE', label: 'Berlin' },
|
||||
{ value: 'SL', label: 'Saarland' },
|
||||
{ value: 'RP', label: 'Rheinland-Pfalz' },
|
||||
]
|
||||
|
||||
export default function NewFoerderantragPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="min-h-screen bg-slate-50 flex items-center justify-center"><div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div></div>}>
|
||||
<NewFoerderantragContent />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
function NewFoerderantragContent() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const { t, isRTL } = useLanguage()
|
||||
|
||||
const presets = [
|
||||
{ id: 'breakpilot_basic', nameKey: 'fa_preset_basic_name', descKey: 'fa_preset_basic_desc', budgetKey: 'fa_preset_basic_budget', color: 'blue' },
|
||||
{ id: 'breakpilot_cluster', nameKey: 'fa_preset_cluster_name', descKey: 'fa_preset_cluster_desc', budgetKey: 'fa_preset_cluster_budget', color: 'purple' },
|
||||
{ id: '', nameKey: 'fa_preset_custom_name', descKey: 'fa_preset_custom_desc', budgetKey: 'fa_preset_custom_budget', color: 'slate' },
|
||||
]
|
||||
|
||||
const fundingPrograms = [
|
||||
{ value: 'DIGITALPAKT_2', labelKey: 'fa_program_dp2' },
|
||||
{ value: 'DIGITALPAKT_1', labelKey: 'fa_program_dp1' },
|
||||
{ value: 'LANDESFOERDERUNG', labelKey: 'fa_program_landes' },
|
||||
{ value: 'SCHULTRAEGER', labelKey: 'fa_program_traeger' },
|
||||
]
|
||||
|
||||
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)
|
||||
|
||||
useEffect(() => {
|
||||
const preset = searchParams.get('preset')
|
||||
if (preset) {
|
||||
const presetInfo = presets.find(p => p.id === preset)
|
||||
if (presetInfo && presetInfo.id) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
preset_id: preset,
|
||||
title: `${t(presetInfo.nameKey)} - ${new Date().toLocaleDateString('de-DE')}`,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}, [searchParams])
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError(null)
|
||||
|
||||
if (!formData.title.trim()) {
|
||||
setError(t('fa_project_title_label'))
|
||||
return
|
||||
}
|
||||
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
const mockId = 'demo-' + Date.now()
|
||||
router.push(`/foerderantrag/${mockId}`)
|
||||
} catch {
|
||||
setError('Error')
|
||||
} 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 (
|
||||
<>
|
||||
<Header />
|
||||
<main className={`min-h-screen bg-slate-50 pt-20 ${isRTL ? 'rtl' : ''}`}>
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
{/* Back Link */}
|
||||
<Link
|
||||
href="/foerderantrag"
|
||||
className={`inline-flex items-center gap-2 text-slate-600 hover:text-slate-900 mb-6 ${isRTL ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
<svg className={`w-4 h-4 ${isRTL ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
{t('fa_back_overview')}
|
||||
</Link>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-slate-900">{t('fa_new_title')}</h1>
|
||||
<p className="mt-2 text-slate-600">{t('fa_new_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-8">
|
||||
{/* Preset Selection */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">
|
||||
{t('fa_preset_label')}
|
||||
</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 ? `${t(preset.nameKey)} - ${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 ${isRTL ? 'left-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>
|
||||
)}
|
||||
<h3 className="font-semibold text-slate-900">{t(preset.nameKey)}</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">{t(preset.descKey)}</p>
|
||||
<p className="text-sm font-medium text-slate-700 mt-2">{t(preset.budgetKey)}</p>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Funding Program */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">
|
||||
{t('fa_program_label')} *
|
||||
</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">{t(program.labelKey)}</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>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Federal State */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">
|
||||
{t('fa_state_label')} *
|
||||
</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>
|
||||
{formData.federal_state === 'NI' && (
|
||||
<p className="mt-2 text-sm text-slate-500">{t('fa_ni_hint')}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Project Title */}
|
||||
<div>
|
||||
<label htmlFor="title" className="block text-sm font-medium text-slate-700 mb-2">
|
||||
{t('fa_project_title_label')} *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
|
||||
placeholder={t('fa_project_title_label')}
|
||||
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}
|
||||
/>
|
||||
</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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<Link
|
||||
href="/foerderantrag"
|
||||
className="px-6 py-3 text-slate-600 hover:text-slate-900 font-medium"
|
||||
>
|
||||
{t('fa_cancel')}
|
||||
</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 ${isRTL ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
||||
{t('fa_wizard_saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('fa_submit')}
|
||||
<svg className={`w-4 h-4 ${isRTL ? 'rotate-180' : ''}`} 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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<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">{t('fa_ai_hint_title')}</h3>
|
||||
<p className="mt-1 text-sm text-amber-700">{t('fa_ai_hint_text')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
256
website/app/foerderantrag/page.tsx
Normal file
256
website/app/foerderantrag/page.tsx
Normal file
@@ -0,0 +1,256 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import Header from '@/components/Header'
|
||||
import Footer from '@/components/Footer'
|
||||
import { useLanguage } from '@/lib/LanguageContext'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export default function FoerderantragPage() {
|
||||
const { t, isRTL } = useLanguage()
|
||||
const [applications, setApplications] = useState<FundingApplication[]>([])
|
||||
const [statistics, setStatistics] = useState<Statistics | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setApplications([])
|
||||
setStatistics({
|
||||
total_applications: 0,
|
||||
draft_count: 0,
|
||||
submitted_count: 0,
|
||||
approved_count: 0,
|
||||
total_requested: 0,
|
||||
total_approved: 0,
|
||||
})
|
||||
setLoading(false)
|
||||
}, [])
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
}).format(amount)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={`min-h-screen bg-slate-50 pt-20 ${isRTL ? 'rtl' : ''}`}>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="space-y-8">
|
||||
{/* 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">{t('fa_title')}</h1>
|
||||
<p className="mt-2 text-blue-100 max-w-2xl">
|
||||
{t('fa_subtitle')}
|
||||
</p>
|
||||
<div className="mt-6 flex gap-4">
|
||||
<Link
|
||||
href="/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 ${isRTL ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
<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>
|
||||
{t('fa_new_application')}
|
||||
</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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<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">{t('fa_statistics')}</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 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<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">{t('fa_program_dp2')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Start Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Link
|
||||
href="/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">
|
||||
{t('fa_preset_basic_name')}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
{t('fa_preset_basic_desc')}
|
||||
</p>
|
||||
<div className="mt-4 text-sm font-medium text-blue-600">
|
||||
{t('fa_preset_basic_budget')}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/foerderantrag/new?preset=breakpilot_cluster"
|
||||
className="group bg-white rounded-xl border-2 border-slate-200 p-6 hover:border-purple-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">
|
||||
{t('fa_preset_cluster_name')}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
{t('fa_preset_cluster_desc')}
|
||||
</p>
|
||||
<div className="mt-4 text-sm font-medium text-purple-600">
|
||||
{t('fa_preset_cluster_budget')}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/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">
|
||||
{t('fa_preset_custom_name')}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
{t('fa_preset_custom_desc')}
|
||||
</p>
|
||||
<div className="mt-4 text-sm font-medium text-slate-600">
|
||||
{t('fa_preset_custom_budget')}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Applications List */}
|
||||
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
||||
{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>
|
||||
</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">{t('fa_no_applications')}</h3>
|
||||
<p className="mt-2 text-slate-500">{t('fa_start_first')}</p>
|
||||
<Link
|
||||
href="/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>
|
||||
{t('fa_new_application')}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-slate-100">
|
||||
{applications.map((app) => (
|
||||
<Link
|
||||
key={app.id}
|
||||
href={`/foerderantrag/${app.id}`}
|
||||
className={`flex items-center gap-4 p-4 hover:bg-slate-50 transition-colors ${isRTL ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-slate-900 truncate">{app.title}</h3>
|
||||
<div className="text-sm text-slate-500 mt-1">{app.application_number}</div>
|
||||
</div>
|
||||
<div className={isRTL ? 'text-left' : 'text-right'}>
|
||||
<div className="font-medium text-slate-900">{formatCurrency(app.requested_amount)}</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Info Box */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6">
|
||||
<div className={`flex gap-4 ${isRTL ? 'flex-row-reverse' : ''}`}>
|
||||
<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">{t('fa_info_title')}</h3>
|
||||
<p className="mt-1 text-sm text-blue-700">{t('fa_info_text')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user