Split these files that exceeded the 500-line hard cap: - privacy-policy.ts (965 LOC) -> sections + renderers - academy/api.ts (787 LOC) -> courses + mock-data - whistleblower/api.ts (755 LOC) -> operations + mock-data - vvt-profiling.ts (659 LOC) -> data + logic - cookie-banner.ts (595 LOC) -> config + embed - dsr/types.ts (581 LOC) -> core + api types - tom-generator/rules-engine.ts (560 LOC) -> evaluator + gap-analysis - datapoint-helpers.ts (548 LOC) -> generators + validators Each original file becomes a barrel re-export for backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
188 lines
11 KiB
TypeScript
188 lines
11 KiB
TypeScript
/**
|
|
* Whistleblower API — Mock Data & SDK Proxy
|
|
*
|
|
* Fallback mock data for development and SDK proxy function
|
|
*/
|
|
|
|
import {
|
|
WhistleblowerReport,
|
|
WhistleblowerStatistics,
|
|
ReportCategory,
|
|
ReportStatus,
|
|
generateAccessKey,
|
|
} from './types'
|
|
import {
|
|
fetchReports,
|
|
fetchWhistleblowerStatistics,
|
|
} from './api-operations'
|
|
|
|
// =============================================================================
|
|
// SDK PROXY FUNCTION
|
|
// =============================================================================
|
|
|
|
export async function fetchSDKWhistleblowerList(): Promise<{
|
|
reports: WhistleblowerReport[]
|
|
statistics: WhistleblowerStatistics
|
|
}> {
|
|
try {
|
|
const [reportsResponse, statsResponse] = await Promise.all([
|
|
fetchReports(),
|
|
fetchWhistleblowerStatistics()
|
|
])
|
|
return {
|
|
reports: reportsResponse.reports,
|
|
statistics: statsResponse
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load Whistleblower data from API, using mock data:', error)
|
|
const reports = createMockReports()
|
|
const statistics = createMockStatistics()
|
|
return { reports, statistics }
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// MOCK DATA
|
|
// =============================================================================
|
|
|
|
export function createMockReports(): WhistleblowerReport[] {
|
|
const now = new Date()
|
|
|
|
function calcDeadlines(receivedAt: Date): { ack: string; fb: string } {
|
|
const ack = new Date(receivedAt)
|
|
ack.setDate(ack.getDate() + 7)
|
|
const fb = new Date(receivedAt)
|
|
fb.setMonth(fb.getMonth() + 3)
|
|
return { ack: ack.toISOString(), fb: fb.toISOString() }
|
|
}
|
|
|
|
const received1 = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000)
|
|
const deadlines1 = calcDeadlines(received1)
|
|
const received2 = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000)
|
|
const deadlines2 = calcDeadlines(received2)
|
|
const received3 = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
|
|
const deadlines3 = calcDeadlines(received3)
|
|
const received4 = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000)
|
|
const deadlines4 = calcDeadlines(received4)
|
|
|
|
return [
|
|
{
|
|
id: 'wb-001', referenceNumber: 'WB-2026-000001', accessKey: generateAccessKey(),
|
|
category: 'corruption', status: 'new', priority: 'high',
|
|
title: 'Unregelmaessigkeiten bei Auftragsvergabe',
|
|
description: 'Bei der Vergabe des IT-Rahmenvertrags im November wurden offenbar Angebote eines bestimmten Anbieters bevorzugt. Der zustaendige Abteilungsleiter hat private Verbindungen zum Geschaeftsfuehrer des Anbieters.',
|
|
isAnonymous: true, receivedAt: received1.toISOString(),
|
|
deadlineAcknowledgment: deadlines1.ack, deadlineFeedback: deadlines1.fb,
|
|
measures: [], messages: [], attachments: [],
|
|
auditTrail: [{ id: 'audit-001', action: 'report_created', description: 'Meldung ueber Online-Meldeformular eingegangen', performedBy: 'system', performedAt: received1.toISOString() }]
|
|
},
|
|
{
|
|
id: 'wb-002', referenceNumber: 'WB-2026-000002', accessKey: generateAccessKey(),
|
|
category: 'data_protection', status: 'under_review', priority: 'normal',
|
|
title: 'Unerlaubte Weitergabe von Kundendaten',
|
|
description: 'Ein Mitarbeiter der Vertriebsabteilung gibt regelmaessig Kundenlisten an externe Dienstleister weiter, ohne dass eine Auftragsverarbeitungsvereinbarung vorliegt.',
|
|
isAnonymous: false, reporterName: 'Maria Schmidt', reporterEmail: 'maria.schmidt@example.de',
|
|
assignedTo: 'DSB Mueller', receivedAt: received2.toISOString(),
|
|
acknowledgedAt: new Date(received2.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadlineAcknowledgment: deadlines2.ack, deadlineFeedback: deadlines2.fb,
|
|
measures: [],
|
|
messages: [
|
|
{ id: 'msg-001', reportId: 'wb-002', senderRole: 'ombudsperson', message: 'Vielen Dank fuer Ihre Meldung. Koennen Sie uns mitteilen, welche Dienstleister konkret betroffen sind?', createdAt: new Date(received2.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString(), isRead: true },
|
|
{ id: 'msg-002', reportId: 'wb-002', senderRole: 'reporter', message: 'Es handelt sich um die Firma DataServ GmbH und MarketPro AG. Die Listen werden per unverschluesselter E-Mail versendet.', createdAt: new Date(received2.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(), isRead: true },
|
|
],
|
|
attachments: [{ id: 'att-001', fileName: 'email_screenshot_vertrieb.png', fileSize: 245000, mimeType: 'image/png', uploadedAt: received2.toISOString(), uploadedBy: 'reporter' }],
|
|
auditTrail: [
|
|
{ id: 'audit-002', action: 'report_created', description: 'Meldung per E-Mail eingegangen', performedBy: 'system', performedAt: received2.toISOString() },
|
|
{ id: 'audit-003', action: 'acknowledged', description: 'Eingangsbestaetigung an Hinweisgeber versendet', performedBy: 'DSB Mueller', performedAt: new Date(received2.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString() },
|
|
{ id: 'audit-004', action: 'status_changed', description: 'Status geaendert: Bestaetigt -> In Pruefung', performedBy: 'DSB Mueller', performedAt: new Date(received2.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString() },
|
|
]
|
|
},
|
|
{
|
|
id: 'wb-003', referenceNumber: 'WB-2026-000003', accessKey: generateAccessKey(),
|
|
category: 'product_safety', status: 'investigation', priority: 'critical',
|
|
title: 'Fehlende Sicherheitspruefungen bei Produktfreigabe',
|
|
description: 'In der Fertigung werden seit Wochen Produkte ohne die vorgeschriebenen Sicherheitspruefungen freigegeben. Pruefprotokolle werden nachtraeglich erstellt, ohne dass tatsaechliche Pruefungen stattfinden.',
|
|
isAnonymous: true, assignedTo: 'Qualitaetsbeauftragter Weber',
|
|
receivedAt: received3.toISOString(),
|
|
acknowledgedAt: new Date(received3.getTime() + 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadlineAcknowledgment: deadlines3.ack, deadlineFeedback: deadlines3.fb,
|
|
measures: [
|
|
{ id: 'msr-001', reportId: 'wb-003', title: 'Sofortiger Produktionsstopp fuer betroffene Charge', description: 'Produktion der betroffenen Produktlinie stoppen bis Pruefverfahren sichergestellt ist', status: 'completed', responsible: 'Fertigungsleitung', dueDate: new Date(received3.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString(), completedAt: new Date(received3.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString() },
|
|
{ id: 'msr-002', reportId: 'wb-003', title: 'Externe Pruefung der Pruefprotokolle', description: 'Unabhaengige Pruefstelle mit der Revision aller Pruefprotokolle der letzten 6 Monate beauftragen', status: 'in_progress', responsible: 'Qualitaetsmanagement', dueDate: new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000).toISOString() },
|
|
],
|
|
messages: [],
|
|
attachments: [{ id: 'att-002', fileName: 'pruefprotokoll_vergleich.pdf', fileSize: 890000, mimeType: 'application/pdf', uploadedAt: new Date(received3.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString(), uploadedBy: 'ombudsperson' }],
|
|
auditTrail: [
|
|
{ id: 'audit-005', action: 'report_created', description: 'Meldung ueber Online-Meldeformular eingegangen', performedBy: 'system', performedAt: received3.toISOString() },
|
|
{ id: 'audit-006', action: 'acknowledged', description: 'Eingangsbestaetigung versendet', performedBy: 'Qualitaetsbeauftragter Weber', performedAt: new Date(received3.getTime() + 2 * 24 * 60 * 60 * 1000).toISOString() },
|
|
{ id: 'audit-007', action: 'investigation_started', description: 'Formelle Untersuchung eingeleitet', performedBy: 'Qualitaetsbeauftragter Weber', performedAt: new Date(received3.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString() },
|
|
]
|
|
},
|
|
{
|
|
id: 'wb-004', referenceNumber: 'WB-2026-000004', accessKey: generateAccessKey(),
|
|
category: 'fraud', status: 'closed', priority: 'high',
|
|
title: 'Gefaelschte Reisekostenabrechnungen',
|
|
description: 'Ein leitender Mitarbeiter reicht seit ueber einem Jahr gefaelschte Reisekostenabrechnungen ein. Hotelrechnungen werden manipuliert, Taxiquittungen erfunden.',
|
|
isAnonymous: false, reporterName: 'Thomas Klein', reporterEmail: 'thomas.klein@example.de', reporterPhone: '+49 170 9876543',
|
|
assignedTo: 'Compliance-Abteilung', receivedAt: received4.toISOString(),
|
|
acknowledgedAt: new Date(received4.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadlineAcknowledgment: deadlines4.ack, deadlineFeedback: deadlines4.fb,
|
|
closedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
|
measures: [
|
|
{ id: 'msr-003', reportId: 'wb-004', title: 'Interne Revision der Reisekosten', description: 'Pruefung aller Reisekostenabrechnungen des betroffenen Mitarbeiters der letzten 24 Monate', status: 'completed', responsible: 'Interne Revision', dueDate: new Date(received4.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString(), completedAt: new Date(received4.getTime() + 25 * 24 * 60 * 60 * 1000).toISOString() },
|
|
{ id: 'msr-004', reportId: 'wb-004', title: 'Arbeitsrechtliche Konsequenzen', description: 'Einleitung arbeitsrechtlicher Schritte nach Bestaetigung des Betrugs', status: 'completed', responsible: 'Personalabteilung', dueDate: new Date(received4.getTime() + 60 * 24 * 60 * 60 * 1000).toISOString(), completedAt: new Date(received4.getTime() + 55 * 24 * 60 * 60 * 1000).toISOString() },
|
|
],
|
|
messages: [],
|
|
attachments: [{ id: 'att-003', fileName: 'vergleich_originalrechnung_einreichung.pdf', fileSize: 567000, mimeType: 'application/pdf', uploadedAt: received4.toISOString(), uploadedBy: 'reporter' }],
|
|
auditTrail: [
|
|
{ id: 'audit-008', action: 'report_created', description: 'Meldung per Brief eingegangen', performedBy: 'system', performedAt: received4.toISOString() },
|
|
{ id: 'audit-009', action: 'acknowledged', description: 'Eingangsbestaetigung versendet', performedBy: 'Compliance-Abteilung', performedAt: new Date(received4.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString() },
|
|
{ id: 'audit-010', action: 'closed', description: 'Fall abgeschlossen - Betrug bestaetigt, arbeitsrechtliche Massnahmen eingeleitet', performedBy: 'Compliance-Abteilung', performedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString() },
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
export function createMockStatistics(): WhistleblowerStatistics {
|
|
const reports = createMockReports()
|
|
const now = new Date()
|
|
|
|
const byStatus: Record<ReportStatus, number> = {
|
|
new: 0, acknowledged: 0, under_review: 0, investigation: 0,
|
|
measures_taken: 0, closed: 0, rejected: 0
|
|
}
|
|
|
|
const byCategory: Record<ReportCategory, number> = {
|
|
corruption: 0, fraud: 0, data_protection: 0, discrimination: 0,
|
|
environment: 0, competition: 0, product_safety: 0, tax_evasion: 0, other: 0
|
|
}
|
|
|
|
reports.forEach(r => {
|
|
byStatus[r.status]++
|
|
byCategory[r.category]++
|
|
})
|
|
|
|
const closedStatuses: ReportStatus[] = ['closed', 'rejected']
|
|
|
|
const overdueAcknowledgment = reports.filter(r => {
|
|
if (r.status !== 'new') return false
|
|
return now > new Date(r.deadlineAcknowledgment)
|
|
}).length
|
|
|
|
const overdueFeedback = reports.filter(r => {
|
|
if (closedStatuses.includes(r.status)) return false
|
|
return now > new Date(r.deadlineFeedback)
|
|
}).length
|
|
|
|
return {
|
|
totalReports: reports.length,
|
|
newReports: byStatus.new,
|
|
underReview: byStatus.under_review + byStatus.investigation,
|
|
closed: byStatus.closed + byStatus.rejected,
|
|
overdueAcknowledgment,
|
|
overdueFeedback,
|
|
byCategory,
|
|
byStatus
|
|
}
|
|
}
|