Big refactoring
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user