diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-18 00:19:54 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-18 00:19:54 -0700 |
| commit | b04f744e931c518fe342aa5e43530925bbead4ab (patch) | |
| tree | cddb133a019e8dfe30630f54be2244ee26a51998 /frontend/components/prism/FinancialsCard.tsx | |
| parent | 121aaba0bca0d01584505e6096e7ddb76983094b (diff) | |
feat: add FinancialsCard component with statement tabs and period toggle
Renders income/balance/cash_flow statements with annual/quarterly toggle, section headers, indent levels, total rows, margin rows, and negative-value coloring via --negative.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'frontend/components/prism/FinancialsCard.tsx')
| -rw-r--r-- | frontend/components/prism/FinancialsCard.tsx | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/frontend/components/prism/FinancialsCard.tsx b/frontend/components/prism/FinancialsCard.tsx new file mode 100644 index 0000000..94a6618 --- /dev/null +++ b/frontend/components/prism/FinancialsCard.tsx @@ -0,0 +1,136 @@ +"use client"; +import type { FinancialRow, FinancialsResponse } from "@/types/api"; +import { fmtLarge } from "@/lib/format"; + +type StatementKey = "income" | "balance" | "cash_flow"; +type PeriodKey = "annual" | "quarterly"; + +type Props = { + data: FinancialsResponse; + statement: StatementKey; + period: PeriodKey; + onChangeStatement: (s: StatementKey) => void; + onChangePeriod: (p: PeriodKey) => void; +}; + +const STMT_LABELS: Record<StatementKey, string> = { + income: "INCOME", + balance: "BALANCE", + cash_flow: "CASH FLOW", +}; + +function fmtFinVal(val: number | null | undefined, isMargin: boolean): string { + if (val === null || val === undefined) return "—"; + if (isMargin) return `${(val * 100).toFixed(1)}%`; + return fmtLarge(val); +} + +function FinRow({ row, lastColIdx }: { row: FinancialRow; lastColIdx: number }) { + if (row.is_section) { + return ( + <tr className="psm-fin-section-row"> + <td className="psm-fin-section-label" colSpan={lastColIdx + 2}> + {row.label} + </td> + </tr> + ); + } + + const cls = [ + "psm-fin-row", + row.is_total ? "is-total" : "", + row.is_margin ? "is-margin" : "", + row.indent === 1 ? "is-indent" : "", + ] + .filter(Boolean) + .join(" "); + + return ( + <tr className={cls}> + <td className="psm-fin-label">{row.label}</td> + {row.values.map((val, i) => ( + <td + key={i} + className={[ + "psm-fin-val", + i === lastColIdx ? "accent" : "", + val !== null && val < 0 && !row.is_margin ? "neg" : "", + ] + .filter(Boolean) + .join(" ")} + > + {fmtFinVal(val, row.is_margin)} + </td> + ))} + </tr> + ); +} + +export function FinancialsCard({ + data, + statement, + period, + onChangeStatement, + onChangePeriod, +}: Props) { + const stmt = data[statement]; + const lastColIdx = stmt.columns.length - 1; + + return ( + <section className="psm-card psm-financials-card"> + <div className="psm-fin-header"> + <div className="psm-fin-tabs"> + {(["income", "balance", "cash_flow"] as StatementKey[]).map((key) => ( + <button + key={key} + type="button" + className={`psm-fin-tab${statement === key ? " active" : ""}`} + onClick={() => onChangeStatement(key)} + > + {STMT_LABELS[key]} + </button> + ))} + </div> + <div className="psm-fin-period"> + {(["annual", "quarterly"] as PeriodKey[]).map((p) => ( + <button + key={p} + type="button" + className={`psm-fin-period-btn${period === p ? " active" : ""}`} + onClick={() => onChangePeriod(p)} + > + {p === "annual" ? "ANNUAL" : "QUARTERLY"} + </button> + ))} + </div> + </div> + + {stmt.columns.length === 0 ? ( + <p className="psm-muted-copy psm-fin-empty">Statement data unavailable.</p> + ) : ( + <div className="psm-fin-table-wrap"> + <table className="psm-fin-table"> + <thead> + <tr> + <th className="psm-fin-label-col">USD (millions)</th> + {stmt.columns.map((col, i) => ( + <th + key={i} + className={`psm-fin-val-col${i === lastColIdx ? " accent" : ""}`} + > + {col} + </th> + ))} + </tr> + </thead> + <tbody> + {stmt.rows.map((row, i) => ( + <FinRow key={i} row={row} lastColIdx={lastColIdx} /> + ))} + </tbody> + </table> + </div> + )} + </section> + ); +} |
