)}
- {/* Requirements Alert */}
{state.requirements.length === 0 && !loading && (
@@ -620,7 +112,6 @@ export default function AuditChecklistPage() {
)}
- {/* Checklist Info */}
@@ -649,7 +140,6 @@ export default function AuditChecklistPage() {
- {/* Stats */}
Konform
@@ -669,7 +159,6 @@ export default function AuditChecklistPage() {
- {/* Filter */}
Filter:
{['all', 'not-reviewed', 'non-compliant', 'partial', 'compliant'].map(f => (
@@ -690,10 +179,8 @@ export default function AuditChecklistPage() {
))}
- {/* Loading State */}
{loading &&
}
- {/* Checklist Items */}
{!loading && (
{filteredItems.map(item => (
@@ -720,61 +207,7 @@ export default function AuditChecklistPage() {
)}
- {/* Session History */}
- {pastSessions.length > 0 && (
-
-
Vergangene Audit-Sessions
-
- {pastSessions
- .filter(s => s.id !== activeSessionId)
- .map(session => {
- const statusBadge: Record
= {
- draft: 'bg-slate-100 text-slate-700',
- in_progress: 'bg-blue-100 text-blue-700',
- completed: 'bg-green-100 text-green-700',
- archived: 'bg-purple-100 text-purple-700',
- }
- const statusLabel: Record = {
- draft: 'Entwurf',
- in_progress: 'In Bearbeitung',
- completed: 'Abgeschlossen',
- archived: 'Archiviert',
- }
- return (
- router.push(`/sdk/audit-report/${session.id}`)}
- >
-
-
- {statusLabel[session.status] || session.status}
-
- {session.name}
-
- {new Date(session.created_at).toLocaleDateString('de-DE')}
-
-
-
-
- {session.completed_items}/{session.total_items} Punkte
-
-
= 80 ? 'text-green-600' :
- session.completion_percentage >= 50 ? 'text-yellow-600' : 'text-red-600'
- }`}>
- {session.completion_percentage}%
-
-
-
-
- )
- })}
-
-
- )}
+
)
}
diff --git a/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/BannerPreview.tsx b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/BannerPreview.tsx
new file mode 100644
index 0000000..7401d51
--- /dev/null
+++ b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/BannerPreview.tsx
@@ -0,0 +1,141 @@
+'use client'
+
+import { useState } from 'react'
+import { CookieBannerConfig, SupportedLanguage } from '@/lib/sdk/einwilligungen/types'
+
+interface BannerPreviewProps {
+ config: CookieBannerConfig | null
+ language: SupportedLanguage
+ device: 'desktop' | 'tablet' | 'mobile'
+}
+
+export function BannerPreview({ config, language, device }: BannerPreviewProps) {
+ const [showDetails, setShowDetails] = useState(false)
+
+ if (!config) {
+ return (
+
+
Konfiguration wird geladen...
+
+ )
+ }
+
+ const isDark = config.styling.theme === 'DARK'
+ const bgColor = isDark ? '#1e293b' : config.styling.backgroundColor || '#ffffff'
+ const textColor = isDark ? '#f1f5f9' : config.styling.textColor || '#1e293b'
+
+ const deviceWidths = { desktop: '100%', tablet: '768px', mobile: '375px' }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
{config.texts.title[language]}
+
{config.texts.description[language]}
+
+
+
+
+
+
+
+ {showDetails && (
+
+ {config.categories.map((cat) => (
+
+
+
{cat.name[language]}
+
{cat.description[language]}
+
+
+
+ ))}
+
+
+ )}
+
+
+ {config.texts.privacyPolicyLink[language]}
+
+
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/CategoryList.tsx b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/CategoryList.tsx
new file mode 100644
index 0000000..e4c38dc
--- /dev/null
+++ b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/CategoryList.tsx
@@ -0,0 +1,95 @@
+'use client'
+
+import { useState } from 'react'
+import { ChevronDown, ChevronRight } from 'lucide-react'
+import { CookieBannerConfig, SupportedLanguage } from '@/lib/sdk/einwilligungen/types'
+
+interface CategoryListProps {
+ config: CookieBannerConfig | null
+ language: SupportedLanguage
+}
+
+export function CategoryList({ config, language }: CategoryListProps) {
+ const [expandedCategories, setExpandedCategories] = useState
>(new Set())
+
+ if (!config) return null
+
+ const toggleCategory = (id: string) => {
+ setExpandedCategories((prev) => {
+ const next = new Set(prev)
+ if (next.has(id)) {
+ next.delete(id)
+ } else {
+ next.add(id)
+ }
+ return next
+ })
+ }
+
+ return (
+
+ {config.categories.map((cat) => {
+ const isExpanded = expandedCategories.has(cat.id)
+ return (
+
+
+
+ {isExpanded && (
+
+
{cat.description[language]}
+ {cat.cookies.length > 0 && (
+
+
Cookies
+
+ {cat.cookies.map((cookie, idx) => (
+
+
+ {cookie.name}
+ ({cookie.provider})
+
+
{cookie.expiry}
+
+ ))}
+
+
+ )}
+
+ )}
+
+ )
+ })}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/CookieBannerContent.tsx b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/CookieBannerContent.tsx
new file mode 100644
index 0000000..0a7d7ba
--- /dev/null
+++ b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/CookieBannerContent.tsx
@@ -0,0 +1,152 @@
+'use client'
+
+import { useState, useMemo } from 'react'
+import { useSDK } from '@/lib/sdk'
+import { useEinwilligungen } from '@/lib/sdk/einwilligungen/context'
+import {
+ generateCookieBannerConfig,
+ DEFAULT_COOKIE_BANNER_TEXTS,
+ DEFAULT_COOKIE_BANNER_STYLING,
+} from '@/lib/sdk/einwilligungen/generator/cookie-banner'
+import {
+ CookieBannerStyling,
+ CookieBannerTexts,
+ SupportedLanguage,
+} from '@/lib/sdk/einwilligungen/types'
+import { Cookie, Settings, Palette, Code, Monitor, Smartphone, Tablet } from 'lucide-react'
+import Link from 'next/link'
+import { ArrowLeft } from 'lucide-react'
+import { StylingForm } from './StylingForm'
+import { TextsForm } from './TextsForm'
+import { BannerPreview } from './BannerPreview'
+import { EmbedCodeViewer } from './EmbedCodeViewer'
+import { CategoryList } from './CategoryList'
+
+export function CookieBannerContent() {
+ const { state } = useSDK()
+ const { allDataPoints } = useEinwilligungen()
+
+ const [styling, setStyling] = useState(DEFAULT_COOKIE_BANNER_STYLING)
+ const [texts, setTexts] = useState(DEFAULT_COOKIE_BANNER_TEXTS)
+ const [language, setLanguage] = useState('de')
+ const [activeTab, setActiveTab] = useState<'styling' | 'texts' | 'embed' | 'categories'>('styling')
+ const [device, setDevice] = useState<'desktop' | 'tablet' | 'mobile'>('desktop')
+
+ const config = useMemo(() => {
+ return generateCookieBannerConfig(state.tenantId || 'demo', allDataPoints, texts, styling)
+ }, [state.tenantId, allDataPoints, texts, styling])
+
+ const cookieDataPoints = useMemo(
+ () => allDataPoints.filter((dp) => dp.cookieCategory !== null),
+ [allDataPoints]
+ )
+
+ return (
+
+
+
+ Zurueck zum Katalog
+
+
+
+
+
Cookie-Banner Konfiguration
+
Konfigurieren Sie Ihren DSGVO-konformen Cookie-Banner.
+
+
+
+
+
+
+
+
+
Kategorien
+
{config?.categories.length || 0}
+
+
+
Cookie-Datenpunkte
+
{cookieDataPoints.length}
+
+
+
Erforderlich
+
+ {config?.categories.filter((c) => c.isRequired).length || 0}
+
+
+
+
Optional
+
+ {config?.categories.filter((c) => !c.isRequired).length || 0}
+
+
+
+
+
+
+
+ {[
+ { id: 'styling', label: 'Design', icon: Palette },
+ { id: 'texts', label: 'Texte', icon: Settings },
+ { id: 'categories', label: 'Kategorien', icon: Cookie },
+ { id: 'embed', label: 'Embed-Code', icon: Code },
+ ].map(({ id, label, icon: Icon }) => (
+
+ ))}
+
+
+
+ {activeTab === 'styling' && }
+ {activeTab === 'texts' && }
+ {activeTab === 'categories' && }
+ {activeTab === 'embed' && }
+
+
+
+
+
+
Vorschau
+
+ {[
+ { id: 'desktop', icon: Monitor },
+ { id: 'tablet', icon: Tablet },
+ { id: 'mobile', icon: Smartphone },
+ ].map(({ id, icon: Icon }) => (
+
+ ))}
+
+
+
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/EmbedCodeViewer.tsx b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/EmbedCodeViewer.tsx
new file mode 100644
index 0000000..67c37ba
--- /dev/null
+++ b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/EmbedCodeViewer.tsx
@@ -0,0 +1,96 @@
+'use client'
+
+import { useState, useMemo } from 'react'
+import { Copy, Check } from 'lucide-react'
+import { CookieBannerConfig } from '@/lib/sdk/einwilligungen/types'
+import { generateEmbedCode } from '@/lib/sdk/einwilligungen/generator/cookie-banner'
+
+interface EmbedCodeViewerProps {
+ config: CookieBannerConfig | null
+}
+
+export function EmbedCodeViewer({ config }: EmbedCodeViewerProps) {
+ const [activeTab, setActiveTab] = useState<'script' | 'html' | 'css' | 'js'>('script')
+ const [copied, setCopied] = useState(false)
+
+ const embedCode = useMemo(() => {
+ if (!config) return null
+ return generateEmbedCode(config, '/datenschutz')
+ }, [config])
+
+ const copyToClipboard = async (text: string) => {
+ await navigator.clipboard.writeText(text)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ }
+
+ if (!embedCode) {
+ return (
+
+
Embed-Code wird generiert...
+
+ )
+ }
+
+ const tabs = [
+ { id: 'script', label: 'Script-Tag', content: embedCode.scriptTag },
+ { id: 'html', label: 'HTML', content: embedCode.html },
+ { id: 'css', label: 'CSS', content: embedCode.css },
+ { id: 'js', label: 'JavaScript', content: embedCode.js },
+ ] as const
+
+ const currentContent = tabs.find((t) => t.id === activeTab)?.content || ''
+
+ return (
+
+
+ {tabs.map((tab) => (
+
+ ))}
+
+
+
+
+ {currentContent}
+
+
+
+
+ {activeTab === 'script' && (
+
+
+ Integration: Fuegen Sie den Script-Tag in den{' '}
+ <head> oder vor dem
+ schliessenden{' '}
+ </body>-Tag ein.
+
+
+ )}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/StylingForm.tsx b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/StylingForm.tsx
new file mode 100644
index 0000000..d1dcb1c
--- /dev/null
+++ b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/StylingForm.tsx
@@ -0,0 +1,118 @@
+'use client'
+
+import { CookieBannerStyling } from '@/lib/sdk/einwilligungen/types'
+
+interface StylingFormProps {
+ styling: CookieBannerStyling
+ onChange: (styling: CookieBannerStyling) => void
+}
+
+export function StylingForm({ styling, onChange }: StylingFormProps) {
+ const handleChange = (field: keyof CookieBannerStyling, value: string | number) => {
+ onChange({ ...styling, [field]: value })
+ }
+
+ return (
+
+
+
+
+ {(['BOTTOM', 'TOP', 'CENTER'] as const).map((pos) => (
+
+ ))}
+
+
+
+
+
+
+ {(['LIGHT', 'DARK'] as const).map((theme) => (
+
+ ))}
+
+
+
+
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/TextsForm.tsx b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/TextsForm.tsx
new file mode 100644
index 0000000..f05137b
--- /dev/null
+++ b/admin-compliance/app/sdk/einwilligungen/cookie-banner/_components/TextsForm.tsx
@@ -0,0 +1,53 @@
+'use client'
+
+import { CookieBannerTexts, SupportedLanguage } from '@/lib/sdk/einwilligungen/types'
+
+interface TextsFormProps {
+ texts: CookieBannerTexts
+ language: SupportedLanguage
+ onChange: (texts: CookieBannerTexts) => void
+}
+
+export function TextsForm({ texts, language, onChange }: TextsFormProps) {
+ const handleChange = (field: keyof CookieBannerTexts, value: string) => {
+ onChange({
+ ...texts,
+ [field]: { ...texts[field], [language]: value },
+ })
+ }
+
+ const fields: { key: keyof CookieBannerTexts; label: string; multiline?: boolean }[] = [
+ { key: 'title', label: 'Titel' },
+ { key: 'description', label: 'Beschreibung', multiline: true },
+ { key: 'acceptAll', label: 'Alle akzeptieren Button' },
+ { key: 'rejectAll', label: 'Nur notwendige Button' },
+ { key: 'customize', label: 'Einstellungen Button' },
+ { key: 'save', label: 'Speichern Button' },
+ { key: 'privacyPolicyLink', label: 'Datenschutz-Link Text' },
+ ]
+
+ return (
+
+ {fields.map(({ key, label, multiline }) => (
+
+
+ {multiline ? (
+
+ ))}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/einwilligungen/cookie-banner/page.tsx b/admin-compliance/app/sdk/einwilligungen/cookie-banner/page.tsx
index bd7c6b2..faff017 100644
--- a/admin-compliance/app/sdk/einwilligungen/cookie-banner/page.tsx
+++ b/admin-compliance/app/sdk/einwilligungen/cookie-banner/page.tsx
@@ -6,753 +6,8 @@
* Konfiguriert den Cookie-Banner basierend auf dem Datenpunktkatalog.
*/
-import { useState, useEffect, useMemo } from 'react'
-import { useSDK } from '@/lib/sdk'
-import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
-import {
- EinwilligungenProvider,
- useEinwilligungen,
-} from '@/lib/sdk/einwilligungen/context'
-import {
- generateCookieBannerConfig,
- generateEmbedCode,
- DEFAULT_COOKIE_BANNER_TEXTS,
- DEFAULT_COOKIE_BANNER_STYLING,
-} from '@/lib/sdk/einwilligungen/generator/cookie-banner'
-import {
- CookieBannerConfig,
- CookieBannerStyling,
- CookieBannerTexts,
- SupportedLanguage,
-} from '@/lib/sdk/einwilligungen/types'
-import {
- Cookie,
- Settings,
- Palette,
- Code,
- Copy,
- Check,
- Eye,
- ArrowLeft,
- Monitor,
- Smartphone,
- Tablet,
- ChevronDown,
- ChevronRight,
- ExternalLink,
-} from 'lucide-react'
-import Link from 'next/link'
-
-// =============================================================================
-// STYLING FORM
-// =============================================================================
-
-interface StylingFormProps {
- styling: CookieBannerStyling
- onChange: (styling: CookieBannerStyling) => void
-}
-
-function StylingForm({ styling, onChange }: StylingFormProps) {
- const handleChange = (field: keyof CookieBannerStyling, value: string | number) => {
- onChange({ ...styling, [field]: value })
- }
-
- return (
-
- {/* Position */}
-
-
-
- {(['BOTTOM', 'TOP', 'CENTER'] as const).map((pos) => (
-
- ))}
-
-
-
- {/* Theme */}
-
-
-
- {(['LIGHT', 'DARK'] as const).map((theme) => (
-
- ))}
-
-
-
- {/* Colors */}
-
-
- {/* Border Radius & Max Width */}
-
-
- )
-}
-
-// =============================================================================
-// TEXTS FORM
-// =============================================================================
-
-interface TextsFormProps {
- texts: CookieBannerTexts
- language: SupportedLanguage
- onChange: (texts: CookieBannerTexts) => void
-}
-
-function TextsForm({ texts, language, onChange }: TextsFormProps) {
- const handleChange = (field: keyof CookieBannerTexts, value: string) => {
- onChange({
- ...texts,
- [field]: { ...texts[field], [language]: value },
- })
- }
-
- const fields: { key: keyof CookieBannerTexts; label: string; multiline?: boolean }[] = [
- { key: 'title', label: 'Titel' },
- { key: 'description', label: 'Beschreibung', multiline: true },
- { key: 'acceptAll', label: 'Alle akzeptieren Button' },
- { key: 'rejectAll', label: 'Nur notwendige Button' },
- { key: 'customize', label: 'Einstellungen Button' },
- { key: 'save', label: 'Speichern Button' },
- { key: 'privacyPolicyLink', label: 'Datenschutz-Link Text' },
- ]
-
- return (
-
- {fields.map(({ key, label, multiline }) => (
-
-
- {multiline ? (
-
- ))}
-
- )
-}
-
-// =============================================================================
-// BANNER PREVIEW
-// =============================================================================
-
-interface BannerPreviewProps {
- config: CookieBannerConfig | null
- language: SupportedLanguage
- device: 'desktop' | 'tablet' | 'mobile'
-}
-
-function BannerPreview({ config, language, device }: BannerPreviewProps) {
- const [showDetails, setShowDetails] = useState(false)
-
- if (!config) {
- return (
-
-
Konfiguration wird geladen...
-
- )
- }
-
- const isDark = config.styling.theme === 'DARK'
- const bgColor = isDark ? '#1e293b' : config.styling.backgroundColor || '#ffffff'
- const textColor = isDark ? '#f1f5f9' : config.styling.textColor || '#1e293b'
-
- const deviceWidths = {
- desktop: '100%',
- tablet: '768px',
- mobile: '375px',
- }
-
- return (
-
- {/* Simulated Browser */}
-
-
- {/* Page Content */}
-
- {/* Placeholder Content */}
-
-
- {/* Overlay */}
-
-
- {/* Cookie Banner */}
-
-
-
- {config.texts.title[language]}
-
-
- {config.texts.description[language]}
-
-
-
-
-
-
-
-
- {showDetails && (
-
- {config.categories.map((cat) => (
-
-
-
{cat.name[language]}
-
{cat.description[language]}
-
-
-
- ))}
-
-
- )}
-
-
- {config.texts.privacyPolicyLink[language]}
-
-
-
-
-
- )
-}
-
-// =============================================================================
-// EMBED CODE VIEWER
-// =============================================================================
-
-interface EmbedCodeViewerProps {
- config: CookieBannerConfig | null
-}
-
-function EmbedCodeViewer({ config }: EmbedCodeViewerProps) {
- const [activeTab, setActiveTab] = useState<'script' | 'html' | 'css' | 'js'>('script')
- const [copied, setCopied] = useState(false)
-
- const embedCode = useMemo(() => {
- if (!config) return null
- return generateEmbedCode(config, '/datenschutz')
- }, [config])
-
- const copyToClipboard = async (text: string) => {
- await navigator.clipboard.writeText(text)
- setCopied(true)
- setTimeout(() => setCopied(false), 2000)
- }
-
- if (!embedCode) {
- return (
-
-
Embed-Code wird generiert...
-
- )
- }
-
- const tabs = [
- { id: 'script', label: 'Script-Tag', content: embedCode.scriptTag },
- { id: 'html', label: 'HTML', content: embedCode.html },
- { id: 'css', label: 'CSS', content: embedCode.css },
- { id: 'js', label: 'JavaScript', content: embedCode.js },
- ] as const
-
- const currentContent = tabs.find((t) => t.id === activeTab)?.content || ''
-
- return (
-
- {/* Tabs */}
-
- {tabs.map((tab) => (
-
- ))}
-
-
- {/* Code */}
-
-
- {currentContent}
-
-
-
-
- {/* Integration Instructions */}
- {activeTab === 'script' && (
-
-
- Integration: Fuegen Sie den Script-Tag in den{' '}
- <head> oder vor dem
- schliessenden{' '}
- </body>-Tag ein.
-
-
- )}
-
- )
-}
-
-// =============================================================================
-// CATEGORY LIST
-// =============================================================================
-
-interface CategoryListProps {
- config: CookieBannerConfig | null
- language: SupportedLanguage
-}
-
-function CategoryList({ config, language }: CategoryListProps) {
- const [expandedCategories, setExpandedCategories] = useState>(new Set())
-
- if (!config) return null
-
- const toggleCategory = (id: string) => {
- setExpandedCategories((prev) => {
- const next = new Set(prev)
- if (next.has(id)) {
- next.delete(id)
- } else {
- next.add(id)
- }
- return next
- })
- }
-
- return (
-
- {config.categories.map((cat) => {
- const isExpanded = expandedCategories.has(cat.id)
- return (
-
-
-
- {isExpanded && (
-
-
{cat.description[language]}
-
- {cat.cookies.length > 0 && (
-
-
Cookies
-
- {cat.cookies.map((cookie, idx) => (
-
-
- {cookie.name}
- ({cookie.provider})
-
-
{cookie.expiry}
-
- ))}
-
-
- )}
-
- )}
-
- )
- })}
-
- )
-}
-
-// =============================================================================
-// MAIN CONTENT
-// =============================================================================
-
-function CookieBannerContent() {
- const { state } = useSDK()
- const { allDataPoints } = useEinwilligungen()
-
- const [styling, setStyling] = useState(DEFAULT_COOKIE_BANNER_STYLING)
- const [texts, setTexts] = useState(DEFAULT_COOKIE_BANNER_TEXTS)
- const [language, setLanguage] = useState('de')
- const [activeTab, setActiveTab] = useState<'styling' | 'texts' | 'embed' | 'categories'>('styling')
- const [device, setDevice] = useState<'desktop' | 'tablet' | 'mobile'>('desktop')
-
- const config = useMemo(() => {
- return generateCookieBannerConfig(
- state.tenantId || 'demo',
- allDataPoints,
- texts,
- styling
- )
- }, [state.tenantId, allDataPoints, texts, styling])
-
- const cookieDataPoints = useMemo(
- () => allDataPoints.filter((dp) => dp.cookieCategory !== null),
- [allDataPoints]
- )
-
- return (
-
- {/* Back Link */}
-
-
- Zurueck zum Katalog
-
-
- {/* Header */}
-
-
-
Cookie-Banner Konfiguration
-
- Konfigurieren Sie Ihren DSGVO-konformen Cookie-Banner.
-
-
-
-
-
-
-
- {/* Stats */}
-
-
-
Kategorien
-
{config?.categories.length || 0}
-
-
-
Cookie-Datenpunkte
-
{cookieDataPoints.length}
-
-
-
Erforderlich
-
- {config?.categories.filter((c) => c.isRequired).length || 0}
-
-
-
-
Optional
-
- {config?.categories.filter((c) => !c.isRequired).length || 0}
-
-
-
-
- {/* Two Column Layout */}
-
- {/* Left: Configuration */}
-
- {/* Tabs */}
-
- {[
- { id: 'styling', label: 'Design', icon: Palette },
- { id: 'texts', label: 'Texte', icon: Settings },
- { id: 'categories', label: 'Kategorien', icon: Cookie },
- { id: 'embed', label: 'Embed-Code', icon: Code },
- ].map(({ id, label, icon: Icon }) => (
-
- ))}
-
-
- {/* Tab Content */}
-
- {activeTab === 'styling' && (
-
- )}
- {activeTab === 'texts' && (
-
- )}
- {activeTab === 'categories' && (
-
- )}
- {activeTab === 'embed' && }
-
-
-
- {/* Right: Preview */}
-
- {/* Device Selector */}
-
-
Vorschau
-
- {[
- { id: 'desktop', icon: Monitor },
- { id: 'tablet', icon: Tablet },
- { id: 'mobile', icon: Smartphone },
- ].map(({ id, icon: Icon }) => (
-
- ))}
-
-
-
- {/* Preview */}
-
-
-
-
- )
-}
-
-// =============================================================================
-// MAIN PAGE
-// =============================================================================
+import { EinwilligungenProvider } from '@/lib/sdk/einwilligungen/context'
+import { CookieBannerContent } from './_components/CookieBannerContent'
export default function CookieBannerPage() {
return (
diff --git a/admin-compliance/app/sdk/escalations/_components/EscalationCard.tsx b/admin-compliance/app/sdk/escalations/_components/EscalationCard.tsx
new file mode 100644
index 0000000..4173b1e
--- /dev/null
+++ b/admin-compliance/app/sdk/escalations/_components/EscalationCard.tsx
@@ -0,0 +1,69 @@
+'use client'
+
+import { Escalation, priorityColors, priorityLabels, statusColors, statusLabels, categoryLabels, formatDate } from './types'
+
+interface CardProps {
+ escalation: Escalation
+ onClick: () => void
+}
+
+export function EscalationCard({ escalation, onClick }: CardProps) {
+ return (
+
+
+
+
+
+ {priorityLabels[escalation.priority]}
+
+ {escalation.category && (
+
+ {categoryLabels[escalation.category] || escalation.category}
+
+ )}
+
+ {statusLabels[escalation.status]}
+
+
+
{escalation.title}
+ {escalation.description && (
+
{escalation.description}
+ )}
+
+
+
+
+
+ {escalation.assignee && (
+
+ Zugewiesen:
+ {escalation.assignee}
+
+ )}
+ {escalation.due_date && (
+
+ Frist:
+ {formatDate(escalation.due_date)}
+
+ )}
+
+ Erstellt:
+ {formatDate(escalation.created_at)}
+
+
+
+
+ {escalation.id}
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/escalations/_components/EscalationCreateModal.tsx b/admin-compliance/app/sdk/escalations/_components/EscalationCreateModal.tsx
new file mode 100644
index 0000000..f95bfdb
--- /dev/null
+++ b/admin-compliance/app/sdk/escalations/_components/EscalationCreateModal.tsx
@@ -0,0 +1,155 @@
+'use client'
+
+import { useState } from 'react'
+
+interface CreateModalProps {
+ onClose: () => void
+ onCreated: () => void
+}
+
+export function EscalationCreateModal({ onClose, onCreated }: CreateModalProps) {
+ const [title, setTitle] = useState('')
+ const [description, setDescription] = useState('')
+ const [priority, setPriority] = useState('medium')
+ const [category, setCategory] = useState('')
+ const [assignee, setAssignee] = useState('')
+ const [dueDate, setDueDate] = useState('')
+ const [saving, setSaving] = useState(false)
+ const [error, setError] = useState(null)
+
+ async function handleSave() {
+ if (!title.trim()) {
+ setError('Titel ist erforderlich.')
+ return
+ }
+ setSaving(true)
+ setError(null)
+ try {
+ const res = await fetch('/api/sdk/v1/escalations', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ title: title.trim(),
+ description: description.trim() || null,
+ priority,
+ category: category || null,
+ assignee: assignee.trim() || null,
+ due_date: dueDate || null,
+ }),
+ })
+ if (!res.ok) {
+ const err = await res.json()
+ throw new Error(err.detail || err.error || 'Fehler beim Erstellen')
+ }
+ onCreated()
+ onClose()
+ } catch (e: unknown) {
+ setError(e instanceof Error ? e.message : 'Unbekannter Fehler')
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ return (
+
+
+
+
Neue Eskalation erstellen
+
+
+ {error && (
+
{error}
+ )}
+
+
+ setTitle(e.target.value)}
+ className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
+ placeholder="Kurze Beschreibung der Eskalation"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/escalations/_components/EscalationDetailDrawer.tsx b/admin-compliance/app/sdk/escalations/_components/EscalationDetailDrawer.tsx
new file mode 100644
index 0000000..9cb05ce
--- /dev/null
+++ b/admin-compliance/app/sdk/escalations/_components/EscalationDetailDrawer.tsx
@@ -0,0 +1,238 @@
+'use client'
+
+import { useState } from 'react'
+import { Escalation, priorityColors, priorityLabels, statusColors, statusLabels, categoryLabels, formatDate } from './types'
+
+interface DrawerProps {
+ escalation: Escalation
+ onClose: () => void
+ onUpdated: () => void
+}
+
+export function EscalationDetailDrawer({ escalation, onClose, onUpdated }: DrawerProps) {
+ const [editAssignee, setEditAssignee] = useState(escalation.assignee || '')
+ const [editPriority, setEditPriority] = useState(escalation.priority)
+ const [editDueDate, setEditDueDate] = useState(
+ escalation.due_date ? escalation.due_date.slice(0, 10) : ''
+ )
+ const [saving, setSaving] = useState(false)
+ const [statusSaving, setStatusSaving] = useState(false)
+ const [isDeleting, setIsDeleting] = useState(false)
+
+ async function handleDeleteEscalation() {
+ if (!window.confirm(`Eskalation "${escalation.title}" wirklich löschen?`)) return
+ setIsDeleting(true)
+ try {
+ const res = await fetch(`/api/sdk/v1/escalations/${escalation.id}`, { method: 'DELETE' })
+ if (!res.ok) throw new Error(`Fehler: ${res.status}`)
+ onUpdated()
+ onClose()
+ } catch (err) {
+ console.error('Löschen fehlgeschlagen:', err)
+ alert('Löschen fehlgeschlagen.')
+ } finally {
+ setIsDeleting(false)
+ }
+ }
+
+ async function handleSaveEdit() {
+ setSaving(true)
+ try {
+ await fetch(`/api/sdk/v1/escalations/${escalation.id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ assignee: editAssignee || null,
+ priority: editPriority,
+ due_date: editDueDate || null,
+ }),
+ })
+ onUpdated()
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ async function handleStatusChange(newStatus: string) {
+ setStatusSaving(true)
+ try {
+ await fetch(`/api/sdk/v1/escalations/${escalation.id}/status`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ status: newStatus }),
+ })
+ onUpdated()
+ onClose()
+ } finally {
+ setStatusSaving(false)
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+ {priorityLabels[escalation.priority]}
+
+
+ {statusLabels[escalation.status]}
+
+ {escalation.category && (
+
+ {categoryLabels[escalation.category] || escalation.category}
+
+ )}
+
+
{escalation.title}
+
+
+
+
+
+ {escalation.description && (
+
+
Beschreibung
+
{escalation.description}
+
+ )}
+
+
+
+
Erstellt
+
{formatDate(escalation.created_at)}
+
+ {escalation.reporter && (
+
+
Gemeldet von
+
{escalation.reporter}
+
+ )}
+ {escalation.source_module && (
+
+
Quell-Modul
+
{escalation.source_module}
+
+ )}
+ {escalation.resolved_at && (
+
+
Geloest am
+
{formatDate(escalation.resolved_at)}
+
+ )}
+
+
+
+
Bearbeiten
+
+
+ setEditAssignee(e.target.value)}
+ className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
+ placeholder="Name oder Team"
+ />
+
+
+
+
+
+
+
+
+ setEditDueDate(e.target.value)}
+ className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
+ />
+
+
+
+
+
+
+
Status-Aktionen
+
+ {escalation.status === 'open' && (
+
+ )}
+ {escalation.status === 'in_progress' && (
+
+ )}
+ {(escalation.status === 'escalated' || escalation.status === 'in_progress') && (
+
+ )}
+ {escalation.status === 'resolved' && (
+
+ )}
+ {escalation.status === 'closed' && (
+
Eskalation ist geschlossen.
+ )}
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/escalations/_components/types.ts b/admin-compliance/app/sdk/escalations/_components/types.ts
new file mode 100644
index 0000000..72950c5
--- /dev/null
+++ b/admin-compliance/app/sdk/escalations/_components/types.ts
@@ -0,0 +1,73 @@
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export interface Escalation {
+ id: string
+ title: string
+ description: string | null
+ priority: 'low' | 'medium' | 'high' | 'critical'
+ status: 'open' | 'in_progress' | 'escalated' | 'resolved' | 'closed'
+ category: string | null
+ assignee: string | null
+ reporter: string | null
+ source_module: string | null
+ due_date: string | null
+ resolved_at: string | null
+ created_at: string
+ updated_at: string
+}
+
+export interface EscalationStats {
+ by_status: Record
+ by_priority: Record
+ total: number
+ active: number
+}
+
+// =============================================================================
+// HELPERS
+// =============================================================================
+
+export const priorityColors: Record = {
+ critical: 'bg-red-500 text-white',
+ high: 'bg-orange-500 text-white',
+ medium: 'bg-yellow-500 text-white',
+ low: 'bg-green-500 text-white',
+}
+
+export const priorityLabels: Record = {
+ critical: 'Kritisch',
+ high: 'Hoch',
+ medium: 'Mittel',
+ low: 'Niedrig',
+}
+
+export const statusColors: Record = {
+ open: 'bg-blue-100 text-blue-700',
+ in_progress: 'bg-yellow-100 text-yellow-700',
+ escalated: 'bg-red-100 text-red-700',
+ resolved: 'bg-green-100 text-green-700',
+ closed: 'bg-gray-100 text-gray-600',
+}
+
+export const statusLabels: Record = {
+ open: 'Offen',
+ in_progress: 'In Bearbeitung',
+ escalated: 'Eskaliert',
+ resolved: 'Geloest',
+ closed: 'Geschlossen',
+}
+
+export const categoryLabels: Record = {
+ dsgvo_breach: 'DSGVO-Verletzung',
+ ai_act: 'AI Act',
+ vendor: 'Vendor',
+ internal: 'Intern',
+ other: 'Sonstiges',
+}
+
+export function formatDate(iso: string | null): string {
+ if (!iso) return '—'
+ return new Date(iso).toLocaleDateString('de-DE')
+}
diff --git a/admin-compliance/app/sdk/escalations/_hooks/useEscalations.ts b/admin-compliance/app/sdk/escalations/_hooks/useEscalations.ts
new file mode 100644
index 0000000..9687b33
--- /dev/null
+++ b/admin-compliance/app/sdk/escalations/_hooks/useEscalations.ts
@@ -0,0 +1,53 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import { Escalation, EscalationStats } from '../_components/types'
+
+export function useEscalations(filter: string) {
+ const [escalations, setEscalations] = useState([])
+ const [stats, setStats] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ async function loadEscalations() {
+ try {
+ const params = new URLSearchParams({ limit: '100' })
+ if (filter !== 'all' && ['open', 'in_progress', 'escalated', 'resolved', 'closed'].includes(filter)) {
+ params.set('status', filter)
+ } else if (filter !== 'all' && ['low', 'medium', 'high', 'critical'].includes(filter)) {
+ params.set('priority', filter)
+ }
+ const res = await fetch(`/api/sdk/v1/escalations?${params}`)
+ if (res.ok) {
+ const data = await res.json()
+ setEscalations(data.items || [])
+ }
+ } catch (e) {
+ console.error('Failed to load escalations', e)
+ }
+ }
+
+ async function loadStats() {
+ try {
+ const res = await fetch('/api/sdk/v1/escalations/stats')
+ if (res.ok) {
+ const data = await res.json()
+ setStats(data)
+ }
+ } catch (e) {
+ console.error('Failed to load stats', e)
+ }
+ }
+
+ async function loadAll() {
+ setLoading(true)
+ await Promise.all([loadEscalations(), loadStats()])
+ setLoading(false)
+ }
+
+ useEffect(() => {
+ loadAll()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [filter])
+
+ return { escalations, stats, loading, loadAll }
+}
diff --git a/admin-compliance/app/sdk/escalations/page.tsx b/admin-compliance/app/sdk/escalations/page.tsx
index aedc1e9..88620b9 100644
--- a/admin-compliance/app/sdk/escalations/page.tsx
+++ b/admin-compliance/app/sdk/escalations/page.tsx
@@ -1,623 +1,28 @@
'use client'
-import React, { useState, useEffect } from 'react'
-import { useSDK } from '@/lib/sdk'
+import React, { useState } from 'react'
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
-
-// =============================================================================
-// TYPES
-// =============================================================================
-
-interface Escalation {
- id: string
- title: string
- description: string | null
- priority: 'low' | 'medium' | 'high' | 'critical'
- status: 'open' | 'in_progress' | 'escalated' | 'resolved' | 'closed'
- category: string | null
- assignee: string | null
- reporter: string | null
- source_module: string | null
- due_date: string | null
- resolved_at: string | null
- created_at: string
- updated_at: string
-}
-
-interface EscalationStats {
- by_status: Record
- by_priority: Record
- total: number
- active: number
-}
-
-// =============================================================================
-// HELPERS
-// =============================================================================
-
-const priorityColors: Record = {
- critical: 'bg-red-500 text-white',
- high: 'bg-orange-500 text-white',
- medium: 'bg-yellow-500 text-white',
- low: 'bg-green-500 text-white',
-}
-
-const priorityLabels: Record = {
- critical: 'Kritisch',
- high: 'Hoch',
- medium: 'Mittel',
- low: 'Niedrig',
-}
-
-const statusColors: Record = {
- open: 'bg-blue-100 text-blue-700',
- in_progress: 'bg-yellow-100 text-yellow-700',
- escalated: 'bg-red-100 text-red-700',
- resolved: 'bg-green-100 text-green-700',
- closed: 'bg-gray-100 text-gray-600',
-}
-
-const statusLabels: Record = {
- open: 'Offen',
- in_progress: 'In Bearbeitung',
- escalated: 'Eskaliert',
- resolved: 'Geloest',
- closed: 'Geschlossen',
-}
-
-const categoryLabels: Record = {
- dsgvo_breach: 'DSGVO-Verletzung',
- ai_act: 'AI Act',
- vendor: 'Vendor',
- internal: 'Intern',
- other: 'Sonstiges',
-}
-
-function formatDate(iso: string | null): string {
- if (!iso) return '—'
- return new Date(iso).toLocaleDateString('de-DE')
-}
-
-// =============================================================================
-// CREATE MODAL
-// =============================================================================
-
-interface CreateModalProps {
- onClose: () => void
- onCreated: () => void
-}
-
-function EscalationCreateModal({ onClose, onCreated }: CreateModalProps) {
- const [title, setTitle] = useState('')
- const [description, setDescription] = useState('')
- const [priority, setPriority] = useState('medium')
- const [category, setCategory] = useState('')
- const [assignee, setAssignee] = useState('')
- const [dueDate, setDueDate] = useState('')
- const [saving, setSaving] = useState(false)
- const [error, setError] = useState(null)
-
- async function handleSave() {
- if (!title.trim()) {
- setError('Titel ist erforderlich.')
- return
- }
- setSaving(true)
- setError(null)
- try {
- const res = await fetch('/api/sdk/v1/escalations', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- title: title.trim(),
- description: description.trim() || null,
- priority,
- category: category || null,
- assignee: assignee.trim() || null,
- due_date: dueDate || null,
- }),
- })
- if (!res.ok) {
- const err = await res.json()
- throw new Error(err.detail || err.error || 'Fehler beim Erstellen')
- }
- onCreated()
- onClose()
- } catch (e: unknown) {
- setError(e instanceof Error ? e.message : 'Unbekannter Fehler')
- } finally {
- setSaving(false)
- }
- }
-
- return (
-
-
-
-
Neue Eskalation erstellen
-
-
- {error && (
-
{error}
- )}
-
-
- setTitle(e.target.value)}
- className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
- placeholder="Kurze Beschreibung der Eskalation"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-// =============================================================================
-// DETAIL DRAWER
-// =============================================================================
-
-interface DrawerProps {
- escalation: Escalation
- onClose: () => void
- onUpdated: () => void
-}
-
-function EscalationDetailDrawer({ escalation, onClose, onUpdated }: DrawerProps) {
- const [editAssignee, setEditAssignee] = useState(escalation.assignee || '')
- const [editPriority, setEditPriority] = useState(escalation.priority)
- const [editDueDate, setEditDueDate] = useState(
- escalation.due_date ? escalation.due_date.slice(0, 10) : ''
- )
- const [saving, setSaving] = useState(false)
- const [statusSaving, setStatusSaving] = useState(false)
- const [isDeleting, setIsDeleting] = useState(false)
-
- async function handleDeleteEscalation() {
- if (!window.confirm(`Eskalation "${escalation.title}" wirklich löschen?`)) return
- setIsDeleting(true)
- try {
- const res = await fetch(`/api/sdk/v1/escalations/${escalation.id}`, { method: 'DELETE' })
- if (!res.ok) throw new Error(`Fehler: ${res.status}`)
- onUpdated()
- onClose()
- } catch (err) {
- console.error('Löschen fehlgeschlagen:', err)
- alert('Löschen fehlgeschlagen.')
- } finally {
- setIsDeleting(false)
- }
- }
-
- async function handleSaveEdit() {
- setSaving(true)
- try {
- await fetch(`/api/sdk/v1/escalations/${escalation.id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- assignee: editAssignee || null,
- priority: editPriority,
- due_date: editDueDate || null,
- }),
- })
- onUpdated()
- } finally {
- setSaving(false)
- }
- }
-
- async function handleStatusChange(newStatus: string) {
- setStatusSaving(true)
- try {
- await fetch(`/api/sdk/v1/escalations/${escalation.id}/status`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ status: newStatus }),
- })
- onUpdated()
- onClose()
- } finally {
- setStatusSaving(false)
- }
- }
-
- return (
-
- {/* Backdrop */}
-
- {/* Panel */}
-
- {/* Header */}
-
-
-
-
- {priorityLabels[escalation.priority]}
-
-
- {statusLabels[escalation.status]}
-
- {escalation.category && (
-
- {categoryLabels[escalation.category] || escalation.category}
-
- )}
-
-
{escalation.title}
-
-
-
-
-
- {/* Description */}
- {escalation.description && (
-
-
Beschreibung
-
{escalation.description}
-
- )}
-
- {/* Meta */}
-
-
-
Erstellt
-
{formatDate(escalation.created_at)}
-
- {escalation.reporter && (
-
-
Gemeldet von
-
{escalation.reporter}
-
- )}
- {escalation.source_module && (
-
-
Quell-Modul
-
{escalation.source_module}
-
- )}
- {escalation.resolved_at && (
-
-
Geloest am
-
{formatDate(escalation.resolved_at)}
-
- )}
-
-
- {/* Edit fields */}
-
-
Bearbeiten
-
-
- setEditAssignee(e.target.value)}
- className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
- placeholder="Name oder Team"
- />
-
-
-
-
-
-
-
-
- setEditDueDate(e.target.value)}
- className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
- />
-
-
-
-
-
- {/* Status transitions */}
-
-
Status-Aktionen
-
- {escalation.status === 'open' && (
-
- )}
- {escalation.status === 'in_progress' && (
-
- )}
- {(escalation.status === 'escalated' || escalation.status === 'in_progress') && (
-
- )}
- {escalation.status === 'resolved' && (
-
- )}
- {escalation.status === 'closed' && (
-
Eskalation ist geschlossen.
- )}
-
-
-
- {/* Delete */}
-
-
-
-
-
-
- )
-}
-
-// =============================================================================
-// ESCALATION CARD
-// =============================================================================
-
-interface CardProps {
- escalation: Escalation
- onClick: () => void
-}
-
-function EscalationCard({ escalation, onClick }: CardProps) {
- return (
-
-
-
-
-
- {priorityLabels[escalation.priority]}
-
- {escalation.category && (
-
- {categoryLabels[escalation.category] || escalation.category}
-
- )}
-
- {statusLabels[escalation.status]}
-
-
-
{escalation.title}
- {escalation.description && (
-
{escalation.description}
- )}
-
-
-
-
-
- {escalation.assignee && (
-
- Zugewiesen:
- {escalation.assignee}
-
- )}
- {escalation.due_date && (
-
- Frist:
- {formatDate(escalation.due_date)}
-
- )}
-
- Erstellt:
- {formatDate(escalation.created_at)}
-
-
-
-
- {escalation.id}
-
-
- )
-}
-
-// =============================================================================
-// MAIN PAGE
-// =============================================================================
+import { useEscalations } from './_hooks/useEscalations'
+import { EscalationCard } from './_components/EscalationCard'
+import { EscalationCreateModal } from './_components/EscalationCreateModal'
+import { EscalationDetailDrawer } from './_components/EscalationDetailDrawer'
+import { Escalation } from './_components/types'
export default function EscalationsPage() {
- const { state } = useSDK()
- const [escalations, setEscalations] = useState([])
- const [stats, setStats] = useState(null)
- const [loading, setLoading] = useState(true)
const [filter, setFilter] = useState('all')
const [showCreateModal, setShowCreateModal] = useState(false)
const [selectedEscalation, setSelectedEscalation] = useState(null)
-
- async function loadEscalations() {
- try {
- const params = new URLSearchParams({ limit: '100' })
- if (filter !== 'all' && ['open', 'in_progress', 'escalated', 'resolved', 'closed'].includes(filter)) {
- params.set('status', filter)
- } else if (filter !== 'all' && ['low', 'medium', 'high', 'critical'].includes(filter)) {
- params.set('priority', filter)
- }
- const res = await fetch(`/api/sdk/v1/escalations?${params}`)
- if (res.ok) {
- const data = await res.json()
- setEscalations(data.items || [])
- }
- } catch (e) {
- console.error('Failed to load escalations', e)
- }
- }
-
- async function loadStats() {
- try {
- const res = await fetch('/api/sdk/v1/escalations/stats')
- if (res.ok) {
- const data = await res.json()
- setStats(data)
- }
- } catch (e) {
- console.error('Failed to load stats', e)
- }
- }
-
- async function loadAll() {
- setLoading(true)
- await Promise.all([loadEscalations(), loadStats()])
- setLoading(false)
- }
-
- useEffect(() => {
- loadAll()
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [filter])
+ const { escalations, stats, loading, loadAll } = useEscalations(filter)
const criticalCount = stats?.by_priority?.critical ?? 0
const escalatedCount = stats?.by_status?.escalated ?? 0
const openCount = stats?.by_status?.open ?? 0
const activeCount = stats?.active ?? 0
- const filteredEscalations = filter === 'all' || ['open', 'in_progress', 'escalated', 'resolved', 'closed'].includes(filter) || ['low', 'medium', 'high', 'critical'].includes(filter)
- ? escalations
- : escalations
-
const stepInfo = STEP_EXPLANATIONS['escalations']
return (
- {/* Step Header */}
- {/* Stats */}
Gesamt aktiv
-
- {loading ? '…' : activeCount}
-
+
{loading ? '…' : activeCount}
Kritisch
@@ -658,7 +60,6 @@ export default function EscalationsPage() {
- {/* Critical Alert */}
{criticalCount > 0 && (
@@ -673,7 +74,6 @@ export default function EscalationsPage() {
)}
- {/* Filter */}
Filter:
{[
@@ -699,12 +99,11 @@ export default function EscalationsPage() {
))}
- {/* List */}
{loading ? (
Lade Eskalationen…
) : (
- {filteredEscalations
+ {escalations
.sort((a, b) => {
const priorityOrder: Record
= { critical: 0, high: 1, medium: 2, low: 3 }
const statusOrder: Record = { escalated: 0, open: 1, in_progress: 2, resolved: 3, closed: 4 }
@@ -720,7 +119,7 @@ export default function EscalationsPage() {
/>
))}
- {filteredEscalations.length === 0 && (
+ {escalations.length === 0 && (
)}
- {/* Modals */}
{showCreateModal && (
setShowCreateModal(false)}