feat(cra): Konformitätspfad-Kacheln — "Mit BreakPilot"-Rolle + aufklappbarer Info-Text
Reframe: BreakPilot ist Audit-VORBEREITER, nicht Prüfstelle. Jede Kachel zeigt jetzt eine "Mit BreakPilot"-Zeile (Selbstbewertung = end-to-end; EUCC/benannte Stelle = audit-fähig machen, formale Prüfung durch ITSEF/benannte Stelle) plus aufklappbaren Erklär-Text (was EUCC ist, wie es läuft, was der Nutzer tut). Normtext (ISO/IEC 15408/18045) nur referenziert, nicht reproduziert. Kachel von <button> auf <div> + separater Wählen-Button + Info-Toggle umgebaut. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ interface PathOption {
|
|||||||
details: string[]
|
details: string[]
|
||||||
available?: boolean // false = rechtlich vorgesehen, aber noch nicht nutzbar
|
available?: boolean // false = rechtlich vorgesehen, aber noch nicht nutzbar
|
||||||
note?: string
|
note?: string
|
||||||
|
bpRole: string // was BreakPilot bei diesem Weg leistet (immer sichtbar)
|
||||||
|
info?: string[] // aufklappbarer Erklaer-Text
|
||||||
}
|
}
|
||||||
|
|
||||||
const PATHS: PathOption[] = [
|
const PATHS: PathOption[] = [
|
||||||
@@ -30,12 +32,17 @@ const PATHS: PathOption[] = [
|
|||||||
modul: 'Modul A',
|
modul: 'Modul A',
|
||||||
title: 'Selbstbewertung (interne Kontrolle)',
|
title: 'Selbstbewertung (interne Kontrolle)',
|
||||||
short: 'Hersteller erklaert die Konformitaet selbst',
|
short: 'Hersteller erklaert die Konformitaet selbst',
|
||||||
|
bpRole: 'Komplett mit BreakPilot durchführbar — keine externe Stelle nötig.',
|
||||||
details: [
|
details: [
|
||||||
'Hersteller fuehrt die Konformitaetsbewertung selbst durch',
|
'Hersteller fuehrt die Konformitaetsbewertung selbst durch',
|
||||||
'Geringster externer Aufwand, schnelle Umsetzung',
|
'Geringster externer Aufwand, schnelle Umsetzung',
|
||||||
'Nur fuer Standard- und (mit Norm) wichtige Klasse-I-Produkte',
|
'Nur fuer Standard- und (mit Norm) wichtige Klasse-I-Produkte',
|
||||||
'Technische Dokumentation + Konformitaetserklaerung bleiben Pflicht',
|
'Technische Dokumentation + Konformitaetserklaerung bleiben Pflicht',
|
||||||
],
|
],
|
||||||
|
info: [
|
||||||
|
'Bei Modul A erklärst du die CRA-Konformität selbst, ohne externe Prüfstelle.',
|
||||||
|
'BreakPilot erzeugt dafür Risikobeurteilung, technische Dokumentation und die Konformitätserklärung direkt im Tool — du gehst end-to-end mit uns durch.',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'harmonized_standard',
|
id: 'harmonized_standard',
|
||||||
@@ -44,6 +51,7 @@ const PATHS: PathOption[] = [
|
|||||||
short: 'Vermutungswirkung durch eine harmonisierte EU-Norm',
|
short: 'Vermutungswirkung durch eine harmonisierte EU-Norm',
|
||||||
available: false,
|
available: false,
|
||||||
note: 'Fuer den CRA noch keine harmonisierte Norm veroeffentlicht — Entwuerfe erwartet ~Ende 2026, Listung im Amtsblatt voraussichtlich 2027.',
|
note: 'Fuer den CRA noch keine harmonisierte Norm veroeffentlicht — Entwuerfe erwartet ~Ende 2026, Listung im Amtsblatt voraussichtlich 2027.',
|
||||||
|
bpRole: 'Sobald die Normen gelistet sind, prüfen wir die Vermutungswirkung automatisch mit.',
|
||||||
details: [
|
details: [
|
||||||
'Kein eigenes Modul, sondern Grundlage der Konformitaetsvermutung',
|
'Kein eigenes Modul, sondern Grundlage der Konformitaetsvermutung',
|
||||||
'Wer danach baut, gilt als CRA-konform und darf (Standard/Klasse I) selbst bewerten',
|
'Wer danach baut, gilt als CRA-konform und darf (Standard/Klasse I) selbst bewerten',
|
||||||
@@ -55,24 +63,36 @@ const PATHS: PathOption[] = [
|
|||||||
modul: 'EUCC',
|
modul: 'EUCC',
|
||||||
title: 'EU-Cybersicherheitszertifikat',
|
title: 'EU-Cybersicherheitszertifikat',
|
||||||
short: 'Zertifizierung nach EUCC-Schema (Common-Criteria-basiert)',
|
short: 'Zertifizierung nach EUCC-Schema (Common-Criteria-basiert)',
|
||||||
|
bpRole: 'Wir machen dich audit-fähig — die formale Prüfung macht ein akkreditiertes Labor (ITSEF), nicht BreakPilot.',
|
||||||
details: [
|
details: [
|
||||||
'EUCC-Zertifikat (in der Regel Stufe „substanziell")',
|
'EUCC-Zertifikat (in der Regel Stufe „substanziell")',
|
||||||
'Eigener Weg unter dem Cybersecurity Act, keine benannte Stelle noetig',
|
'Eigener Weg unter dem Cybersecurity Act',
|
||||||
'Hohe Anerkennung in EU + Drittstaaten, ITSEF-Pruefung',
|
'Hohe Anerkennung in EU + Drittstaaten',
|
||||||
'Regulaerer Weg fuer wichtige Klasse II und kritische Produkte',
|
'Regulaerer Weg fuer wichtige Klasse II und kritische Produkte',
|
||||||
],
|
],
|
||||||
|
info: [
|
||||||
|
'EUCC ist das EU-Cybersicherheits-Zertifizierungsschema (VO 2024/482) unter dem Cybersecurity Act, methodisch auf Common Criteria (ISO/IEC 15408) aufgebaut. Zertifiziert wird ein konkretes Produkt/eine Komponente — nicht die ganze Anlage.',
|
||||||
|
'Stufen: „substanziell" oder „hoch", Gültigkeit i. d. R. bis 5 Jahre. Die formale Prüfung führt ein akkreditiertes Labor (ITSEF) durch; das Zertifikat stellt eine Zertifizierungsstelle aus (Stufe „hoch": mit dem BSI).',
|
||||||
|
'Mit BreakPilot: Prüfobjekt abgrenzen, Nachweise (sichere Entwicklung, Konfiguration, Schwachstellen-Management, Anwender-Doku) strukturieren und Lücken VOR dem Labor schließen. Du gehst vorbereitet ins ITSEF-Audit — das spart Zeit und teure Nachprüf-Schleifen.',
|
||||||
|
'Oft sinnvoller: eine bereits EUCC-zertifizierte Komponente zukaufen und die Vermutungswirkung erben.',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'notified_body',
|
id: 'notified_body',
|
||||||
modul: 'Modul B+C',
|
modul: 'Modul B+C',
|
||||||
title: 'Benannte Stelle (Baumusterpruefung)',
|
title: 'Benannte Stelle (Baumusterpruefung)',
|
||||||
short: 'Dritte Stelle prueft (EU-Baumusterpruefung + Produktionskontrolle)',
|
short: 'Dritte Stelle prueft (EU-Baumusterpruefung + Produktionskontrolle)',
|
||||||
|
bpRole: 'Wir bereiten Doku + Nachweise auf — geprüft wird von der benannten Stelle.',
|
||||||
details: [
|
details: [
|
||||||
'EU-Baumusterpruefung (Modul B) durch akkreditierte benannte Stelle',
|
'EU-Baumusterpruefung (Modul B) durch akkreditierte benannte Stelle',
|
||||||
'gefolgt von Produktionskontrolle (Modul C); alternativ volle QS (Modul H)',
|
'gefolgt von Produktionskontrolle (Modul C); alternativ volle QS (Modul H)',
|
||||||
'Pflichtweg fuer wichtige Klasse II und kritische Produkte (Annex IV)',
|
'Pflichtweg fuer wichtige Klasse II und kritische Produkte (Annex IV)',
|
||||||
'Hoechste Auditierbarkeit, hoechste Laufzeit + Kosten',
|
'Hoechste Auditierbarkeit, hoechste Laufzeit + Kosten',
|
||||||
],
|
],
|
||||||
|
info: [
|
||||||
|
'Eine akkreditierte benannte Stelle prüft das Baumuster (Modul B) und die Produktionskontrolle (Modul C) — alternativ ein vollständiges Qualitätssicherungssystem (Modul H).',
|
||||||
|
'Mit BreakPilot: wir erstellen die technische Dokumentation und das Nachweis-Paket und machen einen Gap-Check, damit das Audit glatt läuft. Die Prüfung selbst macht die benannte Stelle.',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -100,6 +120,7 @@ export default function PathSelectPage({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [project, setProject] = useState<CRAProject | null>(null)
|
const [project, setProject] = useState<CRAProject | null>(null)
|
||||||
const [selected, setSelected] = useState<PathId | null>(null)
|
const [selected, setSelected] = useState<PathId | null>(null)
|
||||||
|
const [infoOpen, setInfoOpen] = useState<Record<string, boolean>>({})
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
@@ -193,14 +214,12 @@ export default function PathSelectPage({
|
|||||||
const selectable = allowed && available
|
const selectable = allowed && available
|
||||||
const isSelected = selected === path.id
|
const isSelected = selected === path.id
|
||||||
return (
|
return (
|
||||||
<button
|
<div
|
||||||
key={path.id}
|
key={path.id}
|
||||||
onClick={() => selectable && setSelected(path.id)}
|
|
||||||
disabled={!selectable}
|
|
||||||
className={`text-left p-5 rounded-xl border-2 transition-all ${
|
className={`text-left p-5 rounded-xl border-2 transition-all ${
|
||||||
isSelected ? 'border-red-500 bg-red-50' :
|
isSelected ? 'border-red-500 bg-red-50' :
|
||||||
selectable ? 'border-gray-200 bg-white hover:border-red-300 hover:shadow-md' :
|
selectable ? 'border-gray-200 bg-white' :
|
||||||
'border-gray-200 bg-gray-50 opacity-60 cursor-not-allowed'
|
'border-gray-200 bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-2 gap-2">
|
<div className="flex items-start justify-between mb-2 gap-2">
|
||||||
@@ -220,7 +239,10 @@ export default function PathSelectPage({
|
|||||||
{path.note && (
|
{path.note && (
|
||||||
<p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1.5 mb-2">{path.note}</p>
|
<p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1.5 mb-2">{path.note}</p>
|
||||||
)}
|
)}
|
||||||
<ul className="text-xs text-gray-600 space-y-1">
|
<p className="text-sm text-purple-800 bg-purple-50 border border-purple-200 rounded px-2 py-1.5 mb-2">
|
||||||
|
<span className="font-semibold">Mit BreakPilot:</span> {path.bpRole}
|
||||||
|
</p>
|
||||||
|
<ul className="text-sm text-gray-600 space-y-1">
|
||||||
{path.details.map((d, i) => (
|
{path.details.map((d, i) => (
|
||||||
<li key={i} className="flex items-start gap-1.5">
|
<li key={i} className="flex items-start gap-1.5">
|
||||||
<span className="text-gray-400 mt-0.5">•</span>
|
<span className="text-gray-400 mt-0.5">•</span>
|
||||||
@@ -228,7 +250,40 @@ export default function PathSelectPage({
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
{path.info && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setInfoOpen(o => ({ ...o, [path.id]: !o[path.id] }))}
|
||||||
|
className="mt-3 text-sm text-purple-600 hover:underline"
|
||||||
|
>
|
||||||
|
{infoOpen[path.id] ? 'Weniger anzeigen ▲' : 'Mehr erfahren ▼'}
|
||||||
</button>
|
</button>
|
||||||
|
{infoOpen[path.id] && (
|
||||||
|
<div className="mt-2 space-y-2 text-sm text-gray-600 border-t border-gray-100 pt-3">
|
||||||
|
{path.info.map((p, i) => (<p key={i}>{p}</p>))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="mt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => selectable && setSelected(path.id)}
|
||||||
|
disabled={!selectable}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||||
|
isSelected ? 'bg-red-600 text-white' :
|
||||||
|
selectable ? 'border border-red-300 text-red-700 hover:bg-red-50' :
|
||||||
|
'border border-gray-200 text-gray-400 cursor-not-allowed'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isSelected ? 'Ausgewählt ✓'
|
||||||
|
: selectable ? 'Diesen Weg wählen'
|
||||||
|
: !available ? 'Noch nicht verfügbar'
|
||||||
|
: 'Nicht zulässig'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user