Files
breakpilot-compliance/admin-compliance/app/sdk/company-profile/_components/StepLegalFramework.tsx
Sharang Parnerkar f7b77fd504 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>
2026-04-11 18:50:30 +02:00

144 lines
8.4 KiB
TypeScript

'use client'
import { CompanyProfile } from '@/lib/sdk/types'
import { CertificationEntry } from './types'
import { CERTIFICATIONS } from './constants'
export function StepLegalFramework({
data,
onChange,
}: {
data: Partial<CompanyProfile>
onChange: (updates: Record<string, unknown>) => void
}) {
const contacts = (data as any).technicalContacts || []
const existingCerts: CertificationEntry[] = (data as any).existingCertifications || []
const targetCerts: string[] = (data as any).targetCertifications || []
const targetCertOther: string = (data as any).targetCertificationOther || ''
const toggleExistingCert = (certId: string) => {
const exists = existingCerts.find((c: CertificationEntry) => c.certId === certId)
if (exists) {
onChange({ existingCertifications: existingCerts.filter((c: CertificationEntry) => c.certId !== certId) })
} else {
onChange({ existingCertifications: [...existingCerts, { certId }] })
}
}
const updateExistingCert = (certId: string, updates: Partial<CertificationEntry>) => {
onChange({ existingCertifications: existingCerts.map((c: CertificationEntry) => c.certId === certId ? { ...c, ...updates } : c) })
}
const toggleTargetCert = (certId: string) => {
if (targetCerts.includes(certId)) {
onChange({ targetCertifications: targetCerts.filter((c: string) => c !== certId) })
} else {
onChange({ targetCertifications: [...targetCerts, certId] })
}
}
const addContact = () => { onChange({ technicalContacts: [...contacts, { name: '', role: '', email: '' }] }) }
const removeContact = (i: number) => { onChange({ technicalContacts: contacts.filter((_: { name: string; role: string; email: string }, idx: number) => idx !== i) }) }
const updateContact = (i: number, updates: Partial<{ name: string; role: string; email: string }>) => {
const updated = [...contacts]
updated[i] = { ...updated[i], ...updates }
onChange({ technicalContacts: updated })
}
return (
<div className="space-y-8">
{/* Bestehende Zertifizierungen */}
<div>
<h3 className="text-sm font-medium text-gray-700 mb-1">Bestehende Zertifizierungen</h3>
<p className="text-sm text-gray-500 mb-3">Ueber welche Zertifizierungen verfuegt Ihr Unternehmen aktuell? Mehrfachauswahl moeglich.</p>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{CERTIFICATIONS.map(cert => {
const selected = existingCerts.some((c: CertificationEntry) => c.certId === cert.id)
return (
<button key={cert.id} type="button" onClick={() => toggleExistingCert(cert.id)}
className={`p-3 rounded-lg border-2 text-left transition-all ${selected ? 'border-purple-500 bg-purple-50 text-purple-700' : 'border-gray-200 hover:border-purple-300 text-gray-700'}`}>
<div className="font-medium text-sm">{cert.label}</div>
<div className="text-xs text-gray-500 mt-0.5">{cert.desc}</div>
</button>
)
})}
</div>
{existingCerts.length > 0 && (
<div className="mt-4 space-y-3">
{existingCerts.map((entry: CertificationEntry) => {
const cert = CERTIFICATIONS.find(c => c.id === entry.certId)
const label = cert?.label || entry.certId
return (
<div key={entry.certId} className="p-4 bg-purple-50 border border-purple-200 rounded-lg">
<div className="font-medium text-sm text-purple-800 mb-2">
{entry.certId === 'other' ? 'Sonstige Zertifizierung' : label}
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{entry.certId === 'other' && (
<input type="text" value={entry.customName || ''} onChange={e => updateExistingCert(entry.certId, { customName: e.target.value })} placeholder="Name der Zertifizierung" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
)}
<input type="text" value={entry.certifier || ''} onChange={e => updateExistingCert(entry.certId, { certifier: e.target.value })} placeholder="Zertifizierer (z.B. T\u00DCV, DEKRA)" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="date" value={entry.lastDate || ''} onChange={e => updateExistingCert(entry.certId, { lastDate: e.target.value })} title="Datum der letzten Zertifizierung" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
</div>
</div>
)
})}
</div>
)}
</div>
{/* Angestrebte Zertifizierungen */}
<div className="border-t border-gray-200 pt-6">
<h3 className="text-sm font-medium text-gray-700 mb-1">Streben Sie eine Zertifizierung an?</h3>
<p className="text-sm text-gray-500 mb-3">Welche Zertifizierungen planen Sie? Mehrfachauswahl moeglich.</p>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{CERTIFICATIONS.map(cert => {
const selected = targetCerts.includes(cert.id)
const alreadyHas = existingCerts.some((c: CertificationEntry) => c.certId === cert.id)
return (
<button key={cert.id} type="button" onClick={() => !alreadyHas && toggleTargetCert(cert.id)} disabled={alreadyHas}
className={`p-3 rounded-lg border-2 text-left transition-all ${alreadyHas ? 'border-gray-100 bg-gray-50 text-gray-400 cursor-not-allowed' : selected ? 'border-green-500 bg-green-50 text-green-700' : 'border-gray-200 hover:border-green-300 text-gray-700'}`}>
<div className="font-medium text-sm">{cert.label}</div>
{alreadyHas && <div className="text-xs mt-0.5">Bereits vorhanden</div>}
{!alreadyHas && <div className="text-xs text-gray-500 mt-0.5">{cert.desc}</div>}
</button>
)
})}
</div>
{targetCerts.includes('other') && (
<div className="mt-3">
<input type="text" value={targetCertOther} onChange={e => onChange({ targetCertificationOther: e.target.value })} placeholder="Name der angestrebten Zertifizierung" 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>
{/* Technical Contacts */}
<div className="border-t border-gray-200 pt-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-sm font-medium text-gray-700">Technische Ansprechpartner</h3>
<p className="text-xs text-gray-500">CISO, IT-Manager, DSB etc.</p>
</div>
<button type="button" onClick={addContact} className="px-3 py-1.5 text-sm bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200">
+ Kontakt
</button>
</div>
{contacts.length === 0 && (
<div className="text-center py-4 text-gray-400 border-2 border-dashed rounded-lg text-sm">Noch keine Kontakte</div>
)}
<div className="space-y-3">
{contacts.map((c: { name: string; role: string; email: string }, i: number) => (
<div key={i} className="flex gap-3 items-center">
<input type="text" value={c.name} onChange={e => updateContact(i, { name: e.target.value })} placeholder="Name" className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="text" value={c.role} onChange={e => updateContact(i, { role: e.target.value })} placeholder="Rolle (z.B. CISO)" className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="email" value={c.email} onChange={e => updateContact(i, { email: e.target.value })} placeholder="E-Mail" className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<button type="button" onClick={() => removeContact(i)} className="text-red-400 hover:text-red-600 text-sm">X</button>
</div>
))}
</div>
</div>
</div>
)
}