website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
248 lines
10 KiB
TypeScript
248 lines
10 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { useParams, useRouter } from 'next/navigation'
|
|
import Link from 'next/link'
|
|
import { useLanguage } from '@/lib/LanguageContext'
|
|
import { WizardStep, FormData, DEFAULT_STEPS } from './_components/types'
|
|
import { stepIcons } from './_components/StepIcons'
|
|
import { Step1, Step2, Step3, Step4, Step5, Step6, Step7, Step8 } from './_components/WizardSteps'
|
|
import { AssistantSidebar } from './_components/AssistantSidebar'
|
|
|
|
export default function FoerderantragWizardPage() {
|
|
const params = useParams()
|
|
const router = useRouter()
|
|
const { t, isRTL } = useLanguage()
|
|
const applicationId = params.applicationId as string
|
|
|
|
const [currentStep, setCurrentStep] = useState(1)
|
|
const [steps, setSteps] = useState<WizardStep[]>(DEFAULT_STEPS)
|
|
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 && (
|
|
<AssistantSidebar
|
|
isRTL={isRTL}
|
|
t={t}
|
|
assistantHistory={assistantHistory}
|
|
assistantMessage={assistantMessage}
|
|
setAssistantMessage={setAssistantMessage}
|
|
onAskAssistant={handleAskAssistant}
|
|
onClose={() => setShowAssistant(false)}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|