feat: investment scenario UX improvements and interest calculations
- Refresh Recommendations now shows inline processing banner with animated progress bar while keeping existing results visible (dimmed). Auto-scrolls to AI section and shows titled notification on completion. - Investment recommendations now auto-calculate purchase and maturity dates from a configurable start date (defaults to today) in the "Add to Plan" modal, so scenarios build projections immediately. - Projection engine computes per-investment and total interest earned, ROI percentage, and total principal invested. Summary cards on the Investment Scenario detail page display these metrics prominently. - Replaced dropdown action menu with inline Edit/Execute/Remove icon buttons matching the assessment scenarios pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -107,17 +107,30 @@ export class BoardPlanningService {
|
||||
async addInvestmentFromRecommendation(scenarioId: string, dto: any) {
|
||||
await this.getScenarioRow(scenarioId);
|
||||
|
||||
// Helper: compute maturity date from purchase date + term months
|
||||
const computeMaturityDate = (purchaseDate: string | null, termMonths: number | null): string | null => {
|
||||
if (!purchaseDate || !termMonths) return null;
|
||||
const d = new Date(purchaseDate);
|
||||
d.setMonth(d.getMonth() + termMonths);
|
||||
return d.toISOString().split('T')[0];
|
||||
};
|
||||
|
||||
const startDate = dto.startDate || null; // ISO date string e.g. "2026-03-16"
|
||||
|
||||
// If the recommendation has components (e.g. CD ladder with multiple CDs), create one row per component
|
||||
const components = dto.components as any[] | undefined;
|
||||
if (components && Array.isArray(components) && components.length > 0) {
|
||||
const results: any[] = [];
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
const comp = components[i];
|
||||
const termMonths = comp.term_months || null;
|
||||
const maturityDate = computeMaturityDate(startDate, termMonths);
|
||||
const rows = await this.tenant.query(
|
||||
`INSERT INTO scenario_investments
|
||||
(scenario_id, source_recommendation_id, label, investment_type, fund_type,
|
||||
principal, interest_rate, term_months, institution, notes, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
principal, interest_rate, term_months, institution, purchase_date, maturity_date,
|
||||
notes, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
RETURNING *`,
|
||||
[
|
||||
scenarioId, dto.sourceRecommendationId || null,
|
||||
@@ -125,7 +138,8 @@ export class BoardPlanningService {
|
||||
comp.investment_type || dto.investmentType || null,
|
||||
dto.fundType || 'reserve',
|
||||
comp.amount || 0, comp.rate || null,
|
||||
comp.term_months || null, comp.bank_name || dto.bankName || null,
|
||||
termMonths, comp.bank_name || dto.bankName || null,
|
||||
startDate, maturityDate,
|
||||
dto.rationale || dto.notes || null,
|
||||
i,
|
||||
],
|
||||
@@ -137,18 +151,21 @@ export class BoardPlanningService {
|
||||
}
|
||||
|
||||
// Single investment (no components)
|
||||
const termMonths = dto.termMonths || null;
|
||||
const maturityDate = computeMaturityDate(startDate, termMonths);
|
||||
const rows = await this.tenant.query(
|
||||
`INSERT INTO scenario_investments
|
||||
(scenario_id, source_recommendation_id, label, investment_type, fund_type,
|
||||
principal, interest_rate, term_months, institution, notes)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
principal, interest_rate, term_months, institution, purchase_date, maturity_date, notes)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
RETURNING *`,
|
||||
[
|
||||
scenarioId, dto.sourceRecommendationId || null,
|
||||
dto.title || dto.label || 'AI Recommendation',
|
||||
dto.investmentType || null, dto.fundType || 'reserve',
|
||||
dto.suggestedAmount || 0, dto.suggestedRate || null,
|
||||
dto.termMonths || null, dto.bankName || null,
|
||||
termMonths, dto.bankName || null,
|
||||
startDate, maturityDate,
|
||||
dto.rationale || dto.notes || null,
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user