feat: investment chart alignment, auto-renew records, fund transfers, capital planning report, and upcoming activities (v2026.3.24)

- Lock InvestmentTimeline and ProjectionChart to shared X axis range
- Auto-create renewal scenario_investments records when auto_renew is true
- Add fund transfer mechanism between asset accounts with journal entries
- Add Capital Planning Report (5-year forecast grouped by category)
- Add Upcoming Investment Activities dashboard card (maturities + planned purchases)
- Bump version to 2026.3.24

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 14:41:02 -04:00
parent ae856bfb2f
commit 2b331bb3ef
15 changed files with 801 additions and 21 deletions

View File

@@ -13,9 +13,12 @@ const typeColors: Record<string, string> = {
interface Props {
investments: any[];
/** Optional shared time range to align with ProjectionChart */
sharedStartDate?: Date;
sharedEndDate?: Date;
}
export function InvestmentTimeline({ investments }: Props) {
export function InvestmentTimeline({ investments, sharedStartDate, sharedEndDate }: Props) {
const { items, startDate, endDate, totalMonths } = useMemo(() => {
const now = new Date();
const items = investments
@@ -28,16 +31,24 @@ export function InvestmentTimeline({ investments }: Props) {
if (!items.length) return { items: [], startDate: now, endDate: now, totalMonths: 1 };
const allDates = items.flatMap((i: any) => [i.start, i.end].filter(Boolean)) as Date[];
const startDate = new Date(Math.min(...allDates.map((d) => d.getTime())));
const endDate = new Date(Math.max(...allDates.map((d) => d.getTime())));
// Use shared range if provided (to align with ProjectionChart), otherwise compute from investments
let startDate: Date;
let endDate: Date;
if (sharedStartDate && sharedEndDate) {
startDate = sharedStartDate;
endDate = sharedEndDate;
} else {
const allDates = items.flatMap((i: any) => [i.start, i.end].filter(Boolean)) as Date[];
startDate = new Date(Math.min(...allDates.map((d) => d.getTime())));
endDate = new Date(Math.max(...allDates.map((d) => d.getTime())));
}
const totalMonths = Math.max(
(endDate.getFullYear() - startDate.getFullYear()) * 12 + (endDate.getMonth() - startDate.getMonth()) + 1,
1,
);
return { items, startDate, endDate, totalMonths };
}, [investments]);
}, [investments, sharedStartDate, sharedEndDate]);
if (!items.length) return null;