'use client' import { useState, useEffect } from 'react' import { useSDK, Requirement as SDKRequirement, RequirementStatus, RiskSeverity } from '@/lib/sdk' import { AddRequirementData } from '../_types' import { requirementTemplates } from '../_data' export function useRequirementsData() { const { state, dispatch } = useSDK() const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [ragExtracting, setRagExtracting] = useState(false) const [ragResult, setRagResult] = useState<{ created: number; skipped_duplicates: number; message: string } | null>(null) const extractFromRAG = async () => { setRagExtracting(true) setRagResult(null) try { const res = await fetch('/api/sdk/v1/compliance/extract-requirements-from-rag', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ max_per_query: 20 }), }) if (res.ok) { const data = await res.json() setRagResult({ created: data.created, skipped_duplicates: data.skipped_duplicates, message: data.message }) // Reload requirements list const listRes = await fetch('/api/sdk/v1/compliance/requirements') if (listRes.ok) { const listData = await listRes.json() const reqs = listData.requirements || listData if (Array.isArray(reqs) && reqs.length > 0) { const mapped = reqs.map((r: Record) => ({ id: (r.requirement_id || r.id) as string, regulation: (r.regulation_code || r.regulation || '') as string, article: (r.article || '') as string, title: (r.title || '') as string, description: (r.description || '') as string, criticality: ((r.criticality || r.priority || 'MEDIUM') as string).toUpperCase() as RiskSeverity, applicableModules: [] as string[], status: 'NOT_STARTED' as RequirementStatus, controls: [] as string[], })) dispatch({ type: 'SET_STATE', payload: { requirements: mapped } }) } } } else { setRagResult({ created: 0, skipped_duplicates: 0, message: 'RAG-Extraktion fehlgeschlagen' }) } } catch { setRagResult({ created: 0, skipped_duplicates: 0, message: 'RAG-Extraktion nicht erreichbar' }) } finally { setRagExtracting(false) } } // Fetch requirements from backend on mount useEffect(() => { const fetchRequirements = async () => { try { setLoading(true) const res = await fetch('/api/sdk/v1/compliance/requirements') if (res.ok) { const data = await res.json() const backendRequirements = data.requirements || data if (Array.isArray(backendRequirements) && backendRequirements.length > 0) { // Map backend data to SDK format and load into state const mapped: SDKRequirement[] = backendRequirements.map((r: Record) => ({ id: (r.requirement_id || r.id) as string, regulation: (r.regulation_code || r.regulation || '') as string, article: (r.article || '') as string, title: (r.title || '') as string, description: (r.description || '') as string, criticality: ((r.criticality || r.priority || 'MEDIUM') as string).toUpperCase() as RiskSeverity, applicableModules: (r.applicable_modules || []) as string[], status: (r.status || 'NOT_STARTED') as RequirementStatus, controls: (r.controls || []) as string[], })) dispatch({ type: 'SET_STATE', payload: { requirements: mapped } }) setError(null) return } } // If backend returns empty or fails, fall back to templates loadFromTemplates() } catch { // Backend unavailable — use templates loadFromTemplates() } finally { setLoading(false) } } const loadFromTemplates = () => { if (state.requirements.length > 0) return // Already have data if (state.modules.length === 0) return // No modules yet const activeModuleIds = state.modules.map(m => m.id) const relevantRequirements = requirementTemplates.filter(r => r.applicableModules.some(m => activeModuleIds.includes(m)) ) relevantRequirements.forEach(req => { const sdkRequirement: SDKRequirement = { id: req.id, regulation: req.regulation, article: req.article, title: req.title, description: req.description, criticality: req.criticality, applicableModules: req.applicableModules, status: 'NOT_STARTED', controls: [], } dispatch({ type: 'ADD_REQUIREMENT', payload: sdkRequirement }) }) } fetchRequirements() }, []) // eslint-disable-line react-hooks/exhaustive-deps const handleStatusChange = async (requirementId: string, status: RequirementStatus) => { const previousStatus = state.requirements.find(r => r.id === requirementId)?.status dispatch({ type: 'UPDATE_REQUIREMENT', payload: { id: requirementId, data: { status } }, }) // Persist to backend try { const res = await fetch(`/api/sdk/v1/compliance/requirements/${requirementId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ implementation_status: status.toLowerCase() }), }) if (!res.ok) { // Rollback on failure if (previousStatus) { dispatch({ type: 'UPDATE_REQUIREMENT', payload: { id: requirementId, data: { status: previousStatus } } }) } setError('Status-Aenderung konnte nicht gespeichert werden') } } catch { if (previousStatus) { dispatch({ type: 'UPDATE_REQUIREMENT', payload: { id: requirementId, data: { status: previousStatus } } }) } setError('Backend nicht erreichbar — Aenderung zurueckgesetzt') } } const handleDeleteRequirement = async (requirementId: string) => { if (!confirm('Anforderung wirklich loeschen?')) return try { const res = await fetch(`/api/sdk/v1/compliance/requirements/${requirementId}`, { method: 'DELETE', }) if (res.ok) { dispatch({ type: 'SET_STATE', payload: { requirements: state.requirements.filter(r => r.id !== requirementId) } }) } else { setError('Loeschen fehlgeschlagen') } } catch { setError('Backend nicht erreichbar') } } const handleAddRequirement = async (data: AddRequirementData): Promise => { // Try to resolve regulation_id from backend let regulationId = '' try { const regRes = await fetch(`/api/sdk/v1/compliance/regulations/${data.regulation}`) if (regRes.ok) { const regData = await regRes.json() regulationId = regData.id } } catch { // Regulation not found — still add locally } const priorityMap: Record = { LOW: 1, MEDIUM: 2, HIGH: 3, CRITICAL: 4 } if (regulationId) { try { const res = await fetch('/api/sdk/v1/compliance/requirements', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ regulation_id: regulationId, article: data.article, title: data.title, description: data.description, priority: priorityMap[data.criticality] || 2, }), }) if (res.ok) { const created = await res.json() const newReq: SDKRequirement = { id: created.id, regulation: data.regulation, article: data.article, title: data.title, description: data.description, criticality: data.criticality, applicableModules: [], status: 'NOT_STARTED', controls: [], } dispatch({ type: 'ADD_REQUIREMENT', payload: newReq }) return true } } catch { // Fall through to local-only add } } // Fallback: add locally only const newReq: SDKRequirement = { id: `req-${Date.now()}`, regulation: data.regulation, article: data.article, title: data.title, description: data.description, criticality: data.criticality, applicableModules: [], status: 'NOT_STARTED', controls: [], } dispatch({ type: 'ADD_REQUIREMENT', payload: newReq }) return true } return { state, loading, error, setError, ragExtracting, ragResult, setRagResult, extractFromRAG, handleStatusChange, handleDeleteRequirement, handleAddRequirement, } }