import type { VendorComplianceState, VendorComplianceAction, VendorStatistics, ComplianceStatistics, RiskOverview, VendorStatus, VendorRole, RiskLevel, FindingType, FindingSeverity, } from './types' import { getRiskLevelFromScore } from './types' // ========================================== // INITIAL STATE // ========================================== export const initialState: VendorComplianceState = { processingActivities: [], vendors: [], contracts: [], findings: [], controls: [], controlInstances: [], riskAssessments: [], isLoading: false, error: null, selectedVendorId: null, selectedActivityId: null, activeTab: 'overview', lastModified: null, } // ========================================== // REDUCER // ========================================== export function vendorComplianceReducer( state: VendorComplianceState, action: VendorComplianceAction ): VendorComplianceState { const updateState = (updates: Partial): VendorComplianceState => ({ ...state, ...updates, lastModified: new Date(), }) switch (action.type) { // Processing Activities case 'SET_PROCESSING_ACTIVITIES': return updateState({ processingActivities: action.payload }) case 'ADD_PROCESSING_ACTIVITY': return updateState({ processingActivities: [...state.processingActivities, action.payload], }) case 'UPDATE_PROCESSING_ACTIVITY': return updateState({ processingActivities: state.processingActivities.map((activity) => activity.id === action.payload.id ? { ...activity, ...action.payload.data, updatedAt: new Date() } : activity ), }) case 'DELETE_PROCESSING_ACTIVITY': return updateState({ processingActivities: state.processingActivities.filter( (activity) => activity.id !== action.payload ), }) // Vendors case 'SET_VENDORS': return updateState({ vendors: action.payload }) case 'ADD_VENDOR': return updateState({ vendors: [...state.vendors, action.payload], }) case 'UPDATE_VENDOR': return updateState({ vendors: state.vendors.map((vendor) => vendor.id === action.payload.id ? { ...vendor, ...action.payload.data, updatedAt: new Date() } : vendor ), }) case 'DELETE_VENDOR': return updateState({ vendors: state.vendors.filter((vendor) => vendor.id !== action.payload), }) // Contracts case 'SET_CONTRACTS': return updateState({ contracts: action.payload }) case 'ADD_CONTRACT': return updateState({ contracts: [...state.contracts, action.payload], }) case 'UPDATE_CONTRACT': return updateState({ contracts: state.contracts.map((contract) => contract.id === action.payload.id ? { ...contract, ...action.payload.data, updatedAt: new Date() } : contract ), }) case 'DELETE_CONTRACT': return updateState({ contracts: state.contracts.filter((contract) => contract.id !== action.payload), }) // Findings case 'SET_FINDINGS': return updateState({ findings: action.payload }) case 'ADD_FINDINGS': return updateState({ findings: [...state.findings, ...action.payload], }) case 'UPDATE_FINDING': return updateState({ findings: state.findings.map((finding) => finding.id === action.payload.id ? { ...finding, ...action.payload.data, updatedAt: new Date() } : finding ), }) // Controls case 'SET_CONTROLS': return updateState({ controls: action.payload }) case 'SET_CONTROL_INSTANCES': return updateState({ controlInstances: action.payload }) case 'UPDATE_CONTROL_INSTANCE': return updateState({ controlInstances: state.controlInstances.map((instance) => instance.id === action.payload.id ? { ...instance, ...action.payload.data } : instance ), }) // Risk Assessments case 'SET_RISK_ASSESSMENTS': return updateState({ riskAssessments: action.payload }) case 'UPDATE_RISK_ASSESSMENT': return updateState({ riskAssessments: state.riskAssessments.map((assessment) => assessment.id === action.payload.id ? { ...assessment, ...action.payload.data } : assessment ), }) // UI State case 'SET_LOADING': return { ...state, isLoading: action.payload } case 'SET_ERROR': return { ...state, error: action.payload } case 'SET_SELECTED_VENDOR': return { ...state, selectedVendorId: action.payload } case 'SET_SELECTED_ACTIVITY': return { ...state, selectedActivityId: action.payload } case 'SET_ACTIVE_TAB': return { ...state, activeTab: action.payload } default: return state } } // ========================================== // COMPUTED VALUES (pure functions of state) // ========================================== export function computeVendorStats(state: VendorComplianceState): VendorStatistics { const vendors = state.vendors const byStatus = vendors.reduce( (acc, v) => { acc[v.status] = (acc[v.status] || 0) + 1 return acc }, {} as Record ) const byRole = vendors.reduce( (acc, v) => { acc[v.role] = (acc[v.role] || 0) + 1 return acc }, {} as Record ) const byRiskLevel = vendors.reduce( (acc, v) => { const level = getRiskLevelFromScore(v.residualRiskScore / 4) // Normalize to 1-25 acc[level] = (acc[level] || 0) + 1 return acc }, {} as Record ) const now = new Date() const pendingReviews = vendors.filter( (v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now ).length const withExpiredContracts = vendors.filter((v) => state.contracts.some( (c) => c.vendorId === v.id && c.expirationDate && new Date(c.expirationDate) <= now && c.status === 'ACTIVE' ) ).length return { total: vendors.length, byStatus, byRole, byRiskLevel, pendingReviews, withExpiredContracts, } } export function computeComplianceStats(state: VendorComplianceState): ComplianceStatistics { const findings = state.findings const contracts = state.contracts const controlInstances = state.controlInstances const averageComplianceScore = contracts.length > 0 ? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) / contracts.filter((c) => c.complianceScore !== undefined).length || 0 : 0 const findingsByType = findings.reduce( (acc, f) => { acc[f.type] = (acc[f.type] || 0) + 1 return acc }, {} as Record ) const findingsBySeverity = findings.reduce( (acc, f) => { acc[f.severity] = (acc[f.severity] || 0) + 1 return acc }, {} as Record ) const openFindings = findings.filter( (f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS' ).length const resolvedFindings = findings.filter( (f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE' ).length const passedControls = controlInstances.filter( (ci) => ci.status === 'PASS' ).length const applicableControls = controlInstances.filter( (ci) => ci.status !== 'NOT_APPLICABLE' ).length const controlPassRate = applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0 return { averageComplianceScore, findingsByType, findingsBySeverity, openFindings, resolvedFindings, controlPassRate, } } export function computeRiskOverview(state: VendorComplianceState): RiskOverview { const vendors = state.vendors const findings = state.findings const averageInherentRisk = vendors.length > 0 ? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length : 0 const averageResidualRisk = vendors.length > 0 ? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length : 0 const highRiskVendors = vendors.filter( (v) => v.residualRiskScore >= 60 ).length const criticalFindings = findings.filter( (f) => f.severity === 'CRITICAL' && f.status === 'OPEN' ).length const transfersToThirdCountries = vendors.filter((v) => v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate) ).length return { averageInherentRisk, averageResidualRisk, highRiskVendors, criticalFindings, transfersToThirdCountries, } }