import { useCallback, useMemo } from "react"; import { Box } from "../../../ui"; import { useAsyncResource, useAutoRefresh, usePluginPaneState, useShortcut, useUpdatedAgo } from "../../../public/react"; import { CompositeChart, MarketBoardStack, PaneStatusBody, StatGrid, statGridRows, usePaneNoticeFooter, usePaneStatusLinkFooter, type StatItem } from "../../../components"; import { colors } from "../../../theme/colors"; import { ApiRequestError } from "../../../api-client/errors"; import type { CentralBankRow } from "../../../api-client/central-bank-rates"; import { staticSeries } from "../../../components/chart/static/series"; import type { PaneProps } from "../../../types/plugin"; import { isPlainKey } from "../../../utils/keyboard"; import { useResearchCloudSession } from "../shared/research-cloud-session"; import { getCachedCentralBankRates, loadCentralBankRates } from "./client"; import { hasNoPolicyRate, policyBoardRow, policyChange, policyHistory, policyLevel, policyNotices, policyRate } from "./model"; const PANELS = [{ id: "main" }]; const clearDenied = (error: unknown) => error instanceof ApiRequestError && [401, 403].includes(error.status ?? 0); function PolicyDetail({ row, width, height, focused }: { row: CentralBankRow; width: number; height: number; focused: boolean }) { const history = useMemo(() => policyHistory(row), [row]); const series = useMemo(() => [staticSeries(history.map((point) => ({ date: new Date(point.date), observedAt: new Date(point.date), value: point.value })), { id: row.id, label: row.instrument, color: colors.positive, calendarSpaced: true, style: "step" })], [row, history]); const p = row.percentile; // The chart shows the rank window, so the range carries no window or sample // count; short details keep the figures several to a row. const items: StatItem[] = [ { id: "level", label: row.range ? "Target range" : "Policy rate", value: policyLevel(row), detail: `${p.value == null ? "--" : p.value.toFixed(0)} pctl 1Y` }, { id: "move", label: "Last move", value: policyChange(row), detail: row.lastChangeDate ?? undefined }, // A target range ranks and charts its midpoint, which the band's range would not say. { id: "range", label: "1Y range", value: `${policyRate(p.min)} to ${policyRate(p.max)}`, detail: row.range ? "midpoint" : undefined }, { id: "asOf", label: "As of", value: row.asOf ?? "--", tone: row.status === "stale" ? "warning" : undefined, detail: [row.publicationFrequency, row.lagDays == null ? null : `${row.lagDays}d lag`, row.confirmedAt ? `confirmed ${row.confirmedAt.slice(0, 10)}` : null, row.status === "stale" ? "stale" : null] .filter(Boolean).join(" ยท ") || undefined }, { id: "meeting", label: "Next meeting", value: row.nextMeeting?.date ?? "--" }, { id: "source", label: "Source", value: row.source?.toUpperCase() ?? "--", detail: row.sourceSeriesIds.join(", ") || undefined }, { id: "instrument", label: "Instrument", value: row.instrument, detail: row.centralBank ?? undefined, wide: true }, ]; const summaryHeight = statGridRows(items, width); return point.value != null)} subject="policy history" emptyTitle="No policy history available."> ; } export function CentralBankRatesPane({ width, height, focused }: PaneProps) { const session = useResearchCloudSession(); const loader = useCallback((force: boolean) => loadCentralBankRates(force), [session.requestKey]); const resource = useAsyncResource(loader, { initialData: getCachedCentralBankRates, clearOnError: clearDenied }); const [selectedId, setSelectedId] = usePluginPaneState("selected", null); const [openId, setOpenId] = usePluginPaneState("open", null); const data = resource.data?.payload; const rows = useMemo(() => data?.rows.filter((row) => !hasNoPolicyRate(row)).map(policyBoardRow) ?? [], [data]); const selected = rows.find((row) => row.id === openId) ?? rows.find((row) => row.id === selectedId); const updatedAgo = useUpdatedAgo(resource.updatedAt); useAutoRefresh(resource.updatedAt, resource.load); useShortcut((event) => { if (focused && isPlainKey(event, "r")) { event.preventDefault(); void resource.reload(); } }); usePaneNoticeFooter({ registrationId: "central-bank-rates:notices", focused, notices: [...(data ? policyNotices(data) : []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] }); usePaneStatusLinkFooter({ registrationId: "central-bank-rates", focused, loading: resource.loading, error: resource.error, url: selected?.observation.sourceUrl ?? null, showOpenHint: true, info: data ? [ ...(updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : []), ...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []), ] : [], }); return {data ? } /> : null} ; }