import React, { useMemo } from "react"; import { Chart as ChartJS, ArcElement, DoughnutController, Tooltip, type ChartOptions, type ChartData, } from "chart.js"; import { Chart } from "react-chartjs-2"; import { useThemeVars } from "@/lib/theme-provider"; import { Card, CardContent, CardHeader, CardTitle } from "./card"; import { Empty, EmptyDescription } from "./empty"; import { Skeleton } from "./skeleton"; import { cn } from "@/lib/utils"; import { formatAbbrev, DoughnutLegendRow, FALLBACK_PRIMARY, FALLBACK_SECONDARY, } from "./chart-shared"; ChartJS.register(ArcElement, DoughnutController, Tooltip); // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface PropertyDebtEquityDoughnutChartProps { /** Current property estimate value in dollars */ estimateValue?: number | null; /** Outstanding debt (mortgage) in dollars */ debtValue?: number | null; title?: string; showLegend?: boolean; /** Chart canvas height in pixels */ height?: number; /** Card max-width in pixels or CSS string */ width?: number | string; className?: string; /** Show skeleton loading state instead of the chart */ isLoading?: boolean; } // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function PropertyDebtEquityDoughnutChart({ estimateValue, debtValue, title = "Debt & Equity", showLegend = true, height = 200, width = "100%", className, isLoading = false, }: PropertyDebtEquityDoughnutChartProps) { const themeVars = useThemeVars(); const brandPrimary: string = (themeVars["--theme-primary"] as string | undefined) || FALLBACK_PRIMARY; const brandSecondary: string = (themeVars["--theme-secondary"] as string | undefined) || FALLBACK_SECONDARY; const fontFamily: string = (themeVars["--font-sans"] as string | undefined) || "Figtree, sans-serif"; const estimate = estimateValue ?? 0; const debt = Math.min(debtValue ?? 0, estimate); const equity = Math.max(0, estimate - debt); const hasData = estimate > 0; const chartData = useMemo>( () => ({ labels: ["Equity", "Debt"], datasets: [ { data: [equity, debt], backgroundColor: [brandPrimary, brandSecondary], borderWidth: 0, hoverOffset: 4, }, ], }), [equity, debt, brandPrimary, brandSecondary], ); const options = useMemo>( () => ({ responsive: true, maintainAspectRatio: false, cutout: "68%", animation: { duration: 600, easing: "easeOutQuart" }, plugins: { legend: { display: false }, tooltip: { padding: 10, cornerRadius: 0, titleFont: { size: 11, weight: 600 }, bodyFont: { size: 12, weight: 500 }, callbacks: { label: (ctx) => { const val = ctx.raw as number; const pct = estimate > 0 ? `${((val / estimate) * 100).toFixed(1)}%` : "0%"; return ` ${ctx.label}: ${formatAbbrev(val)} (${pct})`; }, }, }, }, }), [estimate], ); const equityPct = estimate > 0 ? `${((equity / estimate) * 100).toFixed(1)}%` : "0%"; const debtPct = estimate > 0 ? `${((debt / estimate) * 100).toFixed(1)}%` : "0%"; return ( {title} {isLoading ? ( ) : !hasData ? ( No data available ) : (
{formatAbbrev(equity)}
Equity
{showLegend && (
)}
)}
); }