Files
market_screener/ui/src/routes/+page.svelte
T
2026-06-11 19:18:19 -04:00

119 lines
3.9 KiB
Svelte

<script lang="ts">
import { screenerStore } from '$lib/stores/screener.store.svelte.js';
import Spinner from '$lib/components/shared/Spinner.svelte';
import MarketContextStrip from '$lib/components/shared/MarketContextStrip.svelte';
import AssetTable from '$lib/components/screener/AssetTable.svelte';
import AnalysisSidebar from '$lib/components/screener/AnalysisSidebar.svelte';
import WatchlistPanel from '$lib/components/screener/WatchlistPanel.svelte';
import SectorPulse from '$lib/components/screener/SectorPulse.svelte';
import SectorPanel from '$lib/components/screener/SectorPanel.svelte';
const s = screenerStore;
let { data: _data } = $props();
// Pure UI state — not shared, kept local
let searchOpen = $state(false);
// Boot — fetch catalysts + screen on mount
let _booted = false;
$effect(() => {
if (_booted) return;
_booted = true;
s.reloadCatalysts();
s.loadSectorPulse();
});
</script>
<div class="screener-page">
<!-- ── Market pulse — page-level header band (sectors today) ──────── -->
<SectorPulse />
<SectorPanel />
<!-- ── Toolbar ────────────────────────────────────────────────────── -->
<div class="toolbar">
<div class="toolbar-top">
<button
onclick={() => searchOpen = !searchOpen}
class="btn-search-toggle"
title="Screen custom tickers"
>
🔍 {searchOpen ? 'Hide search' : 'Search tickers'}
</button>
{#if s.screenedAt}
<span class="screened-at">Last screened {s.screenedAt}</span>
{/if}
</div>
{#if searchOpen}
<div class="search-row">
<input
class="search-input"
bind:value={s.input}
placeholder="AAPL, MSFT, VOO …"
onkeydown={e => e.key === 'Enter' && s.screen()}
/>
<button onclick={() => s.screen()} disabled={s.loading || s.loadingCats} class="btn-screen">
{#if s.loading}<Spinner size="sm" />{:else}Screen{/if}
</button>
</div>
{/if}
{#if s.ctx}
<MarketContextStrip ctx={s.ctx} />
{/if}
</div>
{#if s.error}
<div class="error-banner">{s.error}</div>
{/if}
{#if s.dataHealth}
<div class="warn-banner" role="alert">
<span>{s.dataHealth.message}</span>
<button class="warn-dismiss" onclick={() => s.healthDismissed = true} title="Dismiss"></button>
</div>
{/if}
{#if s.loading || s.loadingCats}
<div class="loading-area">
<Spinner size="lg" label={s.loadingCats ? 'Fetching news catalysts…' : 'Screening tickers…'} />
</div>
{/if}
{#if s.results && !s.loading && !s.loadingCats}
<!-- ── Per-type detail tables ────────────────────────────────────── -->
{#each (['STOCK', 'ETF', 'BOND'] as const) as type}
{#if s.results[type]?.length}
<AssetTable
{type}
rows={s.results[type]}
analyzeLoading={s.sidebar.loading && s.sidebar.type === type}
onAnalyze={() => s.runTabAnalysis(type)}
/>
{/if}
{/each}
<!-- ── Failed tickers ────────────────────────────────────────────── -->
{#if s.results.ERROR?.length}
<section class="section">
<h2>Failed <span class="count">{s.results.ERROR.length}</span></h2>
<div class="error-list">
{#each s.results.ERROR as e}
<div class="error-item"><span class="ticker">{e.ticker}</span> {e.message}</div>
{/each}
</div>
</section>
{/if}
{/if}
<WatchlistPanel />
</div>
<AnalysisSidebar
sidebar={s.sidebar}
onClose={() => s.closeSidebar()}
onScreenTickers={(tickers) => { s.input = tickers.join(', '); s.screen(); }}
/>