Files
breakpilot-compliance/admin-compliance/app/sdk/advisory-board/page.tsx
Sharang Parnerkar 554320770a refactor(admin): split advisory-board page.tsx into colocated components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:02:35 +02:00

250 lines
8.9 KiB
TypeScript

'use client'
import React, { useState, useEffect, Suspense } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import Link from 'next/link'
import { useSDK } from '@/lib/sdk'
import type { AdvisoryForm } from './_types'
import { industryToDomain } from './_data'
import { StepIndicator } from './_components/StepIndicator'
import { Step1Basics } from './_components/Step1Basics'
import { Step2DataCategories } from './_components/Step2DataCategories'
import { Step3Purposes } from './_components/Step3Purposes'
import { Step4Automation } from './_components/Step4Automation'
import { Step5Hosting } from './_components/Step5Hosting'
import { Step6Transfer } from './_components/Step6Transfer'
import { Step7Retention } from './_components/Step7Retention'
import { Step8Contracts } from './_components/Step8Contracts'
import { NavigationButtons } from './_components/NavigationButtons'
import { ResultView } from './_components/ResultView'
// =============================================================================
// MAIN COMPONENT
// =============================================================================
function AdvisoryBoardPageInner() {
const router = useRouter()
const searchParams = useSearchParams()
const { state: sdkState } = useSDK()
const editId = searchParams.get('edit')
const isEditMode = !!editId
const [currentStep, setCurrentStep] = useState(1)
const [isSubmitting, setIsSubmitting] = useState(false)
const [editLoading, setEditLoading] = useState(false)
const [result, setResult] = useState<unknown>(null)
const [error, setError] = useState<string | null>(null)
// Derive domain from company profile industry
const profileIndustry = sdkState.companyProfile?.industry
const derivedDomain = industryToDomain(
Array.isArray(profileIndustry) ? profileIndustry : profileIndustry ? [profileIndustry] : []
)
// Form state — tile-based multi-select via arrays
const [form, setForm] = useState<AdvisoryForm>({
title: '',
use_case_text: '',
domain: 'general',
category: '',
data_categories: [],
custom_data_types: [],
purposes: [],
automation: '',
hosting_provider: '',
hosting_region: '',
model_usage: [],
transfer_targets: [],
transfer_countries: [],
transfer_mechanism: '',
retention_period: '',
retention_purpose: '',
contracts: [],
subprocessors: '',
})
const updateForm = (updates: Partial<AdvisoryForm>) => {
setForm(prev => ({ ...prev, ...updates }))
}
// Auto-set domain from profile
useEffect(() => {
if (!isEditMode && derivedDomain !== 'general') {
updateForm({ domain: derivedDomain })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [derivedDomain])
// Pre-fill form when in edit mode
useEffect(() => {
if (!editId) return
setEditLoading(true)
fetch(`/api/sdk/v1/ucca/assessments/${editId}`)
.then(r => r.json())
.then(data => {
const intake = data.intake || {}
setForm({
title: data.title || '',
use_case_text: intake.use_case_text || '',
domain: data.domain || 'general',
category: data.category || intake.category || '',
data_categories: intake.data_categories || [],
custom_data_types: intake.data_types?.custom_data_types || intake.custom_data_types || [],
purposes: intake.purposes || [],
automation: intake.automation || '',
hosting_provider: intake.hosting?.provider || '',
hosting_region: intake.hosting?.region || '',
model_usage: intake.model_usage_list || [],
transfer_targets: intake.transfer_targets || [],
transfer_countries: intake.international_transfer?.countries || intake.transfer_countries || [],
transfer_mechanism: intake.transfer_mechanism || intake.international_transfer?.mechanism || '',
retention_period: intake.retention_period || '',
retention_purpose: intake.retention?.purpose || intake.retention_purpose || '',
contracts: intake.contracts_list || [],
subprocessors: intake.contracts?.subprocessors || intake.subprocessors || '',
})
})
.catch(() => {})
.finally(() => setEditLoading(false))
}, [editId])
const handleSubmit = async () => {
setIsSubmitting(true)
setError(null)
try {
const intake = {
title: form.title,
use_case_text: form.use_case_text,
domain: form.domain,
category: form.category,
data_categories: form.data_categories,
custom_data_types: form.custom_data_types.filter(s => s.trim()),
purposes: form.purposes,
automation: form.automation,
hosting: {
provider: form.hosting_provider,
region: form.hosting_region,
},
model_usage_list: form.model_usage,
transfer_targets: form.transfer_targets,
transfer_countries: form.transfer_countries,
transfer_mechanism: form.transfer_mechanism,
retention_period: form.retention_period,
retention_purpose: form.retention_purpose,
contracts_list: form.contracts,
subprocessors: form.subprocessors,
store_raw_text: true,
}
const url = isEditMode
? `/api/sdk/v1/ucca/assessments/${editId}`
: '/api/sdk/v1/ucca/assess'
const method = isEditMode ? 'PUT' : 'POST'
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(intake),
})
if (!response.ok) {
const errData = await response.json().catch(() => null)
throw new Error(errData?.error || `HTTP ${response.status}`)
}
if (isEditMode) {
router.push(`/sdk/use-cases/${editId}`)
return
}
const data = await response.json()
setResult(data)
} catch (err) {
setError(err instanceof Error ? err.message : 'Fehler bei der Bewertung')
} finally {
setIsSubmitting(false)
}
}
// If we have a result, show it
if (result) {
return (
<ResultView
result={result}
onGoToAssessment={(id) => router.push(`/sdk/use-cases/${id}`)}
onGoToOverview={() => router.push('/sdk/use-cases')}
/>
)
}
return (
<div className="max-w-3xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">
{isEditMode ? 'Assessment bearbeiten' : 'Use Case Workshop'}
</h1>
<p className="mt-1 text-gray-500">
{isEditMode
? 'Angaben anpassen und Assessment neu bewerten'
: 'Erfassen Sie Ihren KI-Anwendungsfall Schritt fuer Schritt'}
</p>
</div>
<Link
href="/sdk/advisory-board/documentation"
className="inline-flex items-center gap-2 px-4 py-2 text-sm text-purple-600 hover:text-purple-700 hover:bg-purple-50 border border-purple-300 rounded-lg transition-colors"
>
UCCA-Dokumentation
</Link>
</div>
{/* Edit loading indicator */}
{editLoading && (
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4 text-purple-700 text-sm">
Lade Assessment-Daten...
</div>
)}
<StepIndicator currentStep={currentStep} onStepClick={setCurrentStep} />
{/* Error */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">{error}</div>
)}
{/* Step Content */}
<div className="bg-white rounded-xl border border-gray-200 p-6">
{currentStep === 1 && (
<Step1Basics form={form} updateForm={updateForm} profileIndustry={profileIndustry} />
)}
{currentStep === 2 && <Step2DataCategories form={form} updateForm={updateForm} />}
{currentStep === 3 && <Step3Purposes form={form} updateForm={updateForm} />}
{currentStep === 4 && <Step4Automation form={form} updateForm={updateForm} />}
{currentStep === 5 && <Step5Hosting form={form} updateForm={updateForm} />}
{currentStep === 6 && <Step6Transfer form={form} updateForm={updateForm} />}
{currentStep === 7 && <Step7Retention form={form} updateForm={updateForm} />}
{currentStep === 8 && <Step8Contracts form={form} updateForm={updateForm} />}
</div>
<NavigationButtons
currentStep={currentStep}
isSubmitting={isSubmitting}
isEditMode={isEditMode}
titleEmpty={!form.title}
onBack={() => currentStep > 1 ? setCurrentStep(currentStep - 1) : router.push('/sdk/use-cases')}
onNext={() => setCurrentStep(currentStep + 1)}
onSubmit={handleSubmit}
/>
</div>
)
}
export default function AdvisoryBoardPage() {
return (
<Suspense fallback={<div className="flex items-center justify-center h-64 text-gray-500">Lade...</div>}>
<AdvisoryBoardPageInner />
</Suspense>
)
}