refactor(admin): split 9 more oversized lib/ files into focused modules
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>
This commit is contained in:
384
admin-compliance/lib/sdk/einwilligungen/provider.tsx
Normal file
384
admin-compliance/lib/sdk/einwilligungen/provider.tsx
Normal file
@@ -0,0 +1,384 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Einwilligungen Provider
|
||||
*
|
||||
* React Context Provider fuer das Einwilligungen-Modul.
|
||||
* Stellt State, computed values und Actions bereit.
|
||||
*/
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useReducer,
|
||||
useCallback,
|
||||
useMemo,
|
||||
ReactNode,
|
||||
Dispatch,
|
||||
} from 'react'
|
||||
import {
|
||||
EinwilligungenState,
|
||||
EinwilligungenAction,
|
||||
EinwilligungenTab,
|
||||
DataPoint,
|
||||
CookieBannerConfig,
|
||||
CompanyInfo,
|
||||
SupportedLanguage,
|
||||
ExportFormat,
|
||||
DataPointCategory,
|
||||
LegalBasis,
|
||||
RiskLevel,
|
||||
} from './types'
|
||||
import {
|
||||
PREDEFINED_DATA_POINTS,
|
||||
DEFAULT_COOKIE_CATEGORIES,
|
||||
createDefaultCatalog,
|
||||
} from './catalog/loader'
|
||||
import { einwilligungenReducer, initialState } from './reducer'
|
||||
|
||||
// =============================================================================
|
||||
// CONTEXT
|
||||
// =============================================================================
|
||||
|
||||
export interface EinwilligungenContextValue {
|
||||
state: EinwilligungenState
|
||||
dispatch: Dispatch<EinwilligungenAction>
|
||||
|
||||
// Computed Values
|
||||
allDataPoints: DataPoint[]
|
||||
selectedDataPointsData: DataPoint[]
|
||||
dataPointsByCategory: Record<DataPointCategory, DataPoint[]>
|
||||
categoryStats: Record<DataPointCategory, number>
|
||||
riskStats: Record<RiskLevel, number>
|
||||
legalBasisStats: Record<LegalBasis, number>
|
||||
|
||||
// Actions
|
||||
initializeCatalog: (tenantId: string) => void
|
||||
loadCatalog: (tenantId: string) => Promise<void>
|
||||
saveCatalog: () => Promise<void>
|
||||
toggleDataPoint: (id: string) => void
|
||||
addCustomDataPoint: (dataPoint: DataPoint) => void
|
||||
updateDataPoint: (id: string, data: Partial<DataPoint>) => void
|
||||
deleteCustomDataPoint: (id: string) => void
|
||||
setActiveTab: (tab: EinwilligungenTab) => void
|
||||
setPreviewLanguage: (language: SupportedLanguage) => void
|
||||
setPreviewFormat: (format: ExportFormat) => void
|
||||
setCompanyInfo: (info: CompanyInfo) => void
|
||||
generatePrivacyPolicy: () => Promise<void>
|
||||
generateCookieBannerConfig: () => void
|
||||
}
|
||||
|
||||
export const EinwilligungenContext = createContext<EinwilligungenContextValue | null>(null)
|
||||
|
||||
// =============================================================================
|
||||
// PROVIDER
|
||||
// =============================================================================
|
||||
|
||||
interface EinwilligungenProviderProps {
|
||||
children: ReactNode
|
||||
tenantId?: string
|
||||
}
|
||||
|
||||
export function EinwilligungenProvider({ children, tenantId }: EinwilligungenProviderProps) {
|
||||
const [state, dispatch] = useReducer(einwilligungenReducer, initialState)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// COMPUTED VALUES
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const allDataPoints = useMemo(() => {
|
||||
if (!state.catalog) return PREDEFINED_DATA_POINTS
|
||||
return [...state.catalog.dataPoints, ...state.catalog.customDataPoints]
|
||||
}, [state.catalog])
|
||||
|
||||
const selectedDataPointsData = useMemo(() => {
|
||||
return allDataPoints.filter((dp) => state.selectedDataPoints.includes(dp.id))
|
||||
}, [allDataPoints, state.selectedDataPoints])
|
||||
|
||||
const dataPointsByCategory = useMemo(() => {
|
||||
const result: Partial<Record<DataPointCategory, DataPoint[]>> = {}
|
||||
const categories: DataPointCategory[] = [
|
||||
'MASTER_DATA', 'CONTACT_DATA', 'AUTHENTICATION', 'CONSENT',
|
||||
'COMMUNICATION', 'PAYMENT', 'USAGE_DATA', 'LOCATION',
|
||||
'DEVICE_DATA', 'MARKETING', 'ANALYTICS', 'SOCIAL_MEDIA',
|
||||
'HEALTH_DATA', 'EMPLOYEE_DATA', 'CONTRACT_DATA', 'LOG_DATA',
|
||||
'AI_DATA', 'SECURITY',
|
||||
]
|
||||
for (const cat of categories) {
|
||||
result[cat] = selectedDataPointsData.filter((dp) => dp.category === cat)
|
||||
}
|
||||
return result as Record<DataPointCategory, DataPoint[]>
|
||||
}, [selectedDataPointsData])
|
||||
|
||||
const categoryStats = useMemo(() => {
|
||||
const counts: Partial<Record<DataPointCategory, number>> = {}
|
||||
for (const dp of selectedDataPointsData) {
|
||||
counts[dp.category] = (counts[dp.category] || 0) + 1
|
||||
}
|
||||
return counts as Record<DataPointCategory, number>
|
||||
}, [selectedDataPointsData])
|
||||
|
||||
const riskStats = useMemo(() => {
|
||||
const counts: Record<RiskLevel, number> = { LOW: 0, MEDIUM: 0, HIGH: 0 }
|
||||
for (const dp of selectedDataPointsData) {
|
||||
counts[dp.riskLevel]++
|
||||
}
|
||||
return counts
|
||||
}, [selectedDataPointsData])
|
||||
|
||||
const legalBasisStats = useMemo(() => {
|
||||
const counts: Record<LegalBasis, number> = {
|
||||
CONTRACT: 0, CONSENT: 0, EXPLICIT_CONSENT: 0,
|
||||
LEGITIMATE_INTEREST: 0, LEGAL_OBLIGATION: 0,
|
||||
VITAL_INTERESTS: 0, PUBLIC_INTEREST: 0,
|
||||
}
|
||||
for (const dp of selectedDataPointsData) {
|
||||
counts[dp.legalBasis]++
|
||||
}
|
||||
return counts
|
||||
}, [selectedDataPointsData])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ACTIONS
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const initializeCatalog = useCallback(
|
||||
(tid: string) => {
|
||||
const catalog = createDefaultCatalog(tid)
|
||||
dispatch({ type: 'SET_CATALOG', payload: catalog })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const loadCatalog = useCallback(
|
||||
async (tid: string) => {
|
||||
dispatch({ type: 'SET_LOADING', payload: true })
|
||||
dispatch({ type: 'SET_ERROR', payload: null })
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/sdk/v1/einwilligungen/catalog`, {
|
||||
headers: { 'X-Tenant-ID': tid },
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
dispatch({ type: 'SET_CATALOG', payload: data.catalog })
|
||||
if (data.companyInfo) {
|
||||
dispatch({ type: 'SET_COMPANY_INFO', payload: data.companyInfo })
|
||||
}
|
||||
if (data.cookieBannerConfig) {
|
||||
dispatch({ type: 'SET_COOKIE_BANNER_CONFIG', payload: data.cookieBannerConfig })
|
||||
}
|
||||
} else if (response.status === 404) {
|
||||
initializeCatalog(tid)
|
||||
} else {
|
||||
throw new Error('Failed to load catalog')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading catalog:', error)
|
||||
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Laden des Katalogs' })
|
||||
initializeCatalog(tid)
|
||||
} finally {
|
||||
dispatch({ type: 'SET_LOADING', payload: false })
|
||||
}
|
||||
},
|
||||
[dispatch, initializeCatalog]
|
||||
)
|
||||
|
||||
const saveCatalog = useCallback(async () => {
|
||||
if (!state.catalog) return
|
||||
|
||||
dispatch({ type: 'SET_SAVING', payload: true })
|
||||
dispatch({ type: 'SET_ERROR', payload: null })
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/sdk/v1/einwilligungen/catalog`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Tenant-ID': state.catalog.tenantId,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
catalog: state.catalog,
|
||||
companyInfo: state.companyInfo,
|
||||
cookieBannerConfig: state.cookieBannerConfig,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save catalog')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving catalog:', error)
|
||||
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Speichern des Katalogs' })
|
||||
} finally {
|
||||
dispatch({ type: 'SET_SAVING', payload: false })
|
||||
}
|
||||
}, [state.catalog, state.companyInfo, state.cookieBannerConfig, dispatch])
|
||||
|
||||
const toggleDataPoint = useCallback(
|
||||
(id: string) => {
|
||||
dispatch({ type: 'TOGGLE_DATA_POINT', payload: id })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const addCustomDataPoint = useCallback(
|
||||
(dataPoint: DataPoint) => {
|
||||
dispatch({ type: 'ADD_CUSTOM_DATA_POINT', payload: { ...dataPoint, isCustom: true } })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const updateDataPoint = useCallback(
|
||||
(id: string, data: Partial<DataPoint>) => {
|
||||
dispatch({ type: 'UPDATE_DATA_POINT', payload: { id, data } })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const deleteCustomDataPoint = useCallback(
|
||||
(id: string) => {
|
||||
dispatch({ type: 'DELETE_CUSTOM_DATA_POINT', payload: id })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setActiveTab = useCallback(
|
||||
(tab: EinwilligungenTab) => {
|
||||
dispatch({ type: 'SET_ACTIVE_TAB', payload: tab })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setPreviewLanguage = useCallback(
|
||||
(language: SupportedLanguage) => {
|
||||
dispatch({ type: 'SET_PREVIEW_LANGUAGE', payload: language })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setPreviewFormat = useCallback(
|
||||
(format: ExportFormat) => {
|
||||
dispatch({ type: 'SET_PREVIEW_FORMAT', payload: format })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setCompanyInfo = useCallback(
|
||||
(info: CompanyInfo) => {
|
||||
dispatch({ type: 'SET_COMPANY_INFO', payload: info })
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const generatePrivacyPolicy = useCallback(async () => {
|
||||
if (!state.catalog || !state.companyInfo) {
|
||||
dispatch({ type: 'SET_ERROR', payload: 'Bitte zuerst Firmendaten eingeben' })
|
||||
return
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_LOADING', payload: true })
|
||||
dispatch({ type: 'SET_ERROR', payload: null })
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/sdk/v1/einwilligungen/privacy-policy/generate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Tenant-ID': state.catalog.tenantId,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
dataPointIds: state.selectedDataPoints,
|
||||
companyInfo: state.companyInfo,
|
||||
language: state.previewLanguage,
|
||||
format: state.previewFormat,
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const policy = await response.json()
|
||||
dispatch({ type: 'SET_PRIVACY_POLICY', payload: policy })
|
||||
} else {
|
||||
throw new Error('Failed to generate privacy policy')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating privacy policy:', error)
|
||||
dispatch({ type: 'SET_ERROR', payload: 'Fehler bei der Generierung der Datenschutzerklaerung' })
|
||||
} finally {
|
||||
dispatch({ type: 'SET_LOADING', payload: false })
|
||||
}
|
||||
}, [
|
||||
state.catalog,
|
||||
state.companyInfo,
|
||||
state.selectedDataPoints,
|
||||
state.previewLanguage,
|
||||
state.previewFormat,
|
||||
dispatch,
|
||||
])
|
||||
|
||||
const generateCookieBannerConfig = useCallback(() => {
|
||||
if (!state.catalog) return
|
||||
|
||||
const config: CookieBannerConfig = {
|
||||
id: `cookie-banner-${state.catalog.tenantId}`,
|
||||
tenantId: state.catalog.tenantId,
|
||||
categories: DEFAULT_COOKIE_CATEGORIES.map((cat) => ({
|
||||
...cat,
|
||||
dataPointIds: cat.dataPointIds.filter((id) => state.selectedDataPoints.includes(id)),
|
||||
})),
|
||||
styling: {
|
||||
position: 'BOTTOM',
|
||||
theme: 'LIGHT',
|
||||
primaryColor: '#6366f1',
|
||||
borderRadius: 12,
|
||||
},
|
||||
texts: {
|
||||
title: { de: 'Cookie-Einstellungen', en: 'Cookie Settings' },
|
||||
description: {
|
||||
de: 'Wir verwenden Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen.',
|
||||
en: 'We use cookies to provide you with the best possible experience on our website.',
|
||||
},
|
||||
acceptAll: { de: 'Alle akzeptieren', en: 'Accept All' },
|
||||
rejectAll: { de: 'Alle ablehnen', en: 'Reject All' },
|
||||
customize: { de: 'Anpassen', en: 'Customize' },
|
||||
save: { de: 'Auswahl speichern', en: 'Save Selection' },
|
||||
privacyPolicyLink: { de: 'Datenschutzerklaerung', en: 'Privacy Policy' },
|
||||
},
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_COOKIE_BANNER_CONFIG', payload: config })
|
||||
}, [state.catalog, state.selectedDataPoints, dispatch])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CONTEXT VALUE
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const value: EinwilligungenContextValue = {
|
||||
state,
|
||||
dispatch,
|
||||
allDataPoints,
|
||||
selectedDataPointsData,
|
||||
dataPointsByCategory,
|
||||
categoryStats,
|
||||
riskStats,
|
||||
legalBasisStats,
|
||||
initializeCatalog,
|
||||
loadCatalog,
|
||||
saveCatalog,
|
||||
toggleDataPoint,
|
||||
addCustomDataPoint,
|
||||
updateDataPoint,
|
||||
deleteCustomDataPoint,
|
||||
setActiveTab,
|
||||
setPreviewLanguage,
|
||||
setPreviewFormat,
|
||||
setCompanyInfo,
|
||||
generatePrivacyPolicy,
|
||||
generateCookieBannerConfig,
|
||||
}
|
||||
|
||||
return (
|
||||
<EinwilligungenContext.Provider value={value}>{children}</EinwilligungenContext.Provider>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user