'use client' import { useCallback } from 'react' import { VendorComplianceState, VendorComplianceAction, ProcessingActivity, Vendor, ContractDocument, Finding, ControlInstance, ExportFormat, } from './types' const API_BASE = '/api/sdk/v1/vendor-compliance' /** * Encapsulates all vendor-compliance API action callbacks. * Called from the provider so that dispatch/state stay internal. */ export function useVendorComplianceActions( state: VendorComplianceState, dispatch: React.Dispatch ) { // ========================================== // DATA LOADING // ========================================== const loadData = useCallback(async () => { dispatch({ type: 'SET_LOADING', payload: true }) dispatch({ type: 'SET_ERROR', payload: null }) try { const [ activitiesRes, vendorsRes, contractsRes, findingsRes, controlsRes, controlInstancesRes, ] = await Promise.all([ fetch(`${API_BASE}/processing-activities`), fetch(`${API_BASE}/vendors`), fetch(`${API_BASE}/contracts`), fetch(`${API_BASE}/findings`), fetch(`${API_BASE}/controls`), fetch(`${API_BASE}/control-instances`), ]) if (activitiesRes.ok) { const data = await activitiesRes.json() dispatch({ type: 'SET_PROCESSING_ACTIVITIES', payload: data.data || [] }) } if (vendorsRes.ok) { const data = await vendorsRes.json() dispatch({ type: 'SET_VENDORS', payload: data.data || [] }) } if (contractsRes.ok) { const data = await contractsRes.json() dispatch({ type: 'SET_CONTRACTS', payload: data.data || [] }) } if (findingsRes.ok) { const data = await findingsRes.json() dispatch({ type: 'SET_FINDINGS', payload: data.data || [] }) } if (controlsRes.ok) { const data = await controlsRes.json() dispatch({ type: 'SET_CONTROLS', payload: data.data || [] }) } if (controlInstancesRes.ok) { const data = await controlInstancesRes.json() dispatch({ type: 'SET_CONTROL_INSTANCES', payload: data.data || [] }) } } catch (error) { console.error('Failed to load vendor compliance data:', error) dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Laden der Daten' }) } finally { dispatch({ type: 'SET_LOADING', payload: false }) } }, [dispatch]) const refresh = useCallback(async () => { await loadData() }, [loadData]) // ========================================== // PROCESSING ACTIVITIES // ========================================== const createProcessingActivity = useCallback( async ( data: Omit ): Promise => { const response = await fetch(`${API_BASE}/processing-activities`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Erstellen der Verarbeitungstätigkeit') } const result = await response.json() dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: result.data }) return result.data }, [dispatch] ) const updateProcessingActivity = useCallback( async (id: string, data: Partial): Promise => { const response = await fetch(`${API_BASE}/processing-activities/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Aktualisieren der Verarbeitungstätigkeit') } dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, data } }) }, [dispatch] ) const deleteProcessingActivity = useCallback( async (id: string): Promise => { const response = await fetch(`${API_BASE}/processing-activities/${id}`, { method: 'DELETE', }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Löschen der Verarbeitungstätigkeit') } dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id }) }, [dispatch] ) const duplicateProcessingActivity = useCallback( async (id: string): Promise => { const original = state.processingActivities.find((a) => a.id === id) if (!original) { throw new Error('Verarbeitungstätigkeit nicht gefunden') } const { id: _id, vvtId: _vvtId, createdAt: _createdAt, updatedAt: _updatedAt, tenantId: _tenantId, ...rest } = original return createProcessingActivity({ ...rest, vvtId: '', name: { de: `${original.name.de} (Kopie)`, en: `${original.name.en} (Copy)`, }, status: 'DRAFT', }) }, [state.processingActivities, createProcessingActivity] ) // ========================================== // VENDORS // ========================================== const createVendor = useCallback( async ( data: Omit ): Promise => { const response = await fetch(`${API_BASE}/vendors`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Erstellen des Vendors') } const result = await response.json() dispatch({ type: 'ADD_VENDOR', payload: result.data }) return result.data }, [dispatch] ) const updateVendor = useCallback( async (id: string, data: Partial): Promise => { const response = await fetch(`${API_BASE}/vendors/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Aktualisieren des Vendors') } dispatch({ type: 'UPDATE_VENDOR', payload: { id, data } }) }, [dispatch] ) const deleteVendor = useCallback( async (id: string): Promise => { const response = await fetch(`${API_BASE}/vendors/${id}`, { method: 'DELETE', }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Löschen des Vendors') } dispatch({ type: 'DELETE_VENDOR', payload: id }) }, [dispatch] ) // ========================================== // CONTRACTS // ========================================== const uploadContract = useCallback( async ( vendorId: string, file: File, metadata: Partial ): Promise => { const formData = new FormData() formData.append('file', file) formData.append('vendorId', vendorId) formData.append('metadata', JSON.stringify(metadata)) const response = await fetch(`${API_BASE}/contracts`, { method: 'POST', body: formData, }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Hochladen des Vertrags') } const result = await response.json() const contract = result.data dispatch({ type: 'ADD_CONTRACT', payload: contract }) const vendor = state.vendors.find((v) => v.id === vendorId) if (vendor) { dispatch({ type: 'UPDATE_VENDOR', payload: { id: vendorId, data: { contracts: [...vendor.contracts, contract.id] } }, }) } return contract }, [dispatch, state.vendors] ) const updateContract = useCallback( async (id: string, data: Partial): Promise => { const response = await fetch(`${API_BASE}/contracts/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Aktualisieren des Vertrags') } dispatch({ type: 'UPDATE_CONTRACT', payload: { id, data } }) }, [dispatch] ) const deleteContract = useCallback( async (id: string): Promise => { const contract = state.contracts.find((c) => c.id === id) const response = await fetch(`${API_BASE}/contracts/${id}`, { method: 'DELETE', }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Löschen des Vertrags') } dispatch({ type: 'DELETE_CONTRACT', payload: id }) if (contract) { const vendor = state.vendors.find((v) => v.id === contract.vendorId) if (vendor) { dispatch({ type: 'UPDATE_VENDOR', payload: { id: vendor.id, data: { contracts: vendor.contracts.filter((cId) => cId !== id) } }, }) } } }, [dispatch, state.contracts, state.vendors] ) const startContractReview = useCallback( async (contractId: string): Promise => { dispatch({ type: 'UPDATE_CONTRACT', payload: { id: contractId, data: { reviewStatus: 'IN_PROGRESS' } }, }) const response = await fetch(`${API_BASE}/contracts/${contractId}/review`, { method: 'POST', }) if (!response.ok) { dispatch({ type: 'UPDATE_CONTRACT', payload: { id: contractId, data: { reviewStatus: 'FAILED' } }, }) const error = await response.json() throw new Error(error.error || 'Fehler beim Starten der Vertragsprüfung') } const result = await response.json() dispatch({ type: 'UPDATE_CONTRACT', payload: { id: contractId, data: { reviewStatus: 'COMPLETED', reviewCompletedAt: new Date(), complianceScore: result.data.complianceScore, }, }, }) if (result.data.findings && result.data.findings.length > 0) { dispatch({ type: 'ADD_FINDINGS', payload: result.data.findings }) } }, [dispatch] ) // ========================================== // FINDINGS // ========================================== const updateFinding = useCallback( async (id: string, data: Partial): Promise => { const response = await fetch(`${API_BASE}/findings/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Aktualisieren des Findings') } dispatch({ type: 'UPDATE_FINDING', payload: { id, data } }) }, [dispatch] ) const resolveFinding = useCallback( async (id: string, resolution: string): Promise => { await updateFinding(id, { status: 'RESOLVED', resolution, resolvedAt: new Date(), }) }, [updateFinding] ) // ========================================== // CONTROLS // ========================================== const updateControlInstance = useCallback( async (id: string, data: Partial): Promise => { const response = await fetch(`${API_BASE}/control-instances/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Aktualisieren des Control-Status') } dispatch({ type: 'UPDATE_CONTROL_INSTANCE', payload: { id, data } }) }, [dispatch] ) // ========================================== // EXPORTS // ========================================== const exportVVT = useCallback( async (format: ExportFormat, activityIds?: string[]): Promise => { const params = new URLSearchParams({ format }) if (activityIds && activityIds.length > 0) { params.append('activityIds', activityIds.join(',')) } const response = await fetch(`${API_BASE}/export/vvt?${params}`) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Exportieren des VVT') } const blob = await response.blob() return URL.createObjectURL(blob) }, [] ) const exportVendorAuditPack = useCallback( async (vendorId: string, format: ExportFormat): Promise => { const params = new URLSearchParams({ format, vendorId }) const response = await fetch(`${API_BASE}/export/vendor-audit?${params}`) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Exportieren des Vendor Audit Packs') } const blob = await response.blob() return URL.createObjectURL(blob) }, [] ) const exportRoPA = useCallback( async (format: ExportFormat): Promise => { const params = new URLSearchParams({ format }) const response = await fetch(`${API_BASE}/export/ropa?${params}`) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Fehler beim Exportieren des RoPA') } const blob = await response.blob() return URL.createObjectURL(blob) }, [] ) return { loadData, refresh, createProcessingActivity, updateProcessingActivity, deleteProcessingActivity, duplicateProcessingActivity, createVendor, updateVendor, deleteVendor, uploadContract, updateContract, deleteContract, startContractReview, updateFinding, resolveFinding, updateControlInstance, exportVVT, exportVendorAuditPack, exportRoPA, } }