Compare commits
No commits in common. "main" and "v0.6.4" have entirely different histories.
21 changed files with 677 additions and 694 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -45,9 +45,5 @@ imports/*.csv
|
|||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Auto-generated changelogs (synced from root by vite.config.ts)
|
||||
public/CHANGELOG.md
|
||||
public/CHANGELOG.fr.md
|
||||
|
||||
# Tauri generated
|
||||
src-tauri/gen/
|
||||
|
|
|
|||
|
|
@ -2,23 +2,6 @@
|
|||
|
||||
## [Non publié]
|
||||
|
||||
## [0.6.6]
|
||||
|
||||
### Modifié
|
||||
- Tableau de budget : la colonne année précédente affiche maintenant le réel (transactions) au lieu du budget planifié (#34)
|
||||
- Refactorisation de `buildPrevYearTotalMap` en inline et simplification des tests (#39)
|
||||
|
||||
### Corrigé
|
||||
- Fichiers changelog synchronisés automatiquement via plugin Vite, copies obsolètes dans public/ supprimées (#37)
|
||||
|
||||
## [0.6.5]
|
||||
|
||||
### Ajouté
|
||||
- Tableau de bord : menu déroulant de sélection du mois pour la section Budget vs Réel avec le dernier mois complété par défaut (#31)
|
||||
|
||||
### Modifié
|
||||
- Rapports et tableau de bord : police réduite dans le menu déroulant de mois pour un meilleur équilibre visuel (#31)
|
||||
|
||||
## [0.6.4]
|
||||
|
||||
### Ajouté
|
||||
|
|
|
|||
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -2,23 +2,6 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.6.6]
|
||||
|
||||
### Changed
|
||||
- Budget table: previous year column now shows actual transactions instead of planned budget (#34)
|
||||
- Refactored `buildPrevYearTotalMap` inline and simplified tests (#39)
|
||||
|
||||
### Fixed
|
||||
- Changelog files synced automatically via Vite plugin, removed stale public/ copies (#37)
|
||||
|
||||
## [0.6.5]
|
||||
|
||||
### Added
|
||||
- Dashboard: month dropdown selector for the Budget vs Actual section with last completed month as default (#31)
|
||||
|
||||
### Changed
|
||||
- Reports & Dashboard: reduced font size of month dropdown for better visual balance (#31)
|
||||
|
||||
## [0.6.4]
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ La documentation technique est centralisée dans `docs/` :
|
|||
- `CHANGELOG.fr.md` (français) — traduction
|
||||
- Catégories : Added/Ajouté, Changed/Modifié, Fixed/Corrigé, Removed/Supprimé
|
||||
- Format [Keep a Changelog](https://keepachangelog.com/). Le contenu est extrait automatiquement par le CI pour les release notes et affiché dans l'app selon la langue de l'utilisateur.
|
||||
- The `public/` copies are synced automatically: Vite copies them on `dev`/`build` start via `syncChangelogs()` in `vite.config.ts`. No manual sync needed.
|
||||
- Les deux fichiers sont copiés dans `public/` par le CI (`release.yml`). En dev, synchroniser manuellement : `cp CHANGELOG*.md public/`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
344
package-lock.json
generated
344
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "simpl_result_scaffold",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "simpl_result_scaffold",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.4",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
|
@ -35,8 +35,7 @@
|
|||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^6.4.1",
|
||||
"vitest": "^4.0.18"
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
|
|
@ -1764,17 +1763,6 @@
|
|||
"@babel/types": "^7.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/deep-eql": "*",
|
||||
"assertion-error": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||
|
|
@ -1829,13 +1817,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
|
||||
},
|
||||
"node_modules/@types/deep-eql": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
|
|
@ -1903,127 +1884,6 @@
|
|||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
|
||||
"integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.0.18",
|
||||
"@vitest/utils": "4.0.18",
|
||||
"chai": "^6.2.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
|
||||
"integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.0.18",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"vite": "^6.0.0 || ^7.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"msw": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
|
||||
"integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
|
||||
"integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.0.18",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
|
||||
"integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.18",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
|
||||
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
|
||||
"integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.18",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.19",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
||||
|
|
@ -2086,16 +1946,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
|
|
@ -2288,13 +2138,6 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.44.0",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz",
|
||||
|
|
@ -2350,31 +2193,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
|
||||
"integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
|
|
@ -2820,29 +2643,11 @@
|
|||
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
"https://opencollective.com/debug"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/papaparse": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
|
||||
"integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A=="
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
|
|
@ -3115,13 +2920,6 @@
|
|||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
|
|
@ -3131,20 +2929,6 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
|
|
@ -3169,23 +2953,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
||||
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
|
|
@ -3202,16 +2969,6 @@
|
|||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
|
||||
"integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
|
|
@ -3369,84 +3126,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
|
||||
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.18",
|
||||
"@vitest/mocker": "4.0.18",
|
||||
"@vitest/pretty-format": "4.0.18",
|
||||
"@vitest/runner": "4.0.18",
|
||||
"@vitest/snapshot": "4.0.18",
|
||||
"@vitest/spy": "4.0.18",
|
||||
"@vitest/utils": "4.0.18",
|
||||
"es-module-lexer": "^1.7.0",
|
||||
"expect-type": "^1.2.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"obug": "^2.1.1",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"std-env": "^3.10.0",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^1.0.2",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tinyrainbow": "^3.0.3",
|
||||
"vite": "^6.0.0 || ^7.0.0",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.0.18",
|
||||
"@vitest/browser-preview": "4.0.18",
|
||||
"@vitest/browser-webdriverio": "4.0.18",
|
||||
"@vitest/ui": "4.0.18",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-playwright": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-preview": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-webdriverio": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
|
|
@ -3455,23 +3134,6 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
{
|
||||
"name": "simpl_result_scaffold",
|
||||
"private": true,
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.4",
|
||||
"license": "GPL-3.0-only",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
|
@ -39,7 +37,6 @@
|
|||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^6.4.1",
|
||||
"vitest": "^4.0.18"
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
233
public/CHANGELOG.fr.md
Normal file
233
public/CHANGELOG.fr.md
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
# Journal des modifications
|
||||
|
||||
## [Non publié]
|
||||
|
||||
## [0.6.3]
|
||||
|
||||
### Ajouté
|
||||
- Tableau de bord : histogramme empilé des dépenses par catégorie et par mois (#15)
|
||||
- Tableau de bord : tableau budget vs réel du mois courant avec écart en $ et % (#15)
|
||||
- Tableau de budget et rapport Budget vs Réel : formatage des sous-totaux de section avec poids visuel croissant (#14)
|
||||
|
||||
### Modifié
|
||||
- Tableau de bord : période par défaut changée de « mois » à « année à ce jour » (#15)
|
||||
- Tableau de bord : section des transactions récentes supprimée (#15)
|
||||
- Tous les rapports tabulaires : les lignes de grand total utilisent maintenant une police plus grande (text-sm), gras et bordure supérieure plus épaisse pour une meilleure hiérarchie visuelle (#14)
|
||||
|
||||
### Corrigé
|
||||
- Rapport catégories dans le temps : toutes les catégories sont maintenant affichées (limite passée de 8 à 50) (#13)
|
||||
- Graphique par catégorie : les noms sur l'axe Y utilisent maintenant la couleur du texte principal au lieu du gris (#13)
|
||||
- Graphique catégories dans le temps : le texte de la légende utilise maintenant la couleur du texte principal au lieu d'hériter la couleur de la catégorie (#13)
|
||||
|
||||
## [0.6.2]
|
||||
|
||||
### Ajouté
|
||||
- Tableau de budget : sous-totaux par section (dépenses, revenus, transferts) affichés après chaque groupe (#11)
|
||||
- Rapport Budget vs Réel : sous-totaux par section avec réel, prévu, écart ($) et écart (%) par type (#11)
|
||||
|
||||
### Corrigé
|
||||
- Page catégories : le panneau de détail reste maintenant visible lors du défilement d'une longue liste de catégories (#12)
|
||||
|
||||
## [0.6.1]
|
||||
|
||||
### Ajouté
|
||||
- Page historique des versions : historique complet accessible depuis les Paramètres à tout moment
|
||||
- Changelog bilingue : les notes de version s'affichent dans la langue choisie par l'utilisateur (FR/EN)
|
||||
|
||||
### Corrigé
|
||||
- Visibilité des labels de graphiques : les montants sur les barres empilées utilisent maintenant du texte noir avec contour blanc pour un meilleur contraste (#8)
|
||||
- Tableau de budget : les cellules éditables affichent maintenant un fond au survol, un curseur pointeur et une info-bulle pour clarifier l'interaction (#9)
|
||||
|
||||
## [0.6.0]
|
||||
|
||||
### Ajouté
|
||||
- Rapports : bascule entre vue tableau et graphique pour les onglets Tendances, Par catégorie et Évolution
|
||||
- Rapports : option « Afficher les montants » pour afficher les valeurs directement sur les barres et courbes
|
||||
- Rapports : panneau de filtres avec cases à cocher par catégorie (recherche, tout sélectionner/désélectionner) et filtre par source
|
||||
- Rapports : le filtre source s'applique au niveau SQL pour des totaux filtrés précis
|
||||
- Rapports : en-têtes de tableau fixes sur tous les tableaux de rapports (Rapport dynamique, Budget vs Réel)
|
||||
- Rapports : survol interactif — barres non survolées estompées, info-bulle filtrée sur la catégorie survolée
|
||||
- Rapports : le survol de la légende met en évidence la catégorie sur tous les mois (graphique Évolution)
|
||||
|
||||
### Corrigé
|
||||
- Tableau des transactions : l'icône de commentaire devient orange (comme l'icône de ventilation) quand une note est présente (#7)
|
||||
|
||||
## [0.5.0]
|
||||
|
||||
### Ajouté
|
||||
- Gestion d'erreurs : intercepte les plantages React et affiche une page d'erreur au lieu d'un écran blanc
|
||||
- Délai de démarrage (10 s) sur la connexion à la base de données — affiche une page d'erreur au lieu d'un indicateur de chargement infini
|
||||
- Page d'erreur avec « Rafraîchir », « Vérifier les mises à jour » et liens de contact
|
||||
- Visionneuse de journaux dans les paramètres — capture la sortie console, filtrable par niveau, copiable, persiste entre les rafraîchissements
|
||||
- Licence GPL-3.0 — le projet est maintenant open source
|
||||
|
||||
### Modifié
|
||||
- Modale détail de rapport : colonnes triables — cliquez sur les en-têtes pour trier par date, description ou montant (#1)
|
||||
- Modale détail de rapport : bascule pour afficher/masquer la colonne des montants (#3)
|
||||
- Tableau de budget : les en-têtes de colonnes restent fixes lors du défilement vertical (#2)
|
||||
|
||||
### Corrigé
|
||||
- Mise à jour automatique sur Linux : le champ version de `latest.json` n'a plus le préfixe `v`, téléversement au registre de paquets plus robuste
|
||||
- Nouvelle tentative au démarrage : la connexion BD réessaie jusqu'à 3 fois avant d'afficher la page d'erreur (corrige l'échec au premier lancement sur Windows)
|
||||
- Somme de contrôle de migration : répare automatiquement la somme de contrôle obsolète de la migration 1 au démarrage
|
||||
|
||||
## [0.4.4]
|
||||
|
||||
### Corrigé
|
||||
- Le binaire Linux est maintenant compatible avec glibc 2.35+ (Ubuntu 22.04 / Pop!_OS) — le CI compile dans un conteneur Ubuntu 22.04
|
||||
|
||||
## [0.4.3]
|
||||
|
||||
### Corrigé
|
||||
- Le point de terminaison de mise à jour automatique utilise maintenant le registre de paquets Forgejo pour une URL stable
|
||||
- Les signatures Linux (.AppImage.sig) sont maintenant correctement collectées dans le CI
|
||||
- Toutes les signatures de plateforme (.deb.sig, .rpm.sig) sont maintenant incluses dans les assets de la release
|
||||
|
||||
## [0.4.2]
|
||||
|
||||
### Modifié
|
||||
- La mise à jour automatique pointe maintenant vers l'instance Forgejo auto-hébergée
|
||||
- Les builds Windows sont maintenant compilés en croisé via cargo-xwin
|
||||
|
||||
## [0.4.1]
|
||||
|
||||
### Corrigé
|
||||
- Application bloquée sur un indicateur de chargement infini après mise à jour depuis v0.3.x (somme de contrôle de migration incompatible sur seed_categories.sql)
|
||||
- Les erreurs de connexion BD sont maintenant journalisées dans la console au lieu d'échouer silencieusement
|
||||
|
||||
## [0.4.0]
|
||||
|
||||
### Ajouté
|
||||
- Catégories : support de 3 niveaux de hiérarchie (ex : Dépenses récurrentes → Assurances → Assurance-auto)
|
||||
- Rapport dynamique : nouveau champ « Catégorie (Niveau 3) »
|
||||
- Budget : sous-totaux intermédiaires et indentation 3 niveaux pour les catégories imbriquées
|
||||
- Catégories : gestion automatique de `is_inputable` à la création/suppression de sous-catégories
|
||||
- Catégories : la validation de profondeur empêche la création d'un 4e niveau
|
||||
- Données initiales : Assurances divisées en Assurance-auto, Assurance-habitation, Assurance-vie
|
||||
|
||||
### Corrigé
|
||||
- Auto-catégorisation : les mots-clés commençant/finissant par des caractères spéciaux (`[`, `]`, `(`, `)`, `-`, etc.) sont maintenant reconnus
|
||||
- Auto-catégorisation : pré-compilation des regex pour de meilleures performances en lot
|
||||
|
||||
## [0.3.11]
|
||||
|
||||
### Ajouté
|
||||
- Rapport dynamique : support de plusieurs dimensions en colonnes (clés composites)
|
||||
|
||||
### Corrigé
|
||||
- Rapport dynamique : n'est plus affecté par les filtres de date globaux — utilise uniquement ses propres filtres du panneau
|
||||
|
||||
## [0.3.10]
|
||||
|
||||
### Ajouté
|
||||
- Rapport dynamique : les champs peuvent maintenant être utilisés dans plusieurs zones simultanément (lignes + filtres, colonnes + filtres)
|
||||
- Rapport dynamique : clic-droit sur une valeur de filtre pour l'exclure (affiché barré en rouge)
|
||||
- Option de période « Cette année » dans les rapports et le tableau de bord (du 1er janvier à aujourd'hui)
|
||||
|
||||
## [0.3.9]
|
||||
|
||||
### Ajouté
|
||||
- Rapport dynamique (tableau croisé) : composez des rapports personnalisés en assignant des dimensions aux lignes, colonnes, filtres et mesures aux valeurs
|
||||
- Suppression de mots-clés depuis la vue « Tous les mots-clés »
|
||||
|
||||
## [0.3.8]
|
||||
|
||||
### Ajouté
|
||||
- Sélecteur de plage de dates personnalisée pour les rapports et le tableau de bord
|
||||
- Bascule pour positionner les sous-totaux au-dessus ou en dessous des lignes de détail
|
||||
- Affichage des notes de version du CHANGELOG dans les releases et le système de mise à jour
|
||||
|
||||
## [0.3.7]
|
||||
|
||||
### Corrigé
|
||||
- Suppression du bundle MSI pour éviter le conflit de chemin d'installation du système de mise à jour
|
||||
- Changement du mode d'installation Windows à basicUi
|
||||
- Amélioration de la visibilité de l'indicateur de ventilation et de la mise en page des ajustements
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Nouvelles fonctionnalités
|
||||
- **Support Linux** : ajout des builds Linux (`.deb`, `.rpm`, `.AppImage`) au workflow de release
|
||||
- **Ventilations sur la page Ajustements** : visualisez les ajustements de ventilation des transactions dans une section dédiée
|
||||
|
||||
### Corrigé
|
||||
- Correction de cas limites de détection automatique CSV
|
||||
- Suppression de l'accent dans productName pour la compatibilité `.deb` Linux
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Corrigé
|
||||
- Toujours afficher le sélecteur de profil dans la barre latérale (#2)
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Nouvelles fonctionnalités
|
||||
- **Profils multiples** : créez plusieurs profils avec des bases de données séparées, des noms et couleurs personnalisés
|
||||
- **Protection par NIP** : protégez les profils avec un NIP numérique optionnel
|
||||
- **Sélecteur de profil** : changement rapide de profil depuis la barre latérale
|
||||
- **Glisser-déposer les catégories** : réordonnez les catégories ou changez le parent par glisser-déposer dans l'arborescence
|
||||
- **Ventilation des transactions** : ventilez une transaction sur plusieurs catégories avec des montants ajustables
|
||||
|
||||
## 0.2.10
|
||||
|
||||
### Nouvelles fonctionnalités
|
||||
- **Sélection rapide de période** : boutons de filtre rapide (Ce mois, Mois dernier, etc.) sur la page Transactions
|
||||
- **Rapport Budget vs Réel** : tableau comparatif mensuel et cumulatif annuel dans les Rapports
|
||||
- **Sous-totaux de catégories parentes** : la page Budget affiche les sous-totaux agrégés pour les catégories parentes
|
||||
- **Guide utilisateur** : documentation complète accessible depuis les Paramètres, imprimable en PDF
|
||||
|
||||
### Améliorations
|
||||
- Persistance de la sélection de modèle et ajout du bouton Mettre à jour le modèle
|
||||
- Ne plus pré-sélectionner les fichiers déjà importés à l'entrée de la configuration source
|
||||
- Rendre les imports de données de paramètres visibles dans l'historique d'import
|
||||
- Remplacer les boutons de suppression par modèle par un seul bouton sur la sélection
|
||||
- Remplacer l'icône de rafraîchissement par une icône de sauvegarde sur le bouton de mise à jour du modèle
|
||||
- Ajout de la convention de signe à la page budget
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Corrigé
|
||||
- Permettre les fichiers à contenu identique avec des noms différents (#1)
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Nouvelles fonctionnalités
|
||||
- **Export/import de données** : exportez et importez vos données (transactions, catégories, ou les deux) avec chiffrement AES-256-GCM optionnel (#3)
|
||||
|
||||
### Corrigé
|
||||
- Détection de doublons inter-fichiers et suivi d'import par fichier
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Nouvelles fonctionnalités
|
||||
- **Modèles de configuration d'import** : sauvegardez et chargez les configurations de source d'import comme modèles réutilisables
|
||||
- **Grille de budget 12 mois** : vue budgétaire annuelle complète avec cellules mensuelles et totaux annuels
|
||||
|
||||
### Corrigé
|
||||
- Corrections du budget et des catégories
|
||||
- Problème de somme de contrôle de migration (schema.sql ne doit pas être modifié après la release initiale)
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Nouvelles fonctionnalités
|
||||
- **Motifs de graphiques** : ajout de motifs de remplissage SVG (lignes diagonales, points, hachures, etc.) pour différencier les catégories dans les graphiques au-delà de la couleur
|
||||
- **Menu contextuel des graphiques** : clic-droit sur une catégorie dans un graphique pour la masquer ou voir ses transactions dans une fenêtre de détail
|
||||
- **Catégories masquées** : les catégories masquées apparaissent comme des puces au-dessus des graphiques avec un bouton « Tout afficher »
|
||||
- **Modale de détail des transactions** : visualisez toutes les transactions composant le total d'une catégorie directement depuis n'importe quel graphique
|
||||
- **Aperçu d'import en popup** : l'aperçu des données est maintenant une modale popup au lieu d'une étape séparée de l'assistant
|
||||
- **Vérification directe des doublons** : nouveau bouton « Vérifier les doublons » sur la page de configuration d'import
|
||||
|
||||
### Améliorations
|
||||
- Flux de l'assistant d'import simplifié : configuration source → vérification des doublons (l'aperçu est optionnel via popup)
|
||||
- Le bouton retour de la vérification des doublons retourne maintenant à la configuration source
|
||||
|
||||
## 0.2.2
|
||||
|
||||
- Mise à jour de version
|
||||
|
||||
## 0.2.1
|
||||
|
||||
- Ajout de la vue « Tous les mots-clés » sur la page Catégories
|
||||
- Ajout du mode sombre avec palette de gris chauds
|
||||
- Correction des catégories orphelines, persistance de has_header pour les imports, ajout de la réinitialisation
|
||||
- Ajout des pages Budget et Ajustements
|
||||
234
public/CHANGELOG.md
Normal file
234
public/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.6.3]
|
||||
|
||||
### Added
|
||||
- Dashboard: expenses over time stacked bar chart by category and month (#15)
|
||||
- Dashboard: budget vs actual table for current month with variance in $ and % (#15)
|
||||
- Budget table and Budget vs Actual report: section subtotal formatting with increasing visual weight (#14)
|
||||
|
||||
### Changed
|
||||
- Dashboard: default period changed from "month" to "year to date" (#15)
|
||||
- Dashboard: removed recent transactions section (#15)
|
||||
- All report tables: grand total rows now use larger font (text-sm), bold weight, and thicker top border for better visual hierarchy (#14)
|
||||
|
||||
### Fixed
|
||||
- Category over time report: all categories now displayed (limit increased from 8 to 50) (#13)
|
||||
- Category bar chart: Y-axis labels now use foreground color instead of muted gray (#13)
|
||||
- Category over time chart: legend text now uses foreground color instead of inheriting category color (#13)
|
||||
|
||||
## [0.6.2]
|
||||
|
||||
### Added
|
||||
- Budget table: section subtotals for expenses, income, and transfers displayed after each group (#11)
|
||||
- Budget vs Actual report: section subtotals with actual, planned, variation ($) and variation (%) per type (#11)
|
||||
|
||||
### Fixed
|
||||
- Category page: detail panel now stays visible when scrolling through a long category list (#12)
|
||||
|
||||
## [0.6.1]
|
||||
|
||||
### Added
|
||||
- Changelog page: full version history accessible from Settings at any time
|
||||
- Bilingual changelog: release notes displayed in the user's selected language (EN/FR)
|
||||
|
||||
### Fixed
|
||||
- Chart label visibility: amount labels on stacked bar charts now use black text with white outline for better contrast (#8)
|
||||
- Budget table: editable cells now show hover background, pointer cursor, and tooltip hint for clearer affordance (#9)
|
||||
|
||||
## [0.6.0]
|
||||
|
||||
### Added
|
||||
- Reports: toggle between table and chart view for Trends, By Category, and Over Time tabs
|
||||
- Reports: "Show amounts" toggle displays values directly on chart bars and area curves
|
||||
- Reports: filter panel with category checkboxes (search, select all/none) and source dropdown
|
||||
- Reports: source filter applies at SQL level for accurate filtered totals
|
||||
- Reports: sticky table headers on all report tables (Dynamic Report, Budget vs Actual)
|
||||
- Reports: interactive hover — dimmed non-hovered bars, tooltip filtered to hovered category
|
||||
- Reports: legend hover highlights category across all months (Over Time chart)
|
||||
|
||||
### Fixed
|
||||
- Transaction table: comment icon now turns orange (like split icon) when a note is present (#7)
|
||||
|
||||
## [0.5.0]
|
||||
|
||||
### Added
|
||||
- Error boundary catches React crashes and displays an error page instead of a white screen
|
||||
- Startup timeout (10s) on database connection — shows error page instead of infinite spinner
|
||||
- Error page with "Refresh", "Check for updates", and contact/issue links
|
||||
- Log viewer in settings page — captures console output, filterable by level, copyable, persists across refresh
|
||||
- GPL-3.0 license — project is now open source
|
||||
|
||||
### Changed
|
||||
- Report detail modal: sortable columns — click headers to sort by date, description, or amount (#1)
|
||||
- Report detail modal: toggle to show/hide amounts column (#3)
|
||||
- Budget table: column headers stay fixed when scrolling vertically (#2)
|
||||
|
||||
### Fixed
|
||||
- Auto-updater on Linux: `latest.json` version field no longer has `v` prefix, package registry upload is more robust
|
||||
- Startup retry: DB connection retries up to 3 times before showing error page (fixes first-launch failure on Windows)
|
||||
- Migration checksum mismatch: automatically repairs stale migration 1 checksum on startup
|
||||
|
||||
## [0.4.4]
|
||||
|
||||
### Fixed
|
||||
- Linux binary now compatible with glibc 2.35+ (Ubuntu 22.04 / Pop!_OS) — CI builds in Ubuntu 22.04 container
|
||||
|
||||
## [0.4.3]
|
||||
|
||||
### Fixed
|
||||
- Auto-updater endpoint now uses Forgejo package registry for stable URL
|
||||
- Linux updater signatures (.AppImage.sig) now correctly collected in CI
|
||||
- All platform signatures (.deb.sig, .rpm.sig) now included in release assets
|
||||
|
||||
## [0.4.2]
|
||||
|
||||
### Changed
|
||||
- Auto-updater now points to self-hosted Forgejo instance
|
||||
- Windows builds now cross-compiled via cargo-xwin
|
||||
|
||||
## [0.4.1]
|
||||
|
||||
### Fixed
|
||||
- App stuck on infinite spinner after updating from v0.3.x (migration checksum mismatch on seed_categories.sql)
|
||||
- DB connection errors now logged to console instead of silently failing
|
||||
|
||||
## [0.4.0]
|
||||
|
||||
### Added
|
||||
- Categories: support for 3 levels of hierarchy (e.g., Dépenses récurrentes → Assurances → Assurance-auto)
|
||||
- Dynamic Report: new "Category (Level 3)" pivot field
|
||||
- Budget: intermediate subtotals and 3-level indentation for nested categories
|
||||
- Categories: automatic `is_inputable` management when creating/deleting subcategories
|
||||
- Categories: depth validation prevents creating a 4th level
|
||||
- Seed data: Assurances split into Assurance-auto, Assurance-habitation, Assurance-vie
|
||||
|
||||
### Fixed
|
||||
- Auto-categorization: keywords starting/ending with special characters (`[`, `]`, `(`, `)`, `-`, etc.) now match correctly
|
||||
- Auto-categorization: pre-compile regex patterns for better batch performance
|
||||
|
||||
## [0.3.11]
|
||||
|
||||
### Added
|
||||
- Dynamic Report: support multiple column dimensions (composite column keys)
|
||||
|
||||
### Fixed
|
||||
- Dynamic Report: no longer affected by global page date filters — uses only its own panel filters
|
||||
|
||||
## [0.3.10]
|
||||
|
||||
### Added
|
||||
- Dynamic Report: fields can now be used in multiple zones simultaneously (rows + filters, columns + filters)
|
||||
- Dynamic Report: right-click on a filter value to exclude it (shown with strikethrough in red)
|
||||
- "This year" period option in reports and dashboard (Jan 1 to today)
|
||||
|
||||
## [0.3.9]
|
||||
|
||||
### Added
|
||||
- Dynamic Report (pivot table): compose custom reports by assigning dimensions to rows, columns, filters and measures to values
|
||||
- Delete keywords from the "All Keywords" view
|
||||
|
||||
## [0.3.8]
|
||||
|
||||
### Added
|
||||
- Custom date range picker for reports and dashboard
|
||||
- Toggle to position subtotals above or below detail rows
|
||||
- Display release notes from CHANGELOG in GitHub releases and in-app updater
|
||||
|
||||
## [0.3.7]
|
||||
|
||||
### Fixes
|
||||
- Remove MSI bundle to prevent updater install path conflict
|
||||
- Change Windows updater installMode to basicUi
|
||||
- Improve split indicator visibility and adjustments layout
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### New Features
|
||||
- **Linux support**: Add Linux build (`.deb`, `.rpm`, `.AppImage`) to release workflow
|
||||
- **Transaction splits on Adjustments page**: View transaction split adjustments in a dedicated section on the Adjustments page
|
||||
|
||||
### Fixes
|
||||
- Fix CSV auto-detect edge cases
|
||||
- Remove accent from productName for Linux `.deb` compatibility
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Fixes
|
||||
- Always show profile switcher in sidebar (#2)
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### New Features
|
||||
- **Multiple profiles**: Create multiple profiles with separate databases, custom names, and colors
|
||||
- **PIN protection**: Protect profiles with an optional numeric PIN
|
||||
- **Profile switcher**: Quick profile switching from the sidebar
|
||||
- **Drag-and-drop categories**: Reorder categories or change parent via drag-and-drop in the category tree
|
||||
- **Transaction splits**: Split a transaction across multiple categories with adjustable amounts
|
||||
|
||||
## 0.2.10
|
||||
|
||||
### New Features
|
||||
- **Period quick-select**: Add quick period filter buttons (This month, Last month, etc.) on the Transactions page
|
||||
- **Budget vs Actual report**: Monthly and year-to-date comparison table in Reports
|
||||
- **Parent category subtotals**: Budget page shows aggregated subtotals for parent categories
|
||||
- **User guide**: Complete documentation page accessible from Settings, printable to PDF
|
||||
|
||||
### Improvements
|
||||
- Persist template selection and add Update template button
|
||||
- Don't pre-select already-imported files when entering source config
|
||||
- Make settings data imports visible in Import History
|
||||
- Replace per-template delete buttons with single delete on selection
|
||||
- Replace refresh icon with save icon on update template button
|
||||
- Add sign convention to budget page
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Fixes
|
||||
- Allow duplicate-content files with different names (#1)
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### New Features
|
||||
- **Data export/import**: Export and import your data (transactions, categories, or both) with optional AES-256-GCM encryption (#3)
|
||||
|
||||
### Fixes
|
||||
- Cross-file duplicate detection and per-file import tracking
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### New Features
|
||||
- **Import config templates**: Save and load import source configurations as reusable templates
|
||||
- **12-month budget grid**: Full year budget view with monthly cells and annual totals
|
||||
|
||||
### Fixes
|
||||
- Budget and category fixes
|
||||
- Migration checksum issue (schema.sql must not be modified after initial release)
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### New Features
|
||||
- **Chart patterns**: Added SVG fill patterns (diagonal lines, dots, crosshatch, etc.) to differentiate categories in bar charts, pie chart, and stacked bar charts beyond just color
|
||||
- **Chart context menu**: Right-click any category in a chart to hide it or view its transactions in a detail popup
|
||||
- **Hidden categories**: Hidden categories appear as dismissible chips above charts with a "Show all" button to restore them
|
||||
- **Transaction detail modal**: View all transactions composing a category's total directly from any chart
|
||||
- **Import preview popup**: The data preview is now a popup modal instead of a separate wizard step, allowing quick inspection without leaving the configuration page
|
||||
- **Direct duplicate check**: New "Check Duplicates" button on the import configuration page skips directly to duplicate validation without requiring a preview first
|
||||
|
||||
### Improvements
|
||||
- Import wizard flow simplified: source-config → duplicate-check (preview is optional via popup)
|
||||
- Duplicate-check back button now returns to source configuration instead of the removed preview step
|
||||
- Added `categoryIds` map to `CategoryOverTimeData` for proper category resolution in the over-time chart
|
||||
|
||||
## 0.2.2
|
||||
|
||||
- Bump version
|
||||
|
||||
## 0.2.1
|
||||
|
||||
- Add "All Keywords" view on Categories page
|
||||
- Add dark mode with warm gray palette
|
||||
- Fix orphan categories, persist has_header for imports, add re-initialize
|
||||
- Add Budget and Adjustments pages
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "simpl-result"
|
||||
version = "0.6.6"
|
||||
version = "0.6.4"
|
||||
description = "Personal finance management app"
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["you"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Simpl Resultat",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.4",
|
||||
"identifier": "com.simpl.resultat",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
|
|||
monthTotals[m] += row.months[m] * sign;
|
||||
}
|
||||
annualTotal += row.annual * sign;
|
||||
prevYearTotal += row.previousYearTotal; // actuals are already signed in the DB
|
||||
prevYearTotal += row.previousYearTotal * sign;
|
||||
}
|
||||
|
||||
const totalCols = 15; // category + prev year + annual + 12 months
|
||||
|
|
@ -197,7 +197,7 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
|
|||
</div>
|
||||
</td>
|
||||
<td className={`py-2 px-2 text-right text-xs ${isIntermediateParent ? "font-medium" : "font-semibold"} text-[var(--muted-foreground)]`}>
|
||||
{formatSigned(row.previousYearTotal)}
|
||||
{formatSigned(row.previousYearTotal * sign)}
|
||||
</td>
|
||||
<td className={`py-2 px-2 text-right text-xs ${isIntermediateParent ? "font-medium" : "font-semibold"}`}>
|
||||
{formatSigned(row.annual * sign)}
|
||||
|
|
@ -230,7 +230,7 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
|
|||
{/* Previous year total — read-only */}
|
||||
<td className="py-2 px-2 text-right text-[var(--muted-foreground)]">
|
||||
<span className="text-xs px-1 py-0.5">
|
||||
{formatSigned(row.previousYearTotal)}
|
||||
{formatSigned(row.previousYearTotal * sign)}
|
||||
</span>
|
||||
</td>
|
||||
{/* Annual total — editable */}
|
||||
|
|
@ -340,7 +340,7 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu
|
|||
sectionMonthTotals[m] += row.months[m] * sign;
|
||||
}
|
||||
sectionAnnualTotal += row.annual * sign;
|
||||
sectionPrevYearTotal += row.previousYearTotal; // actuals are already signed in the DB
|
||||
sectionPrevYearTotal += row.previousYearTotal * sign;
|
||||
}
|
||||
return (
|
||||
<Fragment key={type}>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { BudgetYearRow, BudgetTemplate } from "../shared/types";
|
|||
import {
|
||||
getAllActiveCategories,
|
||||
getBudgetEntriesForYear,
|
||||
getActualTotalsForYear,
|
||||
upsertBudgetEntry,
|
||||
upsertBudgetEntriesForYear,
|
||||
getAllTemplates,
|
||||
|
|
@ -73,10 +72,10 @@ export function useBudget() {
|
|||
dispatch({ type: "SET_ERROR", payload: null });
|
||||
|
||||
try {
|
||||
const [allCategories, entries, prevYearActuals, templates] = await Promise.all([
|
||||
const [allCategories, entries, prevYearEntries, templates] = await Promise.all([
|
||||
getAllActiveCategories(),
|
||||
getBudgetEntriesForYear(year),
|
||||
getActualTotalsForYear(year - 1),
|
||||
getBudgetEntriesForYear(year - 1),
|
||||
getAllTemplates(),
|
||||
]);
|
||||
|
||||
|
|
@ -89,11 +88,10 @@ export function useBudget() {
|
|||
entryMap.get(e.category_id)!.set(e.month, e.amount);
|
||||
}
|
||||
|
||||
// Build a map for previous year actuals: categoryId -> annual actual total
|
||||
// Amounts are already signed (expenses negative, income positive) — stored as-is.
|
||||
// Build a map for previous year totals: categoryId -> annual total
|
||||
const prevYearTotalMap = new Map<number, number>();
|
||||
for (const a of prevYearActuals) {
|
||||
if (a.category_id != null) prevYearTotalMap.set(a.category_id, a.actual);
|
||||
for (const e of prevYearEntries) {
|
||||
prevYearTotalMap.set(e.category_id, (prevYearTotalMap.get(e.category_id) ?? 0) + e.amount);
|
||||
}
|
||||
|
||||
// Helper: build months array from entryMap
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import {
|
|||
} from "../services/dashboardService";
|
||||
import { getCategoryOverTime } from "../services/reportService";
|
||||
import { getBudgetVsActualData } from "../services/budgetService";
|
||||
import { computeDateRange } from "../utils/dateRange";
|
||||
|
||||
interface DashboardState {
|
||||
summary: DashboardSummary;
|
||||
|
|
@ -20,8 +19,6 @@ interface DashboardState {
|
|||
categoryOverTime: CategoryOverTimeData;
|
||||
budgetVsActual: BudgetVsActualRow[];
|
||||
period: DashboardPeriod;
|
||||
budgetYear: number;
|
||||
budgetMonth: number;
|
||||
customDateFrom: string;
|
||||
customDateTo: string;
|
||||
isLoading: boolean;
|
||||
|
|
@ -41,7 +38,6 @@ type DashboardAction =
|
|||
};
|
||||
}
|
||||
| { type: "SET_PERIOD"; payload: DashboardPeriod }
|
||||
| { type: "SET_BUDGET_MONTH"; payload: { year: number; month: number } }
|
||||
| { type: "SET_CUSTOM_DATES"; payload: { dateFrom: string; dateTo: string } };
|
||||
|
||||
const now = new Date();
|
||||
|
|
@ -54,8 +50,6 @@ const initialState: DashboardState = {
|
|||
categoryOverTime: { categories: [], data: [], colors: {}, categoryIds: {} },
|
||||
budgetVsActual: [],
|
||||
period: "year",
|
||||
budgetYear: now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear(),
|
||||
budgetMonth: now.getMonth() === 0 ? 12 : now.getMonth(),
|
||||
customDateFrom: yearStartStr,
|
||||
customDateTo: todayStr,
|
||||
isLoading: false,
|
||||
|
|
@ -79,8 +73,6 @@ function reducer(state: DashboardState, action: DashboardAction): DashboardState
|
|||
};
|
||||
case "SET_PERIOD":
|
||||
return { ...state, period: action.payload };
|
||||
case "SET_BUDGET_MONTH":
|
||||
return { ...state, budgetYear: action.payload.year, budgetMonth: action.payload.month };
|
||||
case "SET_CUSTOM_DATES":
|
||||
return { ...state, period: "custom" as DashboardPeriod, customDateFrom: action.payload.dateFrom, customDateTo: action.payload.dateTo };
|
||||
default:
|
||||
|
|
@ -88,28 +80,68 @@ function reducer(state: DashboardState, action: DashboardAction): DashboardState
|
|||
}
|
||||
}
|
||||
|
||||
function computeDateRange(
|
||||
period: DashboardPeriod,
|
||||
customDateFrom?: string,
|
||||
customDateTo?: string,
|
||||
): { dateFrom?: string; dateTo?: string } {
|
||||
if (period === "all") return {};
|
||||
if (period === "custom" && customDateFrom && customDateTo) {
|
||||
return { dateFrom: customDateFrom, dateTo: customDateTo };
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth();
|
||||
const day = now.getDate();
|
||||
|
||||
const dateTo = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||
|
||||
let from: Date;
|
||||
switch (period) {
|
||||
case "month":
|
||||
from = new Date(year, month, 1);
|
||||
break;
|
||||
case "3months":
|
||||
from = new Date(year, month - 2, 1);
|
||||
break;
|
||||
case "6months":
|
||||
from = new Date(year, month - 5, 1);
|
||||
break;
|
||||
case "year":
|
||||
from = new Date(year, 0, 1);
|
||||
break;
|
||||
case "12months":
|
||||
from = new Date(year, month - 11, 1);
|
||||
break;
|
||||
default:
|
||||
from = new Date(year, month, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
const dateFrom = `${from.getFullYear()}-${String(from.getMonth() + 1).padStart(2, "0")}-${String(from.getDate()).padStart(2, "0")}`;
|
||||
|
||||
return { dateFrom, dateTo };
|
||||
}
|
||||
|
||||
export function useDashboard() {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const fetchIdRef = useRef(0);
|
||||
|
||||
const fetchData = useCallback(async (
|
||||
period: DashboardPeriod,
|
||||
customFrom: string | undefined,
|
||||
customTo: string | undefined,
|
||||
bYear: number,
|
||||
bMonth: number,
|
||||
) => {
|
||||
const fetchData = useCallback(async (period: DashboardPeriod, customFrom?: string, customTo?: string) => {
|
||||
const fetchId = ++fetchIdRef.current;
|
||||
dispatch({ type: "SET_LOADING", payload: true });
|
||||
dispatch({ type: "SET_ERROR", payload: null });
|
||||
|
||||
try {
|
||||
const { dateFrom, dateTo } = computeDateRange(period, customFrom, customTo);
|
||||
const currentMonth = new Date().getMonth() + 1;
|
||||
const currentYear = new Date().getFullYear();
|
||||
const [summary, categoryBreakdown, categoryOverTime, budgetVsActual] = await Promise.all([
|
||||
getDashboardSummary(dateFrom, dateTo),
|
||||
getExpensesByCategory(dateFrom, dateTo),
|
||||
getCategoryOverTime(dateFrom, dateTo),
|
||||
getBudgetVsActualData(bYear, bMonth),
|
||||
getBudgetVsActualData(currentYear, currentMonth),
|
||||
]);
|
||||
|
||||
if (fetchId !== fetchIdRef.current) return;
|
||||
|
|
@ -124,8 +156,8 @@ export function useDashboard() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(state.period, state.customDateFrom, state.customDateTo, state.budgetYear, state.budgetMonth);
|
||||
}, [state.period, state.customDateFrom, state.customDateTo, state.budgetYear, state.budgetMonth, fetchData]);
|
||||
fetchData(state.period, state.customDateFrom, state.customDateTo);
|
||||
}, [state.period, state.customDateFrom, state.customDateTo, fetchData]);
|
||||
|
||||
const setPeriod = useCallback((period: DashboardPeriod) => {
|
||||
dispatch({ type: "SET_PERIOD", payload: period });
|
||||
|
|
@ -135,9 +167,5 @@ export function useDashboard() {
|
|||
dispatch({ type: "SET_CUSTOM_DATES", payload: { dateFrom, dateTo } });
|
||||
}, []);
|
||||
|
||||
const setBudgetMonth = useCallback((year: number, month: number) => {
|
||||
dispatch({ type: "SET_BUDGET_MONTH", payload: { year, month } });
|
||||
}, []);
|
||||
|
||||
return { state, setPeriod, setCustomDates, setBudgetMonth };
|
||||
return { state, setPeriod, setCustomDates };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import type {
|
|||
import { getMonthlyTrends, getCategoryOverTime, getDynamicReportData } from "../services/reportService";
|
||||
import { getExpensesByCategory } from "../services/dashboardService";
|
||||
import { getBudgetVsActualData } from "../services/budgetService";
|
||||
import { computeDateRange } from "../utils/dateRange";
|
||||
|
||||
interface ReportsState {
|
||||
tab: ReportTab;
|
||||
|
|
@ -102,6 +101,50 @@ function reducer(state: ReportsState, action: ReportsAction): ReportsState {
|
|||
}
|
||||
}
|
||||
|
||||
function computeDateRange(
|
||||
period: DashboardPeriod,
|
||||
customDateFrom?: string,
|
||||
customDateTo?: string,
|
||||
): { dateFrom?: string; dateTo?: string } {
|
||||
if (period === "all") return {};
|
||||
if (period === "custom" && customDateFrom && customDateTo) {
|
||||
return { dateFrom: customDateFrom, dateTo: customDateTo };
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth();
|
||||
const day = now.getDate();
|
||||
|
||||
const dateTo = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||
|
||||
let from: Date;
|
||||
switch (period) {
|
||||
case "month":
|
||||
from = new Date(year, month, 1);
|
||||
break;
|
||||
case "3months":
|
||||
from = new Date(year, month - 2, 1);
|
||||
break;
|
||||
case "6months":
|
||||
from = new Date(year, month - 5, 1);
|
||||
break;
|
||||
case "year":
|
||||
from = new Date(year, 0, 1);
|
||||
break;
|
||||
case "12months":
|
||||
from = new Date(year, month - 11, 1);
|
||||
break;
|
||||
default:
|
||||
from = new Date(year, month, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
const dateFrom = `${from.getFullYear()}-${String(from.getMonth() + 1).padStart(2, "0")}-${String(from.getDate()).padStart(2, "0")}`;
|
||||
|
||||
return { dateFrom, dateTo };
|
||||
}
|
||||
|
||||
export function useReports() {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const fetchIdRef = useRef(0);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback, useMemo } from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Wallet, TrendingUp, TrendingDown } from "lucide-react";
|
||||
import { useDashboard } from "../hooks/useDashboard";
|
||||
|
|
@ -8,14 +8,40 @@ import CategoryPieChart from "../components/dashboard/CategoryPieChart";
|
|||
import CategoryOverTimeChart from "../components/reports/CategoryOverTimeChart";
|
||||
import BudgetVsActualTable from "../components/reports/BudgetVsActualTable";
|
||||
import TransactionDetailModal from "../components/shared/TransactionDetailModal";
|
||||
import type { CategoryBreakdownItem } from "../shared/types";
|
||||
import { computeDateRange, buildMonthOptions } from "../utils/dateRange";
|
||||
import type { CategoryBreakdownItem, DashboardPeriod } from "../shared/types";
|
||||
|
||||
const fmt = new Intl.NumberFormat("en-CA", { style: "currency", currency: "CAD" });
|
||||
|
||||
function computeDateRange(
|
||||
period: DashboardPeriod,
|
||||
customDateFrom?: string,
|
||||
customDateTo?: string,
|
||||
): { dateFrom?: string; dateTo?: string } {
|
||||
if (period === "all") return {};
|
||||
if (period === "custom" && customDateFrom && customDateTo) {
|
||||
return { dateFrom: customDateFrom, dateTo: customDateTo };
|
||||
}
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth();
|
||||
const day = now.getDate();
|
||||
const dateTo = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||
let from: Date;
|
||||
switch (period) {
|
||||
case "month": from = new Date(year, month, 1); break;
|
||||
case "3months": from = new Date(year, month - 2, 1); break;
|
||||
case "6months": from = new Date(year, month - 5, 1); break;
|
||||
case "year": from = new Date(year, 0, 1); break;
|
||||
case "12months": from = new Date(year, month - 11, 1); break;
|
||||
default: from = new Date(year, month, 1); break;
|
||||
}
|
||||
const dateFrom = `${from.getFullYear()}-${String(from.getMonth() + 1).padStart(2, "0")}-${String(from.getDate()).padStart(2, "0")}`;
|
||||
return { dateFrom, dateTo };
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { state, setPeriod, setCustomDates, setBudgetMonth } = useDashboard();
|
||||
const { t } = useTranslation();
|
||||
const { state, setPeriod, setCustomDates } = useDashboard();
|
||||
const { summary, categoryBreakdown, categoryOverTime, budgetVsActual, period, isLoading } = state;
|
||||
|
||||
const [hiddenCategories, setHiddenCategories] = useState<Set<string>>(new Set());
|
||||
|
|
@ -65,8 +91,6 @@ export default function DashboardPage() {
|
|||
},
|
||||
];
|
||||
|
||||
const monthOptions = useMemo(() => buildMonthOptions(i18n.language), [i18n.language]);
|
||||
|
||||
const { dateFrom, dateTo } = computeDateRange(period, state.customDateFrom, state.customDateTo);
|
||||
|
||||
return (
|
||||
|
|
@ -114,23 +138,7 @@ export default function DashboardPage() {
|
|||
/>
|
||||
</div>
|
||||
<div className="lg:col-span-3">
|
||||
<h2 className="text-lg font-semibold mb-3 flex items-center gap-2 flex-wrap">
|
||||
{t("reports.bva.titlePrefix")}
|
||||
<select
|
||||
value={`${state.budgetYear}-${state.budgetMonth}`}
|
||||
onChange={(e) => {
|
||||
const [y, m] = e.target.value.split("-").map(Number);
|
||||
setBudgetMonth(y, m);
|
||||
}}
|
||||
className="text-base font-semibold bg-[var(--card)] border border-[var(--border)] rounded-lg px-2 py-0.5 cursor-pointer hover:bg-[var(--muted)] transition-colors"
|
||||
>
|
||||
{monthOptions.map((opt) => (
|
||||
<option key={opt.key} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</h2>
|
||||
<h2 className="text-lg font-semibold mb-3">{t("dashboard.budgetVsActual")}</h2>
|
||||
<BudgetVsActualTable data={budgetVsActual} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { Hash, Table, BarChart3 } from "lucide-react";
|
||||
import { useReports } from "../hooks/useReports";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
import type { ReportTab, CategoryBreakdownItem, ImportSource } from "../shared/types";
|
||||
import type { ReportTab, CategoryBreakdownItem, DashboardPeriod, ImportSource } from "../shared/types";
|
||||
import { getAllSources } from "../services/importSourceService";
|
||||
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
||||
import MonthlyTrendsChart from "../components/reports/MonthlyTrendsChart";
|
||||
|
|
@ -16,10 +16,36 @@ import BudgetVsActualTable from "../components/reports/BudgetVsActualTable";
|
|||
import DynamicReport from "../components/reports/DynamicReport";
|
||||
import ReportFilterPanel from "../components/reports/ReportFilterPanel";
|
||||
import TransactionDetailModal from "../components/shared/TransactionDetailModal";
|
||||
import { computeDateRange, buildMonthOptions } from "../utils/dateRange";
|
||||
|
||||
const TABS: ReportTab[] = ["trends", "byCategory", "overTime", "budgetVsActual", "dynamic"];
|
||||
|
||||
function computeDateRange(
|
||||
period: DashboardPeriod,
|
||||
customDateFrom?: string,
|
||||
customDateTo?: string,
|
||||
): { dateFrom?: string; dateTo?: string } {
|
||||
if (period === "all") return {};
|
||||
if (period === "custom" && customDateFrom && customDateTo) {
|
||||
return { dateFrom: customDateFrom, dateTo: customDateTo };
|
||||
}
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth();
|
||||
const day = now.getDate();
|
||||
const dateTo = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||
let from: Date;
|
||||
switch (period) {
|
||||
case "month": from = new Date(year, month, 1); break;
|
||||
case "3months": from = new Date(year, month - 2, 1); break;
|
||||
case "6months": from = new Date(year, month - 5, 1); break;
|
||||
case "year": from = new Date(year, 0, 1); break;
|
||||
case "12months": from = new Date(year, month - 11, 1); break;
|
||||
default: from = new Date(year, month, 1); break;
|
||||
}
|
||||
const dateFrom = `${from.getFullYear()}-${String(from.getMonth() + 1).padStart(2, "0")}-${String(from.getDate()).padStart(2, "0")}`;
|
||||
return { dateFrom, dateTo };
|
||||
}
|
||||
|
||||
export default function ReportsPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { state, setTab, setPeriod, setCustomDates, setBudgetMonth, setPivotConfig, setSourceId } = useReports();
|
||||
|
|
@ -66,7 +92,18 @@ export default function ReportsPage() {
|
|||
return [];
|
||||
}, [state.tab, state.categorySpending, state.categoryOverTime]);
|
||||
|
||||
const monthOptions = useMemo(() => buildMonthOptions(i18n.language), [i18n.language]);
|
||||
const monthOptions = useMemo(() => {
|
||||
const now = new Date();
|
||||
const currentMonth = now.getMonth();
|
||||
const currentYear = now.getFullYear();
|
||||
return Array.from({ length: 24 }, (_, i) => {
|
||||
const d = new Date(currentYear, currentMonth - i, 1);
|
||||
const y = d.getFullYear();
|
||||
const m = d.getMonth() + 1;
|
||||
const label = new Intl.DateTimeFormat(i18n.language, { month: "long", year: "numeric" }).format(d);
|
||||
return { key: `${y}-${m}`, value: `${y}-${m}`, label: label.charAt(0).toUpperCase() + label.slice(1) };
|
||||
});
|
||||
}, [i18n.language]);
|
||||
|
||||
const hasCategories = ["byCategory", "overTime"].includes(state.tab) && filterCategories.length > 0;
|
||||
const showFilterPanel = hasCategories || (state.tab === "trends" && sources.length > 1);
|
||||
|
|
@ -84,7 +121,7 @@ export default function ReportsPage() {
|
|||
const [y, m] = e.target.value.split("-").map(Number);
|
||||
setBudgetMonth(y, m);
|
||||
}}
|
||||
className="text-lg font-bold bg-[var(--card)] border border-[var(--border)] rounded-lg px-2 py-0.5 cursor-pointer hover:bg-[var(--muted)] transition-colors"
|
||||
className="text-2xl font-bold bg-[var(--card)] border border-[var(--border)] rounded-lg px-3 py-1 cursor-pointer hover:bg-[var(--muted)] transition-colors"
|
||||
>
|
||||
{monthOptions.map((opt) => (
|
||||
<option key={opt.key} value={opt.value}>
|
||||
|
|
|
|||
|
|
@ -178,16 +178,6 @@ export async function deleteTemplate(templateId: number): Promise<void> {
|
|||
await db.execute("DELETE FROM budget_templates WHERE id = $1", [templateId]);
|
||||
}
|
||||
|
||||
// --- Actuals helpers ---
|
||||
|
||||
export async function getActualTotalsForYear(
|
||||
year: number
|
||||
): Promise<Array<{ category_id: number | null; actual: number }>> {
|
||||
const dateFrom = `${year}-01-01`;
|
||||
const dateTo = `${year}-12-31`;
|
||||
return getActualsByCategoryRange(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
// --- Budget vs Actual ---
|
||||
|
||||
async function getActualsByCategoryRange(
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export interface BudgetYearRow {
|
|||
depth?: number;
|
||||
months: number[]; // index 0-11 = Jan-Dec planned amounts
|
||||
annual: number; // computed sum
|
||||
previousYearTotal: number; // actual (transactions) total from the previous year
|
||||
previousYearTotal: number; // total budget from the previous year
|
||||
}
|
||||
|
||||
export interface ImportConfigTemplate {
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { computeDateRange, buildMonthOptions } from "./dateRange";
|
||||
|
||||
describe("computeDateRange", () => {
|
||||
beforeEach(() => {
|
||||
// Fix "now" to 2025-07-15 for deterministic tests
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date(2025, 6, 15)); // July 15, 2025
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('returns empty object for "all" period', () => {
|
||||
expect(computeDateRange("all")).toEqual({});
|
||||
});
|
||||
|
||||
it('returns custom range for "custom" period', () => {
|
||||
expect(computeDateRange("custom", "2025-01-01", "2025-06-30")).toEqual({
|
||||
dateFrom: "2025-01-01",
|
||||
dateTo: "2025-06-30",
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to default when "custom" has missing dates', () => {
|
||||
const result = computeDateRange("custom");
|
||||
// Should fall through to default (same as "month")
|
||||
expect(result.dateFrom).toBe("2025-07-01");
|
||||
expect(result.dateTo).toBe("2025-07-15");
|
||||
});
|
||||
|
||||
it('computes "month" period (first of current month to today)', () => {
|
||||
const result = computeDateRange("month");
|
||||
expect(result).toEqual({ dateFrom: "2025-07-01", dateTo: "2025-07-15" });
|
||||
});
|
||||
|
||||
it('computes "3months" period (3 months back)', () => {
|
||||
const result = computeDateRange("3months");
|
||||
expect(result).toEqual({ dateFrom: "2025-05-01", dateTo: "2025-07-15" });
|
||||
});
|
||||
|
||||
it('computes "6months" period (6 months back)', () => {
|
||||
const result = computeDateRange("6months");
|
||||
expect(result).toEqual({ dateFrom: "2025-02-01", dateTo: "2025-07-15" });
|
||||
});
|
||||
|
||||
it('computes "year" period (Jan 1st of current year)', () => {
|
||||
const result = computeDateRange("year");
|
||||
expect(result).toEqual({ dateFrom: "2025-01-01", dateTo: "2025-07-15" });
|
||||
});
|
||||
|
||||
it('computes "12months" period (12 months back)', () => {
|
||||
const result = computeDateRange("12months");
|
||||
expect(result).toEqual({ dateFrom: "2024-08-01", dateTo: "2025-07-15" });
|
||||
});
|
||||
|
||||
it("handles January rollover for 3months period", () => {
|
||||
vi.setSystemTime(new Date(2025, 1, 10)); // Feb 10, 2025
|
||||
const result = computeDateRange("3months");
|
||||
expect(result).toEqual({ dateFrom: "2024-12-01", dateTo: "2025-02-10" });
|
||||
});
|
||||
|
||||
it("handles January rollover for 6months period", () => {
|
||||
vi.setSystemTime(new Date(2025, 0, 20)); // Jan 20, 2025
|
||||
const result = computeDateRange("6months");
|
||||
expect(result).toEqual({ dateFrom: "2024-08-01", dateTo: "2025-01-20" });
|
||||
});
|
||||
|
||||
it("handles January rollover for 12months period", () => {
|
||||
vi.setSystemTime(new Date(2025, 0, 5)); // Jan 5, 2025
|
||||
const result = computeDateRange("12months");
|
||||
expect(result).toEqual({ dateFrom: "2024-02-01", dateTo: "2025-01-05" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildMonthOptions", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date(2025, 6, 15)); // July 15, 2025
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("returns 24 month options", () => {
|
||||
const options = buildMonthOptions("fr");
|
||||
expect(options).toHaveLength(24);
|
||||
});
|
||||
|
||||
it("starts with the current month", () => {
|
||||
const options = buildMonthOptions("en");
|
||||
expect(options[0].key).toBe("2025-7");
|
||||
expect(options[0].value).toBe("2025-7");
|
||||
});
|
||||
|
||||
it("ends 23 months ago", () => {
|
||||
const options = buildMonthOptions("en");
|
||||
expect(options[23].key).toBe("2023-8");
|
||||
expect(options[23].value).toBe("2023-8");
|
||||
});
|
||||
|
||||
it("handles January rollover correctly", () => {
|
||||
vi.setSystemTime(new Date(2025, 0, 15)); // Jan 15, 2025
|
||||
const options = buildMonthOptions("en");
|
||||
expect(options[0].key).toBe("2025-1");
|
||||
expect(options[1].key).toBe("2024-12");
|
||||
expect(options[12].key).toBe("2024-1");
|
||||
});
|
||||
|
||||
it("capitalizes the first letter of labels", () => {
|
||||
const options = buildMonthOptions("fr");
|
||||
for (const opt of options) {
|
||||
expect(opt.label[0]).toBe(opt.label[0].toUpperCase());
|
||||
}
|
||||
});
|
||||
|
||||
it("labels contain year information", () => {
|
||||
const options = buildMonthOptions("en");
|
||||
expect(options[0].label).toContain("2025");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import type { DashboardPeriod } from "../shared/types";
|
||||
|
||||
/**
|
||||
* Compute a date range (dateFrom / dateTo) based on the selected period.
|
||||
* Shared between useDashboard, useReports, DashboardPage and ReportsPage.
|
||||
*/
|
||||
export function computeDateRange(
|
||||
period: DashboardPeriod,
|
||||
customDateFrom?: string,
|
||||
customDateTo?: string,
|
||||
): { dateFrom?: string; dateTo?: string } {
|
||||
if (period === "all") return {};
|
||||
if (period === "custom" && customDateFrom && customDateTo) {
|
||||
return { dateFrom: customDateFrom, dateTo: customDateTo };
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth();
|
||||
const day = now.getDate();
|
||||
|
||||
const dateTo = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||
|
||||
let from: Date;
|
||||
switch (period) {
|
||||
case "month":
|
||||
from = new Date(year, month, 1);
|
||||
break;
|
||||
case "3months":
|
||||
from = new Date(year, month - 2, 1);
|
||||
break;
|
||||
case "6months":
|
||||
from = new Date(year, month - 5, 1);
|
||||
break;
|
||||
case "year":
|
||||
from = new Date(year, 0, 1);
|
||||
break;
|
||||
case "12months":
|
||||
from = new Date(year, month - 11, 1);
|
||||
break;
|
||||
default:
|
||||
from = new Date(year, month, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
const dateFrom = `${from.getFullYear()}-${String(from.getMonth() + 1).padStart(2, "0")}-${String(from.getDate()).padStart(2, "0")}`;
|
||||
|
||||
return { dateFrom, dateTo };
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array of month options for the budget month dropdown.
|
||||
* Returns the last 24 months with localized labels.
|
||||
*/
|
||||
export function buildMonthOptions(language: string): Array<{ key: string; value: string; label: string }> {
|
||||
const now = new Date();
|
||||
const currentMonth = now.getMonth();
|
||||
const currentYear = now.getFullYear();
|
||||
return Array.from({ length: 24 }, (_, i) => {
|
||||
const d = new Date(currentYear, currentMonth - i, 1);
|
||||
const y = d.getFullYear();
|
||||
const m = d.getMonth() + 1;
|
||||
const label = new Intl.DateTimeFormat(language, { month: "long", year: "numeric" }).format(d);
|
||||
return { key: `${y}-${m}`, value: `${y}-${m}`, label: label.charAt(0).toUpperCase() + label.slice(1) };
|
||||
});
|
||||
}
|
||||
|
|
@ -1,53 +1,33 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { copyFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
|
||||
// Sync root CHANGELOG files to public/ so the app always shows the latest version history
|
||||
function syncChangelogs() {
|
||||
const root = import.meta.dirname;
|
||||
const files = ["CHANGELOG.md", "CHANGELOG.fr.md"];
|
||||
for (const file of files) {
|
||||
try {
|
||||
copyFileSync(resolve(root, file), resolve(root, "public", file));
|
||||
} catch {
|
||||
// Ignore if source file doesn't exist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(async () => {
|
||||
// Sync changelogs before starting dev server or building
|
||||
syncChangelogs();
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react(), tailwindcss()],
|
||||
|
||||
return {
|
||||
plugins: [react(), tailwindcss()],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent Vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell Vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent Vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell Vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
Loading…
Reference in a new issue