'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, REPORT_CATEGORY_INFO, REPORT_STATUS_INFO, isAcknowledgmentOverdue, isFeedbackOverdue, getDaysUntilAcknowledgment, getDaysUntilFeedback } from '@/lib/sdk/whistleblower/types' import { fetchSDKWhistleblowerList } from '@/lib/sdk/whistleblower/api' // ============================================================================= // TYPES // ============================================================================= type TabId = 'overview' | 'new_reports' | 'investigation' | 'closed' | 'settings' interface Tab { id: TabId label: string count?: number countColor?: string } // ============================================================================= // COMPONENTS // ============================================================================= function TabNavigation({ tabs, activeTab, onTabChange }: { tabs: Tab[] activeTab: TabId onTabChange: (tab: TabId) => void }) { return (
) } function StatCard({ label, value, color = 'gray', icon, trend }: { label: string value: number | string color?: 'gray' | 'blue' | 'yellow' | 'red' | 'green' | 'purple' icon?: React.ReactNode trend?: { value: number; label: string } }) { const colorClasses = { gray: 'border-gray-200 text-gray-900', blue: 'border-blue-200 text-blue-600', yellow: 'border-yellow-200 text-yellow-600', red: 'border-red-200 text-red-600', green: 'border-green-200 text-green-600', purple: 'border-purple-200 text-purple-600' } return (
{label}
{value}
{trend && (
= 0 ? 'text-green-600' : 'text-red-600'}`}> {trend.value >= 0 ? '+' : ''}{trend.value} {trend.label}
)}
{icon && (
{icon}
)}
) } function FilterBar({ selectedCategory, selectedStatus, selectedPriority, onCategoryChange, onStatusChange, onPriorityChange, onClear }: { selectedCategory: ReportCategory | 'all' selectedStatus: ReportStatus | 'all' selectedPriority: ReportPriority | 'all' onCategoryChange: (category: ReportCategory | 'all') => void onStatusChange: (status: ReportStatus | 'all') => void onPriorityChange: (priority: ReportPriority | 'all') => void onClear: () => void }) { const hasFilters = selectedCategory !== 'all' || selectedStatus !== 'all' || selectedPriority !== 'all' return (
Filter: {/* Category Filter */} {/* Status Filter */} {/* Priority Filter */} {/* Clear Filters */} {hasFilters && ( )}
) } function ReportCard({ report, onClick }: { report: WhistleblowerReport; onClick?: () => void }) { const categoryInfo = REPORT_CATEGORY_INFO[report.category] const statusInfo = REPORT_STATUS_INFO[report.status] const isClosed = report.status === 'closed' || report.status === 'rejected' const ackOverdue = isAcknowledgmentOverdue(report) const fbOverdue = isFeedbackOverdue(report) const daysAck = getDaysUntilAcknowledgment(report) const daysFb = getDaysUntilFeedback(report) const completedMeasures = report.measures.filter(m => m.status === 'completed').length const totalMeasures = report.measures.length const priorityLabels: Record = { low: 'Niedrig', normal: 'Normal', high: 'Hoch', critical: 'Kritisch' } return (
{/* Header Badges */}
{report.referenceNumber} {categoryInfo.label} {statusInfo.label} {report.isAnonymous && ( Anonym )} {report.priority === 'critical' && ( Kritisch )} {report.priority === 'high' && ( Hoch )}
{/* Title */}

{report.title}

{/* Description Preview */} {report.description && (

{report.description}

)} {/* Deadline Info */} {!isClosed && (
{report.status === 'new' && ( {ackOverdue ? `Bestaetigung ${Math.abs(daysAck)} Tage ueberfaellig` : `Bestaetigung in ${daysAck} Tagen` } )} {fbOverdue ? `Rueckmeldung ${Math.abs(daysFb)} Tage ueberfaellig` : `Rueckmeldung in ${daysFb} Tagen` }
)}
{/* Right Side - Date & Priority */}
{isClosed ? statusInfo.label : ackOverdue ? 'Ueberfaellig' : priorityLabels[report.priority] }
{new Date(report.receivedAt).toLocaleDateString('de-DE')}
{/* Footer */}
{report.assignedTo ? `Zugewiesen: ${report.assignedTo}` : 'Nicht zugewiesen' }
{report.attachments.length > 0 && ( {report.attachments.length} Anhang{report.attachments.length !== 1 ? 'e' : ''} )} {totalMeasures > 0 && ( {completedMeasures}/{totalMeasures} Massnahmen )} {report.messages.length > 0 && ( {report.messages.length} Nachricht{report.messages.length !== 1 ? 'en' : ''} )}
{!isClosed && ( Bearbeiten )} {isClosed && ( Details )}
) } // ============================================================================= // WHISTLEBLOWER CREATE MODAL // ============================================================================= function WhistleblowerCreateModal({ onClose, onSuccess }: { onClose: () => void onSuccess: () => void }) { const [title, setTitle] = useState('') const [description, setDescription] = useState('') const [category, setCategory] = useState('corruption') const [priority, setPriority] = useState('normal') const [isAnonymous, setIsAnonymous] = useState(true) const [reporterName, setReporterName] = useState('') const [reporterEmail, setReporterEmail] = useState('') const [isSaving, setIsSaving] = useState(false) const [error, setError] = useState(null) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!title.trim() || !description.trim()) return setIsSaving(true) setError(null) try { const body: Record = { title: title.trim(), description: description.trim(), category, priority, isAnonymous, status: 'new' } if (!isAnonymous) { body.reporterName = reporterName.trim() body.reporterEmail = reporterEmail.trim() } const res = await fetch('/api/sdk/v1/whistleblower/reports', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data?.detail || data?.message || `Fehler ${res.status}`) } onSuccess() } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setIsSaving(false) } } return (
{/* Backdrop */}
{/* Modal */}

Neue Meldung erfassen

{error && (
{error}
)}
{/* Title */}
setTitle(e.target.value)} required className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm" placeholder="Kurze Beschreibung des Vorfalls" />
{/* Description */}