'use client' import React, { useState, useEffect, useCallback } from 'react' import GapAnalysisView from '@/components/sdk/obligations/GapAnalysisView' import { useSDK } from '@/lib/sdk' import { buildAssessmentPayload } from '@/lib/sdk/scope-to-facts' import type { ApplicableRegulation } from '@/lib/sdk/compliance-scope-types' import { API, UCCA_API, mapPriority, type Obligation, type ObligationStats, type ObligationFormData, } from './_types' import ObligationModal from './_components/ObligationModal' import ObligationDetail from './_components/ObligationDetail' import ObligationCard from './_components/ObligationCard' import ObligationsHeader from './_components/ObligationsHeader' import StatsGrid from './_components/StatsGrid' import FilterBar from './_components/FilterBar' import { ApplicableRegsBanner, NoProfileWarning, OverdueAlert, EmptyList, } from './_components/InfoBanners' export default function ObligationsPage() { const { state: sdkState } = useSDK() const [obligations, setObligations] = useState([]) const [stats, setStats] = useState(null) const [filter, setFilter] = useState('all') const [regulationFilter, setRegulationFilter] = useState('all') const [searchQuery, setSearchQuery] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showModal, setShowModal] = useState(false) const [editObligation, setEditObligation] = useState(null) const [detailObligation, setDetailObligation] = useState(null) const [showGapAnalysis, setShowGapAnalysis] = useState(false) const [profiling, setProfiling] = useState(false) const [applicableRegs, setApplicableRegs] = useState([]) 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) : [], 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) : [], notes: form.notes || null, }), }) if (!res.ok) throw new Error('Aktualisierung fehlgeschlagen') await loadData() // Refresh detail if open 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) // Refresh stats 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 { // Build payload from real CompanyProfile + Scope data const profile = sdkState.companyProfile const scopeState = sdkState.complianceScope const scopeAnswers = scopeState?.answers || [] const scopeDecision = scopeState?.decision || null let payload: Record if (profile) { payload = buildAssessmentPayload(profile, scopeAnswers, scopeDecision) as unknown as Record } else { // Fallback: Minimaldaten wenn kein Profil vorhanden 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() // Store applicable regulations for the info box const regs: ApplicableRegulation[] = data.overview?.applicable_regulations || data.applicable_regulations || [] setApplicableRegs(regs) // Extract obligations from response (can be nested under overview) const rawObls = data.overview?.obligations || data.obligations || [] if (rawObls.length > 0) { const autoObls: Obligation[] = rawObls.map((o: Record) => ({ 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 => { // Status/priority filter if (filter === 'ai') { if (!o.source.toLowerCase().includes('ai')) return false } // Regulation filter if (regulationFilter !== 'all') { const src = o.source?.toLowerCase() || '' const key = regulationFilter.toLowerCase() if (!src.includes(key)) return false } return true }) return (
{/* Modals */} {(showModal || editObligation) && !detailObligation && ( { setShowModal(false); setEditObligation(null) }} onSave={async (form) => { if (editObligation) { await handleUpdate(editObligation.id, form) setEditObligation(null) } else { await handleCreate(form) setShowModal(false) } }} /> )} {detailObligation && ( setDetailObligation(null)} onStatusChange={handleStatusChange} onDelete={handleDelete} onEdit={() => { setEditObligation(detailObligation) setDetailObligation(null) }} /> )} setShowGapAnalysis(v => !v)} onAdd={() => setShowModal(true)} /> {error && (
{error}
)} {!sdkState.companyProfile && } {showGapAnalysis && } {loading &&
Lade Pflichten...
} {!loading && (
{filteredObligations.map(o => ( setDetailObligation(o)} /> ))} {filteredObligations.length === 0 && ( setShowModal(true)} /> )}
)}
) }