Files split by agents before rate limit: - dsr/api.ts (669 → barrel + helpers) - einwilligungen/context.tsx (669 → barrel + hooks/reducer) - export.ts (753 → barrel + domain exporters) - incidents/api.ts (845 → barrel + api-helpers) - tom-generator/context.tsx (720 → barrel + hooks/reducer) - vendor-compliance/context.tsx (1010 → 234 provider + hooks/reducer) - api-docs/endpoints.ts — partially split (3 domain files created) - academy/api.ts — partially split (helpers extracted) - whistleblower/api.ts — partially split (helpers extracted) next build passes. api-client.ts (885) deferred to next session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
474 lines
14 KiB
TypeScript
474 lines
14 KiB
TypeScript
'use client'
|
|
|
|
// =============================================================================
|
|
// TOM Generator Provider
|
|
// Context provider component for the TOM Generator Wizard
|
|
// =============================================================================
|
|
|
|
import React, {
|
|
createContext,
|
|
useReducer,
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
ReactNode,
|
|
} from 'react'
|
|
import {
|
|
TOMGeneratorState,
|
|
TOMGeneratorStepId,
|
|
CompanyProfile,
|
|
DataProfile,
|
|
ArchitectureProfile,
|
|
SecurityProfile,
|
|
RiskProfile,
|
|
EvidenceDocument,
|
|
DerivedTOM,
|
|
ExportRecord,
|
|
WizardStep,
|
|
createInitialTOMGeneratorState,
|
|
TOM_GENERATOR_STEPS,
|
|
getStepIndex,
|
|
} from './types'
|
|
import { TOMRulesEngine } from './rules-engine'
|
|
import { tomGeneratorReducer, TOMGeneratorAction } from './reducer'
|
|
|
|
// =============================================================================
|
|
// CONTEXT VALUE INTERFACE
|
|
// =============================================================================
|
|
|
|
export interface TOMGeneratorContextValue {
|
|
state: TOMGeneratorState
|
|
dispatch: React.Dispatch<TOMGeneratorAction>
|
|
|
|
// Navigation
|
|
currentStepIndex: number
|
|
totalSteps: number
|
|
canGoNext: boolean
|
|
canGoPrevious: boolean
|
|
goToStep: (stepId: TOMGeneratorStepId) => void
|
|
goToNextStep: () => void
|
|
goToPreviousStep: () => void
|
|
completeCurrentStep: (data: unknown) => void
|
|
|
|
// Profile setters
|
|
setCompanyProfile: (profile: CompanyProfile) => void
|
|
updateCompanyProfile: (data: Partial<CompanyProfile>) => void
|
|
setDataProfile: (profile: DataProfile) => void
|
|
updateDataProfile: (data: Partial<DataProfile>) => void
|
|
setArchitectureProfile: (profile: ArchitectureProfile) => void
|
|
updateArchitectureProfile: (data: Partial<ArchitectureProfile>) => void
|
|
setSecurityProfile: (profile: SecurityProfile) => void
|
|
updateSecurityProfile: (data: Partial<SecurityProfile>) => void
|
|
setRiskProfile: (profile: RiskProfile) => void
|
|
updateRiskProfile: (data: Partial<RiskProfile>) => void
|
|
|
|
// Evidence management
|
|
addEvidence: (document: EvidenceDocument) => void
|
|
updateEvidence: (id: string, data: Partial<EvidenceDocument>) => void
|
|
deleteEvidence: (id: string) => void
|
|
|
|
// TOM derivation
|
|
deriveTOMs: () => void
|
|
updateDerivedTOM: (id: string, data: Partial<DerivedTOM>) => void
|
|
bulkUpdateTOMs: (updates: Array<{ id: string; data: Partial<DerivedTOM> }>) => void
|
|
|
|
// Gap analysis
|
|
runGapAnalysis: () => void
|
|
|
|
// Export
|
|
addExport: (record: ExportRecord) => void
|
|
|
|
// Persistence
|
|
saveState: () => Promise<void>
|
|
loadState: () => Promise<void>
|
|
resetState: () => void
|
|
|
|
// Status
|
|
isStepCompleted: (stepId: TOMGeneratorStepId) => boolean
|
|
getCompletionPercentage: () => number
|
|
isLoading: boolean
|
|
error: string | null
|
|
}
|
|
|
|
// =============================================================================
|
|
// CONTEXT
|
|
// =============================================================================
|
|
|
|
export const TOMGeneratorContext = createContext<TOMGeneratorContextValue | null>(null)
|
|
|
|
// =============================================================================
|
|
// STORAGE KEYS
|
|
// =============================================================================
|
|
|
|
const STORAGE_KEY_PREFIX = 'tom-generator-state-'
|
|
|
|
function getStorageKey(tenantId: string): string {
|
|
return `${STORAGE_KEY_PREFIX}${tenantId}`
|
|
}
|
|
|
|
// =============================================================================
|
|
// PROVIDER COMPONENT
|
|
// =============================================================================
|
|
|
|
interface TOMGeneratorProviderProps {
|
|
children: ReactNode
|
|
tenantId: string
|
|
initialState?: TOMGeneratorState
|
|
enablePersistence?: boolean
|
|
}
|
|
|
|
export function TOMGeneratorProvider({
|
|
children,
|
|
tenantId,
|
|
initialState,
|
|
enablePersistence = true,
|
|
}: TOMGeneratorProviderProps) {
|
|
const [state, dispatch] = useReducer(
|
|
tomGeneratorReducer,
|
|
initialState ?? createInitialTOMGeneratorState(tenantId)
|
|
)
|
|
|
|
const [isLoading, setIsLoading] = React.useState(false)
|
|
const [error, setError] = React.useState<string | null>(null)
|
|
|
|
const rulesEngineRef = useRef<TOMRulesEngine | null>(null)
|
|
|
|
// Initialize rules engine
|
|
useEffect(() => {
|
|
if (!rulesEngineRef.current) {
|
|
rulesEngineRef.current = new TOMRulesEngine()
|
|
}
|
|
}, [])
|
|
|
|
// Load state from localStorage on mount
|
|
useEffect(() => {
|
|
if (enablePersistence && typeof window !== 'undefined') {
|
|
try {
|
|
const stored = localStorage.getItem(getStorageKey(tenantId))
|
|
if (stored) {
|
|
const parsed = JSON.parse(stored)
|
|
if (parsed.createdAt) parsed.createdAt = new Date(parsed.createdAt)
|
|
if (parsed.updatedAt) parsed.updatedAt = new Date(parsed.updatedAt)
|
|
if (parsed.steps) {
|
|
parsed.steps = parsed.steps.map((step: WizardStep) => ({
|
|
...step,
|
|
validatedAt: step.validatedAt ? new Date(step.validatedAt) : null,
|
|
}))
|
|
}
|
|
if (parsed.documents) {
|
|
parsed.documents = parsed.documents.map((doc: EvidenceDocument) => ({
|
|
...doc,
|
|
uploadedAt: new Date(doc.uploadedAt),
|
|
validFrom: doc.validFrom ? new Date(doc.validFrom) : null,
|
|
validUntil: doc.validUntil ? new Date(doc.validUntil) : null,
|
|
aiAnalysis: doc.aiAnalysis
|
|
? {
|
|
...doc.aiAnalysis,
|
|
analyzedAt: new Date(doc.aiAnalysis.analyzedAt),
|
|
}
|
|
: null,
|
|
}))
|
|
}
|
|
if (parsed.derivedTOMs) {
|
|
parsed.derivedTOMs = parsed.derivedTOMs.map((tom: DerivedTOM) => ({
|
|
...tom,
|
|
implementationDate: tom.implementationDate
|
|
? new Date(tom.implementationDate)
|
|
: null,
|
|
reviewDate: tom.reviewDate ? new Date(tom.reviewDate) : null,
|
|
}))
|
|
}
|
|
if (parsed.gapAnalysis?.generatedAt) {
|
|
parsed.gapAnalysis.generatedAt = new Date(parsed.gapAnalysis.generatedAt)
|
|
}
|
|
if (parsed.exports) {
|
|
parsed.exports = parsed.exports.map((exp: ExportRecord) => ({
|
|
...exp,
|
|
generatedAt: new Date(exp.generatedAt),
|
|
}))
|
|
}
|
|
dispatch({ type: 'LOAD_STATE', payload: parsed })
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to load TOM Generator state from localStorage:', e)
|
|
}
|
|
}
|
|
}, [tenantId, enablePersistence])
|
|
|
|
// Save state to localStorage on changes
|
|
useEffect(() => {
|
|
if (enablePersistence && typeof window !== 'undefined') {
|
|
try {
|
|
localStorage.setItem(getStorageKey(tenantId), JSON.stringify(state))
|
|
} catch (e) {
|
|
console.error('Failed to save TOM Generator state to localStorage:', e)
|
|
}
|
|
}
|
|
}, [state, tenantId, enablePersistence])
|
|
|
|
// Navigation helpers
|
|
const currentStepIndex = getStepIndex(state.currentStep)
|
|
const totalSteps = TOM_GENERATOR_STEPS.length
|
|
|
|
const canGoNext = currentStepIndex < totalSteps - 1
|
|
const canGoPrevious = currentStepIndex > 0
|
|
|
|
const goToStep = useCallback((stepId: TOMGeneratorStepId) => {
|
|
dispatch({ type: 'SET_CURRENT_STEP', payload: stepId })
|
|
}, [])
|
|
|
|
const goToNextStep = useCallback(() => {
|
|
if (canGoNext) {
|
|
const nextStep = TOM_GENERATOR_STEPS[currentStepIndex + 1]
|
|
dispatch({ type: 'SET_CURRENT_STEP', payload: nextStep.id })
|
|
}
|
|
}, [canGoNext, currentStepIndex])
|
|
|
|
const goToPreviousStep = useCallback(() => {
|
|
if (canGoPrevious) {
|
|
const prevStep = TOM_GENERATOR_STEPS[currentStepIndex - 1]
|
|
dispatch({ type: 'SET_CURRENT_STEP', payload: prevStep.id })
|
|
}
|
|
}, [canGoPrevious, currentStepIndex])
|
|
|
|
const completeCurrentStep = useCallback(
|
|
(data: unknown) => {
|
|
dispatch({
|
|
type: 'COMPLETE_STEP',
|
|
payload: { stepId: state.currentStep, data },
|
|
})
|
|
},
|
|
[state.currentStep]
|
|
)
|
|
|
|
// Profile setters
|
|
const setCompanyProfile = useCallback((profile: CompanyProfile) => {
|
|
dispatch({ type: 'SET_COMPANY_PROFILE', payload: profile })
|
|
}, [])
|
|
|
|
const updateCompanyProfile = useCallback((data: Partial<CompanyProfile>) => {
|
|
dispatch({ type: 'UPDATE_COMPANY_PROFILE', payload: data })
|
|
}, [])
|
|
|
|
const setDataProfile = useCallback((profile: DataProfile) => {
|
|
dispatch({ type: 'SET_DATA_PROFILE', payload: profile })
|
|
}, [])
|
|
|
|
const updateDataProfile = useCallback((data: Partial<DataProfile>) => {
|
|
dispatch({ type: 'UPDATE_DATA_PROFILE', payload: data })
|
|
}, [])
|
|
|
|
const setArchitectureProfile = useCallback((profile: ArchitectureProfile) => {
|
|
dispatch({ type: 'SET_ARCHITECTURE_PROFILE', payload: profile })
|
|
}, [])
|
|
|
|
const updateArchitectureProfile = useCallback(
|
|
(data: Partial<ArchitectureProfile>) => {
|
|
dispatch({ type: 'UPDATE_ARCHITECTURE_PROFILE', payload: data })
|
|
},
|
|
[]
|
|
)
|
|
|
|
const setSecurityProfile = useCallback((profile: SecurityProfile) => {
|
|
dispatch({ type: 'SET_SECURITY_PROFILE', payload: profile })
|
|
}, [])
|
|
|
|
const updateSecurityProfile = useCallback((data: Partial<SecurityProfile>) => {
|
|
dispatch({ type: 'UPDATE_SECURITY_PROFILE', payload: data })
|
|
}, [])
|
|
|
|
const setRiskProfile = useCallback((profile: RiskProfile) => {
|
|
dispatch({ type: 'SET_RISK_PROFILE', payload: profile })
|
|
}, [])
|
|
|
|
const updateRiskProfile = useCallback((data: Partial<RiskProfile>) => {
|
|
dispatch({ type: 'UPDATE_RISK_PROFILE', payload: data })
|
|
}, [])
|
|
|
|
// Evidence management
|
|
const addEvidence = useCallback((document: EvidenceDocument) => {
|
|
dispatch({ type: 'ADD_EVIDENCE', payload: document })
|
|
}, [])
|
|
|
|
const updateEvidence = useCallback(
|
|
(id: string, data: Partial<EvidenceDocument>) => {
|
|
dispatch({ type: 'UPDATE_EVIDENCE', payload: { id, data } })
|
|
},
|
|
[]
|
|
)
|
|
|
|
const deleteEvidence = useCallback((id: string) => {
|
|
dispatch({ type: 'DELETE_EVIDENCE', payload: id })
|
|
}, [])
|
|
|
|
// TOM derivation
|
|
const deriveTOMs = useCallback(() => {
|
|
if (!rulesEngineRef.current) return
|
|
|
|
const derivedTOMs = rulesEngineRef.current.deriveAllTOMs({
|
|
companyProfile: state.companyProfile,
|
|
dataProfile: state.dataProfile,
|
|
architectureProfile: state.architectureProfile,
|
|
securityProfile: state.securityProfile,
|
|
riskProfile: state.riskProfile,
|
|
})
|
|
|
|
dispatch({ type: 'SET_DERIVED_TOMS', payload: derivedTOMs })
|
|
}, [
|
|
state.companyProfile,
|
|
state.dataProfile,
|
|
state.architectureProfile,
|
|
state.securityProfile,
|
|
state.riskProfile,
|
|
])
|
|
|
|
const updateDerivedTOM = useCallback(
|
|
(id: string, data: Partial<DerivedTOM>) => {
|
|
dispatch({ type: 'UPDATE_DERIVED_TOM', payload: { id, data } })
|
|
},
|
|
[]
|
|
)
|
|
|
|
const bulkUpdateTOMs = useCallback(
|
|
(updates: Array<{ id: string; data: Partial<DerivedTOM> }>) => {
|
|
for (const { id, data } of updates) {
|
|
dispatch({ type: 'UPDATE_DERIVED_TOM', payload: { id, data } })
|
|
}
|
|
},
|
|
[]
|
|
)
|
|
|
|
// Gap analysis
|
|
const runGapAnalysis = useCallback(() => {
|
|
if (!rulesEngineRef.current) return
|
|
|
|
const result = rulesEngineRef.current.performGapAnalysis(
|
|
state.derivedTOMs,
|
|
state.documents
|
|
)
|
|
|
|
dispatch({ type: 'SET_GAP_ANALYSIS', payload: result })
|
|
}, [state.derivedTOMs, state.documents])
|
|
|
|
// Export
|
|
const addExport = useCallback((record: ExportRecord) => {
|
|
dispatch({ type: 'ADD_EXPORT', payload: record })
|
|
}, [])
|
|
|
|
// Persistence
|
|
const saveState = useCallback(async () => {
|
|
setIsLoading(true)
|
|
setError(null)
|
|
try {
|
|
const response = await fetch('/api/sdk/v1/tom-generator/state', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ tenantId, state }),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to save state')
|
|
}
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : 'Unknown error')
|
|
throw e
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [tenantId, state])
|
|
|
|
const loadState = useCallback(async () => {
|
|
setIsLoading(true)
|
|
setError(null)
|
|
try {
|
|
const response = await fetch(
|
|
`/api/sdk/v1/tom-generator/state?tenantId=${tenantId}`
|
|
)
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load state')
|
|
}
|
|
|
|
const data = await response.json()
|
|
if (data.state) {
|
|
dispatch({ type: 'LOAD_STATE', payload: data.state })
|
|
}
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : 'Unknown error')
|
|
throw e
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [tenantId])
|
|
|
|
const resetState = useCallback(() => {
|
|
dispatch({ type: 'RESET', payload: { tenantId } })
|
|
}, [tenantId])
|
|
|
|
// Status helpers
|
|
const isStepCompleted = useCallback(
|
|
(stepId: TOMGeneratorStepId) => {
|
|
const step = state.steps.find((s) => s.id === stepId)
|
|
return step?.completed ?? false
|
|
},
|
|
[state.steps]
|
|
)
|
|
|
|
const getCompletionPercentage = useCallback(() => {
|
|
const completedSteps = state.steps.filter((s) => s.completed).length
|
|
return Math.round((completedSteps / totalSteps) * 100)
|
|
}, [state.steps, totalSteps])
|
|
|
|
const contextValue: TOMGeneratorContextValue = {
|
|
state,
|
|
dispatch,
|
|
|
|
currentStepIndex,
|
|
totalSteps,
|
|
canGoNext,
|
|
canGoPrevious,
|
|
goToStep,
|
|
goToNextStep,
|
|
goToPreviousStep,
|
|
completeCurrentStep,
|
|
|
|
setCompanyProfile,
|
|
updateCompanyProfile,
|
|
setDataProfile,
|
|
updateDataProfile,
|
|
setArchitectureProfile,
|
|
updateArchitectureProfile,
|
|
setSecurityProfile,
|
|
updateSecurityProfile,
|
|
setRiskProfile,
|
|
updateRiskProfile,
|
|
|
|
addEvidence,
|
|
updateEvidence,
|
|
deleteEvidence,
|
|
|
|
deriveTOMs,
|
|
updateDerivedTOM,
|
|
bulkUpdateTOMs,
|
|
|
|
runGapAnalysis,
|
|
|
|
addExport,
|
|
|
|
saveState,
|
|
loadState,
|
|
resetState,
|
|
|
|
isStepCompleted,
|
|
getCompletionPercentage,
|
|
isLoading,
|
|
error,
|
|
}
|
|
|
|
return (
|
|
<TOMGeneratorContext.Provider value={contextValue}>
|
|
{children}
|
|
</TOMGeneratorContext.Provider>
|
|
)
|
|
}
|