refactor(admin): split company-profile page.tsx (3017 LOC) into colocated components
Extract the monolithic company-profile wizard into _components/ and _hooks/ following Next.js 15 conventions from AGENTS.typescript.md: - _components/constants.ts: wizard steps, legal forms, industries, certifications - _components/types.ts: local interfaces (ProcessingActivity, AISystem, etc.) - _components/activity-data.ts: DSGVO data categories, department/activity templates - _components/ai-system-data.ts: AI system template catalog - _components/StepBasicInfo.tsx: step 1 (company name, legal form, industry) - _components/StepBusinessModel.tsx: step 2 (B2B/B2C, offerings) - _components/StepCompanySize.tsx: step 3 (size, revenue) - _components/StepLocations.tsx: step 4 (headquarters, target markets) - _components/StepDataProtection.tsx: step 5 (DSGVO roles, DPO) - _components/StepProcessing.tsx: processing activities with category checkboxes - _components/StepAISystems.tsx: AI system inventory - _components/StepLegalFramework.tsx: certifications and contacts - _components/StepMachineBuilder.tsx: machine builder profile (step 7) - _components/ProfileSummary.tsx: completion summary view - _hooks/useCompanyProfileForm.ts: form state, auto-save, navigation logic - page.tsx: thin orchestrator (160 LOC), imports and composes sections All 16 files are under 500 LOC (largest: StepProcessing at 343). Build verified: npx next build passes cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
'use client'
|
||||
|
||||
import { CompanyProfile, LegalForm } from '@/lib/sdk/types'
|
||||
import { INDUSTRIES, LEGAL_FORM_LABELS } from './constants'
|
||||
|
||||
export function StepBasicInfo({
|
||||
data,
|
||||
onChange,
|
||||
}: {
|
||||
data: Partial<CompanyProfile>
|
||||
onChange: (updates: Partial<CompanyProfile>) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Firmenname <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.companyName || ''}
|
||||
onChange={e => onChange({ companyName: e.target.value })}
|
||||
placeholder="Ihre Firma (ohne Rechtsform)"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Rechtsform <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={data.legalForm || ''}
|
||||
onChange={e => onChange({ legalForm: e.target.value as LegalForm })}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Bitte wählen...</option>
|
||||
{Object.entries(LEGAL_FORM_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Branche(n)</label>
|
||||
<p className="text-sm text-gray-500 mb-3">Mehrfachauswahl moeglich</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
{INDUSTRIES.map(ind => {
|
||||
const selected = (data.industry || []).includes(ind)
|
||||
return (
|
||||
<button
|
||||
key={ind}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const current = data.industry || []
|
||||
const updated = selected
|
||||
? current.filter(i => i !== ind)
|
||||
: [...current, ind]
|
||||
onChange({ industry: updated })
|
||||
}}
|
||||
className={`p-3 rounded-lg border-2 text-sm text-left transition-all ${
|
||||
selected
|
||||
? 'border-purple-500 bg-purple-50 text-purple-700 font-medium'
|
||||
: 'border-gray-200 hover:border-purple-300 text-gray-700'
|
||||
}`}
|
||||
>
|
||||
{ind}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{(data.industry || []).includes('Sonstige') && (
|
||||
<div className="mt-3">
|
||||
<input
|
||||
type="text"
|
||||
value={data.industryOther || ''}
|
||||
onChange={e => onChange({ industryOther: e.target.value })}
|
||||
placeholder="Ihre Branche eingeben..."
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Gründungsjahr</label>
|
||||
<input
|
||||
type="number"
|
||||
value={data.foundedYear || ''}
|
||||
onChange={e => {
|
||||
const val = parseInt(e.target.value)
|
||||
onChange({ foundedYear: isNaN(val) ? null : val })
|
||||
}}
|
||||
onFocus={e => {
|
||||
if (!data.foundedYear) onChange({ foundedYear: 2000 })
|
||||
}}
|
||||
placeholder="2020"
|
||||
min="1900"
|
||||
max={new Date().getFullYear()}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user