'use client' import React, { createContext, useContext, useReducer, useCallback, useMemo, useEffect, useState, } from 'react' import type { VendorComplianceContextValue, ProcessingActivity, Vendor, ContractDocument, Finding, ControlInstance, ExportFormat, } from './types' import { vendorComplianceReducer, initialState, computeVendorStats, computeComplianceStats, computeRiskOverview, } from './context-reducer' import { loadAllData, apiCreateProcessingActivity, apiUpdateProcessingActivity, apiDeleteProcessingActivity, apiDuplicateProcessingActivity, apiCreateVendor, apiUpdateVendor, apiDeleteVendor, apiUploadContract, apiUpdateContract, apiDeleteContract, apiStartContractReview, apiUpdateFinding, apiResolveFinding, apiUpdateControlInstance, apiExportVVT, apiExportVendorAuditPack, apiExportRoPA, } from './context-actions' // ========================================== // CONTEXT // ========================================== const VendorComplianceContext = createContext(null) // ========================================== // PROVIDER // ========================================== interface VendorComplianceProviderProps { children: React.ReactNode tenantId?: string } export function VendorComplianceProvider({ children, tenantId, }: VendorComplianceProviderProps) { const [state, dispatch] = useReducer(vendorComplianceReducer, initialState) const [isInitialized, setIsInitialized] = useState(false) // ========================================== // COMPUTED VALUES // ========================================== const vendorStats = useMemo( () => computeVendorStats(state), [state.vendors, state.contracts] ) const complianceStats = useMemo( () => computeComplianceStats(state), [state.findings, state.contracts, state.controlInstances] ) const riskOverview = useMemo( () => computeRiskOverview(state), [state.vendors, state.findings] ) // ========================================== // ACTION WRAPPERS // ========================================== const loadData = useCallback(async () => { await loadAllData(dispatch) }, []) const refresh = useCallback(async () => { await loadData() }, [loadData]) const createProcessingActivity = useCallback( async (data: Omit) => { return apiCreateProcessingActivity(dispatch, data) }, [] ) const updateProcessingActivity = useCallback( async (id: string, data: Partial) => { await apiUpdateProcessingActivity(dispatch, id, data) }, [] ) const deleteProcessingActivity = useCallback( async (id: string) => { await apiDeleteProcessingActivity(dispatch, id) }, [] ) const duplicateProcessingActivity = useCallback( async (id: string) => { return apiDuplicateProcessingActivity(dispatch, state, id) }, [state] ) const createVendor = useCallback( async (data: Omit) => { return apiCreateVendor(dispatch, data) }, [] ) const updateVendor = useCallback( async (id: string, data: Partial) => { await apiUpdateVendor(dispatch, id, data) }, [] ) const deleteVendor = useCallback( async (id: string) => { await apiDeleteVendor(dispatch, id) }, [] ) const uploadContract = useCallback( async (vendorId: string, file: File, metadata: Partial) => { return apiUploadContract(dispatch, state, vendorId, file, metadata) }, [state] ) const updateContract = useCallback( async (id: string, data: Partial) => { await apiUpdateContract(dispatch, id, data) }, [] ) const deleteContract = useCallback( async (id: string) => { await apiDeleteContract(dispatch, state, id) }, [state] ) const startContractReview = useCallback( async (contractId: string) => { await apiStartContractReview(dispatch, contractId) }, [] ) const updateFinding = useCallback( async (id: string, data: Partial) => { await apiUpdateFinding(dispatch, id, data) }, [] ) const resolveFinding = useCallback( async (id: string, resolution: string) => { await apiResolveFinding(dispatch, id, resolution) }, [] ) const updateControlInstance = useCallback( async (id: string, data: Partial) => { await apiUpdateControlInstance(dispatch, id, data) }, [] ) const exportVVT = useCallback( async (format: ExportFormat, activityIds?: string[]) => { return apiExportVVT(format, activityIds) }, [] ) const exportVendorAuditPack = useCallback( async (vendorId: string, format: ExportFormat) => { return apiExportVendorAuditPack(vendorId, format) }, [] ) const exportRoPA = useCallback( async (format: ExportFormat) => { return apiExportRoPA(format) }, [] ) // ========================================== // INITIALIZATION // ========================================== useEffect(() => { if (!isInitialized) { loadData() setIsInitialized(true) } }, [isInitialized, loadData]) // ========================================== // CONTEXT VALUE // ========================================== const contextValue = useMemo( () => ({ ...state, dispatch, vendorStats, complianceStats, riskOverview, createProcessingActivity, updateProcessingActivity, deleteProcessingActivity, duplicateProcessingActivity, createVendor, updateVendor, deleteVendor, uploadContract, updateContract, deleteContract, startContractReview, updateFinding, resolveFinding, updateControlInstance, exportVVT, exportVendorAuditPack, exportRoPA, loadData, refresh, }), [ state, vendorStats, complianceStats, riskOverview, createProcessingActivity, updateProcessingActivity, deleteProcessingActivity, duplicateProcessingActivity, createVendor, updateVendor, deleteVendor, uploadContract, updateContract, deleteContract, startContractReview, updateFinding, resolveFinding, updateControlInstance, exportVVT, exportVendorAuditPack, exportRoPA, loadData, refresh, ] ) return ( {children} ) } // ========================================== // HOOK // ========================================== export function useVendorCompliance(): VendorComplianceContextValue { const context = useContext(VendorComplianceContext) if (!context) { throw new Error( 'useVendorCompliance must be used within a VendorComplianceProvider' ) } return context } // ========================================== // SELECTORS // ========================================== export function useVendor(vendorId: string | null) { const { vendors } = useVendorCompliance() return useMemo( () => vendors.find((v) => v.id === vendorId) ?? null, [vendors, vendorId] ) } export function useProcessingActivity(activityId: string | null) { const { processingActivities } = useVendorCompliance() return useMemo( () => processingActivities.find((a) => a.id === activityId) ?? null, [processingActivities, activityId] ) } export function useVendorContracts(vendorId: string | null) { const { contracts } = useVendorCompliance() return useMemo( () => contracts.filter((c) => c.vendorId === vendorId), [contracts, vendorId] ) } export function useVendorFindings(vendorId: string | null) { const { findings } = useVendorCompliance() return useMemo( () => findings.filter((f) => f.vendorId === vendorId), [findings, vendorId] ) } export function useContractFindings(contractId: string | null) { const { findings } = useVendorCompliance() return useMemo( () => findings.filter((f) => f.contractId === contractId), [findings, contractId] ) } export function useControlInstancesForEntity( entityType: 'VENDOR' | 'PROCESSING_ACTIVITY', entityId: string | null ) { const { controlInstances, controls } = useVendorCompliance() return useMemo(() => { if (!entityId) return [] return controlInstances .filter((ci) => ci.entityType === entityType && ci.entityId === entityId) .map((ci) => ({ ...ci, control: controls.find((c) => c.id === ci.controlId), })) }, [controlInstances, controls, entityType, entityId]) }