[split-required] Split 500-1000 LOC files across all services
backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
171
website/app/admin/edu-search/useEduSearchData.ts
Normal file
171
website/app/admin/edu-search/useEduSearchData.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import type { SeedURL, Category, CrawlStats } from './types'
|
||||
import { DEFAULT_CATEGORIES, DEFAULT_STATS, apiSeedToFrontend } from './types'
|
||||
|
||||
export function useEduSearchData() {
|
||||
const [seeds, setSeeds] = useState<SeedURL[]>([])
|
||||
const [allSeeds, setAllSeeds] = useState<SeedURL[]>([])
|
||||
const [categories, setCategories] = useState<Category[]>(DEFAULT_CATEGORIES)
|
||||
const [stats, setStats] = useState<CrawlStats>(DEFAULT_STATS)
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('all')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [initialLoading, setInitialLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetchCategories = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/admin/edu-search?action=categories`)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
if (data.categories && data.categories.length > 0) {
|
||||
setCategories(data.categories.map((cat: { id: string; name: string; display_name: string; description: string; icon: string; sort_order: number; is_active: boolean }) => ({
|
||||
id: cat.id, name: cat.name, display_name: cat.display_name,
|
||||
description: cat.description || '', icon: cat.icon || '📁',
|
||||
sort_order: cat.sort_order, is_active: cat.is_active,
|
||||
})))
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch categories:', err)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fetchAllSeeds = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/admin/edu-search?action=seeds`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
setAllSeeds((data.seeds || []).map(apiSeedToFrontend))
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch all seeds:', err)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fetchSeeds = useCallback(async () => {
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
params.append('action', 'seeds')
|
||||
if (selectedCategory !== 'all') params.append('category', selectedCategory)
|
||||
const res = await fetch(`/api/admin/edu-search?${params}`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
const fetchedSeeds = (data.seeds || []).map(apiSeedToFrontend)
|
||||
setSeeds(fetchedSeeds)
|
||||
if (selectedCategory === 'all') setAllSeeds(fetchedSeeds)
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch seeds:', err)
|
||||
setError('Seeds konnten nicht geladen werden. API nicht erreichbar.')
|
||||
}
|
||||
}, [selectedCategory])
|
||||
|
||||
const fetchStats = useCallback(async (preserveCrawlStatus = false) => {
|
||||
try {
|
||||
const res = await fetch(`/api/admin/edu-search?action=stats`)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setStats(prev => ({
|
||||
totalDocuments: data.total_documents || 0,
|
||||
totalSeeds: data.total_seeds || 0,
|
||||
lastCrawlTime: data.last_crawl_time || prev.lastCrawlTime,
|
||||
crawlStatus: preserveCrawlStatus ? prev.crawlStatus : (data.crawl_status || 'idle'),
|
||||
documentsPerCategory: data.seeds_per_category || {},
|
||||
documentsPerDocType: {},
|
||||
avgTrustScore: data.avg_trust_boost || 0,
|
||||
}))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch stats:', err)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
setInitialLoading(true)
|
||||
await Promise.all([fetchCategories(), fetchSeeds(), fetchAllSeeds(), fetchStats()])
|
||||
setInitialLoading(false)
|
||||
}
|
||||
loadData()
|
||||
}, [fetchCategories, fetchSeeds, fetchAllSeeds, fetchStats])
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialLoading) fetchSeeds()
|
||||
}, [selectedCategory, initialLoading, fetchSeeds])
|
||||
|
||||
const pollCrawlStatus = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/admin/edu-search?action=legal-crawler-status', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}),
|
||||
})
|
||||
if (res.ok) return (await res.json()).status
|
||||
} catch { /* Ignore */ }
|
||||
return 'idle'
|
||||
}, [])
|
||||
|
||||
const handleStartCrawl = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
setStats(prev => ({ ...prev, crawlStatus: 'running', lastCrawlTime: new Date().toISOString() }))
|
||||
try {
|
||||
const response = await fetch('/api/admin/edu-search?action=crawl', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}),
|
||||
})
|
||||
const data = await response.json()
|
||||
if (response.ok) {
|
||||
const checkStatus = async () => {
|
||||
const status = await pollCrawlStatus()
|
||||
if (status === 'running') { setStats(prev => ({ ...prev, crawlStatus: 'running' })); setTimeout(checkStatus, 3000) }
|
||||
else if (status === 'completed' || status === 'idle') { setStats(prev => ({ ...prev, crawlStatus: 'idle' })); setLoading(false); await fetchStats(false) }
|
||||
else { setStats(prev => ({ ...prev, crawlStatus: 'error' })); setLoading(false) }
|
||||
}
|
||||
setTimeout(checkStatus, 2000)
|
||||
} else {
|
||||
setError(data.error || 'Fehler beim Starten des Crawls')
|
||||
setLoading(false)
|
||||
setStats(prev => ({ ...prev, crawlStatus: 'idle' }))
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Netzwerkfehler beim Starten des Crawls')
|
||||
setLoading(false)
|
||||
setStats(prev => ({ ...prev, crawlStatus: 'idle' }))
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!confirm('Seed-URL wirklich löschen?')) return
|
||||
try {
|
||||
const res = await fetch(`/api/admin/edu-search?id=${id}`, { method: 'DELETE' })
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
await fetchSeeds(); await fetchStats()
|
||||
} catch (err) {
|
||||
console.error('Failed to delete seed:', err)
|
||||
alert('Fehler beim Löschen')
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleEnabled = async (id: string) => {
|
||||
const seed = seeds.find(s => s.id === id)
|
||||
if (!seed) return
|
||||
try {
|
||||
const res = await fetch(`/api/admin/edu-search?id=${id}`, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled: !seed.enabled }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
setSeeds(seeds.map(s => s.id === id ? { ...s, enabled: !s.enabled } : s))
|
||||
} catch (err) {
|
||||
console.error('Failed to toggle seed:', err); await fetchSeeds()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaved = async () => { await fetchSeeds(); await fetchStats() }
|
||||
|
||||
return {
|
||||
seeds, allSeeds, categories, stats, selectedCategory, setSelectedCategory,
|
||||
loading, initialLoading, error, setError,
|
||||
handleStartCrawl, handleDelete, handleToggleEnabled, handleSaved,
|
||||
fetchSeeds, fetchStats,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user