This implementation adds an amortization schedule table to the InterestCalculator component for borrow repayments, addressing issue #496.
generateAmortizationSchedule(): Generates per-period amortization schedule- Reuses
calculateQuote()fromlib/lending/quote.tsfor consistent interest calculations - Returns structured data with period-by-period breakdown (principal, interest, remaining balance)
- Handles edge cases: zero interest, single-period terms, long-term loans, rounding
- Uses same monthly payment formula as the existing quote system
- Reuses
formatCurrency(): Utility for consistent currency formattingshouldCollapseSchedule(): Determines when to collapse long schedules (>6 periods)
- Displays amortization schedule in an accessible table format
- Summary Stats: Shows monthly payment, total interest, and total repayment
- Schedule Table:
- Columns: Period, Principal, Interest, Payment, Balance
- Accessible semantics with proper ARIA labels and table roles
- Collapsible for long schedules (shows first 2 and last 2 periods)
- Expand/collapse button with proper ARIA attributes
- Responsive Design: Horizontal scroll for mobile devices
- Wired AmortizationSchedule into InterestCalculator
- Only displays for
type === 'borrow' - Generates schedule alongside existing calculation
- Maintains all existing functionality for lend mode
-
generateAmortizationSchedule:
- Standard loan calculation
- Zero interest rate handling (rejection)
- Single period term (30 days)
- Long-term loans (365 days = 13 periods)
- Final period balance = 0 verification
- Negative amount rejection
- Default duration handling
- Interest distribution over time (front-loaded interest)
- Consistency with calculateQuote totals
- Very small amounts
- High interest rates
-
formatCurrency: Positive values, zero, decimals, large numbers
-
shouldCollapseSchedule: Short schedules, exactly 6 periods, >6 periods, very long schedules
- Renders summary correctly
- Displays all periods
- Correct value display
- Expand/collapse functionality
- Collapsed indicator for long schedules
- No expand button for short schedules
- Accessible table semantics (ARIA roles, scope attributes)
- Proper ARIA attributes on interactive elements
- Payment calculation per row
- Zero balance handling
All interest calculations reuse calculateQuote() from lib/lending/quote.ts, ensuring consistency between the summary and detailed schedule.
- Proper table semantics with
<table>,<thead>,<tbody>,<th>,<td> scope="col"on header cellsrole="table"andaria-labelon tablearia-expandedandaria-controlson expand button- Keyboard accessible (focus management)
- Schedules >6 periods are collapsed by default
- Shows first 2 and last 2 periods with "X more periods" indicator
- Smooth expand/collapse with visual feedback
- Clear button labels with period count
- Zero interest: Properly rejected with validation
- Single period: Works correctly (30-day loan)
- Long-term loans: Handles 365+ day loans
- Rounding: Final period adjusts to ensure balance = 0
- Small amounts: Handles fractional principals
- High rates: Manages high interest scenarios
- All currency values formatted as
$X.XX - Matches existing calculator formatting
- Clear visual hierarchy
The implementation includes comprehensive tests covering:
- ✅ Standard loan scenarios
- ✅ Edge cases (zero interest, single period, long-term)
- ✅ Rounding and precision
- ✅ Component rendering and interaction
- ✅ Accessibility semantics
- ✅ Expand/collapse functionality
- ✅ Currency formatting
lib/lending/amortization.ts- Core utility functionslib/lending/amortization.test.ts- Unit testscomponents/features/lending/components/AmortizationSchedule.tsx- UI componentcomponents/features/lending/components/AmortizationSchedule.test.tsx- Component tests
components/features/lending/components/InterestCalculator.tsx- Integrated amortization schedule
The amortization schedule automatically appears when:
- User selects "borrow" mode
- Valid amount (>0) and interest rate (>0) are entered
- Calculation is successful
The schedule shows:
- Monthly payment amount
- Total interest over loan duration
- Total repayment amount
- Period-by-period breakdown of principal vs interest
- Remaining balance after each payment
- Reused calculateQuote(): Ensures interest calculations are identical between summary and schedule
- Collapsible UI: Prevents overwhelming users with long schedules
- Type-safe: Full TypeScript coverage with proper interfaces
- Error handling: Graceful degradation with error messages
- Floating point precision: Handles rounding issues in final period
✅ Generates per-period schedule (principal, interest, remaining balance)
✅ Reuses lib/lending/quote.ts math (no duplicated formulas)
✅ Collapses long schedules (show first/last with expand)
✅ Accessible table semantics
✅ Comprehensive test coverage
✅ Edge case handling (zero interest, single period, long-term, rounding)
✅ Clear documentation
✅ No duplicated interest math
To run tests:
# Install dependencies if not already installed
npm install
# Run all tests
npm test
# Run specific test file
npx vitest run lib/lending/amortization.test.ts
npx vitest run components/features/lending/components/AmortizationSchedule.test.tsx
# Run with coverage
npm run test:coverageThe implementation is complete and ready for review.