Agent-completed splits committed after agents hit rate limits before committing their work. All 4 pages now under 500 LOC: - consent-management: 1303 -> 193 LOC (+ 7 _components, _hooks, _data, _types) - control-library: 1210 -> 298 LOC (+ _components, _types) - incidents: 1150 -> 373 LOC (+ _components) - training: 1127 -> 366 LOC (+ _components) Verification: next build clean (142 pages generated). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
292 lines
9.6 KiB
TypeScript
292 lines
9.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { API_BASE } from '../_types'
|
|
import type {
|
|
Tab, Document, Version, ApiEmailTemplate, ApiGdprProcess,
|
|
ConsentStats, DsrOverview, EmailTemplateData,
|
|
} from '../_types'
|
|
|
|
export function useConsentData(activeTab: Tab, selectedDocument: string) {
|
|
const [documents, setDocuments] = useState<Document[]>([])
|
|
const [versions, setVersions] = useState<Version[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
// Stats state
|
|
const [consentStats, setConsentStats] = useState<ConsentStats>({ activeConsents: 0, documentCount: 0, openDSRs: 0 })
|
|
|
|
// GDPR tab state
|
|
const [dsrCounts, setDsrCounts] = useState<Record<string, number>>({})
|
|
const [dsrOverview, setDsrOverview] = useState<DsrOverview>({ open: 0, completed: 0, in_progress: 0, overdue: 0 })
|
|
|
|
// Email template editor state
|
|
const [savedTemplates, setSavedTemplates] = useState<Record<string, EmailTemplateData>>({})
|
|
|
|
// API-backed email templates and GDPR processes
|
|
const [apiEmailTemplates, setApiEmailTemplates] = useState<ApiEmailTemplate[]>([])
|
|
const [apiGdprProcesses, setApiGdprProcesses] = useState<ApiGdprProcess[]>([])
|
|
const [templatesLoading, setTemplatesLoading] = useState(false)
|
|
const [gdprLoading, setGdprLoading] = useState(false)
|
|
const [savingTemplateId, setSavingTemplateId] = useState<string | null>(null)
|
|
const [savingProcessId, setSavingProcessId] = useState<string | null>(null)
|
|
|
|
// Auth token (in production, get from auth context)
|
|
const [authToken, setAuthToken] = useState<string>('')
|
|
|
|
useEffect(() => {
|
|
const token = localStorage.getItem('bp_admin_token')
|
|
if (token) {
|
|
setAuthToken(token)
|
|
}
|
|
// Load saved email templates from localStorage
|
|
try {
|
|
const saved = localStorage.getItem('sdk-email-templates')
|
|
if (saved) {
|
|
setSavedTemplates(JSON.parse(saved))
|
|
}
|
|
} catch { /* ignore */ }
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (activeTab === 'documents') {
|
|
loadDocuments()
|
|
} else if (activeTab === 'versions' && selectedDocument) {
|
|
loadVersions(selectedDocument)
|
|
} else if (activeTab === 'stats') {
|
|
loadStats()
|
|
} else if (activeTab === 'gdpr') {
|
|
loadGDPRData()
|
|
loadApiGdprProcesses()
|
|
} else if (activeTab === 'emails') {
|
|
loadApiEmailTemplates()
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [activeTab, selectedDocument, authToken])
|
|
|
|
async function loadApiEmailTemplates() {
|
|
setTemplatesLoading(true)
|
|
try {
|
|
const res = await fetch('/api/sdk/v1/consent-templates')
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setApiEmailTemplates(Array.isArray(data) ? data : [])
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to load email templates from API:', err)
|
|
} finally {
|
|
setTemplatesLoading(false)
|
|
}
|
|
}
|
|
|
|
async function loadApiGdprProcesses() {
|
|
setGdprLoading(true)
|
|
try {
|
|
const res = await fetch('/api/sdk/v1/consent-templates/gdpr-processes')
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setApiGdprProcesses(Array.isArray(data) ? data : [])
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to load GDPR processes from API:', err)
|
|
} finally {
|
|
setGdprLoading(false)
|
|
}
|
|
}
|
|
|
|
async function saveApiEmailTemplate(template: { id: string; subject: string; body: string }) {
|
|
setSavingTemplateId(template.id)
|
|
try {
|
|
const res = await fetch(`/api/sdk/v1/consent-templates/${template.id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ subject: template.subject, body: template.body }),
|
|
})
|
|
if (res.ok) {
|
|
const updated = await res.json()
|
|
setApiEmailTemplates(prev => prev.map(t => t.id === updated.id ? updated : t))
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to save email template:', err)
|
|
} finally {
|
|
setSavingTemplateId(null)
|
|
}
|
|
}
|
|
|
|
async function saveApiGdprProcess(process: { id: string; title: string; description: string }) {
|
|
setSavingProcessId(process.id)
|
|
try {
|
|
const res = await fetch(`/api/sdk/v1/consent-templates/gdpr-processes/${process.id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ title: process.title, description: process.description }),
|
|
})
|
|
if (res.ok) {
|
|
const updated = await res.json()
|
|
setApiGdprProcesses(prev => prev.map(p => p.id === updated.id ? updated : p))
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to save GDPR process:', err)
|
|
} finally {
|
|
setSavingProcessId(null)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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 {
|
|
setError('Verbindungsfehler zum Server')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
async function loadStats() {
|
|
try {
|
|
const token = localStorage.getItem('bp_admin_token')
|
|
const [statsRes, docsRes] = await Promise.all([
|
|
fetch(`${API_BASE}/stats`, {
|
|
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
|
|
}),
|
|
fetch(`${API_BASE}/documents`, {
|
|
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
|
|
}),
|
|
])
|
|
|
|
let activeConsents = 0
|
|
let documentCount = 0
|
|
let openDSRs = 0
|
|
|
|
if (statsRes.ok) {
|
|
const statsData = await statsRes.json()
|
|
activeConsents = statsData.total_consents || statsData.active_consents || 0
|
|
}
|
|
|
|
if (docsRes.ok) {
|
|
const docsData = await docsRes.json()
|
|
documentCount = (docsData.documents || []).length
|
|
}
|
|
|
|
// Try to get DSR count
|
|
try {
|
|
const dsrRes = await fetch('/api/sdk/v1/compliance/dsr', {
|
|
headers: {
|
|
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
|
|
'X-User-ID': localStorage.getItem('bp_user_id') || '',
|
|
}
|
|
})
|
|
if (dsrRes.ok) {
|
|
const dsrData = await dsrRes.json()
|
|
const dsrs = dsrData.dsrs || []
|
|
openDSRs = dsrs.filter((r: any) => r.status !== 'completed' && r.status !== 'rejected').length
|
|
}
|
|
} catch { /* DSR endpoint might not be available */ }
|
|
|
|
setConsentStats({ activeConsents, documentCount, openDSRs })
|
|
} catch (err) {
|
|
console.error('Failed to load stats:', err)
|
|
}
|
|
}
|
|
|
|
async function loadGDPRData() {
|
|
try {
|
|
const res = await fetch('/api/sdk/v1/compliance/dsr', {
|
|
headers: {
|
|
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
|
|
'X-User-ID': localStorage.getItem('bp_user_id') || '',
|
|
}
|
|
})
|
|
if (!res.ok) return
|
|
|
|
const data = await res.json()
|
|
const dsrs = data.dsrs || []
|
|
const now = new Date()
|
|
|
|
// Count per article type
|
|
const counts: Record<string, number> = {}
|
|
const typeMapping: Record<string, string> = {
|
|
'access': '15',
|
|
'rectification': '16',
|
|
'erasure': '17',
|
|
'restriction': '18',
|
|
'portability': '20',
|
|
'objection': '21',
|
|
}
|
|
|
|
for (const dsr of dsrs) {
|
|
if (dsr.status === 'completed' || dsr.status === 'rejected') continue
|
|
const article = typeMapping[dsr.request_type]
|
|
if (article) {
|
|
counts[article] = (counts[article] || 0) + 1
|
|
}
|
|
}
|
|
setDsrCounts(counts)
|
|
|
|
// Calculate overview
|
|
const open = dsrs.filter((r: any) => r.status === 'received' || r.status === 'verified').length
|
|
const completed = dsrs.filter((r: any) => r.status === 'completed').length
|
|
const in_progress = dsrs.filter((r: any) => r.status === 'in_progress').length
|
|
const overdue = dsrs.filter((r: any) => {
|
|
if (r.status === 'completed' || r.status === 'rejected') return false
|
|
const deadline = r.extended_deadline_at ? new Date(r.extended_deadline_at) : new Date(r.deadline_at)
|
|
return deadline < now
|
|
}).length
|
|
|
|
setDsrOverview({ open, completed, in_progress, overdue })
|
|
} catch (err) {
|
|
console.error('Failed to load GDPR data:', err)
|
|
}
|
|
}
|
|
|
|
function saveEmailTemplate(template: EmailTemplateData) {
|
|
const updated = { ...savedTemplates, [template.key]: template }
|
|
setSavedTemplates(updated)
|
|
localStorage.setItem('sdk-email-templates', JSON.stringify(updated))
|
|
}
|
|
|
|
return {
|
|
documents, versions, loading, error, setError,
|
|
consentStats, dsrCounts, dsrOverview,
|
|
savedTemplates, saveEmailTemplate,
|
|
apiEmailTemplates, apiGdprProcesses,
|
|
templatesLoading, gdprLoading,
|
|
savingTemplateId, savingProcessId,
|
|
saveApiEmailTemplate, saveApiGdprProcess,
|
|
loadApiEmailTemplates,
|
|
authToken, setAuthToken,
|
|
}
|
|
}
|