From 27f18994289ed9858e22e788ce745a2cc7296987 Mon Sep 17 00:00:00 2001 From: Benjamin Boenisch Date: Fri, 13 Feb 2026 21:12:30 +0100 Subject: [PATCH] feat: Sync SDK modules, API routes, blog and docs from admin-v2 - DSB Portal, Industry Templates, Multi-Tenant, SSO frontend pages - All SDK API proxy routes (academy, crawler, incidents, vendors, whistleblower, etc.) - Blog section with compliance articles - BYOEH system documentation Co-Authored-By: Claude Opus 4.6 --- .../app/(sdk)/sdk/dsb-portal/page.tsx | 2068 +++++++++++++++++ .../app/(sdk)/sdk/industry-templates/page.tsx | 879 +++++++ .../app/(sdk)/sdk/multi-tenant/page.tsx | 1663 +++++++++++++ admin-lehrer/app/(sdk)/sdk/sso/page.tsx | 1482 ++++++++++++ .../api/sdk/compliance-advisor/chat/route.ts | 225 ++ .../app/api/sdk/drafting-engine/chat/route.ts | 184 ++ .../api/sdk/drafting-engine/draft/route.ts | 168 ++ .../api/sdk/drafting-engine/validate/route.ts | 188 ++ .../api/sdk/v1/academy/[[...path]]/route.ts | 136 ++ .../app/api/sdk/v1/checkpoints/route.ts | 235 ++ .../app/api/sdk/v1/demo/clear/route.ts | 52 + .../app/api/sdk/v1/demo/seed/route.ts | 77 + .../app/api/sdk/v1/documents/upload/route.ts | 214 ++ .../app/api/sdk/v1/dsb/[[...path]]/route.ts | 109 + .../app/api/sdk/v1/dsgvo/[...path]/route.ts | 130 ++ .../sdk/v1/einwilligungen/catalog/route.ts | 255 ++ .../sdk/v1/einwilligungen/consent/route.ts | 369 +++ .../cookie-banner/config/route.ts | 215 ++ .../cookie-banner/embed-code/route.ts | 256 ++ .../privacy-policy/generate/route.ts | 186 ++ admin-lehrer/app/api/sdk/v1/export/route.ts | 88 + admin-lehrer/app/api/sdk/v1/flow/route.ts | 150 ++ admin-lehrer/app/api/sdk/v1/generate/route.ts | 309 +++ .../api/sdk/v1/incidents/[[...path]]/route.ts | 137 ++ .../api/sdk/v1/industry/[[...path]]/route.ts | 74 + .../sdk/v1/multi-tenant/[[...path]]/route.ts | 111 + .../api/sdk/v1/reporting/[[...path]]/route.ts | 75 + .../app/api/sdk/v1/sso/[[...path]]/route.ts | 111 + admin-lehrer/app/api/sdk/v1/state/route.ts | 345 +++ .../tom-generator/controls/evaluate/route.ts | 107 + .../sdk/v1/tom-generator/controls/route.ts | 128 + .../evidence/[id]/analyze/route.ts | 121 + .../sdk/v1/tom-generator/evidence/route.ts | 153 ++ .../v1/tom-generator/evidence/upload/route.ts | 155 ++ .../api/sdk/v1/tom-generator/export/route.ts | 245 ++ .../v1/tom-generator/gap-analysis/route.ts | 205 ++ .../api/sdk/v1/tom-generator/state/route.ts | 250 ++ .../sdk/v1/ucca/obligations/assess/route.ts | 40 + .../ucca/obligations/export/direct/route.ts | 40 + .../contracts/[id]/review/route.ts | 197 ++ .../v1/vendor-compliance/contracts/route.ts | 88 + .../v1/vendor-compliance/controls/route.ts | 28 + .../export/[reportId]/download/route.ts | 75 + .../export/[reportId]/route.ts | 44 + .../sdk/v1/vendor-compliance/export/route.ts | 118 + .../v1/vendor-compliance/findings/route.ts | 43 + .../processing-activities/[id]/route.ts | 70 + .../processing-activities/route.ts | 84 + .../sdk/v1/vendor-compliance/vendors/route.ts | 82 + .../api/sdk/v1/vendors/[[...path]]/route.ts | 135 ++ .../sdk/v1/whistleblower/[[...path]]/route.ts | 147 ++ .../byoeh-system-erklaerung.md | 456 ++++ mkdocs.yml | 1 + website/app/blog/ai-act-ueberblick/page.tsx | 741 ++++++ website/app/blog/dsgvo-vvt-erstellen/page.tsx | 725 ++++++ website/app/blog/glossar/page.tsx | 396 ++++ website/app/blog/layout.tsx | 73 + website/app/blog/nis2-checkliste/page.tsx | 552 +++++ website/app/blog/page.tsx | 268 +++ 59 files changed, 16258 insertions(+) create mode 100644 admin-lehrer/app/(sdk)/sdk/dsb-portal/page.tsx create mode 100644 admin-lehrer/app/(sdk)/sdk/industry-templates/page.tsx create mode 100644 admin-lehrer/app/(sdk)/sdk/multi-tenant/page.tsx create mode 100644 admin-lehrer/app/(sdk)/sdk/sso/page.tsx create mode 100644 admin-lehrer/app/api/sdk/compliance-advisor/chat/route.ts create mode 100644 admin-lehrer/app/api/sdk/drafting-engine/chat/route.ts create mode 100644 admin-lehrer/app/api/sdk/drafting-engine/draft/route.ts create mode 100644 admin-lehrer/app/api/sdk/drafting-engine/validate/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/academy/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/checkpoints/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/demo/clear/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/demo/seed/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/documents/upload/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/dsb/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/dsgvo/[...path]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/einwilligungen/catalog/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/einwilligungen/consent/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/einwilligungen/cookie-banner/config/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/einwilligungen/cookie-banner/embed-code/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/einwilligungen/privacy-policy/generate/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/export/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/flow/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/generate/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/incidents/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/industry/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/multi-tenant/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/reporting/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/sso/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/state/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/controls/evaluate/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/controls/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/evidence/[id]/analyze/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/evidence/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/evidence/upload/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/export/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/gap-analysis/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/tom-generator/state/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/ucca/obligations/assess/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/ucca/obligations/export/direct/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/contracts/[id]/review/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/contracts/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/controls/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/export/[reportId]/download/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/export/[reportId]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/export/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/findings/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/processing-activities/[id]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/processing-activities/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendor-compliance/vendors/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/vendors/[[...path]]/route.ts create mode 100644 admin-lehrer/app/api/sdk/v1/whistleblower/[[...path]]/route.ts create mode 100644 docs-src/services/klausur-service/byoeh-system-erklaerung.md create mode 100644 website/app/blog/ai-act-ueberblick/page.tsx create mode 100644 website/app/blog/dsgvo-vvt-erstellen/page.tsx create mode 100644 website/app/blog/glossar/page.tsx create mode 100644 website/app/blog/layout.tsx create mode 100644 website/app/blog/nis2-checkliste/page.tsx create mode 100644 website/app/blog/page.tsx diff --git a/admin-lehrer/app/(sdk)/sdk/dsb-portal/page.tsx b/admin-lehrer/app/(sdk)/sdk/dsb-portal/page.tsx new file mode 100644 index 0000000..4cf7bd4 --- /dev/null +++ b/admin-lehrer/app/(sdk)/sdk/dsb-portal/page.tsx @@ -0,0 +1,2068 @@ +'use client' + +import React, { useState, useEffect, useCallback, useRef } from 'react' + +// ============================================================================= +// TYPES +// ============================================================================= + +interface AssignmentOverview { + id: string + dsb_user_id: string + tenant_id: string + tenant_name: string + tenant_slug: string + status: string + contract_start: string + contract_end: string | null + monthly_hours_budget: number + notes: string + compliance_score: number + hours_this_month: number + hours_budget: number + open_task_count: number + urgent_task_count: number + next_deadline: string | null + created_at: string + updated_at: string +} + +interface DSBDashboard { + assignments: AssignmentOverview[] + total_assignments: number + active_assignments: number + total_hours_this_month: number + open_tasks: number + urgent_tasks: number + generated_at: string +} + +interface HourEntry { + id: string + assignment_id: string + date: string + hours: number + category: string + description: string + billable: boolean + created_at: string +} + +interface Task { + id: string + assignment_id: string + title: string + description: string + category: string + priority: string + status: string + due_date: string | null + completed_at: string | null + created_at: string + updated_at: string +} + +interface Communication { + id: string + assignment_id: string + direction: string + channel: string + subject: string + content: string + participants: string + created_at: string +} + +interface HoursSummary { + total_hours: number + billable_hours: number + by_category: Record + period: string +} + +// ============================================================================= +// CONSTANTS +// ============================================================================= + +const DSB_USER_ID = '00000000-0000-0000-0000-000000000001' + +const TASK_CATEGORIES = [ + 'DSFA-Pruefung', + 'Betroffenenanfrage', + 'Vorfall-Pruefung', + 'Audit-Vorbereitung', + 'Richtlinien-Pruefung', + 'Schulung', + 'Beratung', + 'Sonstiges', +] + +const HOUR_CATEGORIES = [ + 'DSFA-Pruefung', + 'Beratung', + 'Audit', + 'Schulung', + 'Vorfallreaktion', + 'Dokumentation', + 'Besprechung', + 'Sonstiges', +] + +const COMM_CHANNELS = ['E-Mail', 'Telefon', 'Besprechung', 'Portal', 'Brief'] + +const PRIORITY_LABELS: Record = { + urgent: 'Dringend', + high: 'Hoch', + medium: 'Mittel', + low: 'Niedrig', +} + +const PRIORITY_COLORS: Record = { + urgent: 'bg-red-100 text-red-700 border-red-200', + high: 'bg-orange-100 text-orange-700 border-orange-200', + medium: 'bg-blue-100 text-blue-700 border-blue-200', + low: 'bg-gray-100 text-gray-500 border-gray-200', +} + +const TASK_STATUS_LABELS: Record = { + open: 'Offen', + in_progress: 'In Bearbeitung', + waiting: 'Wartend', + completed: 'Erledigt', + cancelled: 'Abgebrochen', +} + +const TASK_STATUS_COLORS: Record = { + open: 'bg-blue-100 text-blue-700', + in_progress: 'bg-yellow-100 text-yellow-700', + waiting: 'bg-orange-100 text-orange-700', + completed: 'bg-green-100 text-green-700', + cancelled: 'bg-gray-100 text-gray-500', +} + +const ASSIGNMENT_STATUS_COLORS: Record = { + active: 'bg-green-100 text-green-700 border-green-300', + paused: 'bg-yellow-100 text-yellow-700 border-yellow-300', + terminated: 'bg-red-100 text-red-700 border-red-300', +} + +const ASSIGNMENT_STATUS_LABELS: Record = { + active: 'Aktiv', + paused: 'Pausiert', + terminated: 'Beendet', +} + +// ============================================================================= +// API HELPERS +// ============================================================================= + +async function apiFetch(url: string, options?: RequestInit): Promise { + const res = await fetch(url, { + ...options, + headers: { + 'Content-Type': 'application/json', + 'X-User-ID': DSB_USER_ID, + ...(options?.headers || {}), + }, + }) + if (!res.ok) { + const text = await res.text().catch(() => '') + throw new Error(`API Error ${res.status}: ${text || res.statusText}`) + } + return res.json() +} + +// ============================================================================= +// TOAST COMPONENT +// ============================================================================= + +interface ToastMessage { + id: number + message: string + type: 'success' | 'error' +} + +let toastIdCounter = 0 + +function useToast() { + const [toasts, setToasts] = useState([]) + + const addToast = useCallback((message: string, type: 'success' | 'error' = 'success') => { + const id = ++toastIdCounter + setToasts((prev) => [...prev, { id, message, type }]) + setTimeout(() => { + setToasts((prev) => prev.filter((t) => t.id !== id)) + }, 3500) + }, []) + + return { toasts, addToast } +} + +function ToastContainer({ toasts }: { toasts: ToastMessage[] }) { + if (toasts.length === 0) return null + return ( +
+ {toasts.map((t) => ( +
+ {t.message} +
+ ))} +
+ ) +} + +// ============================================================================= +// LOADING SKELETON +// ============================================================================= + +function Skeleton({ className = '' }: { className?: string }) { + return
+} + +function DashboardSkeleton() { + return ( +
+
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+
+ {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
+
+ ) +} + +// ============================================================================= +// MODAL COMPONENT +// ============================================================================= + +function Modal({ + open, + onClose, + title, + children, + maxWidth = 'max-w-lg', +}: { + open: boolean + onClose: () => void + title: string + children: React.ReactNode + maxWidth?: string +}) { + const overlayRef = useRef(null) + + useEffect(() => { + if (!open) return + const handleEsc = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', handleEsc) + return () => document.removeEventListener('keydown', handleEsc) + }, [open, onClose]) + + if (!open) return null + + return ( +
{ + if (e.target === overlayRef.current) onClose() + }} + > +
+
+

