Commit Graph

12 Commits

Author SHA1 Message Date
Víctor Falcón ca2e5c09b3
fix(balances): allow saving a zero balance (#664)
## Why

A balance of exactly **0** could not be saved. Zero is a perfectly valid
balance for any account (an emptied wallet, a paid-off loan, a closed
position), so the app was rejecting legitimate input.

## Root cause

The shared `AmountInput` renders a numeric value of `0` as an **empty
string** (so the field shows the `0.00` placeholder instead of a literal
"0.00"). Several balance forms combined that with the native HTML
`required` attribute — which then rejected the *only* value that renders
empty: `0`. `required` on this input never guarded against a missing
value (the state is always numeric; empty == 0), it only ever blocked
zero.

The backend already accepted `0` (`required|integer`, and Laravel's
`required` treats integer `0` as present), so this was purely a frontend
constraint.

## Changes

- Drop `required` from the balance `AmountInput` in the **Update
balance** dialog and the **Balance history** edit modal.
- Remove the explicit `balanceInCents === 0` guard (and `required`) in
the onboarding **Set balance** step.
- Extend the same fix to the CSV-import **reference balance** field
(identical root cause; the compute path already gates on
`referenceBalance !== null`, so an explicit `0` is a valid anchor).
- Remove the now-orphaned `"Please enter a balance"` translation from
`es.json` / `fr.json`.
- Add feature tests asserting a zero balance can be stored and set as
the current balance.

Transaction and budget amount inputs keep `required` — `0` is not a
meaningful value there, so they are intentionally untouched.

## QA

- **Onboarding balance step**: saving with an empty/zero field now
advances instead of showing "Please enter a balance". 
- **Update balance dialog**: set €1,000.00, then overwrote it with €0.00
→ dialog closed and the record persisted. Verified in the DB
(`account_balances.balance = 0`). 
- Backend contract locked by new tests in
`AccountBalanceControllerTest`.

## Demo

<!-- PLACEHOLDER: drag the video here -->

_Video to attach: `~/Downloads/allow-zero-balances-demo.mp4`_
2026-07-10 15:02:38 +02:00
Víctor Falcón 744d874464
fix(import): honor selected date format for CSV imports (#494)
## Problem

Importing a CSV with `DD/MM/YYYY` dates (e.g. `02/06/2026`) parsed them
as `MM/DD/YYYY`, and the **Date Format** selector either didn't appear
or had no effect — the preview showed the same wrong date regardless of
the chosen format.

## Root cause

The `xlsx` parser coerced CSV date-like strings into Excel **serial
numbers** at read time, guessing the format itself. `parseDate` then hit
its numeric short-circuit and **ignored the selected format entirely**,
so the Date Format radio never changed the preview.

On top of that, ambiguous dates (valid as both DD/MM and MM/DD) were
resolved silently by browser locale, and a saved import config force-hid
the selector — so a wrong guess couldn't be corrected.

## Changes

- **`parseFile` reads with `raw: true`** — CSV cells stay as their
original strings; `parseDate` applies the chosen format. Native
`.xls/.xlsx` dates still arrive as numbers and use the serial path.
- **`detectDateFormat()`** now returns `{ format, ambiguous }`. When
multiple formats parse the sample equally, the importer keeps the Date
Format selector visible (defaulting to the locale/saved best guess) —
even when a saved config exists — so a previously-saved wrong format can
be fixed. `autoDetectDateFormat()` kept as a thin wrapper.
- Applied to both the transactions and account-balances import drawers.
- **UI:** moved the Date Format selector above the preview and switched
the mapping form from broken Tailwind v4 `space-y`/`space-x` to
`flex`/`gap`.

## Tests

- New `parseFile` regression test: `02/06/2026` stays a string and
parses to June (DD-MM) vs February (MM-DD).
- New `detectDateFormat` tests for the `ambiguous` flag.
- All 39 `file-parser` tests pass; lint/format clean.
2026-06-05 15:10:48 +02:00
Víctor Falcón f9d1303d98
feat(importer): support YYYYMMDD date format (#470)
## Summary
Adds a compact `YYYYMMDD` (no separators) date format to the
transactions and balance importers.

- New `DateFormat.YearMonthDayCompact = 'YYYYMMDD'` enum value
- `parseDate` parses the 8-digit form (`20241231` → `2024-12-31`)
- `autoDetectDateFormat` recognizes it (unambiguous, no separators)
- Both mapping UIs (transactions + balances) expose it as a selectable
option

## Testing
- `bunx vitest run resources/js/lib/file-parser.test.ts` — 34 passed
- Added tests: compact auto-detection + compact conversion
- `bun run format`, `bun run lint` clean
2026-06-01 16:44:46 +02:00
Víctor Falcón 10da06ed84
feat(transactions): add counterparty fields (#440)
## Summary
- add creditor/debtor fields to transactions with raw_data backfill
- store counterparties on bank sync and CSV/XLS imports
- add creditor/debtor filters plus hidden table columns

## Tests
- php artisan test --compact tests/Feature/LocalizationTest.php
tests/Feature/OpenBanking/TransactionSyncServiceTest.php
tests/Feature/TransactionFilterTest.php
- npm test -- resources/js/lib/file-parser.test.ts --run
- vendor/bin/pint --dirty --format agent

Note: `npm run types` still has pre-existing unrelated errors; no
creditor/debtor/import-related errors remained in filtered output.

## Screenshot
<img width="1241" height="676" alt="8odYWtFcvUM"
src="https://github.com/user-attachments/assets/55653485-d588-4beb-9e6a-5c7c81ba7cf8"
/>
2026-05-27 16:20:55 +02:00
Víctor Falcón 66ff427481
feat(import): calculate balances from transactions (#403)
## Why

Some banks return transactions without a balance column. The CSV/Excel
importer happily creates the transactions but leaves the account without
any balance history, making the account look incomplete.

## What

Opt-in flow that derives per-day balances from the transactions
themselves, using a single reference point (the balance on the date of
the latest transaction).

### Mapping step
- New checkbox **"Calculate balances from transactions"** next to the
  Balance column select.
- Available only when no balance column is mapped (disabled + visually
  faded otherwise).
- When checked, a balance input appears labeled with the latest
  transaction date using a relative format:
  - `Today`
  - `Yesterday`
  - `Monday, 3 of Jun`
- If an `AccountBalance` already exists for that date, it is pre-filled
  automatically (no need to ask) with a small hint.
- The reference balance is **mandatory** when the checkbox is checked;
  the Next button is disabled until it is provided.

### Preview step
- A new `Balance` column shows the balance about to be created for each
  date, regardless of whether it came from the file or was calculated.

### Persistence
- Balances are computed by walking backwards across distinct transaction
  dates and subtracting each day's net movement.
- They flow through the existing import path that POSTs to
  `AccountBalanceController@store`, so no new endpoint was needed.

## Feature flag

Hidden behind a new Pennant feature, **off by default**:

```bash
php artisan feature:enable CalculateBalancesOnImport user@example.com
# or
php artisan feature:enable CalculateBalancesOnImport all
```

The flag is exposed to the frontend via Inertia shared props
(`features.calculateBalancesOnImport`).

## Tests

- New vitest cases for `getLatestTransactionDate` and
  `calculateBalancesFromTransactions` (sparse dates, reference date
  without txns, empty list).
- `LocalizationTest` passes (new ES strings added).
- Full vitest suite green (106 tests).
2026-05-20 10:29:14 +01:00
Víctor Falcón 2b9fd2384a
feat(i18n): localize Spanish translations and currency formatting (#160)
## Why

### Problem
The app had two categories of localization gaps:
1. Many UI strings were not translated — Spanish users saw raw English
text because keys were missing from `lang/es.json`.
2. All currency and number formatting was hardcoded to `'en-US'` (or
`undefined`), so amounts always formatted in English style regardless of
the user's locale setting.

## What

### Changes
- **`lang/es.json`** — Added 22 missing Spanish translation keys
covering onboarding, automation rules, balance history, dashboard, and
more.
- **`utils/currency.ts`** — Added `locale` parameter (default `'en-US'`)
to `formatCurrency`.
- **`utils/chart.tsx`** — Added `locale` parameter to
`formatCurrencyWithCode`; replaced `.toLocaleString()` calls with
locale-aware versions.
- **`hooks/use-dashboard-data.ts`** — Added `locale` param to
`formatMonth` and `deriveAccountMetrics`; wired `useLocale()` inside
`useDashboardData`.
- **`pages/dashboard.tsx`** — Added `useLocale()` and passed `locale` to
`deriveAccountMetrics`.
- **`components/charts/sankey-chart.tsx`** — Added `locale` param to
`formatAmount` and threaded it through `OtherCategoriesBreakdown`.
- **`components/charts/mom-chart.tsx`** — Replaced hardcoded `'en-US'`
in YAxis tickFormatter with `useLocale()`.
- **`components/budgets/budget-list-card.tsx`** — Passed `locale` to all
`formatCurrency` calls.
- **`components/budgets/budget-spending-chart.tsx`** — Passed `locale`
to all `formatCurrency` calls in `CustomTooltip`.
- **`components/ui/amount-display.tsx`** — Added `useLocale()` and
passed to `Intl.NumberFormat`.
- **`components/ui/amount-input.tsx`** — Added `useLocale()` and passed
to `Intl.NumberFormat` used for display formatting.
- **`components/accounts/balances-modal.tsx`** — Added `useLocale()` for
both the balance formatter and `formatDate`.
- **`components/transactions/edit-transaction-dialog.tsx`** — Replaced
hardcoded `'en-US'` with `useLocale()`.
-
**`components/accounts/import-balances/import-balance-step-mapping.tsx`**
— Replaced `'en-US'` with `useLocale()` in preview formatting.
- **`components/transactions/import-step-mapping.tsx`** — Replaced
`'en-US'` with `useLocale()` in preview formatting.
- **`components/onboarding/step-create-account.tsx`** — Fixed garbled
Spanish caused by 3-part JSX string concatenation; replaced with single
translation key.
- **`components/automation-rules/create-automation-rule-dialog.tsx`** —
Fixed multiline description key mismatch with `es.json`.

## Verification

### Tests
- TypeScript check (`npx tsc --noEmit`) passes with no new errors
introduced by these changes.
- `lang/es.json` validated as well-formed JSON.

### Manual
- With locale set to `es`, all previously untranslated strings now
render in Spanish.
- Currency amounts format according to the user's locale (e.g.,
`1.234,56 €` in `es` vs `$1,234.56` in `en-US`).
2026-02-28 18:46:18 +00:00
Víctor Falcón 70b603e901
feat: Spanish localization (#74)
## Pending
- [x] Translate landing page
- [x] Dashboard
- [x] Accounts list page
- [x] Account page
- [x] Cashflow
- [x] Budgets
- [x] Transactions
- [x] Settings

## Screenshots
<img width="1210" height="969" alt="image"
src="https://github.com/user-attachments/assets/c7935e5c-488d-4941-8f19-8834e5668257"
/>
<img width="1211" height="972" alt="image"
src="https://github.com/user-attachments/assets/e94e1daf-233a-4a49-aa65-5678c772d178"
/>
2026-02-08 11:58:08 +01:00
Víctor Falcón 2fefb64710 Fix CI: Update bun.lock and fix linting errors 2025-12-01 15:16:06 +01:00
Víctor Falcón a4ed4e73c1 Add multi-column description mapping support
- Allow description field to map to multiple columns
- Update ColumnMapping type to accept string or string[] for description
- Join multiple description columns with newlines
- Add validation for multi-column descriptions
- Add test asset for multi-description CSV files
2025-12-01 10:30:11 +01:00
Víctor Falcón bec18b925b Add account balances 2025-11-15 20:27:18 +01:00
Víctor Falcón e13a19bdde Add some toast notifications 2025-11-11 11:39:28 +00:00
Víctor Falcón dd0f1380d4 Import CSV 2025-11-08 14:17:11 +00:00