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>
111 lines
4.3 KiB
TypeScript
111 lines
4.3 KiB
TypeScript
'use client'
|
||
|
||
/**
|
||
* Education Search Admin Page
|
||
*
|
||
* Manages seed URLs, crawl settings, and index statistics for the
|
||
* edu-search-service (Tavily alternative for German education content)
|
||
*/
|
||
|
||
import { useState } from 'react'
|
||
import AdminLayout from '@/components/admin/AdminLayout'
|
||
import { useEduSearchData } from './useEduSearchData'
|
||
import SeedsTab from './_components/SeedsTab'
|
||
import CrawlTab from './_components/CrawlTab'
|
||
import StatsTab from './_components/StatsTab'
|
||
import RulesTab from './_components/RulesTab'
|
||
|
||
const tabDefs = [
|
||
{ id: 'seeds' as const, name: 'Seed-URLs', icon: '🌱' },
|
||
{ id: 'crawl' as const, name: 'Crawl-Steuerung', icon: '🕷️' },
|
||
{ id: 'stats' as const, name: 'Statistiken', icon: '📊' },
|
||
{ id: 'rules' as const, name: 'Tagging-Regeln', icon: '🏷️' },
|
||
]
|
||
|
||
export default function EduSearchAdminPage() {
|
||
const [activeTab, setActiveTab] = useState<'seeds' | 'crawl' | 'stats' | 'rules'>('seeds')
|
||
const [searchQuery, setSearchQuery] = useState('')
|
||
|
||
const {
|
||
seeds, allSeeds, categories, stats, selectedCategory, setSelectedCategory,
|
||
loading, initialLoading, error,
|
||
handleStartCrawl, handleDelete, handleToggleEnabled, handleSaved,
|
||
fetchSeeds, fetchStats,
|
||
} = useEduSearchData()
|
||
|
||
return (
|
||
<AdminLayout title="Education Search" description="Bildungsquellen & Crawler verwalten">
|
||
{initialLoading && (
|
||
<div className="flex items-center justify-center py-12">
|
||
<svg className="w-8 h-8 animate-spin text-primary-600" fill="none" viewBox="0 0 24 24">
|
||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||
</svg>
|
||
<span className="ml-3 text-slate-600">Seeds werden geladen...</span>
|
||
</div>
|
||
)}
|
||
|
||
{error && !initialLoading && (
|
||
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-red-500 text-xl">⚠️</span>
|
||
<div>
|
||
<h4 className="font-medium text-red-800">{error}</h4>
|
||
<p className="text-sm text-red-600 mt-1">
|
||
Stelle sicher, dass der Backend-Service (http://localhost:8000) erreichbar ist.
|
||
</p>
|
||
<button
|
||
onClick={() => { fetchSeeds(); fetchStats(); }}
|
||
className="mt-2 text-sm text-red-700 underline hover:no-underline"
|
||
>
|
||
Erneut versuchen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{!initialLoading && (
|
||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 mb-6">
|
||
<div className="border-b border-slate-200">
|
||
<nav className="flex -mb-px">
|
||
{tabDefs.map(tab => (
|
||
<button
|
||
key={tab.id}
|
||
onClick={() => setActiveTab(tab.id)}
|
||
className={`px-6 py-4 text-sm font-medium border-b-2 transition-colors ${
|
||
activeTab === tab.id
|
||
? 'border-primary-600 text-primary-600'
|
||
: 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'
|
||
}`}
|
||
>
|
||
<span className="mr-2">{tab.icon}</span>
|
||
{tab.name}
|
||
</button>
|
||
))}
|
||
</nav>
|
||
</div>
|
||
|
||
<div className="p-6">
|
||
{activeTab === 'seeds' && (
|
||
<SeedsTab
|
||
seeds={seeds} allSeeds={allSeeds} categories={categories}
|
||
searchQuery={searchQuery} setSearchQuery={setSearchQuery}
|
||
selectedCategory={selectedCategory} setSelectedCategory={setSelectedCategory}
|
||
onToggleEnabled={handleToggleEnabled} onDelete={handleDelete} onSaved={handleSaved}
|
||
/>
|
||
)}
|
||
{activeTab === 'crawl' && (
|
||
<CrawlTab stats={stats} loading={loading} onStartCrawl={handleStartCrawl} />
|
||
)}
|
||
{activeTab === 'stats' && (
|
||
<StatsTab stats={stats} categories={categories} />
|
||
)}
|
||
{activeTab === 'rules' && <RulesTab />}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</AdminLayout>
|
||
)
|
||
}
|