{title}

+ +
+
{children}
+
+
+ ) +} + +// ============================================================================= +// STAT CARD +// ============================================================================= + +function StatCard({ + title, + value, + icon, + accent = false, +}: { + title: string + value: string | number + icon: React.ReactNode + accent?: boolean +}) { + return ( +
+
+
+ {icon} +
+
+

{title}

+

+ {value} +

+
+
+
+ ) +} + +// ============================================================================= +// COMPLIANCE PROGRESS BAR +// ============================================================================= + +function ComplianceBar({ score }: { score: number }) { + const color = + score < 40 ? 'bg-red-500' : score < 70 ? 'bg-yellow-500' : 'bg-green-500' + const textColor = + score < 40 ? 'text-red-700' : score < 70 ? 'text-yellow-700' : 'text-green-700' + return ( +
+
+
+
+ + {score}% + +
+ ) +} + +// ============================================================================= +// HOURS PROGRESS BAR +// ============================================================================= + +function HoursBar({ used, budget }: { used: number; budget: number }) { + const pct = budget > 0 ? Math.min((used / budget) * 100, 100) : 0 + const over = used > budget + return ( +
+
+
+
+ + {used}h / {budget}h + +
+ ) +} + +// ============================================================================= +// BADGE +// ============================================================================= + +function Badge({ label, className = '' }: { label: string; className?: string }) { + return ( + + {label} + + ) +} + +// ============================================================================= +// FORM COMPONENTS +// ============================================================================= + +function FormLabel({ children, htmlFor }: { children: React.ReactNode; htmlFor?: string }) { + return ( + + ) +} + +function FormInput({ + id, + type = 'text', + value, + onChange, + placeholder, + required, + min, + max, + step, +}: { + id?: string + type?: string + value: string | number + onChange: (val: string) => void + placeholder?: string + required?: boolean + min?: string | number + max?: string | number + step?: string | number +}) { + return ( + onChange(e.target.value)} + placeholder={placeholder} + required={required} + min={min} + max={max} + step={step} + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500" + /> + ) +} + +function FormTextarea({ + id, + value, + onChange, + placeholder, + rows = 3, +}: { + id?: string + value: string + onChange: (val: string) => void + placeholder?: string + rows?: number +}) { + return ( +