import { useState, useMemo } from 'react'; import { toNumeric } from '../stockUtils'; export default function useReportData({ portfolioHoldings, portfolioSummary, brokerGroups, getBrokerSummary }) { const [reportSortField, setReportSortField] = useState('profit_rate'); const [reportSortDir, setReportSortDir] = useState('desc'); const handleReportSort = (field) => { if (reportSortField === field) { setReportSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); } else { setReportSortField(field); setReportSortDir('desc'); } }; const brokerPieData = useMemo(() => brokerGroups .map(([broker, items]) => ({ name: broker, value: getBrokerSummary(items).totalEval })) .filter((d) => d.value > 0), [brokerGroups, getBrokerSummary] ); const profitBarData = useMemo(() => portfolioHoldings .filter((item) => item.profit_rate != null) .map((item) => ({ name: item.ticker ?? (item.name ?? 'N/A').slice(0, 5), fullName: item.name ?? item.ticker ?? 'N/A', rate: toNumeric(item.profit_rate) ?? 0, })) .sort((a, b) => b.rate - a.rate), [portfolioHoldings] ); const maxAbsRate = useMemo(() => Math.max(1, ...portfolioHoldings.map((h) => Math.abs(toNumeric(h.profit_rate) ?? 0))), [portfolioHoldings] ); const brokerConcentration = useMemo(() => { const totalEval = toNumeric(portfolioSummary.total_eval); if (!totalEval || totalEval === 0) return []; return brokerGroups .map(([broker, items]) => { const { totalEval: brokerEval } = getBrokerSummary(items); const ratio = Math.round((brokerEval / totalEval) * 1000) / 10; return { broker, eval: brokerEval, ratio }; }) .sort((a, b) => b.ratio - a.ratio); }, [brokerGroups, portfolioSummary.total_eval, getBrokerSummary]); const stockConcentration = useMemo(() => { const totalEval = toNumeric(portfolioSummary.total_eval); if (!totalEval || totalEval === 0) return []; return portfolioHoldings .map((item) => { const evalAmt = item.eval_amount != null ? toNumeric(item.eval_amount) : (item.current_price != null && item.quantity != null) ? toNumeric(item.current_price) * toNumeric(item.quantity) : null; if (!evalAmt) return null; return { name: item.name ?? item.ticker ?? 'N/A', ticker: item.ticker ?? '', eval: evalAmt, ratio: Math.round((evalAmt / totalEval) * 1000) / 10, }; }) .filter(Boolean) .sort((a, b) => b.ratio - a.ratio) .slice(0, 5); }, [portfolioHoldings, portfolioSummary.total_eval]); const sortedHoldings = useMemo(() => { const getVal = (item) => { switch (reportSortField) { case 'profit_rate': return toNumeric(item.profit_rate) ?? -Infinity; case 'profit_amount': return toNumeric(item.profit_amount) ?? -Infinity; case 'eval_amount': { const ea = toNumeric(item.eval_amount); if (ea != null) return ea; const cp = toNumeric(item.current_price); const qty = toNumeric(item.quantity); return cp != null && qty != null ? cp * qty : -Infinity; } default: return 0; } }; return [...portfolioHoldings].sort((a, b) => { if (reportSortField === 'name') return reportSortDir === 'asc' ? (a.name ?? '').localeCompare(b.name ?? '') : (b.name ?? '').localeCompare(a.name ?? ''); if (reportSortField === 'broker') return reportSortDir === 'asc' ? (a.broker ?? '').localeCompare(b.broker ?? '') : (b.broker ?? '').localeCompare(a.broker ?? ''); const av = getVal(a); const bv = getVal(b); return reportSortDir === 'asc' ? av - bv : bv - av; }); }, [portfolioHoldings, reportSortField, reportSortDir]); return { reportSortField, reportSortDir, handleReportSort, brokerPieData, profitBarData, maxAbsRate, brokerConcentration, stockConcentration, sortedHoldings, }; }