phase 16: React dashboard with full platform control and analytics
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { useParams } from '@tanstack/react-router';
|
||||
import { useDocument } from '../api/hooks';
|
||||
import { StatusBadge, ConfidenceBar, LoadingSpinner, Card } from '../components/ui';
|
||||
|
||||
export function DocumentDetailPage() {
|
||||
const { id } = useParams({ from: '/documents/$id' });
|
||||
const { data: doc, isLoading } = useDocument(id);
|
||||
|
||||
if (isLoading || !doc) return <LoadingSpinner />;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-gray-100">{doc.title ?? 'Untitled Document'}</h1>
|
||||
<div className="mt-1 flex items-center gap-3 text-sm text-gray-500">
|
||||
<StatusBadge status={doc.status} />
|
||||
<span>{doc.document_type}</span>
|
||||
<span>{doc.source_type}</span>
|
||||
{doc.publisher && <span>{doc.publisher}</span>}
|
||||
{doc.published_at && <span>{new Date(doc.published_at).toLocaleString()}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Metadata */}
|
||||
<Card>
|
||||
<h2 className="mb-2 text-sm font-medium text-gray-400">Metadata</h2>
|
||||
<dl className="grid grid-cols-2 gap-x-8 gap-y-2 text-sm sm:grid-cols-3">
|
||||
<div><dt className="text-gray-500">URL</dt><dd className="truncate text-gray-300">{doc.url ? <a href={doc.url} target="_blank" rel="noopener noreferrer" className="text-brand-400 hover:underline">{doc.url}</a> : '—'}</dd></div>
|
||||
<div><dt className="text-gray-500">Language</dt><dd className="text-gray-300">{doc.language ?? '—'}</dd></div>
|
||||
<div><dt className="text-gray-500">Content Hash</dt><dd className="truncate font-mono text-xs text-gray-400">{doc.content_hash ?? '—'}</dd></div>
|
||||
<div><dt className="text-gray-500">Parse Quality</dt><dd className="text-gray-300">{doc.parse_quality_score?.toFixed(2) ?? '—'}</dd></div>
|
||||
<div><dt className="text-gray-500">Parse Confidence</dt><dd><StatusBadge status={doc.parse_confidence ?? 'unknown'} /></dd></div>
|
||||
<div><dt className="text-gray-500">Retrieved</dt><dd className="text-gray-300">{doc.retrieved_at ? new Date(doc.retrieved_at).toLocaleString() : '—'}</dd></div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
{/* Company Mentions */}
|
||||
{doc.company_mentions.length > 0 && (
|
||||
<Card>
|
||||
<h2 className="mb-2 text-sm font-medium text-gray-400">Company Mentions</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{doc.company_mentions.map((m, i) => (
|
||||
<span key={i} className="rounded-full border border-surface-700 bg-surface-800 px-3 py-1 text-xs text-gray-300">
|
||||
<span className="font-mono font-semibold text-brand-300">{m.ticker}</span> {m.legal_name}
|
||||
<span className="ml-1 text-gray-500">({m.mention_type}, {(m.confidence * 100).toFixed(0)}%)</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Intelligence Extraction */}
|
||||
{doc.intelligence ? (
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Intelligence Extraction</h2>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="mb-1 text-xs text-gray-500">Summary</div>
|
||||
<p className="text-sm text-gray-200">{doc.intelligence.summary ?? '—'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Confidence</div>
|
||||
<ConfidenceBar value={doc.intelligence.confidence} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Validation</div>
|
||||
<StatusBadge status={doc.intelligence.validation_status} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Model</div>
|
||||
<span className="text-xs text-gray-400">{doc.intelligence.model_name ?? '—'} ({doc.intelligence.prompt_version})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{doc.intelligence.macro_themes && doc.intelligence.macro_themes.length > 0 && (
|
||||
<div>
|
||||
<div className="mb-1 text-xs text-gray-500">Macro Themes</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{doc.intelligence.macro_themes.map((t, i) => (
|
||||
<span key={i} className="rounded bg-surface-800 px-2 py-0.5 text-xs text-gray-300">{t}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{doc.intelligence.extraction_warnings && doc.intelligence.extraction_warnings.length > 0 && (
|
||||
<div>
|
||||
<div className="mb-1 text-xs text-gray-500">Warnings</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{doc.intelligence.extraction_warnings.map((w, i) => (
|
||||
<span key={i} className="rounded bg-yellow-900/30 px-2 py-0.5 text-xs text-yellow-400">{w}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Company Impacts */}
|
||||
{doc.intelligence.company_impacts && doc.intelligence.company_impacts.length > 0 && (
|
||||
<div>
|
||||
<div className="mb-2 text-xs text-gray-500">Company Impacts</div>
|
||||
<div className="space-y-3">
|
||||
{doc.intelligence.company_impacts.map((imp, i) => (
|
||||
<div key={i} className="rounded-lg border border-surface-700 bg-surface-950 p-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="font-mono font-semibold text-brand-300">{imp.ticker}</span>
|
||||
<StatusBadge status={imp.sentiment} />
|
||||
<ConfidenceBar value={imp.impact_score} />
|
||||
<span className="text-xs text-gray-500">{imp.catalyst_type}</span>
|
||||
<span className="text-xs text-gray-500">{imp.impact_horizon}</span>
|
||||
</div>
|
||||
{imp.key_facts && imp.key_facts.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<div className="text-xs text-gray-500">Key Facts</div>
|
||||
<ul className="ml-4 list-disc text-xs text-gray-300">
|
||||
{imp.key_facts.map((f, j) => <li key={j}>{f}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{imp.risks && imp.risks.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<div className="text-xs text-gray-500">Risks</div>
|
||||
<ul className="ml-4 list-disc text-xs text-red-400">
|
||||
{imp.risks.map((r, j) => <li key={j}>{r}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<Card>
|
||||
<p className="text-sm text-gray-500">No intelligence extraction available</p>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Storage References */}
|
||||
<Card>
|
||||
<h2 className="mb-2 text-sm font-medium text-gray-400">Storage References</h2>
|
||||
<dl className="space-y-1 text-sm">
|
||||
<div><dt className="inline text-gray-500">Raw: </dt><dd className="inline truncate font-mono text-xs text-gray-400">{doc.raw_storage_ref ?? '—'}</dd></div>
|
||||
<div><dt className="inline text-gray-500">Normalized: </dt><dd className="inline truncate font-mono text-xs text-gray-400">{doc.normalized_storage_ref ?? '—'}</dd></div>
|
||||
</dl>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user