feat: enhance whistleblower HinSchG content, fix control-library filter layout
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 34s
CI/CD / test-python-backend-compliance (push) Successful in 35s
CI/CD / test-python-document-crawler (push) Successful in 26s
CI/CD / test-python-dsms-gateway (push) Successful in 21s
CI/CD / validate-canonical-controls (push) Successful in 12s
CI/CD / Deploy (push) Successful in 2s

- Whistleblower page: expand overview tab with comprehensive HinSchG legal info
  (Gesetzliche Grundlage, Fristen-Cards, Anwendungsbereich, Schutz des Hinweisgebers)
- StepHeader: enrich whistleblower tips with detailed HinSchG paragraphs and sanctions
- Wiki: add migration 054 with 5 new/updated HinSchG articles (Anwendungsbereich,
  Hinweisgeberschutz, Meldestellen, Verfahrensablauf, Datenschutz-Anforderungen)
- MKDocs: rewrite whistleblower docs with full legal basis, architecture, API, DB schema
- Control library: fix filter dropdown overflow by splitting into search + filter rows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-15 00:23:19 +01:00
parent 2ed1c08acf
commit f3e05c1bf7
5 changed files with 746 additions and 110 deletions

View File

@@ -363,83 +363,85 @@ export default function ControlLibraryPage() {
)}
{/* Filters */}
<div className="flex items-center gap-3">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="Controls durchsuchen..."
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
className="w-full pl-9 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
<div className="space-y-3">
<div className="flex items-center gap-3">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="Controls durchsuchen..."
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
className="w-full pl-9 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
</div>
<div className="flex items-center gap-1">
<div className="flex items-center gap-2 flex-wrap">
<Filter className="w-4 h-4 text-gray-400" />
<select
value={severityFilter}
onChange={e => setSeverityFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Schweregrad</option>
<option value="critical">Kritisch</option>
<option value="high">Hoch</option>
<option value="medium">Mittel</option>
<option value="low">Niedrig</option>
</select>
<select
value={domainFilter}
onChange={e => setDomainFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Domain</option>
{domains.map(d => (
<option key={d} value={d}>{d}</option>
))}
</select>
<select
value={stateFilter}
onChange={e => setStateFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Status</option>
<option value="draft">Draft</option>
<option value="approved">Approved</option>
<option value="needs_review">Review noetig</option>
<option value="too_close">Zu aehnlich</option>
<option value="duplicate">Duplikat</option>
</select>
<select
value={verificationFilter}
onChange={e => setVerificationFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Nachweis</option>
{Object.entries(VERIFICATION_METHODS).map(([k, v]) => (
<option key={k} value={k}>{v.label}</option>
))}
</select>
<select
value={categoryFilter}
onChange={e => setCategoryFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Kategorie</option>
{CATEGORY_OPTIONS.map(c => (
<option key={c.value} value={c.value}>{c.label}</option>
))}
</select>
<select
value={audienceFilter}
onChange={e => setAudienceFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Zielgruppe</option>
{Object.entries(TARGET_AUDIENCE_OPTIONS).map(([k, v]) => (
<option key={k} value={k}>{v.label}</option>
))}
</select>
</div>
<select
value={severityFilter}
onChange={e => setSeverityFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Schweregrade</option>
<option value="critical">Kritisch</option>
<option value="high">Hoch</option>
<option value="medium">Mittel</option>
<option value="low">Niedrig</option>
</select>
<select
value={domainFilter}
onChange={e => setDomainFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Domains</option>
{domains.map(d => (
<option key={d} value={d}>{d}</option>
))}
</select>
<select
value={stateFilter}
onChange={e => setStateFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Status</option>
<option value="draft">Draft</option>
<option value="approved">Approved</option>
<option value="needs_review">Review noetig</option>
<option value="too_close">Zu aehnlich</option>
<option value="duplicate">Duplikat</option>
</select>
<select
value={verificationFilter}
onChange={e => setVerificationFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Nachweismethoden</option>
{Object.entries(VERIFICATION_METHODS).map(([k, v]) => (
<option key={k} value={k}>{v.label}</option>
))}
</select>
<select
value={categoryFilter}
onChange={e => setCategoryFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Kategorien</option>
{CATEGORY_OPTIONS.map(c => (
<option key={c.value} value={c.value}>{c.label}</option>
))}
</select>
<select
value={audienceFilter}
onChange={e => setAudienceFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Zielgruppen</option>
{Object.entries(TARGET_AUDIENCE_OPTIONS).map(([k, v]) => (
<option key={k} value={k}>{v.label}</option>
))}
</select>
</div>
{/* Processing Stats */}

View File

@@ -1128,24 +1128,88 @@ export default function WhistleblowerPage() {
</div>
)}
{/* Info Box about HinSchG Deadlines (Overview Tab) */}
{/* Info Box about HinSchG (Overview Tab) */}
{activeTab === 'overview' && (
<div className="bg-blue-50 border border-blue-200 rounded-xl p-4">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<h4 className="font-medium text-blue-800">HinSchG-Fristen</h4>
<p className="text-sm text-blue-600 mt-1">
Nach dem Hinweisgeberschutzgesetz (HinSchG) gelten folgende Fristen:
Die Eingangsbestaetigung muss innerhalb von <strong>7 Tagen</strong> an den
Hinweisgeber versendet werden (ss 17 Abs. 1 S. 2).
Eine Rueckmeldung ueber ergriffene Massnahmen muss innerhalb von <strong>3 Monaten</strong> nach
Eingangsbestaetigung erfolgen (ss 17 Abs. 2).
Der Schutz des Hinweisgebers vor Repressalien ist zwingend sicherzustellen (ss 36).
<div className="space-y-4">
{/* Gesetzliche Grundlage */}
<div className="bg-blue-50 border border-blue-200 rounded-xl p-5">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<h4 className="font-medium text-blue-800">Gesetzliche Grundlage: Hinweisgeberschutzgesetz (HinSchG)</h4>
<p className="text-sm text-blue-600 mt-1">
Das HinSchG setzt die <strong>EU-Whistleblowing-Richtlinie (2019/1937)</strong> in deutsches Recht um
und ist seit dem <strong>2. Juli 2023</strong> in Kraft. Seit dem <strong>17. Dezember 2023</strong> gilt
die Pflicht zur Einrichtung einer internen Meldestelle auch fuer Unternehmen ab 50 Beschaeftigten (ss 12 HinSchG).
</p>
</div>
</div>
</div>
{/* Fristen & Pflichten */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center gap-2 mb-2">
<svg className="w-4 h-4 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h5 className="text-sm font-semibold text-gray-900">7-Tage-Frist</h5>
</div>
<p className="text-xs text-gray-600">
Eingangsbestaetigung an den Hinweisgeber innerhalb von 7 Tagen nach Meldungseingang (ss 17 Abs. 1 S. 2 HinSchG).
</p>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center gap-2 mb-2">
<svg className="w-4 h-4 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<h5 className="text-sm font-semibold text-gray-900">3-Monate-Frist</h5>
</div>
<p className="text-xs text-gray-600">
Rueckmeldung ueber ergriffene Folgemaßnahmen innerhalb von 3 Monaten nach Eingangsbestaetigung (ss 17 Abs. 2 HinSchG).
</p>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center gap-2 mb-2">
<svg className="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<h5 className="text-sm font-semibold text-gray-900">3 Jahre Aufbewahrung</h5>
</div>
<p className="text-xs text-gray-600">
Dokumentation der Meldungen und Folgemaßnahmen ist 3 Jahre nach Abschluss aufzubewahren (ss 11 Abs. 5 HinSchG).
</p>
</div>
</div>
{/* Sachlicher Anwendungsbereich & Schutz */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<h5 className="text-sm font-semibold text-amber-800 mb-2">Sachlicher Anwendungsbereich (ss 2 HinSchG)</h5>
<ul className="text-xs text-amber-700 space-y-1">
<li>Verstoesse gegen Strafvorschriften (StGB, Nebenstrafrecht)</li>
<li>Verstoesse gegen Datenschutzrecht (DSGVO, BDSG)</li>
<li>Geldwaesche und Terrorismusfinanzierung (GwG)</li>
<li>Produktsicherheit und Verbraucherschutz</li>
<li>Umweltschutz und Lebensmittelsicherheit</li>
<li>Arbeitsschutz und Arbeitnehmerrechte</li>
<li>Wettbewerbs- und Kartellrecht</li>
<li>Steuer- und Abgabenrecht (bei Unternehmen)</li>
</ul>
</div>
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
<h5 className="text-sm font-semibold text-green-800 mb-2">Schutz des Hinweisgebers (ss 3637 HinSchG)</h5>
<ul className="text-xs text-green-700 space-y-1">
<li><strong>Repressalienverbot:</strong> Jede Benachteiligung ist untersagt (ss 36)</li>
<li><strong>Beweislastumkehr:</strong> Arbeitgeber muss beweisen, dass Maßnahmen nicht mit Meldung zusammenhaengen</li>
<li><strong>Schadensersatz:</strong> Bei Verstoessen gegen Repressalienverbot (ss 37)</li>
<li><strong>Vertraulichkeit:</strong> Identitaet darf nur bei Zustimmung oder gesetzlicher Pflicht offengelegt werden (ss 8)</li>
<li><strong>Bussgelder:</strong> Bis zu 50.000 EUR bei Verstoessen gegen die Einrichtungspflicht (ss 40)</li>
</ul>
</div>
</div>
</div>
)}

View File

@@ -852,18 +852,28 @@ export const STEP_EXPLANATIONS = {
},
'whistleblower': {
title: 'Hinweisgebersystem',
description: 'Meldestelle gemaess Hinweisgeberschutzgesetz (HinSchG)',
explanation: 'Das Hinweisgebersystem bietet eine sichere, anonyme Meldestelle fuer Compliance-Verstoesse gemaess dem Hinweisgeberschutzgesetz (HinSchG). Unternehmen ab 50 Mitarbeitern sind zur Einrichtung verpflichtet.',
description: 'Interne Meldestelle gemaess Hinweisgeberschutzgesetz (HinSchG) — seit 17. Dezember 2023 Pflicht fuer alle Unternehmen ab 50 Beschaeftigten',
explanation: 'Das Hinweisgebersystem implementiert eine HinSchG-konforme interne Meldestelle fuer die sichere, auch anonyme Meldung von Rechtsverstoessen. Es setzt die EU-Whistleblowing-Richtlinie (2019/1937) in deutsches Recht um. Beschaeftigungsgeber mit mindestens 50 Beschaeftigten sind zur Einrichtung verpflichtet (§ 12 HinSchG). Das System unterstuetzt den gesamten Meldeprozess: Einreichung, Eingangsbestaetigung (7-Tage-Frist), Sachverhaltspruefung, Folgemaßnahmen und Rueckmeldung (3-Monate-Frist).',
tips: [
{
icon: 'warning' as const,
title: 'Pflicht ab 50 MA',
description: 'Seit Juli 2023 muessen Unternehmen ab 50 Mitarbeitern eine interne Meldestelle einrichten (HinSchG §12).',
title: 'Pflicht ab 50 Beschaeftigten',
description: 'Seit 17.12.2023 gilt die Pflicht fuer ALLE Unternehmen ab 50 Beschaeftigten (§ 12 HinSchG). Verstoesse koennen mit Bussgeldern bis zu 50.000 EUR geahndet werden (§ 40 HinSchG).',
},
{
icon: 'info' as const,
title: 'Anonymitaet',
description: 'Die Identitaet des Hinweisgebers muss geschuetzt werden. Repressalien gegen Hinweisgeber sind verboten.',
title: 'Anonymitaet & Vertraulichkeit',
description: 'Die Identitaet des Hinweisgebers ist streng vertraulich zu behandeln (§ 8 HinSchG). Anonyme Meldungen sollen bearbeitet werden. Repressalien sind verboten und loesen Schadensersatzpflicht aus (§ 36, § 37 HinSchG).',
},
{
icon: 'lightbulb' as const,
title: 'Gesetzliche Fristen',
description: 'Eingangsbestaetigung innerhalb von 7 Tagen (§ 17 Abs. 1 S. 2). Rueckmeldung ueber ergriffene Folgemaßnahmen innerhalb von 3 Monaten nach Eingangsbestaetigung (§ 17 Abs. 2). Die Dokumentation muss 3 Jahre aufbewahrt werden (§ 11 HinSchG).',
},
{
icon: 'warning' as const,
title: 'Sachlicher Anwendungsbereich',
description: 'Erfasst werden Verstoesse gegen EU-Recht und nationales Recht, u.a. Strafrecht, Datenschutz (DSGVO/BDSG), Arbeitsschutz, Umweltschutz, Geldwaesche, Produktsicherheit und Verbraucherschutz (§ 2 HinSchG).',
},
],
},