summaryrefslogtreecommitdiff
path: root/frontend/app
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/app')
-rw-r--r--frontend/app/page.tsx63
-rw-r--r--frontend/app/prism-shell.css184
2 files changed, 229 insertions, 18 deletions
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
index 44428aa..3bec411 100644
--- a/frontend/app/page.tsx
+++ b/frontend/app/page.tsx
@@ -4,6 +4,7 @@ import { FormEvent, Suspense, startTransition, useCallback, useEffect, useMemo,
import Image from "next/image";
import { useRouter, useSearchParams } from "next/navigation";
import { AppShell } from "@/components/prism/AppShell";
+import { FinancialsPage } from "@/components/prism/FinancialsPage";
import { ChartCard } from "@/components/prism/ChartCard";
import { KPIStrip } from "@/components/prism/KPIStrip";
import { Sidebar } from "@/components/prism/Sidebar";
@@ -29,6 +30,7 @@ function OverviewClient() {
const router = useRouter();
const searchParams = useSearchParams();
const selectedTicker = (searchParams.get("ticker") || "").toUpperCase();
+ const tab = searchParams.get("tab") || "overview";
const lastTickerRef = useRef("");
const [query, setQuery] = useState("");
@@ -72,11 +74,26 @@ function OverviewClient() {
setOverviewState("loading");
setChartState("loading");
+ const params = new URLSearchParams();
+ params.set("ticker", normalized);
+ if (tab !== "overview") params.set("tab", tab);
startTransition(() => {
- router.push(`/?ticker=${encodeURIComponent(normalized)}`);
+ router.push(`/?${params.toString()}`);
});
},
- [router]
+ [router, tab]
+ );
+
+ const navigateToTab = useCallback(
+ (key: string) => {
+ const params = new URLSearchParams();
+ if (selectedTicker) params.set("ticker", selectedTicker);
+ if (key !== "overview") params.set("tab", key);
+ startTransition(() => {
+ router.push(`/?${params.toString()}`);
+ });
+ },
+ [router, selectedTicker]
);
const clearTicker = useCallback(() => {
@@ -245,12 +262,13 @@ function OverviewClient() {
sidebar={
<Sidebar
navItems={OVERVIEW_NAV_ITEMS}
- selectedKey="overview"
+ selectedKey={tab}
currentTicker={selectedTicker}
watchlist={watchlist}
watchlistError={watchlistError}
onSelectTicker={navigateToTicker}
onRemoveTicker={removeFromWatchlist}
+ onSelectTab={navigateToTab}
/>
}
topbar={
@@ -271,22 +289,31 @@ function OverviewClient() {
{selectedTicker && overviewState === "invalid" ? <InvalidTickerState symbol={selectedTicker} onClear={clearTicker} /> : null}
{selectedTicker && overviewState === "error" ? <ErrorOverviewState message={overviewError || "Ticker data unavailable"} /> : null}
{overview && overviewState === "ready" ? (
- <>
- <TickerHeader overview={overview} onToggleWatchlist={addOrRemoveCurrentTicker} isSaved={isSaved} />
- <KPIStrip items={kpis} />
- <div className="psm-main-grid">
- <div className="psm-column">
- <ChartCard symbol={overview.profile.symbol} period={period} points={history} chartState={chartState} chartError={chartError} onChangePeriod={setPeriod} />
- <SignalCard overview={overview} />
- </div>
- <div className="psm-column">
- <DataStatusCard overview={overview} missingFields={missingFields} />
- <ProfileCard overview={overview} />
- <ShortInterestCard overview={overview} />
- <StatsCard overview={overview} />
+ tab === "financials" ? (
+ <FinancialsPage
+ ticker={selectedTicker}
+ overview={overview}
+ isSaved={isSaved}
+ onToggleWatchlist={addOrRemoveCurrentTicker}
+ />
+ ) : (
+ <>
+ <TickerHeader overview={overview} onToggleWatchlist={addOrRemoveCurrentTicker} isSaved={isSaved} />
+ <KPIStrip items={kpis} />
+ <div className="psm-main-grid">
+ <div className="psm-column">
+ <ChartCard symbol={overview.profile.symbol} period={period} points={history} chartState={chartState} chartError={chartError} onChangePeriod={setPeriod} />
+ <SignalCard overview={overview} />
+ </div>
+ <div className="psm-column">
+ <DataStatusCard overview={overview} missingFields={missingFields} />
+ <ProfileCard overview={overview} />
+ <ShortInterestCard overview={overview} />
+ <StatsCard overview={overview} />
+ </div>
</div>
- </div>
- </>
+ </>
+ )
) : null}
</AppShell>
);
diff --git a/frontend/app/prism-shell.css b/frontend/app/prism-shell.css
index 6ccf9ca..cd0023a 100644
--- a/frontend/app/prism-shell.css
+++ b/frontend/app/prism-shell.css
@@ -1105,3 +1105,187 @@
grid-column: 3;
}
}
+
+/* ── Financials Card ─────────────────────────────── */
+
+.psm-financials-card {
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ min-height: 480px;
+ overflow: hidden;
+}
+
+.psm-fin-header {
+ display: flex;
+ align-items: stretch;
+ border-bottom: 1px solid var(--line-1);
+ padding: 0 var(--sp-4);
+ flex-shrink: 0;
+}
+
+.psm-fin-tabs {
+ display: flex;
+ margin-right: auto;
+}
+
+.psm-fin-tab {
+ padding: var(--sp-3) var(--sp-3);
+ background: none;
+ border: none;
+ border-bottom: 2px solid transparent;
+ color: var(--fg-4);
+ font-family: var(--font-mono);
+ font-size: var(--fs-12);
+ letter-spacing: 0.04em;
+ cursor: pointer;
+ transition: color 150ms ease;
+ margin-bottom: -1px;
+}
+
+.psm-fin-tab:hover {
+ color: var(--fg-2);
+}
+
+.psm-fin-tab.active {
+ border-bottom-color: var(--brass);
+ color: var(--brass);
+}
+
+.psm-fin-period {
+ display: flex;
+ align-items: center;
+ gap: var(--sp-1);
+}
+
+.psm-fin-period-btn {
+ padding: 3px var(--sp-2);
+ background: none;
+ border: 1px solid var(--line-1);
+ border-radius: var(--r-1);
+ color: var(--fg-4);
+ font-family: var(--font-mono);
+ font-size: var(--fs-12);
+ letter-spacing: 0.04em;
+ cursor: pointer;
+ transition: all 150ms ease;
+}
+
+.psm-fin-period-btn:hover {
+ color: var(--fg-2);
+ border-color: var(--line-2);
+}
+
+.psm-fin-period-btn.active {
+ background: rgba(194, 170, 122, 0.1);
+ border-color: rgba(194, 170, 122, 0.3);
+ color: var(--brass);
+}
+
+.psm-fin-table-wrap {
+ overflow: auto;
+ flex: 1;
+}
+
+.psm-fin-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: var(--fs-13);
+}
+
+.psm-fin-table thead tr {
+ border-bottom: 1px solid var(--line-1);
+}
+
+.psm-fin-label-col {
+ text-align: left;
+ padding: var(--sp-2) var(--sp-4);
+ color: var(--fg-4);
+ font-family: var(--font-sans);
+ font-weight: 400;
+ min-width: 180px;
+}
+
+.psm-fin-val-col {
+ text-align: right;
+ padding: var(--sp-2) var(--sp-3);
+ color: var(--fg-4);
+ font-family: var(--font-mono);
+ font-weight: 400;
+ white-space: nowrap;
+}
+
+.psm-fin-val-col.accent {
+ color: var(--brass);
+}
+
+.psm-fin-section-row td {
+ padding: var(--sp-3) var(--sp-4) var(--sp-1);
+}
+
+.psm-fin-section-label {
+ color: var(--fg-4);
+ font-family: var(--font-sans);
+ font-size: var(--fs-12);
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+}
+
+.psm-fin-row td {
+ border-bottom: 1px solid var(--ink-2);
+}
+
+.psm-fin-row.is-total td {
+ border-bottom-color: var(--line-1);
+}
+
+.psm-fin-label {
+ padding: var(--sp-2) var(--sp-4);
+ color: var(--fg-3);
+ font-family: var(--font-sans);
+ white-space: nowrap;
+}
+
+.psm-fin-row.is-indent .psm-fin-label {
+ padding-left: calc(var(--sp-4) + 12px);
+}
+
+.psm-fin-row.is-total .psm-fin-label {
+ color: var(--fg-1);
+ font-weight: 500;
+}
+
+.psm-fin-row.is-margin .psm-fin-label {
+ font-style: italic;
+ color: var(--fg-4);
+ font-size: var(--fs-12);
+}
+
+.psm-fin-val {
+ text-align: right;
+ padding: var(--sp-2) var(--sp-3);
+ color: var(--fg-2);
+ font-family: var(--font-mono);
+ white-space: nowrap;
+}
+
+.psm-fin-val.accent {
+ color: var(--brass);
+}
+
+.psm-fin-val.neg {
+ color: var(--negative);
+}
+
+.psm-fin-row.is-total .psm-fin-val {
+ color: var(--fg-1);
+}
+
+.psm-fin-row.is-margin .psm-fin-val {
+ color: var(--fg-4);
+ font-size: var(--fs-12);
+}
+
+.psm-fin-empty {
+ padding: var(--sp-4);
+}