'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 Deadlines (Overview Tab) */} {activeTab === 'overview' && (

HinSchG-Fristen

Nach dem Hinweisgeberschutzgesetz (HinSchG) gelten folgende Fristen: Die Eingangsbestaetigung muss innerhalb von 7 Tagen an den Hinweisgeber versendet werden (ss 17 Abs. 1 S. 2). Eine Rueckmeldung ueber ergriffene Massnahmen muss innerhalb von 3 Monaten nach Eingangsbestaetigung erfolgen (ss 17 Abs. 2). Der Schutz des Hinweisgebers vor Repressalien ist zwingend sicherzustellen (ss 36).

)} {/* 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() }} /> )}
) }