diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-19 00:35:20 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-19 00:35:20 -0700 |
| commit | f024c46e874b3cacb7af5bf96aec376b88b86156 (patch) | |
| tree | 9c1f28d5a78d1545b224f31c80beaa2640ef6286 /frontend | |
| parent | f66caf34f8cb9aaff878432aa3c18b9cceb01e65 (diff) | |
feat: replace StatsCard with QualityCard (margins, returns, leverage)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/app/page.tsx | 62 | ||||
| -rw-r--r-- | frontend/components/prism/QualityCard.tsx | 39 |
2 files changed, 42 insertions, 59 deletions
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index d6bde2a..6253221 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -11,9 +11,10 @@ import { KPIStrip } from "@/components/prism/KPIStrip"; import { Sidebar } from "@/components/prism/Sidebar"; import { TickerHeader } from "@/components/prism/TickerHeader"; import { TopBar } from "@/components/prism/TopBar"; +import { QualityCard } from "@/components/prism/QualityCard"; import { VolumeCard } from "@/components/prism/VolumeCard"; import { ApiError, api } from "@/lib/api"; -import { deltaClass, fmtCurrency, fmtLarge, fmtNumber, fmtPct } from "@/lib/format"; +import { deltaClass, fmtNumber, fmtPct } from "@/lib/format"; import { buildKpis, limitIndices, marketClock, OVERVIEW_NAV_ITEMS, signalTone } from "@/lib/overview"; import type { HistoryPoint, MarketIndex, SearchResult, TickerOverview, WatchlistResponse } from "@/types/api"; @@ -316,7 +317,7 @@ function OverviewClient() { <div className="psm-column"> <ProfileCard overview={overview} /> <ShortInterestCard overview={overview} /> - <StatsCard overview={overview} /> + <QualityCard overview={overview} /> </div> </div> </> @@ -496,54 +497,6 @@ function ShortInterestCard({ overview }: { overview: TickerOverview }) { ); } -function StatsCard({ overview }: { overview: TickerOverview }) { - const referenceRows = [ - { label: "Market Cap", value: fmtLarge(overview.stats.market_cap), missing: overview.stats.market_cap == null }, - { label: "P/E TTM", value: overview.stats.trailing_pe == null ? "-" : `${fmtNumber(overview.stats.trailing_pe)}x`, missing: overview.stats.trailing_pe == null }, - { label: "EPS TTM", value: fmtCurrency(overview.stats.trailing_eps), missing: overview.stats.trailing_eps == null }, - { label: "P/B", value: overview.ratios.price_to_book == null ? "-" : `${fmtNumber(overview.ratios.price_to_book)}x`, missing: overview.ratios.price_to_book == null }, - { label: "P/S", value: overview.ratios.price_to_sales == null ? "-" : `${fmtNumber(overview.ratios.price_to_sales)}x`, missing: overview.ratios.price_to_sales == null }, - { label: "EV/Sales", value: overview.ratios.ev_to_sales == null ? "-" : `${fmtNumber(overview.ratios.ev_to_sales)}x`, missing: overview.ratios.ev_to_sales == null }, - { label: "EV/EBITDA", value: overview.ratios.ev_to_ebitda == null ? "-" : `${fmtNumber(overview.ratios.ev_to_ebitda)}x`, missing: overview.ratios.ev_to_ebitda == null }, - { label: "Gross Margin", value: fmtPct(overview.ratios.gross_margin_ttm), missing: overview.ratios.gross_margin_ttm == null }, - { label: "Op Margin", value: fmtPct(overview.ratios.operating_margin_ttm), missing: overview.ratios.operating_margin_ttm == null }, - { label: "Net Margin", value: fmtPct(overview.ratios.net_margin_ttm), missing: overview.ratios.net_margin_ttm == null }, - { label: "ROE", value: fmtPct(overview.ratios.roe_ttm), missing: overview.ratios.roe_ttm == null }, - { label: "ROA", value: fmtPct(overview.ratios.roa_ttm), missing: overview.ratios.roa_ttm == null }, - { label: "ROIC", value: fmtPct(overview.ratios.roic_ttm), missing: overview.ratios.roic_ttm == null }, - { label: "D/E", value: overview.ratios.debt_to_equity == null ? "-" : `${fmtNumber(overview.ratios.debt_to_equity)}x`, missing: overview.ratios.debt_to_equity == null }, - { label: "Current Ratio", value: overview.ratios.current_ratio == null ? "-" : `${fmtNumber(overview.ratios.current_ratio)}x`, missing: overview.ratios.current_ratio == null }, - { label: "Dividend Yield", value: fmtPct(overview.ratios.dividend_yield_ttm), missing: overview.ratios.dividend_yield_ttm == null }, - { label: "Payout Ratio", value: fmtPct(overview.ratios.dividend_payout_ratio_ttm), missing: overview.ratios.dividend_payout_ratio_ttm == null } - ]; - - const visibleRows = referenceRows.filter((r) => !r.missing); - const suppressedCount = referenceRows.length - visibleRows.length; - - return ( - <section className="psm-card"> - <div className="psm-card-head"> - <div> - <div className="psm-eyebrow">Reference</div> - <h2 className="psm-card-title">Reference</h2> - </div> - </div> - {visibleRows.length > 0 && ( - <div className="psm-stat-list"> - {visibleRows.map((row) => ( - <StatRow key={row.label} label={row.label} value={row.value} missing={false} /> - ))} - </div> - )} - {suppressedCount > 0 && ( - <p className="psm-muted-copy" style={{ marginTop: visibleRows.length > 0 ? "var(--sp-4)" : 0 }}> - ยท Statement data incomplete - </p> - )} - </section> - ); -} - function DetailItem({ label, value, missing = false }: { label: string; value: string; missing?: boolean }) { return ( <article className="psm-detail-item"> @@ -553,15 +506,6 @@ function DetailItem({ label, value, missing = false }: { label: string; value: s ); } -function StatRow({ label, value, missing = false }: { label: string; value: string; missing?: boolean }) { - return ( - <div className="psm-stat-row"> - <span className="psm-stat-label">{label}</span> - <span className={`psm-stat-value${missing ? " missing" : ""}`}>{missing ? "Unavailable" : value}</span> - </div> - ); -} - function LoadingShell() { return ( <AppShell diff --git a/frontend/components/prism/QualityCard.tsx b/frontend/components/prism/QualityCard.tsx new file mode 100644 index 0000000..e220fce --- /dev/null +++ b/frontend/components/prism/QualityCard.tsx @@ -0,0 +1,39 @@ +import type { TickerOverview } from "@/types/api"; +import { fmtNumber, fmtPct } from "@/lib/format"; + +export function QualityCard({ overview }: { overview: TickerOverview }) { + const rows = [ + { label: "Gross Margin", value: fmtPct(overview.ratios.gross_margin_ttm), missing: overview.ratios.gross_margin_ttm == null }, + { label: "Op Margin", value: fmtPct(overview.ratios.operating_margin_ttm), missing: overview.ratios.operating_margin_ttm == null }, + { label: "Net Margin", value: fmtPct(overview.ratios.net_margin_ttm), missing: overview.ratios.net_margin_ttm == null }, + { label: "ROE", value: fmtPct(overview.ratios.roe_ttm), missing: overview.ratios.roe_ttm == null }, + { label: "ROA", value: fmtPct(overview.ratios.roa_ttm), missing: overview.ratios.roa_ttm == null }, + { label: "ROIC", value: fmtPct(overview.ratios.roic_ttm), missing: overview.ratios.roic_ttm == null }, + { label: "D/E", value: overview.ratios.debt_to_equity == null ? "-" : `${fmtNumber(overview.ratios.debt_to_equity)}x`, missing: overview.ratios.debt_to_equity == null }, + { label: "Current Ratio", value: overview.ratios.current_ratio == null ? "-" : `${fmtNumber(overview.ratios.current_ratio)}x`, missing: overview.ratios.current_ratio == null }, + { label: "Dividend Yield", value: fmtPct(overview.ratios.dividend_yield_ttm), missing: overview.ratios.dividend_yield_ttm == null }, + { label: "Payout Ratio", value: fmtPct(overview.ratios.dividend_payout_ratio_ttm), missing: overview.ratios.dividend_payout_ratio_ttm == null }, + ]; + + const visible = rows.filter((r) => !r.missing); + if (!visible.length) return null; + + return ( + <section className="psm-card"> + <div className="psm-card-head"> + <div> + <div className="psm-eyebrow">Quality</div> + <h2 className="psm-card-title">Quality</h2> + </div> + </div> + <div className="psm-stat-list"> + {visible.map((row) => ( + <div key={row.label} className="psm-stat-row"> + <span className="psm-stat-label">{row.label}</span> + <span className="psm-stat-value">{row.value}</span> + </div> + ))} + </div> + </section> + ); +} |
