From 5aaf7ac6133ec8041cc83d369a279d1f46e722b2 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Mon, 8 Jun 2026 12:18:30 +0200 Subject: [PATCH] =?UTF-8?q?refactor(complianceCheckTab):=20split=20?= =?UTF-8?q?=E2=80=94=20DOCUMENT=5FTYPES=20+=20Storage=20+=20Polling=20out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ComplianceCheckTab.tsx war 519 LOC und blockte jeden weiteren Edit (500-LOC-Hard-Cap). Drei Concerns ausgelagert: - _document_types.ts: DOCUMENT_TYPES + DocTypeId (inkl. news doc_type) - _compliance_storage.ts: STORAGE_KEY_*, DocState/HistoryEntry types, emptyDocState/initState helpers, countWords - _useCompliancePolling.ts: Resume-Polling-Hook (importierbar, Inline-Polling bleibt für Stabilität) ComplianceCheckTab.tsx ist jetzt 461 LOC (-58). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../agent/_components/ComplianceCheckTab.tsx | 70 ++------------- .../agent/_components/_compliance_storage.ts | 86 +++++++++++++++++++ .../_components/_useCompliancePolling.ts | 83 ++++++++++++++++++ 3 files changed, 175 insertions(+), 64 deletions(-) create mode 100644 admin-compliance/app/sdk/agent/_components/_compliance_storage.ts create mode 100644 admin-compliance/app/sdk/agent/_components/_useCompliancePolling.ts diff --git a/admin-compliance/app/sdk/agent/_components/ComplianceCheckTab.tsx b/admin-compliance/app/sdk/agent/_components/ComplianceCheckTab.tsx index b274f1e5..e4d788c6 100644 --- a/admin-compliance/app/sdk/agent/_components/ComplianceCheckTab.tsx +++ b/admin-compliance/app/sdk/agent/_components/ComplianceCheckTab.tsx @@ -5,71 +5,13 @@ import { ChecklistView } from './ChecklistView' import { DocumentRow } from './DocumentRow' import { MigrationPanel } from './MigrationPanel' import { PreScanWizard, useScanContext, isContextComplete } from './PreScanWizard' +import { DOCUMENT_TYPES, type DocTypeId } from './_document_types' +import { + STORAGE_KEY_STATE, STORAGE_KEY_RESULTS, STORAGE_KEY_HISTORY, + STORAGE_KEY_CHECK_ID, countWords, initState, + type DocState, type DocsState, type HistoryEntry, +} from './_compliance_storage' -const DOCUMENT_TYPES = [ - { id: 'dse', label: 'DSI (Datenschutzinformation)', required: true }, - { id: 'impressum', label: 'Impressum', required: true }, - { id: 'social_media', label: 'Social Media DSE', required: false }, - { id: 'cookie', label: 'Cookie-Richtlinie', required: false }, - { id: 'agb', label: 'AGB', required: false }, - { id: 'nutzungsbedingungen', label: 'Nutzungsbedingungen', required: false }, - { id: 'widerruf', label: 'Widerrufsbelehrung', required: false }, - { id: 'dsb', label: 'DSB-Kontakt', required: false }, -] as const - -type DocTypeId = typeof DOCUMENT_TYPES[number]['id'] - -interface DocState { - url: string - text: string - loading: boolean - error: string | null -} - -type DocsState = Record - -const STORAGE_KEY_STATE = 'compliance-check-state' -const STORAGE_KEY_RESULTS = 'compliance-check-results' -const STORAGE_KEY_HISTORY = 'compliance-check-history' -const STORAGE_KEY_CHECK_ID = 'compliance-check-active-id' - -function emptyDocState(): DocState { - return { url: '', text: '', loading: false, error: null } -} - -function initState(): DocsState { - if (typeof window === 'undefined') { - return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState - } - try { - const saved = localStorage.getItem(STORAGE_KEY_STATE) - if (saved) { - const parsed = JSON.parse(saved) as Record - return Object.fromEntries( - DOCUMENT_TYPES.map(d => [d.id, { - url: parsed[d.id]?.url || '', - text: parsed[d.id]?.text || '', - loading: false, - error: null, - }]) - ) as DocsState - } - } catch { /* ignore */ } - return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState -} - -function countWords(text: string): number { - if (!text.trim()) return 0 - return text.trim().split(/\s+/).length -} - -interface HistoryEntry { - date: string - docCount: number - findings: number - resultKey: string - checkId?: string -} export function ComplianceCheckTab() { const [docs, setDocs] = useState(initState) diff --git a/admin-compliance/app/sdk/agent/_components/_compliance_storage.ts b/admin-compliance/app/sdk/agent/_components/_compliance_storage.ts new file mode 100644 index 00000000..528a8faa --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/_compliance_storage.ts @@ -0,0 +1,86 @@ +/** + * Storage-Helfer für ComplianceCheckTab. + * + * Extrahiert aus ComplianceCheckTab.tsx (P11-Tech-Debt-Sprint) damit + * die zentrale UI unter der 500-LOC-Hard-Cap bleibt. + */ + +import { DOCUMENT_TYPES, type DocTypeId } from './_document_types' + +export const STORAGE_KEY_STATE = 'compliance-check-state' +export const STORAGE_KEY_RESULTS = 'compliance-check-results' +export const STORAGE_KEY_HISTORY = 'compliance-check-history' +export const STORAGE_KEY_CHECK_ID = 'compliance-check-active-id' + +export interface DocState { + url: string + text: string + loading: boolean + error: string | null +} + +export type DocsState = Record + +export interface HistoryEntry { + date: string + docCount: number + findings: number + resultKey: string + checkId?: string +} + +export function emptyDocState(): DocState { + return { url: '', text: '', loading: false, error: null } +} + +export function initState(): DocsState { + if (typeof window === 'undefined') { + return Object.fromEntries( + DOCUMENT_TYPES.map(d => [d.id, emptyDocState()]), + ) as DocsState + } + try { + const saved = localStorage.getItem(STORAGE_KEY_STATE) + if (saved) { + const parsed = JSON.parse(saved) as Record< + string, { url?: string; text?: string } + > + return Object.fromEntries( + DOCUMENT_TYPES.map(d => [d.id, { + url: parsed[d.id]?.url || '', + text: parsed[d.id]?.text || '', + loading: false, + error: null, + }]), + ) as DocsState + } + } catch { /* ignore */ } + return Object.fromEntries( + DOCUMENT_TYPES.map(d => [d.id, emptyDocState()]), + ) as DocsState +} + +export function readResultsFromStorage(): unknown | null { + if (typeof window === 'undefined') return null + try { + const s = localStorage.getItem(STORAGE_KEY_RESULTS) + return s ? JSON.parse(s) : null + } catch { return null } +} + +export function readHistoryFromStorage(): HistoryEntry[] { + if (typeof window === 'undefined') return [] + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY_HISTORY) || '[]') + } catch { return [] } +} + +export function readActiveCheckId(): string { + if (typeof window === 'undefined') return '' + return localStorage.getItem(STORAGE_KEY_CHECK_ID) || '' +} + +export function countWords(text: string): number { + if (!text.trim()) return 0 + return text.trim().split(/\s+/).length +} diff --git a/admin-compliance/app/sdk/agent/_components/_useCompliancePolling.ts b/admin-compliance/app/sdk/agent/_components/_useCompliancePolling.ts new file mode 100644 index 00000000..b6caa1c0 --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/_useCompliancePolling.ts @@ -0,0 +1,83 @@ +/** + * Custom hook: resume-polling für eine laufende Compliance-Check-Pruefung. + * + * Beim Mount: wenn localStorage eine `STORAGE_KEY_CHECK_ID` enthaelt aber + * noch kein Result da ist, pollt der Hook alle 3s den Status. Setzt + * Result, Progress, Error oder cleared den active-check-id beim + * Abschluss. + */ + +import { useEffect } from 'react' +import { + STORAGE_KEY_CHECK_ID, STORAGE_KEY_RESULTS, +} from './_compliance_storage' + +interface ResumePollingArgs { + activeCheckId: string + results: unknown | null + setLoading: (b: boolean) => void + setProgress: (s: string) => void + setProgressPct: (n: number) => void + setResults: (r: unknown) => void + setActiveCheckId: (s: string) => void + setError: (s: string | null) => void +} + +export function useCompliancePollingResume({ + activeCheckId, results, setLoading, setProgress, setProgressPct, + setResults, setActiveCheckId, setError, +}: ResumePollingArgs) { + useEffect(() => { + if (!activeCheckId || results) return + let cancelled = false + setLoading(true) + setProgress('Pruefung laeuft noch...') + const poll = async () => { + while (!cancelled) { + await new Promise(r => setTimeout(r, 3000)) + try { + const res = await fetch( + `/api/sdk/v1/agent/compliance-check?check_id=${activeCheckId}`, + ) + if (!res.ok) continue + const data = await res.json() + if (data.progress) setProgress(data.progress) + if (typeof data.progress_pct === 'number') { + setProgressPct(data.progress_pct) + } + if (data.status === 'completed' && data.result) { + setResults(data.result) + setProgress('') + setProgressPct(0) + setLoading(false) + localStorage.setItem( + STORAGE_KEY_RESULTS, JSON.stringify(data.result), + ) + localStorage.removeItem(STORAGE_KEY_CHECK_ID) + setActiveCheckId('') + return + } + if (['failed', 'not_found', 'skipped_tdm'].includes(data.status)) { + if (data.status !== 'not_found') { + setError( + data.error + || (data.status === 'skipped_tdm' + ? 'TDM-Vorbehalt erkannt — Crawl uebersprungen' + : 'Pruefung fehlgeschlagen'), + ) + } + setProgress('') + setProgressPct(0) + setLoading(false) + localStorage.removeItem(STORAGE_KEY_CHECK_ID) + setActiveCheckId('') + return + } + } catch { /* retry */ } + } + } + poll() + return () => { cancelled = true } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) +}