Files
signal-platform/frontend/src/components/admin/PipelineReadinessPanel.tsx
T
Dennis Thiessen 0a011d4ce9
Deploy / lint (push) Failing after 21s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped
Big refactoring
2026-03-03 15:20:18 +01:00

106 lines
5.0 KiB
TypeScript

import { useQueryClient } from '@tanstack/react-query';
import { usePipelineReadiness } from '../../hooks/useAdmin';
import { useFetchSymbolData } from '../../hooks/useFetchSymbolData';
import { formatDateTime } from '../../lib/format';
import { SkeletonTable } from '../ui/Skeleton';
function scoreBadge(score: number | null) {
if (score === null) return <span className="text-[11px] text-gray-500"></span>;
const cls = score >= 60 ? 'text-emerald-400' : score >= 40 ? 'text-amber-400' : 'text-red-400';
return <span className={`text-[11px] font-medium ${cls}`}>{score.toFixed(0)}</span>;
}
export function PipelineReadinessPanel() {
const queryClient = useQueryClient();
const { data, isLoading, isError, error, isFetching } = usePipelineReadiness();
const fetchMutation = useFetchSymbolData({
includeSymbolPrefix: true,
invalidatePipelineReadiness: true,
});
if (isLoading) return <SkeletonTable rows={6} cols={6} />;
if (isError) return <p className="text-sm text-red-400">{(error as Error)?.message || 'Failed to load pipeline readiness'}</p>;
const rows = data ?? [];
return (
<div className="glass p-4 space-y-3">
<div className="flex items-center justify-between gap-3">
<div>
<h3 className="text-sm font-semibold text-gray-200">Pipeline Readiness</h3>
<p className="text-xs text-gray-500">Shows why tickers may be missing in scanner/rankings and what is incomplete.</p>
</div>
<button
type="button"
className="rounded border border-white/[0.12] px-3 py-1.5 text-xs text-gray-300 hover:text-white disabled:opacity-50"
onClick={() => queryClient.invalidateQueries({ queryKey: ['admin', 'pipeline-readiness'] })}
disabled={isFetching}
>
{isFetching ? 'Refreshing…' : 'Refresh'}
</button>
</div>
{rows.length === 0 ? (
<p className="text-sm text-gray-500">No tickers available.</p>
) : (
<div className="overflow-x-auto">
<table className="w-full text-left text-xs">
<thead>
<tr className="border-b border-white/[0.08] text-gray-500 uppercase tracking-wider">
<th className="px-2 py-2">Symbol</th>
<th className="px-2 py-2">OHLCV</th>
<th className="px-2 py-2">Dims</th>
<th className="px-2 py-2">S/R</th>
<th className="px-2 py-2">Scanner</th>
<th className="px-2 py-2">Missing Reasons</th>
<th className="px-2 py-2">Action</th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row.symbol} className="border-b border-white/[0.05] align-top">
<td className="px-2 py-2 font-medium text-gray-200">{row.symbol}</td>
<td className="px-2 py-2 text-gray-300">
<div>{row.ohlcv_bars} bars</div>
<div className="text-[11px] text-gray-500">{row.ohlcv_last_date ? formatDateTime(row.ohlcv_last_date) : '—'}</div>
</td>
<td className="px-2 py-2 text-gray-300">
<div className="grid grid-cols-5 gap-1">
{scoreBadge(row.dimensions.technical)}
{scoreBadge(row.dimensions.sr_quality)}
{scoreBadge(row.dimensions.sentiment)}
{scoreBadge(row.dimensions.fundamental)}
{scoreBadge(row.dimensions.momentum)}
</div>
<div className="mt-1 text-[10px] text-gray-500">T SR S F M</div>
</td>
<td className="px-2 py-2 text-gray-300">{row.sr_level_count}</td>
<td className="px-2 py-2">
<span className={`inline-block rounded px-2 py-0.5 text-[11px] ${row.ready_for_scanner ? 'bg-emerald-500/20 text-emerald-300' : 'bg-amber-500/20 text-amber-300'}`}>
{row.ready_for_scanner ? 'Ready' : 'Blocked'}
</span>
<div className="mt-1 text-[11px] text-gray-500">setups: {row.trade_setup_count}</div>
</td>
<td className="px-2 py-2 text-[11px] text-amber-300">
{row.missing_reasons.length ? row.missing_reasons.join(', ') : <span className="text-emerald-300">none</span>}
</td>
<td className="px-2 py-2">
<button
type="button"
className="rounded border border-white/[0.12] px-2.5 py-1 text-[11px] text-gray-300 hover:text-white disabled:opacity-50"
onClick={() => fetchMutation.mutate(row.symbol)}
disabled={fetchMutation.isPending}
>
{fetchMutation.isPending && fetchMutation.variables === row.symbol ? 'Fetching…' : 'Fetch Data'}
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}