Commit graph

26 commits

Author SHA1 Message Date
le king fu
ed3a4bb9a0 fix(import): name a source after its subfolder, read-only
The source name typed during configuration was saved, but the next import
looks the source and its imported files up by the subfolder name, so a
renamed source looked new: format guessed again, every file pre-checked,
and a second source created under the folder name.

The name field becomes a read-only SourceNameDisplay showing the subfolder
name with a hint, and executeImport writes selectedSource.folder_name on
create and update, whatever config.name holds, behind a selectedSource
guard. The import card sentence no longer asks to check the source name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:05:46 -04:00
le king fu
7d3a616312 feat(import): warn about the CSV files placed directly in the import folder
scan_import_folder now returns ImportFolderScan { sources, root_files }:
the CSV/TXT files at the root, which no source reads, are named in a
warning on the Import page (above the source list, or under the "no
source" empty state). One action sets sources and root files, RESET keeps
both. Names stay on screen, never in the Journal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:59:20 -04:00
le king fu
d024f5aeb9 fix(import): mark the import folder read once its mount read is over
The wizard starts with `importFolder` null for lack of reading, which the
Import page took for "no folder": the "No import folder" empty state
flashed before the stored folder came back (#384).

`importFolderLoaded` is set in the `finally` of the mount read, after the
first scan, so neither variant of the empty state flashes, and it is kept
by RESET, which rebuilds from the initial state while the mount read
never runs again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:34:37 -04:00
le king fu
4febed9eab feat(data): announce counter-moving writes on a data-change channel
Add src/services/dataEvents.ts (ADR 0022): notifyDataChanged() and
subscribeDataChanged(), without React, on the model of logService.subscribe.
A throwing subscriber is logged and does not stop the others.

Notify after every successful write of the tables the getting-started
counters read, plus the import folder: setImportFolder, createSource,
deleteSource, createImportedFile, updateFileStatus, both import deletions,
insertBatch, updateTransactionCategory, applyKeywordWithReassignment (after
COMMIT), the four budget writes and runRestore (after COMMIT, one point for
the three restore modes). executeImport notifies once more when it ends,
succeeded or not. A failed write notifies nothing; transactional writes
notify once withTransaction has returned. applyTemplate notifies once for
the whole template through a private writeBudgetEntry helper.

Each service suite proves the notification after a success and its absence
after a failure; executeImport is driven as a plain function with React's
hooks stubbed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:33:47 -04:00
le king fu
624eef7ba8 feat(onboarding): Import empty states, sample folder button and guide links (#356)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m59s
The import page without a source no longer shows a bare "no source found"
card. `ImportEmptyState` (built on the shared `EmptyState`) takes the place
of the empty `SourceList` in both situations: without an import folder it
offers "Choose a folder" as the single primary action; with a folder and no
bank sub-folder it offers "Create a sample folder" (primary, disabled while
in flight) and "Refresh" (secondary). Both variants, and the "?" help
popover through the new optional `linkTo` prop of `PageHelp`, link to the
bank CSV export guide at `/settings/users#import-export`. `SourceList`
stays an untouched pure list.

`useImportWizard` gains `createSampleFolders`: it invokes the Rust command
`create_sample_import_folders` with the folder and the webview's LOCAL date
(built from the local getters, not `toISOString()`), rescans the folder so
the two cards appear without another click, then raises a closable success
banner from the new `sampleCreated` state, which `reset` and a folder change
clear. A refusal from Rust (a sample folder already exists) lands in the
existing error banner. The log line carries the folder NAMES only, never
the absolute path, since the logs travel with a feedback report.

"Browse" on the folder card drops to the secondary style while no folder is
configured, so a single primary button is on screen. The reducer and its
initial state are exported as `wizardReducer` / `initialWizardState` and
unit-tested; `ImportEmptyState` is tested by direct call.

Resolves #356

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:10:25 -04:00
le king fu
c9872fc36b feat(import): recognise known bank layouts and report format drift
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m41s
Two failure modes, both anchored on the header row.

A KNOWN BANK IS NOW READ BY NAME. `bankSignatures.ts` declares the
documented export layout of Desjardins, RBC, Banque Nationale and
Tangerine as a set of normalized header labels plus a delimiter and a
preamble quirk. The table is evaluated BEFORE the generic dictionary and
an unknown file falls straight through to it, unchanged.

The signatures are not decorative. The generic dictionary matches
keywords as substrings, one role at a time, which reads two of these
four layouts wrong — both frozen as counterfactual test pairs, same
rows, header renamed:

  - Tangerine writes `Date,Transaction,Name,Memo,Amount`. `Transaction`
    is a description keyword, so every row of the file was labelled with
    its direction word instead of the merchant.
  - RBC writes its amount column `CAD$`, which no amount keyword
    matches, next to a nearly empty `Cheque Number`. Those two are
    sparse-complementary, so the shape scan paired them as debit/credit
    and the one row carrying a cheque number imported as -247.95 instead
    of -6.95.

A signature stays a set of PREFERENCES all the same: they are written
from documented layouts, without real statements, so every hint is
dropped the moment the data contradicts it. The single exception is the
amount mode, which outranks the sparse-complementary scan — nothing
inside an RBC file can tell that pair from a genuine one — and even that
is refused unless the declared columns are candidates the shape scan
proposed. Failing degrades to the generic path; it never breaks.

FORMAT DRIFT IS NOW REPORTED INSTEAD OF IMPORTED. Every successful
import records the normalized labels of its header row in
`import_sources.header_signature`, as a JSON array and not a hash: the
panel has to be able to name the columns that moved. On the next import,
a header that normalizes differently opens a `FormatDriftPanel` above
the preview — column by column, `Montant : 3 -> 4` — with the two
outcomes that exist: adopt the re-detected format, or keep the stored
one. A cosmetic rename (`Montant` -> `MONTANT ($)`) normalizes
identically and says nothing.

A source whose file has no header row keeps `header_signature` NULL and
drift detection is inoperative on it. Documented, not worked around:
a signature invented from the data would fire on every import.

THE REPAIR PATH IS NOW IN THE INTERFACE, in the drift panel and beside
the preview's sign flip. `findDuplicates` matches on date AND
description AND amount, so re-importing a file "now that it reads right"
does not correct the rows already written — it doubles them, and a
flipped sign produces mirror pairs that net to zero in every report. The
only safe path is deleting the faulty import from the history first.

The drift re-detection reuses `detectFormatForFile`, so there is still
exactly one detector; the static guard on its caller count moves from
two to three deliberately. Its score and bank badge are dropped straight
after: they measure the format the panel offers, not the one in use.

Resolves #330
2026-08-13 14:54:46 -04:00
le king fu
ce19efd476 feat(import): make the preview mandatory and show what the amounts mean
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m42s
A confidence score reports how many rows were READ, never what they say:
the `all-positive` fixture scores a perfect 100 % while every credit is
imported as an expense, because each of those rows is perfectly readable.
Nothing between that score and the database looked at the signs — the
preview was an optional modal of 20 rows with no totals, and the final
confirmation listed the delimiter and the date format but neither the
amount mode nor the sign convention.

The `file-preview` step had been declared in `ImportWizardStep` since the
beginning and no dispatch ever aimed at it. It is a real step now,
traversed at every import and gated by nothing — in particular not by the
detection score, which is sign-blind by construction.

- `useImportWizard`: `parseAndPreview` parses and stops at the preview,
  replacing `parsePreview` and the `parseAndCheckDuplicates` that jumped
  straight to the duplicates ("skips preview step"); `checkDuplicates`,
  dead code until now, is the preview's next button, so the rows the user
  validated are the rows that get checked.
- `summarizeParsedRows`: the recap, pure and tested — outflows and their
  total, inflows and theirs, rows in error. Totals stay SIGNED, since
  magnitudes would hide the one thing the recap exists to expose. Computed
  over the whole file, never over the twenty rows displayed.
- `flipSignFormat` + "Inverser les signes": the correction lands on the
  CONFIGURATION, so it is persisted with the source and the next file from
  that bank reads right on its own. In debit/credit mode it swaps the two
  column indices rather than toggling a convention `mapRow` ignores there,
  where a toggle would have been inert.
- `ImportConfirmation` states the amount mode, the sign convention (in the
  mode that applies it) and the column mapping, named by header.
- `FilePreviewModal` removed: it was the redundant surface, and editing
  the table alone would have mutated a still-live copy of it.

The `all-positive` KNOWN DEFECT marker is dropped rather than deleted. Its
three original expectations still hold — an unsigned file carries no
direction and detection cannot invent one — and two cases were added: the
recap tell (six outflows, zero inflows) and the honest limit, that
flipping this particular file only produces its mirror image.

1088 vitest (1054 before), tsc and vite build clean. No DB migration.

Resolves #329
2026-08-13 14:31:49 -04:00
le king fu
bf608b9d67 feat(import): score the detected format and run detection on its own
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m42s
Detection handed back a configuration it had never tested, and only ever
ran behind the magic-wand button. A source opened for the first time
therefore started on `defaultConfig` — `;`, `DD/MM/YYYY`, columns 0/1/2 —
plausible enough to import a whole file wrong rather than fail visibly.

`detectImportFormat` now REPLAYS what it just decided over every data row
of the file and returns the rate as a `DetectionScore`. The replay runs
`mapRow`, the same pure function `parseFilesInternal` runs at import time,
under the same column-level decimal arbitration: two mappers would be the
exact divergence this chantier removes — a score reading 100 % while the
import wrote different amounts. A test asserts the two agree row for row
on every fixture of the corpus.

The threshold is 90 %, and it only colours the banner. Below it the panel
warns and prints the detailed count ("132 of 150 rows read"); at or above
it the banner is neutral. Nothing is blocked, because a perfect score says
nothing about the SIGN of what was read — `all-positive` scores 100 %
while every credit imports as an expense — and the preview step (#329) is
the real net, traversed at every import.

Detection now also fires on its own, guarded on `!existing`: a source that
has never been configured. A source that HAS one is never re-detected, the
stored format wins. The condition is deliberately not `!restored` — a
stored format that fails to decode already reports its own error, and
detecting over it would replace that message with a silent guess.

The button and the automatic run share one `detectFormatForFile`, so the
button replays detection instead of running a second, drifting variant of
it. The score is cleared when its source changes, when the format is
edited by hand (compared through the codec, so a rename keeps it) and when
a template overwrites the format — a banner vouching for a configuration
nobody measured is the misinformation it exists to remove.

Finally, the sign-convention selector is hidden in debit/credit mode.
`mapRow` computes `credit - debit` on magnitudes there and never reads
`signConvention`, so the control changed nothing. Hidden, not reset: the
stored value is left untouched.

Tests: 1054 vitest (+20), build and cargo check green. No DB migration.
CHANGELOG and docs are centralized in link 10 of the stack per the plan.

Resolves #328
2026-08-13 14:15:23 -04:00
le king fu
6f64fc5c1a feat(import): detect transaction columns by header label
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
Detection reasoned on the shape of the data alone, so it could not tell a debit
column from a credit one: a file laid out `Date;Description;Credit;Debit` was
mapped by position and every sign of the import came out inverted — silently,
since the total is merely negated and no aggregate check notices.

A new `headerDictionary.ts` carries the FR/EN transaction dictionary (date,
description, amount, debit, credit, balance) plus the two matching helpers,
moved out of `csvAutoDetect.ts` whose holdings tables stay untouched: `montant`
is an exclusion token there and the primary amount keyword here, so the two
tables cannot be merged. Moving the generic helpers rather than exporting them
keeps the dependency one-way.

`csvAutoDetect` puts that layer in front of the shape heuristics. Labels resolve
the debit/credit order, the date, description and single-amount columns, and
give `detectHeader` a second signal for a header row carrying a bare number.
Every hint is a preference the data can veto — a labelled date column must still
parse, a labelled balance column is never excluded if it would leave nothing to
map — and a mute file (no header row, unknown labels) falls back to the shape
heuristics unchanged.

Files pairing unsigned amounts with an adjacent D/C indicator column are now
detected and REFUSED with a dedicated message, instead of being configured as
`positive_expense` and importing every deposit as an expense. Detection reports
that reason through `detectImportFormat`; `autoDetectConfig` keeps its previous
shape for the callers that only need the configuration.

Resolves #327
2026-08-13 13:58:02 -04:00
le king fu
7b6f063094 fix(import): read amounts with an anchored parser and a real debit/credit rule
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m44s
Three ways an imported amount could be silently wrong, all of them passing
validation, all of them fixed here.

The two-column rule was `isNaN(credit) ? -debit : credit`, so the credit always
won. Many banks write `0,00` in the unused column rather than leaving it empty,
and `isNaN(0)` is false -- every debit of such a file imported as 0,00 and the
expense simply vanished, with no error anywhere. The rule is `credit - debit` on
magnitudes now, which needs no special case for a `0,00` cell (zero is the
identity of the subtraction) and implements the documented convention even when
an export negates its debits. A row unreadable in BOTH columns is an error
instead of a free 0,00 transaction.

`parseFrenchAmount` ended on `parseFloat`, which returns the longest valid
PREFIX instead of rejecting. Measured before the fix: `"50,00-"` -> 5000,
`"1 234,56 CR"` -> 123456, `"100,00 CAD"` -> 10000. A factor-100 error, and it
passes `isNaN`, so those rows counted as VALID everywhere downstream -- which
would have defeated the signed preview (#329), the safety net of the whole
chantier. Validation is anchored over the whole normalized string now and any
residual character yields NaN.

NaN, not a rescued magnitude, for a trailing `CR`/`DB` or currency code. Two
reasons: `CR`/`DB` carry a DIRECTION, so returning a magnitude for both would
trade a loud failure for a silent SIGN error (the D/C-indicator shape is refused
upstream by design, #328); and `"100,00 CAD"` is structurally identical to
`"2025 Montant"`, so whitelisting a trailing word to rescue the first re-blinds
`detectHeader` on the second. Two accounting forms ARE legitimate and supported:
parentheses `(50,00)` and a trailing sign `50,00-`.

The `?? 0` fallbacks read column 0 -- usually the date -- when the mapping was
incomplete. An unmapped amount column is an explicit row error now, reported
ahead of any per-row problem since it is a format error affecting every row.

`1.234` is 1234 in a French column and 1.234 in an English one, and no rule
applied to that cell ALONE can tell. `detectDecimalSeparator` arbitrates from
the decisive siblings of the column and `parseFrenchAmount` takes the verdict as
an option. Detection deliberately stays out of it: it runs before a column is
known to be an amount column at all, so the verdict is applied where the value
actually becomes a transaction.

The rule itself moves out of the hook as a pure `mapRow(raw, format)` in
`importFormat.ts`. That is what lets the corpus tests run the REAL rule -- the
hand-written mirror in `csvAutoDetect.test.ts` and the static guard pinning five
`parseFilesInternal` expressions are both deleted -- and what stops the
detection score (#328) and the signed preview (#329) each re-implementing it.

Hardening is global: the parser is shared by 11 call sites, 8 in
`csvAutoDetect.ts` and 3 in the holdings CSV import (#245), where a price cell
`150,25 CAD` used to store 15025. It is refused now and `buildDetailedLines`
raises on the empty price. An unreadable QUANTITY was worse -- coerced to 0, so a
zero-value position saved in silence; the draft keeps the offending text instead
and the existing `snapshot_priced_quantity_required` fires.

Row errors become i18n keys (`import.rowErrors.*`) rather than the raw English
literals rendered straight into the preview table, since this adds a
user-visible string. The report table also carries raw exception messages, so
both render sites resolve through `isRowErrorKey` and never feed `t()` anything
that is not ours.

Test churn, per link 1's handoff (update the expectation, drop the marker, never
delete the test): the three `#325` KNOWN DEFECT blocks in `amountParser.test.ts`
flip, plus `unused-column-zero` in `csvAutoDetect.test.ts`. One block tagged
`#328` flips too -- `header-numeric-label`, whose own comment reads "#328 adds a
lexical signal to detectHeader, and #325 anchors the parser [...] either fix
closes this". The anchored parser landed first. The other `#328` blocks
(`debit-credit-reversed`, `absolute-indicator`) and the `#329` block are
verified unchanged. CHANGELOG and docs stay centralized in the last link of the
stack, as the plan specifies.

989 vitest (963 before), tsc + vite build clean, cargo check clean. No DB
migration.

Resolves #325
2026-08-13 13:37:06 -04:00
le king fu
7a604e0e0d fix(import): persist the import format and restore it faithfully
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m48s
The root bug of this chantier. `import_sources` carried no `amount_mode` and no
`sign_convention` until v17, so restoring a configured source re-inferred the
mode from `mapping.debitAmount !== undefined` and wrote
`signConvention: "negative_expense"` outright (useImportWizard.ts:321-323). A
credit-card statement configured for positive expenses came back on the default
convention at its second import and `parseFilesInternal` negated every amount:
expenses landed as income, with no error shown anywhere. The format is a read
value now, not a guessed one.

Two types and a codec, not one composed type. The four carriers are
structurally incompatible -- `ImportSource.has_header` is declared boolean,
`ImportConfigTemplate.has_header` is a number, `SourceConfig` is camelCase on a
parsed mapping -- so the guarantee cannot come from a shared shape. It comes
from `src/utils/importFormat.ts` being the single conversion point between
`ImportFormatRow` (persisted: snake_case, mapping as JSON, `has_header`
normalized to 0/1) and `ImportFormat` (domain), and from its completeness test.

That test is enforced on two levels, and both were mutation-checked:
`FORMAT_FIELD_PAIRS` is typed `Record<keyof ImportFormat, keyof
ImportFormatRow>`, so a field added to the format fails to BUILD until it is
listed; the test then compares each codec's real output keys against that table,
so a field listed but not wired fails the TEST. Dropping `sign_convention` from
`formatToRow` -- the shape of the original bug -- fails 14 tests.

`formatFromRow` validates rather than falls back. The v17 CHECK admits
`absolute_indicator` so the third amount mode ships without another migration,
but the app cannot map one: falling through to the `single` branch would read
the wrong column for every row, and anything other than `positive_expense`
would silently mean `negative_expense`. It raises an `ImportFormatError`
carrying an i18n key, and the wizard opens on a fresh configuration so
"reconfigure this source" stays an action the user can actually take.

Also here:

- The config write moves from `checkDuplicatesInternal` to `executeImport`, so
  an import abandoned at the duplicate step leaves no configuration behind. It
  is the only write point in the hook and a guard test holds that.
- Switching amount mode prunes the abandoned mode's columns, so the mode owns
  the mapping rather than the reverse. The column the `<select>` merely displays
  is deliberately not materialized -- #325 turns an unmapped amount column into
  an explicit row error, and writing a 0 here would make it unreachable. The
  mode and the pruned mapping land in ONE state update: the panel's handlers
  each spread the same `config` prop, so two calls would see the same stale
  value.
- `template_id` is recorded and restored as provenance only, never re-read as
  format. `selectedTemplateId` is no longer blanked on every source selection.
  An acceptance test rewrites a template end to end and asserts the linked
  source reads identically, plus a non-vacuity check that the template really
  changed.
- Both template writers go through the codec too, so a new format field cannot
  reach one table and miss the other.

`parseFilesInternal` is untouched: link 1's static guard on its five pinned
expressions still passes. 39 new tests (963 vitest total, was 924), build clean,
`cargo check` clean, no migration.

Resolves #324
2026-08-13 13:16:52 -04:00
Le-King-Fu
b190df4eae feat: show transaction splits on Adjustments page + fix CSV auto-detect
Some checks failed
Release / build (ubuntu-22.04) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
Add a "Répartitions" section below manual adjustments listing all
split transactions. Clicking a split opens the existing modal to
view, edit, or delete it.

Fix CSV auto-detect failing on files with preamble lines (e.g.
Mastercard CSVs with metadata header). Three fixes:
- Delimiter detection uses mode of column counts instead of first-line
- Detect and skip preamble rows before header/data detection
- Exclude date-like columns from amount candidates and prefer columns
  with decimal values when picking the amount column

Bumps version to 0.3.4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 01:41:08 +00:00
Le-King-Fu
172be36f1d fix: don't pre-select already-imported files when entering source config
selectSource had an empty dependency array, causing a stale closure
where importedFilesBySource was always the initial empty Map. This
made the new-file filter select all files including imported ones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 13:21:11 +00:00
Le-King-Fu
ac295d9048 feat: persist template selection and add update template button
The template dropdown now stays on the selected value after applying,
and a new update button lets users save config changes back to the
selected template.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 13:16:55 +00:00
Le-King-Fu
d6e6ce1136 fix: cross-file duplicate detection and per-file import tracking
Detect duplicate transactions across selected files (not just against DB),
create separate imported_files records per file for proper hash tracking,
fix progress display to show current file, and exclude already-imported
files from "Select all".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 10:42:28 +00:00
Le-King-Fu
ccdab1f06a feat: add import config templates, budget/category fixes (v0.2.5)
Add reusable import config templates so users can save and apply CSV
parsing configurations across different import sources. Includes
database table, service, hook integration, and template UI in the
source config panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 15:06:44 +00:00
Le-King-Fu
720f52bad6 feat: 12-month budget grid, import UX improvements, confirmation dialogs (v0.2.4)
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
- Budget page: replace single-month view with 12-month annual grid
  with inline editing, split-evenly button, and year navigation
- Import: pre-select only new files and sort them first, show
  "already imported" badge on previously imported files
- Add confirmation modals for category re-initialization and
  import deletion (single and bulk), replacing native confirm()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 12:59:11 +00:00
Le-King-Fu
29a1a15120 feat: add chart patterns, context menu, and import preview popup (v0.2.3)
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
- Add SVG fill patterns to differentiate chart categories beyond color
- Add right-click context menu on charts to hide categories or view transactions
- Add transaction detail modal showing all transactions for a category
- Change import preview from wizard step to popup modal
- Add direct "Check Duplicates" button skipping preview step
- Bump version to 0.2.3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:55:19 +00:00
Le-King-Fu
c73f466429 fix: persist has_header for imports, fix orphan categories, add re-initialize
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
- Import: persist `has_header` flag to DB (migration v3) so headerless
  CSVs like Desjardins don't lose their first data row on re-import.
- Categories: promote children to root on parent deletion instead of
  cascading deactivation, preventing invisible orphans.
- Categories: add re-initialize button to reset all categories and
  keywords to seed defaults.
- Bump version to 0.2.1 across tauri.conf.json, package.json, Cargo.toml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 11:54:33 +00:00
Le-King-Fu
474c7b947a fix: CSV import bugs and dashboard category filtering
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
- Fix column display for Desjardins-style quoted CSVs (apply preprocessQuotedCSV in header loading)
- Fix column mapping disappearing on back-navigation (generate synthetic headers when hasHeader is false)
- Fix auto-detect picking account number as amount (exclude constant-value columns, treat 0 as empty in debit/credit detection)
- Use category type instead of amount sign for dashboard pie chart and recent transactions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 00:09:10 +00:00
Le-King-Fu
bd6ff3deac feat: add CSV auto-detect configuration button
Analyzes imported CSV files to automatically detect delimiter, header,
date format, column mapping, amount mode, and sign convention. Excludes
running balance columns from amount mapping via consecutive-row math.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:09:35 +00:00
Le-King-Fu
41398f0f34 fix: CAD currency, real-time import progress, per-row duplicate control, orphaned sources cleanup, dashboard expenses-only
- Change all currency formatters from EUR/fr-FR to CAD/en-CA
- Add onProgress callback to insertBatch for real-time progress bar updates
- Replace binary skip/include duplicates with per-row checkboxes and type badges (DB/batch)
- Clean up orphaned import_sources when deleting imports with no remaining files
- Filter dashboard recent transactions to show expenses only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 12:36:12 +00:00
Le-King-Fu
9ff410e9f9 fix: searchable category combobox, import source upsert, and intra-batch duplicate detection
Replace native <select> with a type-to-search CategoryCombobox in both
the filter bar and inline table cells. Fix re-import UNIQUE constraint
error by using INSERT ... ON CONFLICT upsert in createSource(). Detect
duplicate rows within the same import batch using a Set key check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 12:02:23 +00:00
Le-King-Fu
3506c2c87e feat: add import history panel with delete and cross-source duplicate detection
Make duplicate file detection cross-source by removing sourceId from
existsByHash query. Add import history table below source list with
per-import and delete-all functionality.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 01:29:23 +00:00
Le-King-Fu
88219e657f fix: reload column headers on config change and add YYYYMMDD date format
Headers now refresh live when delimiter, encoding, skipLines, or hasHeader
changes. Added YYYYMMDD compact date format to parser and dropdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 04:08:33 +00:00
Le-King-Fu
49e0bd2c94 feat: implement CSV import wizard with folder-based source detection
Full import pipeline: Rust backend (6 Tauri commands for folder scanning,
file reading, encoding detection, hashing, folder picker), TypeScript
services (DB, import sources, transactions, auto-categorization, user
preferences), utility parsers (French amounts, multi-format dates),
12 React components forming a 7-step wizard (source list, config,
column mapping, preview, duplicate detection, import, report), and
i18n support (FR/EN, ~60 keys each).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 03:38:46 +00:00