refactor(admin): split einwilligungen page.tsx into colocated components
Extract nav tabs, detail modal, table row, stats grid, search/filter, records table, pagination, and data-loading hook into _components/ and _hooks/. page.tsx reduced from 833 to 114 LOC. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
171
admin-compliance/app/sdk/einwilligungen/_hooks/useConsents.ts
Normal file
171
admin-compliance/app/sdk/einwilligungen/_hooks/useConsents.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
ConsentRecord,
|
||||
ConsentStatus,
|
||||
ConsentType,
|
||||
HistoryAction,
|
||||
PAGE_SIZE,
|
||||
} from '../_types'
|
||||
|
||||
interface GlobalStats {
|
||||
total: number
|
||||
active: number
|
||||
revoked: number
|
||||
}
|
||||
|
||||
export function useConsents() {
|
||||
const [records, setRecords] = useState<ConsentRecord[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [totalRecords, setTotalRecords] = useState(0)
|
||||
const [globalStats, setGlobalStats] = useState<GlobalStats>({ total: 0, active: 0, revoked: 0 })
|
||||
|
||||
const loadConsents = useCallback(async (page: number) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const offset = (page - 1) * PAGE_SIZE
|
||||
const listResponse = await fetch(
|
||||
`/api/sdk/v1/einwilligungen/consent?limit=${PAGE_SIZE}&offset=${offset}`
|
||||
)
|
||||
if (listResponse.ok) {
|
||||
const listData = await listResponse.json()
|
||||
setTotalRecords(listData.total ?? 0)
|
||||
if (listData.consents?.length > 0) {
|
||||
const mapped: ConsentRecord[] = listData.consents.map((c: {
|
||||
id: string
|
||||
user_id: string
|
||||
data_point_id: string
|
||||
granted: boolean
|
||||
granted_at: string
|
||||
revoked_at?: string
|
||||
consent_version?: string
|
||||
source?: string
|
||||
ip_address?: string
|
||||
user_agent?: string
|
||||
history?: Array<{
|
||||
id: string
|
||||
action: string
|
||||
created_at: string
|
||||
consent_version?: string
|
||||
ip_address?: string
|
||||
user_agent?: string
|
||||
source?: string
|
||||
}>
|
||||
}) => ({
|
||||
id: c.id,
|
||||
identifier: c.user_id,
|
||||
email: c.user_id,
|
||||
consentType: (c.data_point_id as ConsentType) || 'privacy',
|
||||
status: (c.revoked_at ? 'withdrawn' : 'granted') as ConsentStatus,
|
||||
currentVersion: c.consent_version || '1.0',
|
||||
grantedAt: c.granted_at ? new Date(c.granted_at) : null,
|
||||
withdrawnAt: c.revoked_at ? new Date(c.revoked_at) : null,
|
||||
source: c.source ?? null,
|
||||
ipAddress: c.ip_address ?? '',
|
||||
userAgent: c.user_agent ?? '',
|
||||
history: (c.history ?? []).map(h => ({
|
||||
id: h.id,
|
||||
action: h.action as HistoryAction,
|
||||
timestamp: new Date(h.created_at),
|
||||
version: h.consent_version || '1.0',
|
||||
ipAddress: h.ip_address ?? '',
|
||||
userAgent: h.user_agent ?? '',
|
||||
source: h.source ?? '',
|
||||
})),
|
||||
}))
|
||||
setRecords(mapped)
|
||||
} else {
|
||||
setRecords([])
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Backend nicht erreichbar, leere Liste anzeigen
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const loadStats = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/einwilligungen/consent?stats=true')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const s = data.statistics
|
||||
if (s) {
|
||||
setGlobalStats({
|
||||
total: s.total_consents ?? 0,
|
||||
active: s.active_consents ?? 0,
|
||||
revoked: s.revoked_consents ?? 0,
|
||||
})
|
||||
setTotalRecords(s.total_consents ?? 0)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Statistiken nicht erreichbar — lokale Werte behalten
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
loadStats()
|
||||
}, [loadStats])
|
||||
|
||||
useEffect(() => { loadConsents(currentPage) }, [currentPage, loadConsents])
|
||||
|
||||
const handleRevoke = useCallback(async (recordId: string) => {
|
||||
try {
|
||||
const response = await fetch('/api/sdk/v1/einwilligungen/consent', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ consentId: recordId, action: 'revoke' }),
|
||||
})
|
||||
if (response.ok) {
|
||||
const now = new Date()
|
||||
setRecords(prev => prev.map(r => {
|
||||
if (r.id === recordId) {
|
||||
return {
|
||||
...r,
|
||||
status: 'withdrawn' as ConsentStatus,
|
||||
withdrawnAt: now,
|
||||
history: [
|
||||
...r.history,
|
||||
{
|
||||
id: `h-${recordId}-${r.history.length + 1}`,
|
||||
action: 'withdrawn' as HistoryAction,
|
||||
timestamp: now,
|
||||
version: r.currentVersion,
|
||||
ipAddress: 'Admin-Portal',
|
||||
userAgent: 'Admin Action',
|
||||
source: 'Manueller Widerruf durch Admin',
|
||||
notes: 'Widerruf über Admin-Portal durchgeführt',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
return r
|
||||
}))
|
||||
loadStats()
|
||||
}
|
||||
} catch {
|
||||
// Fallback: update local state even if API call fails
|
||||
const now = new Date()
|
||||
setRecords(prev => prev.map(r => {
|
||||
if (r.id === recordId) {
|
||||
return { ...r, status: 'withdrawn' as ConsentStatus, withdrawnAt: now }
|
||||
}
|
||||
return r
|
||||
}))
|
||||
}
|
||||
}, [loadStats])
|
||||
|
||||
return {
|
||||
records,
|
||||
isLoading,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
totalRecords,
|
||||
globalStats,
|
||||
handleRevoke,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user