'use client' /** * useCallback factories extracted from ComplianceProvider. * * Each function creates and returns a memoised callback so provider.tsx * stays under 300 LOC. */ import { useCallback, RefObject } from 'react' import type { SDKState, CheckpointStatus, UseCaseAssessment, Risk, Control, } from '@breakpilot/compliance-sdk-types' import { ComplianceClient, StateSyncManager } from '@breakpilot/compliance-sdk-core' import { SDK_STORAGE_KEY } from './provider-context' // ============================================================================= // CHECKPOINT CALLBACKS // ============================================================================= export function useValidateCheckpoint( state: SDKState, enableBackendSync: boolean, client: ComplianceClient, dispatch: React.Dispatch<{ type: string; payload?: unknown }> ) { return useCallback( async (checkpointId: string): Promise => { if (enableBackendSync) { try { const result = await client.validateCheckpoint(checkpointId, state) const status: CheckpointStatus = { checkpointId: result.checkpointId, passed: result.passed, validatedAt: new Date(result.validatedAt), validatedBy: result.validatedBy, errors: result.errors, warnings: result.warnings, } dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } }) return status } catch { // Fall through to local validation } } const status: CheckpointStatus = { checkpointId, passed: true, validatedAt: new Date(), validatedBy: 'SYSTEM', errors: [], warnings: [], } dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } }) return status }, [state, enableBackendSync, client, dispatch] ) } export function useOverrideCheckpoint( state: SDKState, dispatch: React.Dispatch<{ type: string; payload?: unknown }> ) { return useCallback( async (checkpointId: string, reason: string): Promise => { const existingStatus = state.checkpoints[checkpointId] const overriddenStatus: CheckpointStatus = { ...existingStatus, checkpointId, passed: true, overrideReason: reason, overriddenBy: state.userId, overriddenAt: new Date(), errors: [], warnings: existingStatus?.warnings || [], } dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status: overriddenStatus } }) }, [state.checkpoints, state.userId, dispatch] ) } export function useGetCheckpointStatus(state: SDKState) { return useCallback( (checkpointId: string): CheckpointStatus | undefined => state.checkpoints[checkpointId], [state.checkpoints] ) } // ============================================================================= // STATE UPDATE CALLBACKS // ============================================================================= export function useUpdateUseCase(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) { return useCallback( (id: string, data: Partial) => { dispatch({ type: 'UPDATE_USE_CASE', payload: { id, data } }) }, [dispatch] ) } export function useAddRisk(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) { return useCallback( (risk: Risk) => { dispatch({ type: 'ADD_RISK', payload: risk }) }, [dispatch] ) } export function useUpdateControl(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) { return useCallback( (id: string, data: Partial) => { dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } }) }, [dispatch] ) } // ============================================================================= // PERSISTENCE CALLBACKS // ============================================================================= export function useSaveState( state: SDKState, tenantId: string, enableBackendSync: boolean, syncManagerRef: RefObject, setError: (e: Error) => void ) { return useCallback(async (): Promise => { try { if (typeof window !== 'undefined') { localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state)) } if (enableBackendSync && syncManagerRef.current) { await syncManagerRef.current.forceSync(state) } } catch (err) { setError(err as Error) throw err } }, [state, tenantId, enableBackendSync, syncManagerRef, setError]) } export function useLoadState( tenantId: string, enableBackendSync: boolean, syncManagerRef: RefObject, setIsLoading: (v: boolean) => void, dispatch: React.Dispatch<{ type: string; payload?: unknown }>, setError: (e: Error) => void ) { return useCallback(async (): Promise => { setIsLoading(true) try { if (enableBackendSync && syncManagerRef.current) { const serverState = await syncManagerRef.current.loadFromServer() if (serverState) { dispatch({ type: 'SET_STATE', payload: serverState }) return } } if (typeof window !== 'undefined') { const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`) if (stored) { dispatch({ type: 'SET_STATE', payload: JSON.parse(stored) }) } } } catch (err) { setError(err as Error) throw err } finally { setIsLoading(false) } }, [tenantId, enableBackendSync, syncManagerRef, setIsLoading, dispatch, setError]) } export function useResetState( tenantId: string, dispatch: React.Dispatch<{ type: string; payload?: unknown }> ) { return useCallback(() => { dispatch({ type: 'RESET_STATE' }) if (typeof window !== 'undefined') { localStorage.removeItem(`${SDK_STORAGE_KEY}-${tenantId}`) } }, [tenantId, dispatch]) } // ============================================================================= // SYNC & EXPORT CALLBACKS // ============================================================================= export function useForceSyncToServer( state: SDKState, enableBackendSync: boolean, syncManagerRef: RefObject ) { return useCallback(async (): Promise => { if (enableBackendSync && syncManagerRef.current) { await syncManagerRef.current.forceSync(state) } }, [state, enableBackendSync, syncManagerRef]) } export function useExportState(state: SDKState, client: ComplianceClient) { return useCallback( async (format: 'json' | 'pdf' | 'zip'): Promise => { if (format === 'json') { return new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' }) } return client.exportState(format) }, [state, client] ) }