import type { Estimate, Invoice } from './shared';
import { useMemo } from 'react';
import { Link } from 'react-router';
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import { ArrowDownRight, ArrowUpRight, CircleDollarSign, FileText, Receipt, Wallet } from 'lucide-react';
import { toneFill } from '@/components/Data/tone';
import { formatAmount, formatDate, formatMoney, relativeDue, toMajor } from './format';
import { EmptyState, ErrorState, LoadingState, StatusBadge } from './components';
import { invoicesRecordPath, invoicesSectionPath } from './shared';
import { useSummary } from './useInvoiceShelfData';
// The /invoices landing section — InvoiceShelf's own dashboard, rebuilt.
//
// Every figure here comes from the single /summary call the nav already makes, so arriving on this section
// costs no extra request. Upstream hands the chart over as five parallel arrays (months + four series)
// rather than as rows, so they are zipped into recharts' row shape below.
export const DashboardView = () => {
const { dashboard, currency, isLoading, error } = useSummary();
const chartRows = useMemo(() => {
const chart = dashboard?.chart_data;
if (!chart?.months?.length) return [];
return chart.months.map((month, i) => ({
month,
// recharts needs major units or the axis reads in cents; the tooltip re-formats from these.
// toMajor divides by 100, matching formatMoney — see the note on MINOR_PER_MAJOR in format.ts.
sales: toMajor(chart.invoice_totals?.[i]),
receipts: toMajor(chart.receipt_totals?.[i]),
expenses: toMajor(chart.expense_totals?.[i]),
net: toMajor(chart.net_income_totals?.[i]),
}));
}, [dashboard]);
if (error) return ;
if (isLoading) return ;
if (!dashboard) return ;
const hasChart = chartRows.some((r) => r.sales || r.receipts || r.expenses || r.net);
return (
);
};
const Gradient = ({ id, color }: { id: string; color: string }) => (
);
const LegendDot = ({ className, label }: { className: string; label: string }) => (
{label}
);
/** Axis labels only — a full currency render would not fit and the tooltip carries the exact figure. */
const compact = (v: number): string => {
const abs = Math.abs(v);
if (abs >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
if (abs >= 1_000) return `${(v / 1_000).toFixed(0)}k`;
return String(v);
};
// Every tile here only ever *goes somewhere* — no tile mutates anything — so they are anchors, not
// buttons. That is what makes the figures middle-clickable into a new tab and copyable as a link,
// which is the whole point of the section reading its filter out of the query string.
const MetricCard = ({
icon: Icon,
tone,
label,
value,
hint,
to,
}: {
icon: typeof Wallet;
tone: string;
label: string;
value: string;
hint?: string;
to: string;
}) => (