Files
breakpilot-compliance/admin-compliance/app/sdk/cra-meldewesen/page.tsx
T
Benjamin Bönisch 8f21650d74
CI / detect-changes (push) Successful in 16s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 15s
CI / validate-canonical-controls (push) Successful in 13s
CI / loc-budget (push) Successful in 25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m9s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 31s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
feat(sdk): Kunden-Dokumente + CRA-Meldewesen, Screening aus Frontend genommen
- /sdk/dokumente: Kundensicht nur auf veroeffentlichte Rechtsdokumente
  (Ansehen + Download); Proxy mit Allow-List nur /public — Templates/Drafts/
  Generator bleiben unerreichbar.
- /sdk/cra-meldewesen: CRA Art. 14 Meldewesen (24h/72h/14d-Kaskade) mit
  Fristen-Tracking + ENISA-SRP-Export-Entwurf (kein Live-API). Backend:
  cra_meldewesen (pure, getestet) + cra_incident_store (schema-neutral ueber
  compliance_cra_documents) + /api/v1/cra/incidents (additiv, contract-safe).
- Screening (Self-Scan) aus dem Frontend genommen: Flow-Stepper-Eintrag
  ausgeblendet (visibleWhen), Dashboard-Kachel + Import-Button entfernt.
  Repo-Scanning laeuft extern im Compliance-Scanner; Backend-Router bleibt
  vorerst gemountet (Contract-Stabilitaet).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 21:21:28 +02:00

86 lines
3.9 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useMeldewesen } from './_hooks/useMeldewesen'
import { IncidentCard } from './_components/IncidentCard'
import { NewIncidentForm } from './_components/NewIncidentForm'
// CRA Article 14 Meldewesen: the 24h/72h/14d incident-reporting cascade to ENISA.
// Customer-facing; deadlines + report drafts. No live ENISA API (manual export).
export default function MeldewesenPage() {
const m = useMeldewesen()
const [showForm, setShowForm] = useState(false)
return (
<div className="space-y-6">
<header className="flex flex-wrap items-start justify-between gap-3">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">CRA-Meldewesen</h1>
<p className="text-sm text-gray-600 dark:text-gray-300 mt-1 max-w-2xl">
Meldepflichten nach CRA Artikel 14: Frühwarnung (24 h), Meldung (72 h) und Abschlussbericht
(14 Tage) an die ENISA Single Reporting Platform. Wir behalten die Fristen im Blick und
erstellen die Berichtsentwürfe die Übermittlung bestätigen Sie selbst.
</p>
</div>
{m.meta?.reporting_active_from && (
<span className="text-xs rounded-full bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 px-3 py-1">
Meldepflicht aktiv ab {new Date(m.meta.reporting_active_from).toLocaleDateString('de-DE')}
</span>
)}
</header>
<div className="flex flex-wrap items-center gap-3">
<label className="text-sm text-gray-600 dark:text-gray-300">Projekt:</label>
<select
value={m.projectId}
onChange={(e) => m.setProjectId(e.target.value)}
className="text-sm rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 p-2 min-w-[16rem]"
>
{m.projects.length === 0 && <option value=""> kein CRA-Projekt </option>}
{m.projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
</select>
{m.projectId && !showForm && (
<button onClick={() => setShowForm(true)}
className="ml-auto rounded bg-indigo-600 hover:bg-indigo-700 text-white text-sm px-4 py-2">
+ Neue Meldung
</button>
)}
</div>
{showForm && m.projectId && (
<NewIncidentForm meta={m.meta} onCreate={m.createIncident} onCancel={() => setShowForm(false)} />
)}
{!m.projectId && (
<div className="rounded-xl border border-dashed border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-8 text-center text-sm text-gray-600 dark:text-gray-300">
Legen Sie zuerst ein CRA-Projekt an, um Vorfälle zu erfassen.
</div>
)}
{m.loading && <div className="text-sm text-gray-500">Lade Meldungen </div>}
{m.error && !m.loading && (
<div className="rounded-xl border border-amber-300 bg-amber-50 dark:bg-amber-900/20 text-amber-900 dark:text-amber-200 p-4 text-sm">
Meldungen konnten nicht geladen werden ({m.error}).{' '}
<button onClick={m.reload} className="underline font-medium">Erneut versuchen</button>
</div>
)}
{m.projectId && !m.loading && !m.error && m.incidents.length === 0 && (
<div className="rounded-xl border border-dashed border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-8 text-center">
<p className="text-sm text-gray-700 dark:text-gray-200 font-medium">Keine offenen Meldungen</p>
<p className="text-xs text-gray-500 mt-1">Im Ernstfall erfassen Sie hier den Vorfall die Fristen laufen dann automatisch mit.</p>
</div>
)}
{m.incidents.length > 0 && (
<div className="grid gap-3">
{m.incidents.map((inc) => (
<IncidentCard key={inc.id} inc={inc} onSubmit={m.submitStage} />
))}
</div>
)}
</div>
)
}