Website (14 monoliths split): - compliance/page.tsx (1,519 → 9), docs/audit (1,262 → 20) - quality (1,231 → 16), alerts (1,203 → 10), docs (1,202 → 11) - i18n.ts (1,173 → 8 language files) - unity-bridge (1,094 → 12), backlog (1,087 → 6) - training (1,066 → 8), rag (1,063 → 8) - Deleted index_original.ts (4,899 LOC dead backup) Studio-v2 (5 monoliths split): - meet/page.tsx (1,481 → 9), messages (1,166 → 9) - AlertsB2BContext.tsx (1,165 → 5 modules) - alerts-b2b/page.tsx (1,019 → 6), korrektur/archiv (1,001 → 6) All existing imports preserved. Zero new TypeScript errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
149 lines
6.4 KiB
TypeScript
149 lines
6.4 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { useTheme } from '@/lib/ThemeContext'
|
||
import type { B2BHit } from '@/lib/AlertsB2BContext'
|
||
import { getImportanceLabelColor, getDecisionLabelColor, formatDeadline } from '@/lib/AlertsB2BContext'
|
||
import { DecisionTraceModal } from './DecisionTraceModal'
|
||
|
||
export function HitDetailModal({
|
||
hit,
|
||
onClose,
|
||
onFeedback
|
||
}: {
|
||
hit: B2BHit
|
||
onClose: () => void
|
||
onFeedback: (feedback: 'relevant' | 'irrelevant') => void
|
||
}) {
|
||
const { isDark } = useTheme()
|
||
const [showTrace, setShowTrace] = useState(false)
|
||
|
||
return (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
|
||
<div className={`relative w-full max-w-2xl rounded-3xl border p-8 max-h-[90vh] overflow-y-auto ${
|
||
isDark ? 'bg-slate-900 border-white/20' : 'bg-white border-slate-200 shadow-2xl'
|
||
}`}>
|
||
{/* Header */}
|
||
<div className="flex items-start justify-between mb-4">
|
||
<div className="flex items-center gap-3 flex-wrap">
|
||
<span className={`px-3 py-1 rounded-full text-xs font-medium border ${getImportanceLabelColor(hit.importanceLabel, isDark)}`}>
|
||
{hit.importanceLabel}
|
||
</span>
|
||
<span className={`px-2 py-1 rounded-lg text-xs ${getDecisionLabelColor(hit.decisionLabel, isDark)}`}>
|
||
{hit.decisionLabel === 'relevant' ? 'Relevant' : hit.decisionLabel === 'needs_review' ? 'Pruefung noetig' : 'Irrelevant'}
|
||
</span>
|
||
<span className={`text-xs ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
|
||
Score: {hit.importanceScore}
|
||
</span>
|
||
</div>
|
||
<button onClick={onClose} className={`p-2 rounded-lg ${isDark ? 'hover:bg-white/10' : 'hover:bg-slate-100'}`}>
|
||
<svg className={`w-5 h-5 ${isDark ? 'text-white/60' : 'text-slate-400'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
{/* Title */}
|
||
<h2 className={`text-xl font-bold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
{hit.title}
|
||
</h2>
|
||
|
||
{/* Metadata */}
|
||
<div className={`flex flex-wrap gap-4 mb-4 text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
{hit.buyerGuess && (
|
||
<span className="flex items-center gap-1">
|
||
<span>🏛️</span> {hit.buyerGuess}
|
||
</span>
|
||
)}
|
||
{hit.countryGuess && (
|
||
<span className="flex items-center gap-1">
|
||
<span>🌍</span> {hit.countryGuess}
|
||
</span>
|
||
)}
|
||
{hit.deadlineGuess && (
|
||
<span className={`flex items-center gap-1 ${
|
||
formatDeadline(hit.deadlineGuess).includes('Heute') || formatDeadline(hit.deadlineGuess).includes('Morgen')
|
||
? 'text-red-500 font-medium'
|
||
: ''
|
||
}`}>
|
||
<span>📅</span> Frist: {formatDeadline(hit.deadlineGuess)}
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{/* Snippet */}
|
||
<div className={`rounded-xl p-4 mb-6 ${isDark ? 'bg-white/5' : 'bg-slate-50'}`}>
|
||
<p className={isDark ? 'text-white/80' : 'text-slate-600'}>
|
||
{hit.snippet}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Source Info */}
|
||
<div className={`flex items-center gap-4 mb-6 text-sm ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
|
||
<span className={`px-2 py-1 rounded ${isDark ? 'bg-white/10' : 'bg-slate-100'}`}>
|
||
{hit.sourceType === 'email' ? '📧 Email' : '📡 RSS'}
|
||
</span>
|
||
<a
|
||
href={hit.originalUrl}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className={`hover:underline ${isDark ? 'text-blue-400' : 'text-blue-600'}`}
|
||
>
|
||
Original ansehen →
|
||
</a>
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div className="flex items-center justify-between pt-4 border-t border-white/10">
|
||
{/* Feedback */}
|
||
<div className="flex items-center gap-2">
|
||
<span className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>War das relevant?</span>
|
||
<button
|
||
onClick={() => onFeedback('relevant')}
|
||
className={`p-2 rounded-lg transition-all ${
|
||
hit.userFeedback === 'relevant'
|
||
? 'bg-green-500/20 text-green-400'
|
||
: isDark ? 'hover:bg-white/10 text-white/40' : 'hover:bg-slate-100 text-slate-400'
|
||
}`}
|
||
>
|
||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5" />
|
||
</svg>
|
||
</button>
|
||
<button
|
||
onClick={() => onFeedback('irrelevant')}
|
||
className={`p-2 rounded-lg transition-all ${
|
||
hit.userFeedback === 'irrelevant'
|
||
? 'bg-red-500/20 text-red-400'
|
||
: isDark ? 'hover:bg-white/10 text-white/40' : 'hover:bg-slate-100 text-slate-400'
|
||
}`}
|
||
>
|
||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.736 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.096c.5 0 .905-.405.905-.904 0-.715.211-1.413.608-2.008L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
{/* Decision Trace */}
|
||
<button
|
||
onClick={() => setShowTrace(true)}
|
||
className={`px-4 py-2 rounded-lg text-sm transition-all ${
|
||
isDark
|
||
? 'bg-white/10 text-white/80 hover:bg-white/20'
|
||
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||
}`}
|
||
>
|
||
🔍 Decision Trace
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Decision Trace Modal */}
|
||
{showTrace && (
|
||
<DecisionTraceModal hit={hit} onClose={() => setShowTrace(false)} />
|
||
)}
|
||
</div>
|
||
)
|
||
}
|