'use client' import React, { useState, useEffect, useMemo, useCallback } from 'react' import { useSDK } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' import { WhistleblowerReport, WhistleblowerStatistics, ReportCategory, ReportStatus, ReportPriority, isAcknowledgmentOverdue, isFeedbackOverdue, } from '@/lib/sdk/whistleblower/types' import { fetchSDKWhistleblowerList } from '@/lib/sdk/whistleblower/api' import { TabNavigation, type Tab, type TabId } from './_components/TabNavigation' import { StatCard } from './_components/StatCard' import { FilterBar } from './_components/FilterBar' import { ReportCard } from './_components/ReportCard' import { WhistleblowerCreateModal } from './_components/WhistleblowerCreateModal' import { CaseDetailPanel } from './_components/CaseDetailPanel' // ============================================================================= // MAIN PAGE // ============================================================================= export default function WhistleblowerPage() { const { state } = useSDK() const [activeTab, setActiveTab] = useState('overview') const [reports, setReports] = useState([]) const [statistics, setStatistics] = useState(null) const [isLoading, setIsLoading] = useState(true) const [showCreateModal, setShowCreateModal] = useState(false) const [selectedReport, setSelectedReport] = useState(null) // Filters const [selectedCategory, setSelectedCategory] = useState('all') const [selectedStatus, setSelectedStatus] = useState('all') const [selectedPriority, setSelectedPriority] = useState('all') // Load data from SDK backend const loadData = useCallback(async () => { setIsLoading(true) try { const { reports: wbReports, statistics: wbStats } = await fetchSDKWhistleblowerList() setReports(wbReports) setStatistics(wbStats) } catch (error) { console.error('Failed to load Whistleblower data:', error) } finally { setIsLoading(false) } }, []) useEffect(() => { loadData() }, [loadData]) // Locally computed overdue counts (always fresh) const overdueCounts = useMemo(() => { const overdueAck = reports.filter(r => isAcknowledgmentOverdue(r)).length const overdueFb = reports.filter(r => isFeedbackOverdue(r)).length return { overdueAck, overdueFb } }, [reports]) // Calculate tab counts const tabCounts = useMemo(() => { const investigationStatuses: ReportStatus[] = ['acknowledged', 'under_review', 'investigation', 'measures_taken'] const closedStatuses: ReportStatus[] = ['closed', 'rejected'] return { new_reports: reports.filter(r => r.status === 'new').length, investigation: reports.filter(r => investigationStatuses.includes(r.status)).length, closed: reports.filter(r => closedStatuses.includes(r.status)).length } }, [reports]) // Filter reports based on active tab and filters const filteredReports = useMemo(() => { let filtered = [...reports] // Tab-based filtering const investigationStatuses: ReportStatus[] = ['acknowledged', 'under_review', 'investigation', 'measures_taken'] const closedStatuses: ReportStatus[] = ['closed', 'rejected'] if (activeTab === 'new_reports') { filtered = filtered.filter(r => r.status === 'new') } else if (activeTab === 'investigation') { filtered = filtered.filter(r => investigationStatuses.includes(r.status)) } else if (activeTab === 'closed') { filtered = filtered.filter(r => closedStatuses.includes(r.status)) } // Category filter if (selectedCategory !== 'all') { filtered = filtered.filter(r => r.category === selectedCategory) } // Status filter if (selectedStatus !== 'all') { filtered = filtered.filter(r => r.status === selectedStatus) } // Priority filter if (selectedPriority !== 'all') { filtered = filtered.filter(r => r.priority === selectedPriority) } // Sort: overdue first, then by priority, then by date return filtered.sort((a, b) => { const closedStatuses: ReportStatus[] = ['closed', 'rejected'] const getUrgency = (r: WhistleblowerReport) => { if (closedStatuses.includes(r.status)) return 1000 const ackOd = isAcknowledgmentOverdue(r) const fbOd = isFeedbackOverdue(r) if (ackOd || fbOd) return -100 const priorityScore = { critical: 0, high: 1, normal: 2, low: 3 } return priorityScore[r.priority] ?? 2 } const urgencyDiff = getUrgency(a) - getUrgency(b) if (urgencyDiff !== 0) return urgencyDiff return new Date(b.receivedAt).getTime() - new Date(a.receivedAt).getTime() }) }, [reports, activeTab, selectedCategory, selectedStatus, selectedPriority]) const tabs: Tab[] = [ { id: 'overview', label: 'Uebersicht' }, { id: 'new_reports', label: 'Neue Meldungen', count: tabCounts.new_reports, countColor: 'bg-blue-100 text-blue-600' }, { id: 'investigation', label: 'In Untersuchung', count: tabCounts.investigation, countColor: 'bg-yellow-100 text-yellow-600' }, { id: 'closed', label: 'Abgeschlossen', count: tabCounts.closed, countColor: 'bg-green-100 text-green-600' }, { id: 'settings', label: 'Einstellungen' } ] const stepInfo = STEP_EXPLANATIONS['whistleblower'] const clearFilters = () => { setSelectedCategory('all') setSelectedStatus('all') setSelectedPriority('all') } return (
{/* Step Header */} {/* Tab Navigation */} {/* Loading State */} {isLoading ? (
) : activeTab === 'settings' ? ( /* Settings Tab */

Einstellungen

Hinweisgebersystem-Einstellungen, Meldekanal-Konfiguration, Ombudsperson-Verwaltung und E-Mail-Vorlagen werden in einer spaeteren Version verfuegbar sein.

) : ( <> {/* Statistics (Overview Tab) */} {activeTab === 'overview' && statistics && (
0 ? 'red' : 'green'} />
)} {/* Overdue Alert for Acknowledgment Deadline (7 days HinSchG) */} {(overdueCounts.overdueAck > 0 || overdueCounts.overdueFb > 0) && (activeTab === 'overview' || activeTab === 'new_reports' || activeTab === 'investigation') && (

Achtung: Gesetzliche Fristen ueberschritten

{overdueCounts.overdueAck > 0 && ( {overdueCounts.overdueAck} Meldung(en) ohne Eingangsbestaetigung (mehr als 7 Tage, HinSchG ss 17 Abs. 1). )} {overdueCounts.overdueFb > 0 && ( {overdueCounts.overdueFb} Meldung(en) ohne Rueckmeldung (mehr als 3 Monate, HinSchG ss 17 Abs. 2). )} Handeln Sie umgehend, um Bussgelder und Haftungsrisiken zu vermeiden.

)} {/* Info Box about HinSchG (Overview Tab) */} {activeTab === 'overview' && (
{/* Gesetzliche Grundlage */}

Gesetzliche Grundlage: Hinweisgeberschutzgesetz (HinSchG)

Das HinSchG setzt die EU-Whistleblowing-Richtlinie (2019/1937) in deutsches Recht um und ist seit dem 2. Juli 2023 in Kraft. Seit dem 17. Dezember 2023 gilt die Pflicht zur Einrichtung einer internen Meldestelle auch fuer Unternehmen ab 50 Beschaeftigten (ss 12 HinSchG).

{/* Fristen & Pflichten */}
7-Tage-Frist

Eingangsbestaetigung an den Hinweisgeber innerhalb von 7 Tagen nach Meldungseingang (ss 17 Abs. 1 S. 2 HinSchG).

3-Monate-Frist

Rueckmeldung ueber ergriffene Folgemaßnahmen innerhalb von 3 Monaten nach Eingangsbestaetigung (ss 17 Abs. 2 HinSchG).

3 Jahre Aufbewahrung

Dokumentation der Meldungen und Folgemaßnahmen ist 3 Jahre nach Abschluss aufzubewahren (ss 11 Abs. 5 HinSchG).

{/* Sachlicher Anwendungsbereich & Schutz */}
Sachlicher Anwendungsbereich (ss 2 HinSchG)
  • Verstoesse gegen Strafvorschriften (StGB, Nebenstrafrecht)
  • Verstoesse gegen Datenschutzrecht (DSGVO, BDSG)
  • Geldwaesche und Terrorismusfinanzierung (GwG)
  • Produktsicherheit und Verbraucherschutz
  • Umweltschutz und Lebensmittelsicherheit
  • Arbeitsschutz und Arbeitnehmerrechte
  • Wettbewerbs- und Kartellrecht
  • Steuer- und Abgabenrecht (bei Unternehmen)
Schutz des Hinweisgebers (ss 36–37 HinSchG)
  • Repressalienverbot: Jede Benachteiligung ist untersagt (ss 36)
  • Beweislastumkehr: Arbeitgeber muss beweisen, dass Maßnahmen nicht mit Meldung zusammenhaengen
  • Schadensersatz: Bei Verstoessen gegen Repressalienverbot (ss 37)
  • Vertraulichkeit: Identitaet darf nur bei Zustimmung oder gesetzlicher Pflicht offengelegt werden (ss 8)
  • Bussgelder: Bis zu 50.000 EUR bei Verstoessen gegen die Einrichtungspflicht (ss 40)
)} {/* Filters */} {/* Report List */}
{filteredReports.map(report => ( setSelectedReport(report)} /> ))}
{/* Empty State */} {filteredReports.length === 0 && (

Keine Meldungen gefunden

{selectedCategory !== 'all' || selectedStatus !== 'all' || selectedPriority !== 'all' ? 'Passen Sie die Filter an oder setzen Sie sie zurueck.' : 'Es sind noch keine Meldungen im Hinweisgebersystem vorhanden. Meldungen werden ueber das oeffentliche Meldeformular eingereicht.' }

{(selectedCategory !== 'all' || selectedStatus !== 'all' || selectedPriority !== 'all') && ( )}
)} )} {/* Modals */} {showCreateModal && ( setShowCreateModal(false)} onSuccess={() => { setShowCreateModal(false); loadData() }} /> )} {selectedReport && ( setSelectedReport(null)} onUpdated={() => { setSelectedReport(null); loadData() }} onDeleted={() => { setSelectedReport(null); loadData() }} /> )}
) }