phase 16: React dashboard with full platform control and analytics
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { useParams } from '@tanstack/react-router';
|
||||
import { useRecommendation } from '../api/hooks';
|
||||
import { StatusBadge, ConfidenceBar, LoadingSpinner, Card } from '../components/ui';
|
||||
|
||||
export function RecommendationDetailPage() {
|
||||
const { id } = useParams({ from: '/recommendations/$id' });
|
||||
const { data: rec, isLoading } = useRecommendation(id);
|
||||
|
||||
if (isLoading || !rec) return <LoadingSpinner />;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-xl font-semibold text-gray-100">{rec.ticker}</h1>
|
||||
<StatusBadge status={rec.action} />
|
||||
<StatusBadge status={rec.mode} />
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<dl className="grid grid-cols-2 gap-x-8 gap-y-3 text-sm sm:grid-cols-4">
|
||||
<div><dt className="text-gray-500">Confidence</dt><dd><ConfidenceBar value={rec.confidence} /></dd></div>
|
||||
<div><dt className="text-gray-500">Horizon</dt><dd className="text-gray-200">{rec.time_horizon}</dd></div>
|
||||
<div><dt className="text-gray-500">Risk</dt><dd><StatusBadge status={rec.risk_classification} /></dd></div>
|
||||
<div><dt className="text-gray-500">Generated</dt><dd className="text-gray-300">{new Date(rec.generated_at).toLocaleString()}</dd></div>
|
||||
<div><dt className="text-gray-500">Portfolio %</dt><dd className="text-gray-200">{rec.portfolio_pct != null ? `${(rec.portfolio_pct * 100).toFixed(1)}%` : '—'}</dd></div>
|
||||
<div><dt className="text-gray-500">Max Loss %</dt><dd className="text-gray-200">{rec.max_loss_pct != null ? `${(rec.max_loss_pct * 100).toFixed(2)}%` : '—'}</dd></div>
|
||||
<div><dt className="text-gray-500">Model</dt><dd className="text-xs text-gray-400">{rec.model_version ?? '—'}</dd></div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
{rec.thesis && (
|
||||
<Card>
|
||||
<h2 className="mb-2 text-sm font-medium text-gray-400">Thesis</h2>
|
||||
<p className="text-sm text-gray-200">{rec.thesis}</p>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{rec.invalidation_conditions && rec.invalidation_conditions.length > 0 && (
|
||||
<Card>
|
||||
<h2 className="mb-2 text-sm font-medium text-gray-400">Invalidation Conditions</h2>
|
||||
<ul className="ml-4 list-disc text-sm text-yellow-400">
|
||||
{rec.invalidation_conditions.map((c, i) => <li key={i}>{c}</li>)}
|
||||
</ul>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Risk Evaluation */}
|
||||
{rec.risk_evaluation && (
|
||||
<Card>
|
||||
<h2 className="mb-2 text-sm font-medium text-gray-400">Risk Evaluation</h2>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<StatusBadge status={rec.risk_evaluation.eligible ? 'approved' : 'rejected'} />
|
||||
<span className="text-gray-400">Allowed mode: {rec.risk_evaluation.allowed_mode}</span>
|
||||
</div>
|
||||
{rec.risk_evaluation.rejection_reasons && rec.risk_evaluation.rejection_reasons.length > 0 && (
|
||||
<ul className="mt-2 ml-4 list-disc text-sm text-red-400">
|
||||
{rec.risk_evaluation.rejection_reasons.map((r, i) => <li key={i}>{r}</li>)}
|
||||
</ul>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Evidence */}
|
||||
<Card>
|
||||
<h2 className="mb-3 text-sm font-medium text-gray-400">Evidence ({rec.evidence.length})</h2>
|
||||
{rec.evidence.length === 0 ? (
|
||||
<p className="text-sm text-gray-500">No evidence linked</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{rec.evidence.map((ev) => (
|
||||
<div key={ev.id} className="rounded-lg border border-surface-700 bg-surface-950 p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<StatusBadge status={ev.evidence_type} />
|
||||
<span className="text-sm text-gray-200">{ev.title ?? 'Untitled'}</span>
|
||||
</div>
|
||||
<span className="font-mono text-xs text-gray-500">weight: {ev.weight.toFixed(3)}</span>
|
||||
</div>
|
||||
<div className="mt-1 flex gap-4 text-xs text-gray-500">
|
||||
<span>{ev.document_type}</span>
|
||||
<span>{ev.source_type}</span>
|
||||
{ev.publisher && <span>{ev.publisher}</span>}
|
||||
{ev.published_at && <span>{new Date(ev.published_at).toLocaleDateString()}</span>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user