'use client' import { useState, useEffect, useCallback } from 'react' interface ChangeRequest { id: string triggerType: string targetDocumentType: string targetDocumentId: string | null targetSection: string | null proposalTitle: string proposalBody: string | null proposedChanges: Record status: 'pending' | 'accepted' | 'rejected' | 'edited_and_accepted' priority: 'low' | 'normal' | 'high' | 'critical' decidedBy: string | null decidedAt: string | null rejectionReason: string | null createdBy: string createdAt: string } interface Stats { total_pending: number critical_count: number total_accepted: number total_rejected: number by_document_type: Record } const API_BASE = '/api/sdk/v1/compliance/change-requests' const DOC_TYPE_LABELS: Record = { dsfa: 'DSFA', vvt: 'VVT', tom: 'TOM', loeschfristen: 'Löschfristen', obligation: 'Pflichten', } const PRIORITY_COLORS: Record = { critical: 'bg-red-100 text-red-800', high: 'bg-orange-100 text-orange-800', normal: 'bg-blue-100 text-blue-800', low: 'bg-gray-100 text-gray-700', } const STATUS_COLORS: Record = { pending: 'bg-yellow-100 text-yellow-800', accepted: 'bg-green-100 text-green-800', rejected: 'bg-red-100 text-red-800', edited_and_accepted: 'bg-emerald-100 text-emerald-800', } function snakeToCamel(obj: Record): ChangeRequest { return { id: obj.id as string, triggerType: obj.trigger_type as string, targetDocumentType: obj.target_document_type as string, targetDocumentId: obj.target_document_id as string | null, targetSection: obj.target_section as string | null, proposalTitle: obj.proposal_title as string, proposalBody: obj.proposal_body as string | null, proposedChanges: (obj.proposed_changes || {}) as Record, status: obj.status as ChangeRequest['status'], priority: obj.priority as ChangeRequest['priority'], decidedBy: obj.decided_by as string | null, decidedAt: obj.decided_at as string | null, rejectionReason: obj.rejection_reason as string | null, createdBy: obj.created_by as string, createdAt: obj.created_at as string, } } export default function ChangeRequestsPage() { const [requests, setRequests] = useState([]) const [stats, setStats] = useState(null) const [filter, setFilter] = useState('all') const [statusFilter, setStatusFilter] = useState('pending') const [loading, setLoading] = useState(true) const [actionModal, setActionModal] = useState<{ type: 'accept' | 'reject' | 'edit'; cr: ChangeRequest } | null>(null) const [rejectReason, setRejectReason] = useState('') const [editBody, setEditBody] = useState('') const loadData = useCallback(async () => { try { const statsRes = await fetch(`${API_BASE}/stats`) if (statsRes.ok) setStats(await statsRes.json()) let url = `${API_BASE}?status=${statusFilter}` if (filter !== 'all') url += `&target_document_type=${filter}` const res = await fetch(url) if (res.ok) { const data = await res.json() setRequests((Array.isArray(data) ? data : []).map(snakeToCamel)) } } catch (e) { console.error('Failed to load change requests:', e) } finally { setLoading(false) } }, [filter, statusFilter]) useEffect(() => { loadData() const interval = setInterval(loadData, 60000) return () => clearInterval(interval) }, [loadData]) const handleAccept = async (cr: ChangeRequest) => { const res = await fetch(`${API_BASE}/${cr.id}/accept`, { method: 'POST' }) if (res.ok) { setActionModal(null) loadData() } } const handleReject = async (cr: ChangeRequest) => { const res = await fetch(`${API_BASE}/${cr.id}/reject`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rejection_reason: rejectReason }), }) if (res.ok) { setActionModal(null) setRejectReason('') loadData() } } const handleEdit = async (cr: ChangeRequest) => { const res = await fetch(`${API_BASE}/${cr.id}/edit`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ proposal_body: editBody }), }) if (res.ok) { setActionModal(null) setEditBody('') loadData() } } const handleDelete = async (id: string) => { if (!confirm('Änderungsanfrage wirklich löschen?')) return const res = await fetch(`${API_BASE}/${id}`, { method: 'DELETE' }) if (res.ok) loadData() } return (

Änderungsanfragen

{/* Stats Bar */} {stats && (
{stats.total_pending}
Offen
{stats.critical_count}
Kritisch
{stats.total_accepted}
Angenommen
{stats.total_rejected}
Abgelehnt
)} {/* Filters */}
{['pending', 'accepted', 'rejected'].map(s => ( ))}
{['all', 'dsfa', 'vvt', 'tom', 'loeschfristen', 'obligation'].map(t => ( ))}
{/* Request List */} {loading ? (
Laden...
) : requests.length === 0 ? (
Keine Änderungsanfragen {statusFilter === 'pending' ? 'offen' : 'gefunden'}
) : (
{requests.map(cr => (
{cr.priority} {cr.status} {DOC_TYPE_LABELS[cr.targetDocumentType] || cr.targetDocumentType} {cr.triggerType !== 'manual' && ( {cr.triggerType} )}

{cr.proposalTitle}

{cr.proposalBody && (

{cr.proposalBody}

)}
{new Date(cr.createdAt).toLocaleDateString('de-DE')} — {cr.createdBy} {cr.rejectionReason && ( Grund: {cr.rejectionReason} )}
{cr.status === 'pending' && (
)}
))}
)} {/* Action Modal */} {actionModal && (
setActionModal(null)}>
e.stopPropagation()}> {actionModal.type === 'accept' && ( <>

Änderung annehmen?

{actionModal.cr.proposalTitle}

{Object.keys(actionModal.cr.proposedChanges).length > 0 && (
                    {JSON.stringify(actionModal.cr.proposedChanges, null, 2)}
                  
)}
)} {actionModal.type === 'reject' && ( <>

Änderung ablehnen