summaryrefslogtreecommitdiff
path: root/frontend/components/prism/FinancialsCard.tsx
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 00:19:54 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-18 00:19:54 -0700
commitb04f744e931c518fe342aa5e43530925bbead4ab (patch)
treecddb133a019e8dfe30630f54be2244ee26a51998 /frontend/components/prism/FinancialsCard.tsx
parent121aaba0bca0d01584505e6096e7ddb76983094b (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.tsx136
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>
+ );
+}