refactor(admin): split compliance-hub, obligations, document-generator pages
Each page.tsx was >1000 LOC; extract components to _components/ and hooks to _hooks/ so page files stay under 500 LOC (164 / 255 / 243 respectively). Zero behavior changes — logic relocated verbatim. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
222
admin-compliance/app/sdk/obligations/_hooks/useObligations.ts
Normal file
222
admin-compliance/app/sdk/obligations/_hooks/useObligations.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import { buildAssessmentPayload } from '@/lib/sdk/scope-to-facts'
|
||||
import type { ApplicableRegulation } from '@/lib/sdk/compliance-scope-types'
|
||||
import type { Obligation, ObligationComplianceCheckResult } from '@/lib/sdk/obligations-compliance'
|
||||
import { runObligationComplianceCheck } from '@/lib/sdk/obligations-compliance'
|
||||
import {
|
||||
API, UCCA_API, mapPriority,
|
||||
type ObligationFormData, type ObligationStats,
|
||||
} from '../_types'
|
||||
|
||||
type Tab = 'uebersicht' | 'editor' | 'profiling' | 'gap-analyse' | 'pflichtenregister'
|
||||
|
||||
export function useObligations() {
|
||||
const { state: sdkState } = useSDK()
|
||||
const [obligations, setObligations] = useState<Obligation[]>([])
|
||||
const [stats, setStats] = useState<ObligationStats | null>(null)
|
||||
const [filter, setFilter] = useState('all')
|
||||
const [regulationFilter, setRegulationFilter] = useState('all')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [editObligation, setEditObligation] = useState<Obligation | null>(null)
|
||||
const [detailObligation, setDetailObligation] = useState<Obligation | null>(null)
|
||||
const [profiling, setProfiling] = useState(false)
|
||||
const [applicableRegs, setApplicableRegs] = useState<ApplicableRegulation[]>([])
|
||||
const [activeTab, setActiveTab] = useState<Tab>('uebersicht')
|
||||
|
||||
const complianceResult = useMemo<ObligationComplianceCheckResult | null>(() => {
|
||||
if (obligations.length === 0) return null
|
||||
return runObligationComplianceCheck(obligations)
|
||||
}, [obligations])
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const params = new URLSearchParams({ limit: '200' })
|
||||
if (filter !== 'all' && ['pending', 'in-progress', 'completed', 'overdue'].includes(filter)) {
|
||||
params.set('status', filter)
|
||||
}
|
||||
if (filter === 'critical' || filter === 'high') {
|
||||
params.set('priority', filter)
|
||||
}
|
||||
if (searchQuery) params.set('search', searchQuery)
|
||||
|
||||
const [listRes, statsRes] = await Promise.all([
|
||||
fetch(`${API}?${params}`),
|
||||
fetch(`${API}/stats`),
|
||||
])
|
||||
|
||||
if (listRes.ok) {
|
||||
const data = await listRes.json()
|
||||
setObligations(data.obligations || [])
|
||||
}
|
||||
if (statsRes.ok) {
|
||||
setStats(await statsRes.json())
|
||||
}
|
||||
} catch {
|
||||
setError('Verbindung zum Backend fehlgeschlagen')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [filter, searchQuery])
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [loadData])
|
||||
|
||||
const handleCreate = async (form: ObligationFormData) => {
|
||||
const res = await fetch(API, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title: form.title,
|
||||
description: form.description || null,
|
||||
source: form.source,
|
||||
source_article: form.source_article || null,
|
||||
deadline: form.deadline || null,
|
||||
status: form.status,
|
||||
priority: form.priority,
|
||||
responsible: form.responsible || null,
|
||||
linked_systems: form.linked_systems ? form.linked_systems.split(',').map(s => s.trim()).filter(Boolean) : [],
|
||||
linked_vendor_ids: form.linked_vendor_ids ? form.linked_vendor_ids.split(',').map(s => s.trim()).filter(Boolean) : [],
|
||||
notes: form.notes || null,
|
||||
}),
|
||||
})
|
||||
if (!res.ok) throw new Error('Erstellen fehlgeschlagen')
|
||||
await loadData()
|
||||
}
|
||||
|
||||
const handleUpdate = async (id: string, form: ObligationFormData) => {
|
||||
const res = await fetch(`${API}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title: form.title,
|
||||
description: form.description || null,
|
||||
source: form.source,
|
||||
source_article: form.source_article || null,
|
||||
deadline: form.deadline || null,
|
||||
status: form.status,
|
||||
priority: form.priority,
|
||||
responsible: form.responsible || null,
|
||||
linked_systems: form.linked_systems ? form.linked_systems.split(',').map(s => s.trim()).filter(Boolean) : [],
|
||||
linked_vendor_ids: form.linked_vendor_ids ? form.linked_vendor_ids.split(',').map(s => s.trim()).filter(Boolean) : [],
|
||||
notes: form.notes || null,
|
||||
}),
|
||||
})
|
||||
if (!res.ok) throw new Error('Aktualisierung fehlgeschlagen')
|
||||
await loadData()
|
||||
if (detailObligation?.id === id) {
|
||||
const updated = await fetch(`${API}/${id}`)
|
||||
if (updated.ok) setDetailObligation(await updated.json())
|
||||
}
|
||||
}
|
||||
|
||||
const handleStatusChange = async (id: string, newStatus: string) => {
|
||||
const res = await fetch(`${API}/${id}/status`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: newStatus }),
|
||||
})
|
||||
if (!res.ok) return
|
||||
const updated = await res.json()
|
||||
setObligations(prev => prev.map(o => o.id === id ? updated : o))
|
||||
if (detailObligation?.id === id) setDetailObligation(updated)
|
||||
fetch(`${API}/stats`).then(r => r.json()).then(setStats).catch(() => {})
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
const res = await fetch(`${API}/${id}`, { method: 'DELETE' })
|
||||
if (!res.ok && res.status !== 204) throw new Error('Loeschen fehlgeschlagen')
|
||||
setObligations(prev => prev.filter(o => o.id !== id))
|
||||
setDetailObligation(null)
|
||||
fetch(`${API}/stats`).then(r => r.json()).then(setStats).catch(() => {})
|
||||
}
|
||||
|
||||
const handleAutoProfiling = async () => {
|
||||
setProfiling(true)
|
||||
setError(null)
|
||||
try {
|
||||
const profile = sdkState.companyProfile
|
||||
const scopeState = sdkState.complianceScope
|
||||
const scopeAnswers = scopeState?.answers || []
|
||||
const scopeDecision = scopeState?.decision || null
|
||||
|
||||
let payload: Record<string, unknown>
|
||||
if (profile) {
|
||||
payload = buildAssessmentPayload(profile, scopeAnswers, scopeDecision) as unknown as Record<string, unknown>
|
||||
} else {
|
||||
payload = {
|
||||
employee_count: 50,
|
||||
industry: 'technology',
|
||||
country: 'DE',
|
||||
processes_personal_data: true,
|
||||
is_controller: true,
|
||||
uses_processors: true,
|
||||
determined_level: scopeDecision?.determinedLevel || 'L2',
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(`${UCCA_API}/assess-from-scope`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
|
||||
const regs: ApplicableRegulation[] = data.overview?.applicable_regulations || data.applicable_regulations || []
|
||||
setApplicableRegs(regs)
|
||||
|
||||
const rawObls = data.overview?.obligations || data.obligations || []
|
||||
if (rawObls.length > 0) {
|
||||
const autoObls: Obligation[] = rawObls.map((o: Record<string, unknown>) => ({
|
||||
id: o.id as string,
|
||||
title: o.title as string,
|
||||
description: (o.description as string) || '',
|
||||
source: (o.regulation_id as string || '').toUpperCase(),
|
||||
source_article: '',
|
||||
deadline: null,
|
||||
status: 'pending' as const,
|
||||
priority: mapPriority(o.priority as string),
|
||||
responsible: (o.responsible as string) || '',
|
||||
linked_systems: [],
|
||||
rule_code: 'auto-profiled',
|
||||
}))
|
||||
setObligations(prev => {
|
||||
const existingIds = new Set(prev.map(p => p.id))
|
||||
const newOnes = autoObls.filter(a => !existingIds.has(a.id))
|
||||
return [...prev, ...newOnes]
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Auto-Profiling fehlgeschlagen')
|
||||
} finally {
|
||||
setProfiling(false)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredObligations = obligations.filter(o => {
|
||||
if (regulationFilter !== 'all') {
|
||||
const src = o.source?.toLowerCase() || ''
|
||||
const key = regulationFilter.toLowerCase()
|
||||
if (!src.includes(key)) return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return {
|
||||
sdkState, obligations, stats, filter, regulationFilter, searchQuery,
|
||||
loading, error, showModal, editObligation, detailObligation,
|
||||
profiling, applicableRegs, activeTab, complianceResult, filteredObligations,
|
||||
setFilter, setRegulationFilter, setSearchQuery,
|
||||
setShowModal, setEditObligation, setDetailObligation, setActiveTab,
|
||||
loadData, handleCreate, handleUpdate, handleStatusChange, handleDelete, handleAutoProfiling,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user