Commit Graph

7 Commits

Author SHA1 Message Date
Víctor Falcón 973243277a
feat(accounts): show mortgage data and equity on real estate account page (#243)
## Summary

- Add mortgage balance as a dashed line overlay on the property value
chart when a real estate account has a linked loan
- Display equity (market value - mortgage owed) in the chart header and
as a 3-column summary strip (Market Value | Mortgage Owed | Equity) in
the property details card
- Extend balance evolution API endpoints to include `mortgage_balance`
data points for linked loan accounts
- Add 6 new tests covering mortgage/equity display in account show page
and balance evolution endpoints
2026-03-24 15:56:23 +00:00
Víctor Falcón 395c4ad2c3
feat(accounts): add real estate asset tracking (#241)
## Summary

- Adds **real estate** as a new account type (`real_estate`) for
tracking property assets within the existing Accounts page
- Properties store metadata (type, address, purchase price/date, area,
notes) in a dedicated `real_estate_details` table with a one-to-one
relationship to accounts
- Properties can be **linked to a loan account** (mortgage) to
implicitly calculate equity — property value counts as asset (+), linked
loan counts as liability (-)
- Market value is tracked as the account balance and uses "market value"
terminology throughout the UI

## What's included

### Backend
- `PropertyType` enum (Residential, Commercial, Land, Vacation, Other)
- `RealEstateDetail` model, factory, migration, policy
- `StoreRealEstateDetailRequest` and `UpdateRealEstateDetailRequest`
form requests
- `RealEstateDetailController` with `update()` for editing property
details
- Extended `Settings\AccountController@store` to create
`RealEstateDetail` when type is `real_estate`
- Extended `AccountController@show` to load real estate detail, linked
loan with bank info, and available loan accounts
- Extended `AccountController@index` SQL ordering to include
`real_estate`
- Conditional validation rules in `StoreAccountRequest` for real estate
fields

### Frontend
- New `real_estate` type in `account.ts` with `PropertyType`,
`AreaUnit`, `RealEstateDetail` interface, and helper functions
- Conditional real estate fields in `account-form.tsx` (property type,
address, purchase price, purchase date, area, linked loan, notes)
- `PropertyDetailsCard` component in `Accounts/Show.tsx` with view and
inline edit modes
- "Update market value" terminology in balance update buttons for real
estate accounts
- `real_estate` added to account type ordering and groups on the Index
page
- Wayfinder routes regenerated

### Tests
- 22 feature tests covering creation, validation, show page, index
ordering, updating details, IDOR protection, model relationships, and
soft delete behavior
- Unit test updates for `AccountType` enum (`reducesNetWorth`,
`isNonTransactional`)

### Translations
- 32 new Spanish translation strings for all real estate UI

## Design decisions
- Real estate accounts are **non-transactional** (like
investment/retirement) — balance-only tracking for now. Future iteration
will link transactions directly for rental income/expense tracking
- **Single loan per property** via direct FK from
`real_estate_details.linked_loan_account_id`
- No encryption on address/notes fields (opted out per discussion)
- No separate sidebar page — real estate is a grouped section within the
existing Accounts page
2026-03-24 10:21:32 +00:00
Víctor Falcón 79dd24b23e
fix(ux): improve status badge, hide balance update for connected accounts, localize delete confirm (#159)
## Why

### Problem

Three UX issues were identified:

1. **Connection status badge looked like a button** — the `<Badge>`
component has interactive-looking styles that made it visually ambiguous
as a status indicator rather than a purely informational element.
2. **"Update balance" was shown for bank-connected accounts** — accounts
synced via Open Banking have their balances managed automatically;
showing a manual update button was misleading and could create
confusion.
3. **Delete confirmation word was not localized** — the dialog required
typing `DELETE` even when the UI was in Spanish, where the placeholder
correctly said `ELIMINAR`. The check was hardcoded to `'DELETE'`.

## What

### Changes

<img width="756" height="407" alt="image"
src="https://github.com/user-attachments/assets/4866edf7-5329-486a-9f72-fcd69429f784"
/>
<img width="757" height="424" alt="image"
src="https://github.com/user-attachments/assets/bc9bda54-e0a4-402b-be32-a9fe1897cfd5"
/>
2026-02-27 22:37:16 +00:00
Víctor Falcón b661255d09
refactor: extract AccountMetricsService to deduplicate balance computation logic (#147)
## Why

### Problem
Account balance metrics computation (12-month sparklines, net worth
evolution, currency conversion) was duplicated across three controllers:
- `AccountController` — account index page metrics
- `DashboardController` — dashboard net worth evolution
- `DashboardAnalyticsController` — analytics API net worth evolution
(monthly + daily)

Each had its own `convertBalance()` method, its own BalanceLookup
iteration loop, and its own invested amount handling. Additionally,
`AccountController::show()` re-queried `categories`, `accounts`, and
`banks` — all already available as shared Inertia props from
`HandleInertiaRequests` middleware.

## What

### Changes
- **Extract `AccountMetricsService`** with three public methods:
- `getAccountMetrics()` — per-account balance metrics with sparkline
history (used by accounts index)
- `getNetWorthEvolution()` — monthly net worth evolution with
per-account balances (used by dashboard + analytics API)
- `getNetWorthDailyEvolution()` — daily net worth evolution (used by
analytics API)
- **Refactor `AccountController`** — delegate to
`AccountMetricsService`, remove 3 private methods (`getAccountMetrics`,
`formatMonth`, `convertBalance`)
- **Refactor `DashboardController`** — delegate to
`AccountMetricsService`, remove `getNetWorthEvolution` loop and
`convertBalance`
- **Refactor `DashboardAnalyticsController`** — delegate to
`AccountMetricsService` for `netWorthEvolution` and
`netWorthDailyEvolution`, remove `convertBalance`, `getBalanceAt`,
`getInvestedAmountAt` private methods
- **Remove duplicate queries from `AccountController::show()`** —
`categories`, `accounts`, `banks` are already shared by middleware
- **Add missing `encrypted` column** to the middleware's shared
`accounts` query so components that need it (EditAccountDialog, etc.)
receive it

### Impact
- **Net -87 lines** across 5 files (285 added in new service, 372
removed from controllers)
- Single source of truth for balance metrics computation
- Eliminated 3 copies of `convertBalance()` and 2 copies of the net
worth evolution loop
- Removed 3 redundant database queries from account show page

## Verification

### Tests
- All 84 related tests pass (AccountController, AccountBalance,
Dashboard, DashboardAnalytics, BalanceLookup, CashflowAnalytics) with
529 assertions
- No test changes needed — the refactoring preserves exact output shapes
2026-02-24 09:07:42 +01:00
Víctor Falcón ce9574aa14
perf(accounts): replace client-side API calls with Inertia deferred prop (#144)
## Why

The `/accounts` page was making two extra client-side API calls via the
`useDashboardData()` hook:
- `/api/dashboard/net-worth-evolution` (~14.5s in dev)
- `/api/dashboard/top-categories` (~119ms, completely unused on this
page)

The backend logic itself is fast (~34ms), but the full middleware stack
cost of extra HTTP roundtrips in the dev environment was adding
significant overhead.

## What

### Backend (`AccountController.php`)
- Added `ExchangeRateService` constructor injection
- Added `Inertia::defer()` prop `accountMetrics` to the `index()` action
- Added `getAccountMetrics()`, `formatMonth()`, `convertBalance()`
private methods
- Uses `BalanceLookup::forAccounts()` for efficient batch loading (2 SQL
queries regardless of account count)

### Frontend (`Accounts/Index.tsx`)
- Removed `useDashboardData()` hook import and usage
- Added `accountMetrics` as an optional deferred prop (undefined until
resolved)
- Loading state derived from `!accountMetrics`, which naturally drives
the existing `loading` prop on `AccountListCard` (skeleton UI already
existed)
- Added `handleBalanceUpdated` callback using `router.reload({ only:
['accountMetrics'] })` for targeted refresh

## Verification

### Tests
- 3 new Pest tests covering deferred prop behavior:
- `accounts index defers account metrics` — verifies metrics are missing
from initial response, present after `loadDeferredProps()`, with correct
balance values
- `accounts index deferred metrics includes invested amount for
investment accounts` — verifies invested amount for investment account
types
- `accounts index deferred metrics returns null invested amount for
non-investment accounts` — verifies non-investment accounts get null
invested amount
- All 15 tests in `AccountControllerTest.php` pass
2026-02-23 20:08:41 +01:00
Víctor Falcón 2dc8cb554c
Remove encryption from bank account names (#104)
## Summary

- Store account names in plaintext instead of encrypting them
client-side. Encryption remains only for transaction descriptions and
notes.
- Existing encrypted account names are silently migrated when the user
unlocks their encryption key, via a new `useDecryptAccountNames` hook
that calls API endpoints to update each account.
- New `AccountName` component handles rendering both encrypted (legacy)
and plaintext accounts across the app.

## Test plan

- [x] Create a new account — name should be stored in plaintext
(`encrypted = false`, `name_iv = null`)
- [x] Unlock encryption key with existing encrypted accounts — they
should auto-migrate silently
- [x] After migration, verify accounts show `encrypted = false` in DB
and names are readable plaintext
- [x] Edit a migrated account — should work without encryption
- [x] Verify all account name displays work across dashboard, account
details, transactions, settings
2026-02-09 14:15:26 +01:00
Víctor Falcón 8be7ea98e0
Accounts: new pages to list, and see account details (#13)
## 🆕  Accounts page
<img width="1222" height="1118" alt="image"
src="https://github.com/user-attachments/assets/2ab659b3-c368-4824-9f00-cdeb6ae8e713"
/>

## 🆕  Accounts details page
<img width="1220" height="1171" alt="image"
src="https://github.com/user-attachments/assets/7c64b1cf-c1fd-4ada-8ab0-17ee4f6991de"
/>

## 🆕 Update current account balance
<img width="1069" height="778" alt="image"
src="https://github.com/user-attachments/assets/76586b8a-7088-4ef8-a890-22910644a2d3"
/>

## 🆕 Edit/update account balance history
<img width="1200" height="681" alt="image"
src="https://github.com/user-attachments/assets/fa589553-7be8-4049-bdf4-89996b81a863"
/>
2025-12-05 14:33:18 +01:00