// ============================================================================= // TOM Generator Reducer // Action types and state reducer for the TOM Generator Wizard // ============================================================================= import { TOMGeneratorState, TOMGeneratorStepId, CompanyProfile, DataProfile, ArchitectureProfile, SecurityProfile, RiskProfile, EvidenceDocument, DerivedTOM, GapAnalysisResult, ExportRecord, WizardStep, createInitialTOMGeneratorState, calculateProtectionLevel, isDSFARequired, hasSpecialCategories, } from './types' // ============================================================================= // ACTION TYPES // ============================================================================= export type TOMGeneratorAction = | { type: 'INITIALIZE'; payload: { tenantId: string; state?: TOMGeneratorState } } | { type: 'RESET'; payload: { tenantId: string } } | { type: 'SET_CURRENT_STEP'; payload: TOMGeneratorStepId } | { type: 'SET_COMPANY_PROFILE'; payload: CompanyProfile } | { type: 'UPDATE_COMPANY_PROFILE'; payload: Partial } | { type: 'SET_DATA_PROFILE'; payload: DataProfile } | { type: 'UPDATE_DATA_PROFILE'; payload: Partial } | { type: 'SET_ARCHITECTURE_PROFILE'; payload: ArchitectureProfile } | { type: 'UPDATE_ARCHITECTURE_PROFILE'; payload: Partial } | { type: 'SET_SECURITY_PROFILE'; payload: SecurityProfile } | { type: 'UPDATE_SECURITY_PROFILE'; payload: Partial } | { type: 'SET_RISK_PROFILE'; payload: RiskProfile } | { type: 'UPDATE_RISK_PROFILE'; payload: Partial } | { type: 'COMPLETE_STEP'; payload: { stepId: TOMGeneratorStepId; data: unknown } } | { type: 'UNCOMPLETE_STEP'; payload: TOMGeneratorStepId } | { type: 'ADD_EVIDENCE'; payload: EvidenceDocument } | { type: 'UPDATE_EVIDENCE'; payload: { id: string; data: Partial } } | { type: 'DELETE_EVIDENCE'; payload: string } | { type: 'SET_DERIVED_TOMS'; payload: DerivedTOM[] } | { type: 'UPDATE_DERIVED_TOM'; payload: { id: string; data: Partial } } | { type: 'SET_GAP_ANALYSIS'; payload: GapAnalysisResult } | { type: 'ADD_EXPORT'; payload: ExportRecord } | { type: 'BULK_UPDATE_TOMS'; payload: { updates: Array<{ id: string; data: Partial }> } } | { type: 'LOAD_STATE'; payload: TOMGeneratorState } // ============================================================================= // REDUCER // ============================================================================= export function tomGeneratorReducer( state: TOMGeneratorState, action: TOMGeneratorAction ): TOMGeneratorState { const updateState = (updates: Partial): TOMGeneratorState => ({ ...state, ...updates, updatedAt: new Date(), }) switch (action.type) { case 'INITIALIZE': { if (action.payload.state) { return action.payload.state } return createInitialTOMGeneratorState(action.payload.tenantId) } case 'RESET': { return createInitialTOMGeneratorState(action.payload.tenantId) } case 'SET_CURRENT_STEP': { return updateState({ currentStep: action.payload }) } case 'SET_COMPANY_PROFILE': { return updateState({ companyProfile: action.payload }) } case 'UPDATE_COMPANY_PROFILE': { if (!state.companyProfile) return state return updateState({ companyProfile: { ...state.companyProfile, ...action.payload }, }) } case 'SET_DATA_PROFILE': { const profile: DataProfile = { ...action.payload, hasSpecialCategories: hasSpecialCategories(action.payload.categories), } return updateState({ dataProfile: profile }) } case 'UPDATE_DATA_PROFILE': { if (!state.dataProfile) return state const updatedProfile = { ...state.dataProfile, ...action.payload } if (action.payload.categories) { updatedProfile.hasSpecialCategories = hasSpecialCategories( action.payload.categories ) } return updateState({ dataProfile: updatedProfile }) } case 'SET_ARCHITECTURE_PROFILE': { return updateState({ architectureProfile: action.payload }) } case 'UPDATE_ARCHITECTURE_PROFILE': { if (!state.architectureProfile) return state return updateState({ architectureProfile: { ...state.architectureProfile, ...action.payload }, }) } case 'SET_SECURITY_PROFILE': { return updateState({ securityProfile: action.payload }) } case 'UPDATE_SECURITY_PROFILE': { if (!state.securityProfile) return state return updateState({ securityProfile: { ...state.securityProfile, ...action.payload }, }) } case 'SET_RISK_PROFILE': { const profile: RiskProfile = { ...action.payload, protectionLevel: calculateProtectionLevel(action.payload.ciaAssessment), dsfaRequired: isDSFARequired(state.dataProfile, action.payload), } return updateState({ riskProfile: profile }) } case 'UPDATE_RISK_PROFILE': { if (!state.riskProfile) return state const updatedProfile = { ...state.riskProfile, ...action.payload } if (action.payload.ciaAssessment) { updatedProfile.protectionLevel = calculateProtectionLevel( action.payload.ciaAssessment ) } updatedProfile.dsfaRequired = isDSFARequired(state.dataProfile, updatedProfile) return updateState({ riskProfile: updatedProfile }) } case 'COMPLETE_STEP': { const updatedSteps = state.steps.map((step) => step.id === action.payload.stepId ? { ...step, completed: true, data: action.payload.data, validatedAt: new Date(), } : step ) return updateState({ steps: updatedSteps }) } case 'UNCOMPLETE_STEP': { const updatedSteps = state.steps.map((step) => step.id === action.payload ? { ...step, completed: false, validatedAt: null } : step ) return updateState({ steps: updatedSteps }) } case 'ADD_EVIDENCE': { return updateState({ documents: [...state.documents, action.payload], }) } case 'UPDATE_EVIDENCE': { const updatedDocuments = state.documents.map((doc) => doc.id === action.payload.id ? { ...doc, ...action.payload.data } : doc ) return updateState({ documents: updatedDocuments }) } case 'DELETE_EVIDENCE': { return updateState({ documents: state.documents.filter((doc) => doc.id !== action.payload), }) } case 'SET_DERIVED_TOMS': { return updateState({ derivedTOMs: action.payload }) } case 'UPDATE_DERIVED_TOM': { const updatedTOMs = state.derivedTOMs.map((tom) => tom.id === action.payload.id ? { ...tom, ...action.payload.data } : tom ) return updateState({ derivedTOMs: updatedTOMs }) } case 'SET_GAP_ANALYSIS': { return updateState({ gapAnalysis: action.payload }) } case 'ADD_EXPORT': { return updateState({ exports: [...state.exports, action.payload], }) } case 'BULK_UPDATE_TOMS': { let updatedTOMs = [...state.derivedTOMs] for (const update of action.payload.updates) { updatedTOMs = updatedTOMs.map((tom) => tom.id === update.id ? { ...tom, ...update.data } : tom ) } return updateState({ derivedTOMs: updatedTOMs }) } case 'LOAD_STATE': { return action.payload } default: return state } }