Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
629 lines
28 KiB
TypeScript
629 lines
28 KiB
TypeScript
'use client'
|
||
|
||
/**
|
||
* Consent Admin Panel
|
||
*
|
||
* Admin interface for managing:
|
||
* - Documents (AGB, Privacy, etc.)
|
||
* - Document Versions
|
||
* - Email Templates
|
||
* - Statistics
|
||
*/
|
||
|
||
import { useState, useEffect } from 'react'
|
||
import AdminLayout from '@/components/admin/AdminLayout'
|
||
|
||
// API Proxy URL (avoids CORS issues)
|
||
const API_BASE = '/api/admin/consent'
|
||
|
||
type Tab = 'documents' | 'versions' | 'emails' | 'gdpr' | 'stats'
|
||
|
||
interface Document {
|
||
id: string
|
||
type: string
|
||
name: string
|
||
description: string
|
||
mandatory: boolean
|
||
created_at: string
|
||
updated_at: string
|
||
}
|
||
|
||
interface Version {
|
||
id: string
|
||
document_id: string
|
||
version: string
|
||
language: string
|
||
title: string
|
||
content: string
|
||
status: string
|
||
created_at: string
|
||
}
|
||
|
||
export default function ConsentAdminPage() {
|
||
const [activeTab, setActiveTab] = useState<Tab>('documents')
|
||
const [documents, setDocuments] = useState<Document[]>([])
|
||
const [versions, setVersions] = useState<Version[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const [selectedDocument, setSelectedDocument] = useState<string>('')
|
||
|
||
// Auth token (in production, get from auth context)
|
||
const [authToken, setAuthToken] = useState<string>('')
|
||
|
||
useEffect(() => {
|
||
// Get token from localStorage
|
||
const token = localStorage.getItem('bp_admin_token')
|
||
if (token) {
|
||
setAuthToken(token)
|
||
}
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
if (activeTab === 'documents') {
|
||
loadDocuments()
|
||
} else if (activeTab === 'versions' && selectedDocument) {
|
||
loadVersions(selectedDocument)
|
||
}
|
||
}, [activeTab, selectedDocument, authToken])
|
||
|
||
async function loadDocuments() {
|
||
setLoading(true)
|
||
setError(null)
|
||
try {
|
||
const res = await fetch(`${API_BASE}/documents`, {
|
||
headers: authToken ? { 'Authorization': `Bearer ${authToken}` } : {}
|
||
})
|
||
if (res.ok) {
|
||
const data = await res.json()
|
||
setDocuments(data.documents || [])
|
||
} else {
|
||
const errorData = await res.json().catch(() => ({}))
|
||
setError(errorData.error || 'Fehler beim Laden der Dokumente')
|
||
}
|
||
} catch (err) {
|
||
setError('Verbindungsfehler zum Server')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
async function loadVersions(docId: string) {
|
||
setLoading(true)
|
||
setError(null)
|
||
try {
|
||
const res = await fetch(`${API_BASE}/documents/${docId}/versions`, {
|
||
headers: authToken ? { 'Authorization': `Bearer ${authToken}` } : {}
|
||
})
|
||
if (res.ok) {
|
||
const data = await res.json()
|
||
setVersions(data.versions || [])
|
||
} else {
|
||
const errorData = await res.json().catch(() => ({}))
|
||
setError(errorData.error || 'Fehler beim Laden der Versionen')
|
||
}
|
||
} catch (err) {
|
||
setError('Verbindungsfehler zum Server')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const tabs: { id: Tab; label: string }[] = [
|
||
{ id: 'documents', label: 'Dokumente' },
|
||
{ id: 'versions', label: 'Versionen' },
|
||
{ id: 'emails', label: 'E-Mail Vorlagen' },
|
||
{ id: 'gdpr', label: 'DSGVO Prozesse' },
|
||
{ id: 'stats', label: 'Statistiken' },
|
||
]
|
||
|
||
// 16 Lifecycle Email Templates
|
||
const emailTemplates = [
|
||
// Onboarding
|
||
{ name: 'Willkommens-E-Mail', key: 'welcome', category: 'onboarding', description: 'Wird bei Registrierung versendet' },
|
||
{ name: 'E-Mail Bestätigung', key: 'email_verification', category: 'onboarding', description: 'Bestätigungslink für E-Mail-Adresse' },
|
||
{ name: 'Konto aktiviert', key: 'account_activated', category: 'onboarding', description: 'Bestätigung der Kontoaktivierung' },
|
||
// Security
|
||
{ name: 'Passwort zurücksetzen', key: 'password_reset', category: 'security', description: 'Link zum Zurücksetzen des Passworts' },
|
||
{ name: 'Passwort geändert', key: 'password_changed', category: 'security', description: 'Bestätigung der Passwortänderung' },
|
||
{ name: 'Neue Anmeldung', key: 'new_login', category: 'security', description: 'Benachrichtigung über Anmeldung von neuem Gerät' },
|
||
{ name: '2FA aktiviert', key: '2fa_enabled', category: 'security', description: 'Bestätigung der 2FA-Aktivierung' },
|
||
// Consent & Legal
|
||
{ name: 'Neue Dokumentversion', key: 'new_document_version', category: 'consent', description: 'Info über neue Dokumentversion zur Zustimmung' },
|
||
{ name: 'Zustimmung erteilt', key: 'consent_given', category: 'consent', description: 'Bestätigung der erteilten Zustimmung' },
|
||
{ name: 'Zustimmung widerrufen', key: 'consent_withdrawn', category: 'consent', description: 'Bestätigung des Widerrufs' },
|
||
// Data Subject Rights (GDPR)
|
||
{ name: 'DSR Anfrage erhalten', key: 'dsr_received', category: 'gdpr', description: 'Bestätigung des Eingangs einer DSGVO-Anfrage' },
|
||
{ name: 'Datenexport bereit', key: 'export_ready', category: 'gdpr', description: 'Benachrichtigung über fertigen Datenexport' },
|
||
{ name: 'Daten gelöscht', key: 'data_deleted', category: 'gdpr', description: 'Bestätigung der Datenlöschung' },
|
||
{ name: 'Daten berichtigt', key: 'data_rectified', category: 'gdpr', description: 'Bestätigung der Datenberichtigung' },
|
||
// Account Lifecycle
|
||
{ name: 'Konto deaktiviert', key: 'account_deactivated', category: 'lifecycle', description: 'Konto wurde deaktiviert' },
|
||
{ name: 'Konto gelöscht', key: 'account_deleted', category: 'lifecycle', description: 'Bestätigung der Kontolöschung' },
|
||
]
|
||
|
||
// GDPR Article 15-21 Processes
|
||
const gdprProcesses = [
|
||
{
|
||
article: '15',
|
||
title: 'Auskunftsrecht',
|
||
description: 'Recht auf Bestätigung und Auskunft über verarbeitete personenbezogene Daten',
|
||
actions: ['Datenexport generieren', 'Verarbeitungszwecke auflisten', 'Empfänger auflisten'],
|
||
sla: '30 Tage',
|
||
status: 'active'
|
||
},
|
||
{
|
||
article: '16',
|
||
title: 'Recht auf Berichtigung',
|
||
description: 'Recht auf Berichtigung unrichtiger personenbezogener Daten',
|
||
actions: ['Daten bearbeiten', 'Änderungshistorie führen', 'Benachrichtigung senden'],
|
||
sla: '30 Tage',
|
||
status: 'active'
|
||
},
|
||
{
|
||
article: '17',
|
||
title: 'Recht auf Löschung ("Vergessenwerden")',
|
||
description: 'Recht auf Löschung personenbezogener Daten unter bestimmten Voraussetzungen',
|
||
actions: ['Löschantrag prüfen', 'Daten löschen', 'Aufbewahrungsfristen prüfen', 'Löschbestätigung senden'],
|
||
sla: '30 Tage',
|
||
status: 'active'
|
||
},
|
||
{
|
||
article: '18',
|
||
title: 'Recht auf Einschränkung der Verarbeitung',
|
||
description: 'Recht auf Markierung von Daten zur eingeschränkten Verarbeitung',
|
||
actions: ['Daten markieren', 'Verarbeitung einschränken', 'Benachrichtigung bei Aufhebung'],
|
||
sla: '30 Tage',
|
||
status: 'active'
|
||
},
|
||
{
|
||
article: '19',
|
||
title: 'Mitteilungspflicht',
|
||
description: 'Pflicht zur Mitteilung von Berichtigung, Löschung oder Einschränkung an Empfänger',
|
||
actions: ['Empfänger ermitteln', 'Mitteilungen versenden', 'Protokollierung'],
|
||
sla: 'Unverzüglich',
|
||
status: 'active'
|
||
},
|
||
{
|
||
article: '20',
|
||
title: 'Recht auf Datenübertragbarkeit',
|
||
description: 'Recht auf Erhalt der Daten in strukturiertem, maschinenlesbarem Format',
|
||
actions: ['JSON/CSV Export', 'API-Schnittstelle', 'Direkte Übertragung'],
|
||
sla: '30 Tage',
|
||
status: 'active'
|
||
},
|
||
{
|
||
article: '21',
|
||
title: 'Widerspruchsrecht',
|
||
description: 'Recht auf Widerspruch gegen Verarbeitung aus berechtigtem Interesse oder Direktwerbung',
|
||
actions: ['Widerspruch erfassen', 'Verarbeitung stoppen', 'Marketing-Opt-out'],
|
||
sla: 'Unverzüglich',
|
||
status: 'active'
|
||
},
|
||
]
|
||
|
||
const emailCategories = [
|
||
{ key: 'onboarding', label: 'Onboarding', color: 'bg-blue-100 text-blue-700' },
|
||
{ key: 'security', label: 'Sicherheit', color: 'bg-red-100 text-red-700' },
|
||
{ key: 'consent', label: 'Zustimmung', color: 'bg-green-100 text-green-700' },
|
||
{ key: 'gdpr', label: 'DSGVO', color: 'bg-purple-100 text-purple-700' },
|
||
{ key: 'lifecycle', label: 'Lifecycle', color: 'bg-orange-100 text-orange-700' },
|
||
]
|
||
|
||
return (
|
||
<AdminLayout title="Consent Verwaltung" description="Rechtliche Dokumente & Versionen">
|
||
{/* Token Input */}
|
||
{!authToken && (
|
||
<div className="bg-white rounded-xl border border-slate-200 p-4 mb-6">
|
||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||
Admin Token
|
||
</label>
|
||
<input
|
||
type="password"
|
||
placeholder="JWT Token eingeben..."
|
||
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm"
|
||
onChange={(e) => {
|
||
setAuthToken(e.target.value)
|
||
localStorage.setItem('bp_admin_token', e.target.value)
|
||
}}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* Tabs */}
|
||
<div className="mb-6">
|
||
<div className="flex gap-1 bg-slate-100 p-1 rounded-lg w-fit">
|
||
{tabs.map((tab) => (
|
||
<button
|
||
key={tab.id}
|
||
onClick={() => setActiveTab(tab.id)}
|
||
className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
|
||
activeTab === tab.id
|
||
? 'bg-white text-slate-900 shadow-sm'
|
||
: 'text-slate-600 hover:text-slate-900'
|
||
}`}
|
||
>
|
||
{tab.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Content */}
|
||
<div>
|
||
{error && (
|
||
<div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-700 rounded-lg">
|
||
{error}
|
||
<button
|
||
onClick={() => setError(null)}
|
||
className="ml-4 text-red-500 hover:text-red-700"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
<div className="bg-white rounded-xl border border-slate-200 shadow-sm">
|
||
{/* Documents Tab */}
|
||
{activeTab === 'documents' && (
|
||
<div className="p-6">
|
||
<div className="flex items-center justify-between mb-6">
|
||
<h2 className="text-lg font-semibold text-slate-900">Dokumente verwalten</h2>
|
||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors text-sm font-medium">
|
||
+ Neues Dokument
|
||
</button>
|
||
</div>
|
||
|
||
{loading ? (
|
||
<div className="text-center py-12 text-slate-500">Lade Dokumente...</div>
|
||
) : documents.length === 0 ? (
|
||
<div className="text-center py-12 text-slate-500">
|
||
Keine Dokumente vorhanden
|
||
</div>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead>
|
||
<tr className="border-b border-slate-200">
|
||
<th className="text-left py-3 px-4 text-sm font-medium text-slate-500">Typ</th>
|
||
<th className="text-left py-3 px-4 text-sm font-medium text-slate-500">Name</th>
|
||
<th className="text-left py-3 px-4 text-sm font-medium text-slate-500">Beschreibung</th>
|
||
<th className="text-left py-3 px-4 text-sm font-medium text-slate-500">Pflicht</th>
|
||
<th className="text-left py-3 px-4 text-sm font-medium text-slate-500">Erstellt</th>
|
||
<th className="text-right py-3 px-4 text-sm font-medium text-slate-500">Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{documents.map((doc) => (
|
||
<tr key={doc.id} className="border-b border-slate-100 hover:bg-slate-50">
|
||
<td className="py-3 px-4">
|
||
<span className="px-2 py-1 bg-slate-100 text-slate-700 rounded text-xs font-medium">
|
||
{doc.type}
|
||
</span>
|
||
</td>
|
||
<td className="py-3 px-4 font-medium text-slate-900">{doc.name}</td>
|
||
<td className="py-3 px-4 text-slate-600 text-sm">{doc.description}</td>
|
||
<td className="py-3 px-4">
|
||
{doc.mandatory ? (
|
||
<span className="text-green-600">Ja</span>
|
||
) : (
|
||
<span className="text-slate-400">Nein</span>
|
||
)}
|
||
</td>
|
||
<td className="py-3 px-4 text-sm text-slate-500">
|
||
{new Date(doc.created_at).toLocaleDateString('de-DE')}
|
||
</td>
|
||
<td className="py-3 px-4 text-right">
|
||
<button
|
||
onClick={() => {
|
||
setSelectedDocument(doc.id)
|
||
setActiveTab('versions')
|
||
}}
|
||
className="text-primary-600 hover:text-primary-700 text-sm font-medium mr-3"
|
||
>
|
||
Versionen
|
||
</button>
|
||
<button className="text-slate-500 hover:text-slate-700 text-sm">
|
||
Bearbeiten
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Versions Tab */}
|
||
{activeTab === 'versions' && (
|
||
<div className="p-6">
|
||
<div className="flex items-center justify-between mb-6">
|
||
<div className="flex items-center gap-4">
|
||
<h2 className="text-lg font-semibold text-slate-900">Versionen</h2>
|
||
<select
|
||
value={selectedDocument}
|
||
onChange={(e) => setSelectedDocument(e.target.value)}
|
||
className="px-3 py-2 border border-slate-300 rounded-lg text-sm"
|
||
>
|
||
<option value="">Dokument auswählen...</option>
|
||
{documents.map((doc) => (
|
||
<option key={doc.id} value={doc.id}>
|
||
{doc.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
{selectedDocument && (
|
||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors text-sm font-medium">
|
||
+ Neue Version
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{!selectedDocument ? (
|
||
<div className="text-center py-12 text-slate-500">
|
||
Bitte wählen Sie ein Dokument aus
|
||
</div>
|
||
) : loading ? (
|
||
<div className="text-center py-12 text-slate-500">Lade Versionen...</div>
|
||
) : versions.length === 0 ? (
|
||
<div className="text-center py-12 text-slate-500">
|
||
Keine Versionen vorhanden
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{versions.map((version) => (
|
||
<div
|
||
key={version.id}
|
||
className="border border-slate-200 rounded-lg p-4 hover:border-primary-300 transition-colors"
|
||
>
|
||
<div className="flex items-start justify-between">
|
||
<div>
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<span className="font-semibold text-slate-900">v{version.version}</span>
|
||
<span className="px-2 py-0.5 bg-slate-100 text-slate-600 rounded text-xs">
|
||
{version.language.toUpperCase()}
|
||
</span>
|
||
<span
|
||
className={`px-2 py-0.5 rounded text-xs ${
|
||
version.status === 'published'
|
||
? 'bg-green-100 text-green-700'
|
||
: version.status === 'draft'
|
||
? 'bg-yellow-100 text-yellow-700'
|
||
: 'bg-slate-100 text-slate-600'
|
||
}`}
|
||
>
|
||
{version.status}
|
||
</span>
|
||
</div>
|
||
<h3 className="text-slate-700">{version.title}</h3>
|
||
<p className="text-sm text-slate-500 mt-1">
|
||
Erstellt: {new Date(version.created_at).toLocaleDateString('de-DE')}
|
||
</p>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<button className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 border border-slate-300 rounded-lg hover:border-slate-400">
|
||
Bearbeiten
|
||
</button>
|
||
{version.status === 'draft' && (
|
||
<button className="px-3 py-1.5 text-sm text-white bg-green-600 hover:bg-green-700 rounded-lg">
|
||
Veröffentlichen
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Emails Tab - 16 Lifecycle Templates */}
|
||
{activeTab === 'emails' && (
|
||
<div className="p-6">
|
||
<div className="flex items-center justify-between mb-6">
|
||
<div>
|
||
<h2 className="text-lg font-semibold text-slate-900">E-Mail Vorlagen</h2>
|
||
<p className="text-sm text-slate-500 mt-1">16 Lifecycle-Vorlagen für automatisierte Kommunikation</p>
|
||
</div>
|
||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors text-sm font-medium">
|
||
+ Neue Vorlage
|
||
</button>
|
||
</div>
|
||
|
||
{/* Category Filter */}
|
||
<div className="flex flex-wrap gap-2 mb-6">
|
||
<span className="text-sm text-slate-500 py-1">Filter:</span>
|
||
{emailCategories.map((cat) => (
|
||
<span key={cat.key} className={`px-3 py-1 rounded-full text-xs font-medium ${cat.color}`}>
|
||
{cat.label}
|
||
</span>
|
||
))}
|
||
</div>
|
||
|
||
{/* Templates grouped by category */}
|
||
{emailCategories.map((category) => (
|
||
<div key={category.key} className="mb-8">
|
||
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-3 flex items-center gap-2">
|
||
<span className={`w-2 h-2 rounded-full ${category.color.split(' ')[0]}`}></span>
|
||
{category.label}
|
||
</h3>
|
||
<div className="grid gap-3">
|
||
{emailTemplates
|
||
.filter((t) => t.category === category.key)
|
||
.map((template) => (
|
||
<div
|
||
key={template.key}
|
||
className="border border-slate-200 rounded-lg p-4 flex items-center justify-between hover:border-primary-300 transition-colors bg-white"
|
||
>
|
||
<div className="flex items-center gap-4">
|
||
<div className={`w-10 h-10 rounded-lg ${category.color} flex items-center justify-center text-lg`}>
|
||
{category.key === 'onboarding' && '👋'}
|
||
{category.key === 'security' && '🔒'}
|
||
{category.key === 'consent' && '✓'}
|
||
{category.key === 'gdpr' && '📋'}
|
||
{category.key === 'lifecycle' && '🔄'}
|
||
</div>
|
||
<div>
|
||
<h4 className="font-medium text-slate-900">{template.name}</h4>
|
||
<p className="text-sm text-slate-500">{template.description}</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<span className="px-2 py-1 bg-green-100 text-green-700 rounded text-xs">Aktiv</span>
|
||
<button className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 border border-slate-300 rounded-lg hover:border-slate-400">
|
||
Bearbeiten
|
||
</button>
|
||
<button className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 border border-slate-300 rounded-lg hover:border-slate-400">
|
||
Vorschau
|
||
</button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* GDPR Processes Tab - Articles 15-21 */}
|
||
{activeTab === 'gdpr' && (
|
||
<div className="p-6">
|
||
<div className="flex items-center justify-between mb-6">
|
||
<div>
|
||
<h2 className="text-lg font-semibold text-slate-900">DSGVO Betroffenenrechte</h2>
|
||
<p className="text-sm text-slate-500 mt-1">Artikel 15-21 Prozesse und Vorlagen</p>
|
||
</div>
|
||
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors text-sm font-medium">
|
||
+ DSR Anfrage erstellen
|
||
</button>
|
||
</div>
|
||
|
||
{/* Info Banner */}
|
||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4 mb-6">
|
||
<div className="flex items-start gap-3">
|
||
<span className="text-2xl">⚖️</span>
|
||
<div>
|
||
<h4 className="font-medium text-purple-900">Data Subject Rights (DSR)</h4>
|
||
<p className="text-sm text-purple-700 mt-1">
|
||
Hier verwalten Sie alle DSGVO-Anfragen. Jeder Artikel hat definierte Prozesse, SLAs und automatisierte Workflows.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* GDPR Process Cards */}
|
||
<div className="space-y-4">
|
||
{gdprProcesses.map((process) => (
|
||
<div
|
||
key={process.article}
|
||
className="border border-slate-200 rounded-xl p-5 hover:border-purple-300 transition-colors bg-white"
|
||
>
|
||
<div className="flex items-start justify-between">
|
||
<div className="flex items-start gap-4">
|
||
<div className="w-12 h-12 bg-purple-100 text-purple-700 rounded-lg flex items-center justify-center font-bold text-lg">
|
||
{process.article}
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<h3 className="font-semibold text-slate-900">{process.title}</h3>
|
||
<span className="px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs">Aktiv</span>
|
||
</div>
|
||
<p className="text-sm text-slate-600 mb-3">{process.description}</p>
|
||
|
||
{/* Actions */}
|
||
<div className="flex flex-wrap gap-2 mb-3">
|
||
{process.actions.map((action, idx) => (
|
||
<span key={idx} className="px-2 py-1 bg-slate-100 text-slate-600 rounded text-xs">
|
||
{action}
|
||
</span>
|
||
))}
|
||
</div>
|
||
|
||
{/* SLA */}
|
||
<div className="flex items-center gap-4 text-sm">
|
||
<span className="text-slate-500">
|
||
SLA: <span className="font-medium text-slate-700">{process.sla}</span>
|
||
</span>
|
||
<span className="text-slate-300">|</span>
|
||
<span className="text-slate-500">
|
||
Offene Anfragen: <span className="font-medium text-slate-700">0</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-2">
|
||
<button className="px-3 py-1.5 text-sm text-white bg-purple-600 hover:bg-purple-700 rounded-lg">
|
||
Anfragen
|
||
</button>
|
||
<button className="px-3 py-1.5 text-sm text-slate-600 hover:text-slate-900 border border-slate-300 rounded-lg hover:border-slate-400">
|
||
Vorlage
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* DSR Request Statistics */}
|
||
<div className="mt-8 pt-6 border-t border-slate-200">
|
||
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wider mb-4">DSR Übersicht</h3>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div className="bg-slate-50 rounded-lg p-4 text-center">
|
||
<div className="text-2xl font-bold text-slate-900">0</div>
|
||
<div className="text-xs text-slate-500 mt-1">Offen</div>
|
||
</div>
|
||
<div className="bg-green-50 rounded-lg p-4 text-center">
|
||
<div className="text-2xl font-bold text-green-700">0</div>
|
||
<div className="text-xs text-slate-500 mt-1">Erledigt</div>
|
||
</div>
|
||
<div className="bg-yellow-50 rounded-lg p-4 text-center">
|
||
<div className="text-2xl font-bold text-yellow-700">0</div>
|
||
<div className="text-xs text-slate-500 mt-1">In Bearbeitung</div>
|
||
</div>
|
||
<div className="bg-red-50 rounded-lg p-4 text-center">
|
||
<div className="text-2xl font-bold text-red-700">0</div>
|
||
<div className="text-xs text-slate-500 mt-1">Überfällig</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Stats Tab */}
|
||
{activeTab === 'stats' && (
|
||
<div className="p-6">
|
||
<h2 className="text-lg font-semibold text-slate-900 mb-6">Statistiken</h2>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||
<div className="bg-slate-50 rounded-xl p-6">
|
||
<div className="text-3xl font-bold text-slate-900">0</div>
|
||
<div className="text-sm text-slate-500 mt-1">Aktive Zustimmungen</div>
|
||
</div>
|
||
<div className="bg-slate-50 rounded-xl p-6">
|
||
<div className="text-3xl font-bold text-slate-900">0</div>
|
||
<div className="text-sm text-slate-500 mt-1">Dokumente</div>
|
||
</div>
|
||
<div className="bg-slate-50 rounded-xl p-6">
|
||
<div className="text-3xl font-bold text-slate-900">0</div>
|
||
<div className="text-sm text-slate-500 mt-1">Offene DSR-Anfragen</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="border border-slate-200 rounded-lg p-6">
|
||
<h3 className="font-semibold text-slate-900 mb-4">Zustimmungsrate nach Dokument</h3>
|
||
<div className="text-center py-8 text-slate-500">
|
||
Noch keine Daten verfügbar
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</AdminLayout>
|
||
)
|
||
}
|