Phase 1 — Python (klausur-service): 5 monoliths → 36 files - dsfa_corpus_ingestion.py (1,828 LOC → 5 files) - cv_ocr_engines.py (2,102 LOC → 7 files) - cv_layout.py (3,653 LOC → 10 files) - vocab_worksheet_api.py (2,783 LOC → 8 files) - grid_build_core.py (1,958 LOC → 6 files) Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files - staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3) - policy_handlers.go (700 → 2), repository.go (684 → 2) - search.go (592 → 2), ai_extraction_handlers.go (554 → 2) - seed_data.go (591 → 2), grade_service.go (646 → 2) Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files - sdk/types.ts (2,108 → 16 domain files) - ai/rag/page.tsx (2,686 → 14 files) - 22 page.tsx files split into _components/ + _hooks/ - 11 component files split into sub-components - 10 SDK data catalogs added to loc-exceptions - Deleted dead backup index_original.ts (4,899 LOC) All original public APIs preserved via re-export facades. Zero new errors: Python imports verified, Go builds clean, TypeScript tsc --noEmit shows only pre-existing errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83 lines
3.5 KiB
TypeScript
83 lines
3.5 KiB
TypeScript
'use client'
|
|
|
|
import type { AlertItem } from '../types'
|
|
import { formatTimeAgo, getScoreBadgeClass, getDecisionBadgeClass } from '../useAlertsData'
|
|
|
|
interface InboxTabProps {
|
|
filteredAlerts: AlertItem[]
|
|
inboxFilter: string
|
|
setInboxFilter: (filter: string) => void
|
|
}
|
|
|
|
export function InboxTab({ filteredAlerts, inboxFilter, setInboxFilter }: InboxTabProps) {
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Filters */}
|
|
<div className="flex gap-2 flex-wrap">
|
|
{['all', 'new', 'keep', 'review'].map((filter) => (
|
|
<button
|
|
key={filter}
|
|
onClick={() => setInboxFilter(filter)}
|
|
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
|
|
inboxFilter === filter
|
|
? 'bg-green-600 text-white'
|
|
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
{filter === 'all' && 'Alle'}
|
|
{filter === 'new' && 'Neu'}
|
|
{filter === 'keep' && 'Relevant'}
|
|
{filter === 'review' && 'Pruefung'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Alerts Table */}
|
|
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
<table className="w-full">
|
|
<thead className="bg-slate-50 border-b border-slate-200">
|
|
<tr>
|
|
<th className="text-left p-4 text-xs font-semibold text-slate-500 uppercase">Alert</th>
|
|
<th className="text-left p-4 text-xs font-semibold text-slate-500 uppercase">Topic</th>
|
|
<th className="text-left p-4 text-xs font-semibold text-slate-500 uppercase">Score</th>
|
|
<th className="text-left p-4 text-xs font-semibold text-slate-500 uppercase">Decision</th>
|
|
<th className="text-left p-4 text-xs font-semibold text-slate-500 uppercase">Zeit</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{filteredAlerts.map((alert) => {
|
|
const scoreBadge = getScoreBadgeClass(alert.relevance_score)
|
|
const decBadge = getDecisionBadgeClass(alert.relevance_decision)
|
|
return (
|
|
<tr key={alert.id} className="hover:bg-slate-50">
|
|
<td className="p-4">
|
|
<a href={alert.url} target="_blank" rel="noopener noreferrer" className="font-medium text-slate-900 hover:text-green-600">
|
|
{alert.title}
|
|
</a>
|
|
<p className="text-sm text-slate-500 truncate max-w-md">{alert.snippet}</p>
|
|
</td>
|
|
<td className="p-4 text-sm text-slate-600">{alert.topic_name}</td>
|
|
<td className="p-4">
|
|
{scoreBadge && <span className={`px-2 py-0.5 rounded text-xs font-semibold ${scoreBadge.cls}`}>{scoreBadge.pct}%</span>}
|
|
</td>
|
|
<td className="p-4">
|
|
{decBadge && <span className={`px-2 py-0.5 rounded text-xs font-semibold uppercase ${decBadge.cls}`}>{decBadge.decision}</span>}
|
|
</td>
|
|
<td className="p-4 text-sm text-slate-500">{formatTimeAgo(alert.fetched_at)}</td>
|
|
</tr>
|
|
)
|
|
})}
|
|
{filteredAlerts.length === 0 && (
|
|
<tr>
|
|
<td colSpan={5} className="p-8 text-center text-slate-500">
|
|
Keine Alerts gefunden
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|