docs(onboarding): changelog, architecture, user guide and interactive QA checklist (#357) #383
9 changed files with 511 additions and 48 deletions
|
|
@ -2,6 +2,18 @@
|
|||
|
||||
## [Non publié]
|
||||
|
||||
### Ajouté
|
||||
|
||||
- États vides guidés sur le Tableau de bord, Import, Transactions et Budget : un profil sans transaction explique désormais ce que la page montrera et propose l'étape suivante (« Choisir un dossier d'import », « Importer des transactions ») au lieu d'un graphique ou d'un tableau vides (#352, #353, #356).
|
||||
- Une liste de démarrage sur le Tableau de bord, calculée depuis vos données : six étapes (créer un profil, choisir un dossier d'import, configurer une source d'import, importer un premier fichier, classer une transaction à la main, saisir un premier budget) se cochent d'elles-mêmes au fil de votre progression, et l'étape Budget est marquée « Base et plus » en édition Gratuite. La liste occupe le tableau de bord vide, se réduit à une barre compacte dès qu'il y a des transactions, disparaît quand toutes les étapes sont faites, et « Masquer » la retire pour le profil courant seulement (#351, #352).
|
||||
- « Créer un dossier d'exemple » sur la page Import quand le dossier d'import n'a aucun sous-dossier de banque : le bouton dépose deux sources fictives (« Exemple Desjardins », « Exemple Tangerine »), un relevé chacune couvrant les deux mois précédents, pour essayer l'assistant avant que vos vrais relevés soient en place. Il refuse d'écraser un dossier existant du même nom (#354, #356).
|
||||
- Une sous-section « Exporter un CSV depuis votre banque » dans le guide d'utilisation, avec une procédure par banque (Desjardins, Tangerine, RBC, Banque Nationale, BMO) et une ligne de statut indiquant si chacune a été confirmée sur un compte réel (#355).
|
||||
|
||||
### Modifié
|
||||
|
||||
- Paramètres → Utilisateurs propose « Afficher la liste de démarrage » une fois la liste masquée, et confirme qu'elle réapparaîtra sur le tableau de bord (#352).
|
||||
- Le popover d'aide « ? » de la page Import renvoie désormais à la sous-section « Exporter un CSV depuis votre banque » du guide, comme l'état vide d'Import (#356).
|
||||
|
||||
## [0.16.0] - 2026-10-03
|
||||
|
||||
### Ajouté
|
||||
|
|
|
|||
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -2,6 +2,18 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Guided empty states on the Dashboard, Import, Transactions and Budget pages: a profile without transactions now explains what the page will show and offers the next step ("Choose an import folder", "Import transactions") instead of an empty chart or table (#352, #353, #356).
|
||||
- A getting-started checklist on the Dashboard, computed from your data: six steps (create a profile, choose an import folder, set up an import source, import a first file, categorize a transaction by hand, enter a first budget) tick themselves as you go, and the Budget step is marked "Base and up" in the Free edition. The list fills the empty dashboard, shrinks to a compact bar once transactions exist, disappears when every step is done, and "Hide" dismisses it for the current profile only (#351, #352).
|
||||
- "Create a sample folder" on the Import page when the import folder has no bank sub-folder: it writes two fictitious sources ("Exemple Desjardins", "Exemple Tangerine"), one statement each covering the two previous months, so you can try the wizard before your real statements are in place. It refuses to overwrite an existing folder of the same name (#354, #356).
|
||||
- An "Export a CSV from your bank" sub-section in the user guide, with a procedure per bank (Desjardins, Tangerine, RBC, National Bank, BMO) and a status line saying whether each one was confirmed on a real account (#355).
|
||||
|
||||
### Changed
|
||||
|
||||
- Settings → Users offers "Show the getting-started checklist" once the checklist was hidden, and confirms that it will reappear on the dashboard (#352).
|
||||
- The "?" help popover of the Import page now links to the guide's "Export a CSV from your bank" sub-section, as does the Import empty state (#356).
|
||||
|
||||
## [0.16.0] - 2026-10-03
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -31,21 +31,22 @@ simpl-resultat/
|
|||
│ │ ├── balance/ # 8 composants Bilan (AccountForm, BalanceAccountsTable, BalanceEvolutionChart, BalanceOnboardingCard, BalanceOverviewCard, LinkTransfersModal, SnapshotEditor, SnapshotLineRow)
|
||||
│ │ ├── budget/ # 5 composants
|
||||
│ │ ├── categories/ # 5 composants
|
||||
│ │ ├── dashboard/ # 2 composants
|
||||
│ │ ├── import/ # 14 composants (wizard d'import, dont FormatDriftPanel et RepairPathNotice)
|
||||
│ │ ├── dashboard/ # 7 composants (dont DashboardEmptyState et OnboardingChecklist)
|
||||
│ │ ├── import/ # 15 composants (wizard d'import, dont FormatDriftPanel, RepairPathNotice et ImportEmptyState)
|
||||
│ │ ├── layout/ # AppShell, Sidebar
|
||||
│ │ ├── profile/ # 3 composants (PIN, formulaire, switcher)
|
||||
│ │ ├── reports/ # ~25 composants (hub, faits saillants, tendances, comparables, zoom catégorie)
|
||||
│ │ ├── settings/ # 5 composants (+ LogViewerCard, LicenseCard, LicenseKeyForm, AccountCard)
|
||||
│ │ ├── shared/ # 12 composants réutilisables (dont RequireFeature, UpsellGate, PurchaseButton)
|
||||
│ │ ├── settings/ # 17 composants (dont LogViewerCard, LicenseCard, LicenseKeyForm, AccountCard, DocsContent et DocsSubsections)
|
||||
│ │ ├── shared/ # 12 composants réutilisables (dont RequireFeature, UpsellGate, PurchaseButton, EmptyState)
|
||||
│ │ └── transactions/ # 5 composants
|
||||
│ ├── contexts/ # LicenseContext (licence machine) + ProfileContext (état global profil)
|
||||
│ ├── hooks/ # 18+ hooks custom (useReducer, 5 hooks rapports par domaine)
|
||||
│ ├── hooks/ # 29 hooks custom (useReducer, 5 hooks rapports par domaine)
|
||||
│ ├── pages/ # 14 pages (dont 4 sous-pages rapports)
|
||||
│ ├── services/ # 14 services métier
|
||||
│ ├── services/ # 25 services métier (19 détaillés dans la section « Services TypeScript »)
|
||||
│ ├── shared/ # Types, constantes, matrice d'entitlements (entitlements.ts), gate profils (profileGate.ts), CTA d'upsell (upsellCta.ts), URL d'achat (purchaseUrl.ts), échéance de licence (licenseExpiry.ts)
|
||||
│ ├── utils/ # 13 utilitaires purs (parsing montants/dates, détection CSV, codec de format d'import, dictionnaire lexical, signatures de banques, charts)
|
||||
│ ├── i18n/ # Config i18next + locales FR/EN
|
||||
│ ├── __fixtures__/ # csv/ : corpus de détection (15 cas figés) ; samples/ : gabarits du dossier d'exemple embarqués par Rust
|
||||
│ ├── App.tsx # Router principal
|
||||
│ └── main.tsx # Point d'entrée
|
||||
├── src-tauri/ # Backend Rust
|
||||
|
|
@ -93,7 +94,7 @@ simpl-resultat/
|
|||
| `budget_templates` | Modèles de budget réutilisables |
|
||||
| `budget_template_entries` | Catégories et montants dans les modèles |
|
||||
| `import_config_templates` | Modèles prédéfinis de config d'import |
|
||||
| `user_preferences` | Préférences applicatives (clé-valeur) — `import_folder`, `balance_show_returns`, `balance_starter_proposed`… **et l'état de repli des catégories** (clés `reports-compare-expanded`, `reports-bva-expanded`, `reports-trends-expanded`, `budget-grid-expanded`) : persisté ici **par profil** plutôt qu'en `localStorage`, pour être détruit avec le profil (pas de résidu machine-global survivant à la suppression) — voir [ADR 0016](adr/0016-persistance-etat-ui-par-profil.md) |
|
||||
| `user_preferences` | Préférences applicatives (clé-valeur) — `import_folder`, `balance_show_returns`, `balance_starter_proposed`, `onboarding_checklist_hidden` (« 1 » quand la liste de démarrage a été masquée ; la clé est supprimée par « Afficher la liste de démarrage », #351-#352)… **et l'état de repli des catégories** (clés `reports-compare-expanded`, `reports-bva-expanded`, `reports-trends-expanded`, `budget-grid-expanded`) : persisté ici **par profil** plutôt qu'en `localStorage`, pour être détruit avec le profil (pas de résidu machine-global survivant à la suppression) — voir [ADR 0016](adr/0016-persistance-etat-ui-par-profil.md) |
|
||||
| `balance_categories` | Taxonomie des **classes d'actif** (Liquidités, Fonds/FNB, Actions, Crypto, Autres) — `kind ∈ {simple, priced}` (défaut suggéré pour les nouveaux comptes), `custom_label` pour le renommage bilingue-safe (v12). Les ex-types véhicules (TFSA/RRSP) ont migré vers `balance_accounts.vehicle_type` (Étape 1, v12/v13, [ADR 0014](adr/0014-balance-vehicule-attribut.md)) |
|
||||
| `balance_accounts` | Comptes de bilan (rattachés à une catégorie). `currency` hardcodée à `CAD` au MVP via CHECK. `archived_at` pour soft-delete. `vehicle_type` (enveloppe fiscale nullable, v12, [ADR 0014](adr/0014-balance-vehicule-attribut.md)). `kind ∈ {simple, detailed}` + `detailed_since` (pivot faisant autorité, v15, [ADR 0015](adr/0015-balance-detail-par-titre.md)) — porte désormais l'axe simple/détaillé (auparavant dérivé de `category.kind`). **Issue #179** : 4 comptes de départ seedés (`consolidated_schema.sql`) + proposés aux profils existants via `StarterAccountsModal` |
|
||||
| `balance_snapshots` | Snapshots datés (`snapshot_date` UNIQUE) — éditer = mettre à jour les lignes, pas dupliquer |
|
||||
|
|
@ -165,14 +166,14 @@ v17 est purement additive : elle n'ajoute **ni table ni index** (20 tables / 24
|
|||
|
||||
Pour les **nouveaux profils**, le fichier `consolidated_schema.sql` contient le schéma complet avec toutes les migrations pré-appliquées (pas besoin de rejouer les migrations).
|
||||
|
||||
## Services TypeScript (18)
|
||||
## Services TypeScript (19)
|
||||
|
||||
| Service | Responsabilité |
|
||||
|---------|---------------|
|
||||
| `db.ts` | Wrapper de connexion (tauri-plugin-sql) |
|
||||
| `profileService.ts` | Gestion des profils |
|
||||
| `categoryService.ts` | CRUD catégories hiérarchiques |
|
||||
| `transactionService.ts` | CRUD et filtrage des transactions ; détection d'erreurs FK RESTRICT pour transactions liées à un compte de bilan (typed `TransactionLinkedToBalanceError`) |
|
||||
| `transactionService.ts` | CRUD et filtrage des transactions ; détection d'erreurs FK RESTRICT pour transactions liées à un compte de bilan (typed `TransactionLinkedToBalanceError`) ; `getTransactionCount` (compte brut de la table, sans filtre — signal des états vides de Transactions et Budget, #351/#353) |
|
||||
| `importSourceService.ts` | Configuration des sources d'import — lit et écrit le format complet via le codec `importFormat.ts` ([ADR 0019](adr/0019-format-import-persiste.md)) |
|
||||
| `importedFileService.ts` | Suivi des fichiers importés |
|
||||
| `importConfigTemplateService.ts` | Modèles de configuration d'import |
|
||||
|
|
@ -183,6 +184,7 @@ Pour les **nouveaux profils**, le fichier `consolidated_schema.sql` contient le
|
|||
| `reportService.ts` | Génération de rapports : `getMonthlyTrends`, `getCategoryOverTime`, `getHighlights`, `getCompareMonthOverMonth`, `getCompareYearOverYear`, `getCategoryZoom` (CTE récursive bornée anti-cycle), `getCartesSnapshot` (snapshot dashboard Cartes, requêtes parallèles) |
|
||||
| `dataExportService.ts` | Export de données (chiffré). L'enveloppe SREF porte un `format_version` explicite (`SREF_FORMAT_VERSION = 2`) et **inclut `import_sources` + `import_config_templates`** depuis #331 ; wipe et restauration sous `withTransaction`, modèles restaurés avant les sources avec remap de `template_id` |
|
||||
| `userPreferenceService.ts` | Stockage préférences utilisateur |
|
||||
| `onboardingService.ts` | Liste de démarrage (#351) : `getOnboardingCounters` lit en une passe parallèle les cinq compteurs (`import_sources`, `imported_files` terminés, transactions `is_manually_categorized = 1`, `budget_entries`, transactions), le dossier d'import et la préférence `onboarding_checklist_hidden` ; `hideChecklist` / `showChecklist` posent et retirent la préférence. Aucune UI ici : `computeChecklist` (`utils/onboardingChecklist.ts`, pur) transforme le snapshot en six lignes — voir la section « Onboarding » |
|
||||
| `logService.ts` | Capture des logs console (buffer circulaire, sessionStorage) |
|
||||
| `licenseService.ts` | Validation et gestion de la clé de licence (appels commandes Tauri). Les appels au serveur de licences (activation, désactivation, liste des machines) renvoient un résultat typé `{ ok, value }` / `{ ok, error }` ; `parseLicenseError` lit le JSON `{code}` de `LicenseError` et `LICENSE_ERROR_I18N_MAP` le relie à `license.errors.*` (#270). Les commandes de clé (`store_license`, `read_license`) rejettent avec le même JSON depuis #347 : la carte Licence n'affiche jamais une chaîne brute. `LicenseInfo.revoked` porte l'état de révocation que Rust tient pour la clé, et `checkLicenseRevocation` lance le contrôle du démarrage, qui rend `{ outcome, revoked }` (#365) |
|
||||
| `authService.ts` | OAuth2 PKCE / Compte Maximus (appels commandes Tauri auth_*) |
|
||||
|
|
@ -199,7 +201,7 @@ Un seul service par convention projet (1 service par domaine, splitter seulement
|
|||
|
||||
Le CRUD passe par `getDb()` + `tauri-plugin-sql` direct, **jamais** via une commande Tauri — convention projet. Les commandes Rust sont réservées au filesystem, OAuth, license, profils, feedback et au seul calcul Modified Dietz (qui a besoin d'arithmétique de dates `chrono`).
|
||||
|
||||
## Hooks (17+)
|
||||
## Hooks (25+)
|
||||
|
||||
Chaque hook encapsule la logique d'état via `useReducer` :
|
||||
|
||||
|
|
@ -213,6 +215,7 @@ Chaque hook encapsule la logique d'état via `useReducer` :
|
|||
| `useAdjustments` | Ajustements |
|
||||
| `useBudget` | Budget |
|
||||
| `useDashboard` | Tableau de bord (`/`) — convergé sur le modèle `/reports/cartes` (#279) : KPIs+deltas/top movers/adherence budget via `getCartesSnapshot` sur un mois de référence propre au Dashboard (`referenceYear`/`referenceMonth`, défaut mois précédent), barres classées + tendance par catégorie sur une période flexible propre (`period`/dates custom), filtre compte (`accountIds`) local partagé par les deux axes temporels, tuile valeur nette (Bilan) chargée une fois, indépendante du filtre compte |
|
||||
| `useOnboardingChecklist` | Liste de démarrage (#351) : reducer exporté (`onboardingChecklistReducer`, états `loading` / `ready` / `error`), charge le snapshot d'`onboardingService` au montage et le passe à `computeChecklist` avec `useEntitlement("budget")` (ligne Budget verrouillée hors Base+, exclue du total). Expose `status`, `checklist`, `counters` (le Dashboard lit `transactions` pour l'état vide), `hidden`, `budgetReady`, `hide()` / `show()` / `refresh()`. Un échec de lecture garde les compteurs précédents et laisse `hidden` intact — une lecture ratée ne ressuscite jamais une liste masquée ; le Dashboard rend alors la page sans liste et journalise |
|
||||
| `useReportsPeriod` | Période de reporting synchronisée via query string (bookmarkable) + filtre compte (`accountIds`, `import_sources.id`) via le paramètre `sources`, même mécanique bookmarkable ; défaut `[]` = aucun filtre (fondation #272). Branché sur les 7 services de rapports (#273) et exposé via `<FilterPanel>` (#274) ; adopté sur Tendances (#275) puis Comparaison et Budget (#276). Le Dashboard (`/`) n'utilise pas ce hook — il porte son propre `accountIds` local (`useDashboard`), câblé au même `<FilterPanel>` (#279) |
|
||||
| `useHighlights` | Panneau de faits saillants du hub rapports |
|
||||
| `useTrends` | Rapport Tendances (sous-vue flux global / par catégorie) |
|
||||
|
|
@ -286,6 +289,38 @@ L'étape `file-preview`, déclarée depuis l'origine dans `ImportWizardStep` san
|
|||
|
||||
15 fichiers CSV **synthétiques** (aucune donnée réelle, aucun relevé réel n'entre dans ce dépôt — les signatures de banques sont écrites depuis des dispositions documentées). Ils couvrent des formes qu'un vrai relevé ne réunit jamais toutes : montant signé, débit/crédit, débit/crédit inversés, colonne inutilisée à `0,00`, préambule, en-tête contenant un nombre, en-tête à libellé numérique, sans en-tête, tout-positif, Desjardins quoté, montant absolu + indicateur, plus les quatre dispositions de banque. Posés en #326 **avant** la refonte, avec le comportement fautif figé et marqué `KNOWN DEFECT` + l'issue devant le corriger ; chaque maillon suivant a mis à jour l'attente et retiré le marqueur, sans jamais supprimer un test.
|
||||
|
||||
### Gabarits du dossier d'exemple — `src/__fixtures__/samples/`
|
||||
|
||||
Deux relevés **inventés** (`exemple-desjardins.csv` : compte chèque Desjardins ; `exemple-tangerine.csv` : carte de crédit Tangerine ; 41 lignes chacun), avec les jetons `{{M1}}` / `{{M2}}` que la commande Rust `create_sample_import_folders` remplace par les deux mois complets précédant le clic (`MM/YYYY`). Les fichiers sont embarqués dans le binaire par `include_str!` : le dépôt est la seule source, il n'y a rien à copier au build. Le module est **séparé** de `csv/index.ts` à dessein — ce corpus est figé à quinze cas par `csvAutoDetect.test.ts` et six boucles y passent chaque fixture ; y ajouter les gabarits élargirait le contrat figé en silence. `src/__integration__/onboarding-samples.test.ts` lit les **mêmes** fichiers et fige ce que l'assistant en fera : chaque fichier reconnu par la signature de la banque qu'il imite, toutes les lignes lues, aucune ligne en erreur, totaux signés attendus, et la part catégorisée par les 180 mots-clés du seed — en laissant exprès des lignes à classer à la main (dont « GARDERIE LES PETITS PAS »), pour que la cinquième case de la liste de démarrage ait quelque chose à cocher (#354).
|
||||
|
||||
## Onboarding — états vides et liste de démarrage
|
||||
|
||||
Phase 1 de l'accueil des nouveaux profils (#351-#357 ; analyse d'origine dans `docs/analyse-onboarding-2026-09.md`). Aucune migration ni table : tout se lit dans les données existantes, et une seule préférence est écrite.
|
||||
|
||||
**Les quatre états vides** rendent le composant partagé `EmptyState` (`src/components/shared/` : icône, titre, description, action primaire en `<Link>` ou `<button>`, action secondaire optionnelle, enfants libres). Leur signal est le **compte brut** de transactions du profil, jamais le résultat d'un filtre — un profil filtré jusqu'à zéro ligne garde sa page normale :
|
||||
|
||||
| Page | Signal | Composant | Action proposée |
|
||||
|---|---|---|---|
|
||||
| Tableau de bord `/` | `counters.transactions === 0`, compteur lu par `useOnboardingChecklist` | `DashboardEmptyState` (carte d'accueil + liste en mode `expanded`) | « Choisir un dossier d'import » → `/import` |
|
||||
| Import `/import` | aucun dossier d'import, ou dossier sans aucun sous-dossier | `ImportEmptyState` (deux variantes) | « Choisir un dossier » ; ou « Créer un dossier d'exemple » + « Actualiser » ; lien vers le guide `/settings/users#import-export` dans les deux cas |
|
||||
| Transactions `/transactions` | `showsEmptyState(state)` = `totalInDb === 0 && !isLoading`, `totalInDb` via `getTransactionCount` | `EmptyState` dans la page | « Importer des transactions » → `/import` |
|
||||
| Budget `/budget` | `showsEmptyState(state)` = `totalInDb === 0`, même compteur | `EmptyState` dans la page | « Importer des transactions » → `/import` |
|
||||
|
||||
**La liste de démarrage** (`OnboardingChecklist`, mode `expanded` sur le tableau de bord vide, mode `compact` entre le titre et les filtres dès qu'il y a des transactions) a six lignes dans un ordre fixe. Chaque case est cochée par une **donnée**, jamais par un clic ; la source de vérité est `computeChecklist(counters, { importFolderSet, budgetAllowed })` :
|
||||
|
||||
| Case | Source de vérité |
|
||||
|---|---|
|
||||
| Créer un profil | toujours cochée — la liste n'existe que dans un profil ouvert |
|
||||
| Choisir un dossier d'import | préférence `import_folder` non vide |
|
||||
| Configurer une source d'import | `COUNT(*) FROM import_sources` ≥ 1 |
|
||||
| Importer un premier fichier | `COUNT(*) FROM imported_files WHERE status = 'completed'` ≥ 1 |
|
||||
| Classer une transaction à la main | `COUNT(*) FROM transactions WHERE is_manually_categorized = 1` ≥ 1 — ni « règle créée » ni « transactions catégorisées » : le seed IPC pose 180 mots-clés dans tout profil neuf et le wizard catégorise pendant l'import, ces deux cases seraient donc cochées d'avance |
|
||||
| Saisir un premier budget | `COUNT(*) FROM budget_entries` ≥ 1 ; verrouillée hors Base+ (`useEntitlement("budget")`), alors exclue du total (5 au lieu de 6) et renvoyée vers `/settings/users` |
|
||||
|
||||
`computeChecklist` rend `{ items, done, total, complete }` ; la liste compacte disparaît quand `complete`. « Masquer » écrit `onboarding_checklist_hidden = "1"` dans `user_preferences` — **par profil**, donc détruite avec lui ([ADR 0016](adr/0016-persistance-etat-ui-par-profil.md)) et sans effet sur les autres profils. Paramètres → Utilisateurs montre « Afficher la liste de démarrage » tant que la clé existe et la supprime au clic (`showChecklist`), avec une confirmation en place du bouton.
|
||||
|
||||
**Le dossier d'exemple** : sur un dossier d'import sans sous-dossier, `ImportEmptyState` propose « Créer un dossier d'exemple ». `useImportWizard.createSampleFolders` invoque `create_sample_import_folders` avec le dossier et la date du jour, rescanne, puis affiche `import.empty.sampleCreated` avec les noms rendus par la commande ; le bandeau tombe au changement de dossier. La commande refuse d'écraser (voir son contrat dans « Commandes Tauri ») ; les gabarits et leur test d'intégration sont décrits dans « Gabarits du dossier d'exemple ».
|
||||
|
||||
## Gating par édition (licence)
|
||||
|
||||
Depuis le chantier #297-#301, l'accès aux modules est gaté par l'édition de licence (`free` / `base` / `premium`) **côté interface uniquement** — un soft-paywall assumé (l'app est GPL ; le seul gate appliqué côté serveur reste la récupération de cours Premium via maximus-api). La matrice complète tier → modules, le rationale et les alternatives rejetées sont dans l'[ADR 0017](adr/0017-feature-gating-par-tier.md).
|
||||
|
|
@ -305,9 +340,9 @@ Depuis le chantier #297-#301, l'accès aux modules est gaté par l'édition de l
|
|||
|
||||
Modules Free — jamais gatés, aucune `FeatureKey` : Dashboard, Import, Transactions, Catégories, hub `/reports` + `/reports/trends`, Export/Import, Paramètres, Changelog, docs. Le déclassement d'édition est **non destructif** : les données des modules verrouillés restent en base et redeviennent accessibles avec une clé valide.
|
||||
|
||||
## Commandes Tauri (36)
|
||||
## Commandes Tauri (37)
|
||||
|
||||
### `fs_commands.rs` — Système de fichiers (6)
|
||||
### `fs_commands.rs` — Système de fichiers (7)
|
||||
|
||||
- `scan_import_folder` — Scan récursif de dossier pour fichiers CSV/TXT
|
||||
- `read_file_content` — Lecture avec gestion de l'encodage
|
||||
|
|
@ -315,6 +350,7 @@ Modules Free — jamais gatés, aucune `FeatureKey` : Dashboard, Import, Transac
|
|||
- `detect_encoding` — Détection auto (UTF-8, Windows-1252, ISO-8859-15)
|
||||
- `get_file_preview` — Aperçu des N premières lignes
|
||||
- `pick_folder` — Dialogue de sélection de dossier
|
||||
- `create_sample_import_folders` — Dossier d'exemple (#354). Contrat : reçoit le dossier d'import et la date du jour (`YYYY-MM-DD`) ; écrit « Exemple Desjardins » et « Exemple Tangerine », un relevé `releve-<AAAA-MM>.csv` chacun daté des deux mois complets précédents ; rend les deux **noms** créés, pas les chemins. Refuse si le dossier d'import n'existe pas ou si l'un des deux sous-dossiers existe déjà — les deux existences sont vérifiées **avant** la première écriture, donc rien n'est écrasé ni créé à moitié. Gabarits embarqués par `include_str!` depuis `src/__fixtures__/samples/`
|
||||
|
||||
### `export_import_commands.rs` — Export/Import de données (5)
|
||||
|
||||
|
|
|
|||
|
|
@ -76,15 +76,19 @@ Le tableau de bord vous donne un aperçu rapide de votre situation financière p
|
|||
- Histogramme empilé des dépenses par catégorie et par mois
|
||||
- Sélecteur de période ajustable (par défaut : année à ce jour)
|
||||
- Menu contextuel (clic droit) pour masquer une catégorie ou voir ses transactions
|
||||
- Liste de démarrage pour un profil neuf : six cases qui se cochent d'elles-mêmes selon vos données, avec un bouton « Masquer »
|
||||
|
||||
### Comment faire
|
||||
|
||||
1. Utilisez le sélecteur de période en haut à droite pour choisir une plage de temps (mois, 3 mois, année, etc.)
|
||||
2. Consultez les cartes résumées pour votre solde, revenus totaux et dépenses totales
|
||||
3. Vérifiez le graphique circulaire pour voir comment vos dépenses sont réparties par catégorie
|
||||
4. Consultez le tableau Budget vs Réel pour comparer vos dépenses au budget du mois courant
|
||||
5. Analysez l'histogramme en bas de page pour voir l'évolution de vos dépenses par catégorie dans le temps
|
||||
6. Cliquez droit sur une catégorie dans un graphique pour la masquer ou voir le détail de ses transactions
|
||||
1. Dans un profil sans transaction, le tableau de bord affiche une carte d'accueil et la liste de démarrage : créer un profil (déjà cochée), choisir un dossier d'import, configurer une source d'import, importer un premier fichier, classer une transaction à la main, saisir un premier budget. Chaque ligne non cochée mène à la page où la faire ; la case Budget est marquée « Base et plus » en édition Gratuite et ne compte pas dans le total
|
||||
2. Après votre premier import, la liste se réduit à une barre compacte au-dessus des filtres, avec son compteur (par exemple 4 / 6), et disparaît d'elle-même quand toutes les cases sont cochées
|
||||
3. « Masquer » retire la liste de ce profil seulement ; vous pouvez la réafficher depuis Paramètres → Utilisateurs
|
||||
4. Utilisez le sélecteur de période en haut à droite pour choisir une plage de temps (mois, 3 mois, année, etc.)
|
||||
5. Consultez les cartes résumées pour votre solde, revenus totaux et dépenses totales
|
||||
6. Vérifiez le graphique circulaire pour voir comment vos dépenses sont réparties par catégorie
|
||||
7. Consultez le tableau Budget vs Réel pour comparer vos dépenses au budget du mois courant
|
||||
8. Analysez l'histogramme en bas de page pour voir l'évolution de vos dépenses par catégorie dans le temps
|
||||
9. Cliquez droit sur une catégorie dans un graphique pour la masquer ou voir le détail de ses transactions
|
||||
|
||||
### Astuces
|
||||
|
||||
|
|
@ -92,6 +96,7 @@ Le tableau de bord vous donne un aperçu rapide de votre situation financière p
|
|||
- Le solde est calculé comme les revenus moins les dépenses pour la période sélectionnée
|
||||
- Les catégories masquées apparaissent sous forme de pastilles au-dessus du graphique — cliquez sur Tout afficher pour les restaurer
|
||||
- Les motifs SVG (lignes, points, hachures) aident à distinguer les catégories au-delà des couleurs
|
||||
- Les cases de la liste de démarrage se cochent sur vos données, pas sur un clic : « Classer une transaction à la main » attend une transaction dont vous avez changé la catégorie vous-même, la catégorisation automatique de l'import ne la coche pas
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -178,7 +183,7 @@ Les cartes de crédit n'offrent qu'environ trois mois d'historique.
|
|||
|
||||
#### BMO
|
||||
|
||||
*Procédure reconstituée (fournie par Max le 2026-10-03, d'après une description générale), à confirmer sur un compte réel ; format de fichier non encore reconnu (#371)*
|
||||
*Procédure reconstituée d'après une description générale, à confirmer sur un compte réel ; format de fichier non encore reconnu*
|
||||
|
||||
1. Connectez-vous aux Services bancaires en ligne BMO sur bmo.com (le site, pas l'application)
|
||||
2. Dans le sommaire, cliquez sur le compte voulu (chèques, épargne ou Mastercard)
|
||||
|
|
@ -560,6 +565,7 @@ Configurez les préférences de l'application, vérifiez les mises à jour, acc
|
|||
- Export des données (transactions, catégories, ou les deux) en format JSON ou CSV
|
||||
- Import des données depuis un fichier exporté précédemment
|
||||
- Chiffrement AES-256-GCM optionnel pour les fichiers exportés
|
||||
- Réaffichage de la liste de démarrage du tableau de bord (Paramètres → Utilisateurs) quand elle a été masquée
|
||||
|
||||
### Comment faire
|
||||
|
||||
|
|
@ -570,6 +576,7 @@ Configurez les préférences de l'application, vérifiez les mises à jour, acc
|
|||
5. Utilisez la section Gestion des données pour exporter ou importer vos données
|
||||
6. Lors de l'export, choisissez ce qu'il faut inclure et définissez optionnellement un mot de passe pour le chiffrement
|
||||
7. Lors de l'import, sélectionnez un fichier exporté précédemment — les fichiers chiffrés demanderont le mot de passe
|
||||
8. Pour revoir la liste de démarrage après l'avoir masquée, ouvrez Paramètres → Utilisateurs et cliquez sur « Afficher la liste de démarrage » ; elle réapparaît sur le tableau de bord de ce profil. Le bouton n'est visible que si la liste a été masquée
|
||||
|
||||
### Astuces
|
||||
|
||||
|
|
|
|||
368
docs/qa-onboarding.html
Normal file
368
docs/qa-onboarding.html
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>QA — Parcours d'accueil</title>
|
||||
<!--
|
||||
Checklist de parcours pour la phase 1 de l'accueil (issues #351 à #357).
|
||||
Page statique, français seulement, aucune dépendance réseau : tout est inline.
|
||||
L'état (cases + commentaires) vit dans le localStorage du navigateur qui
|
||||
ouvre le fichier ; « Copier le rapport » produit le texte brut à coller dans
|
||||
Paramètres → Systèmes → Rétroaction.
|
||||
-->
|
||||
<style>
|
||||
:root {
|
||||
--bg: #faf9f7;
|
||||
--card: #ffffff;
|
||||
--fg: #1f1d1a;
|
||||
--muted: #6b665f;
|
||||
--border: #e3dfd9;
|
||||
--accent: #b5542e;
|
||||
--accent-fg: #ffffff;
|
||||
--done-bg: #f0f5ec;
|
||||
--focus: #2f6fb3;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #1c1a18;
|
||||
--card: #262320;
|
||||
--fg: #ece8e2;
|
||||
--muted: #a39d94;
|
||||
--border: #3a3631;
|
||||
--accent: #d9774c;
|
||||
--accent-fg: #1c1a18;
|
||||
--done-bg: #262d22;
|
||||
--focus: #7fb0e8;
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
padding: 24px 16px 64px;
|
||||
}
|
||||
main { max-width: 860px; margin: 0 auto; }
|
||||
h1 { font-size: 1.5rem; margin: 0 0 4px; }
|
||||
h2 { font-size: 1.05rem; margin: 28px 0 8px; }
|
||||
p { margin: 0 0 10px; }
|
||||
.lead { color: var(--muted); }
|
||||
.meta {
|
||||
display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center;
|
||||
margin: 16px 0 8px; color: var(--muted); font-size: 0.9rem;
|
||||
}
|
||||
.meta label { display: inline-flex; gap: 6px; align-items: center; }
|
||||
.meta input[type="text"] {
|
||||
width: 11rem;
|
||||
}
|
||||
.progress {
|
||||
font-variant-numeric: tabular-nums; font-weight: 600; color: var(--fg);
|
||||
}
|
||||
.prereq {
|
||||
background: var(--card); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 12px 16px; margin: 12px 0 20px;
|
||||
}
|
||||
.prereq ul { margin: 6px 0 0; padding-left: 20px; }
|
||||
ol.steps { list-style: none; margin: 0; padding: 0; counter-reset: step; }
|
||||
ol.steps li {
|
||||
background: var(--card); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 12px 14px; margin-bottom: 10px;
|
||||
display: grid; grid-template-columns: auto 1fr; gap: 6px 12px;
|
||||
}
|
||||
ol.steps li.done { background: var(--done-bg); }
|
||||
ol.steps li .check { grid-row: 1 / span 3; padding-top: 2px; }
|
||||
ol.steps li input[type="checkbox"] { width: 20px; height: 20px; margin: 0; cursor: pointer; }
|
||||
ol.steps li .title { font-weight: 600; cursor: pointer; }
|
||||
ol.steps li .title::before {
|
||||
counter-increment: step; content: counter(step) ". ";
|
||||
color: var(--muted); font-weight: 500;
|
||||
}
|
||||
ol.steps li .expect { color: var(--muted); font-size: 0.92rem; }
|
||||
ol.steps li .expect code {
|
||||
font: 0.88em ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 0 4px;
|
||||
}
|
||||
ol.steps li .where { display: grid; gap: 4px; }
|
||||
ol.steps li .where label { font-size: 0.85rem; color: var(--muted); }
|
||||
input[type="text"], textarea {
|
||||
width: 100%; font: inherit; color: var(--fg); background: var(--bg);
|
||||
border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px;
|
||||
}
|
||||
input:focus-visible, textarea:focus-visible, button:focus-visible {
|
||||
outline: 2px solid var(--focus); outline-offset: 1px;
|
||||
}
|
||||
.actions {
|
||||
display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
|
||||
margin-top: 20px; position: sticky; bottom: 0; padding: 12px 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
button {
|
||||
font: inherit; font-weight: 600; cursor: pointer; border-radius: 8px;
|
||||
padding: 8px 14px; border: 1px solid var(--border);
|
||||
background: var(--card); color: var(--fg);
|
||||
}
|
||||
button.primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
|
||||
button:hover { filter: brightness(0.97); }
|
||||
.status { color: var(--muted); font-size: 0.9rem; min-height: 1.4em; }
|
||||
.fallback { margin-top: 12px; }
|
||||
.fallback[hidden] { display: none; }
|
||||
.fallback textarea { min-height: 14rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.85rem; }
|
||||
.note { font-size: 0.85rem; color: var(--muted); margin-top: 16px; }
|
||||
@media (max-width: 520px) {
|
||||
body { padding: 16px 16px 72px; }
|
||||
.meta input[type="text"] { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>QA — Parcours d'accueil</h1>
|
||||
<p class="lead">Première ouverture de Simpl'Résultat jusqu'à la liste de démarrage complète. Coche chaque étape quand elle se passe comme décrit, et note où tu as hésité, même une seconde : c'est l'hésitation qu'on cherche, pas l'échec.</p>
|
||||
|
||||
<div class="meta">
|
||||
<label>Testeur <input type="text" id="tester" placeholder="prénom" autocomplete="off"></label>
|
||||
<label>Version <input type="text" id="version" placeholder="ex. 0.17.0" autocomplete="off"></label>
|
||||
<label>Système <input type="text" id="os" placeholder="Windows 11, Ubuntu 24.04…" autocomplete="off"></label>
|
||||
<span class="progress" id="progress"></span>
|
||||
</div>
|
||||
|
||||
<section class="prereq">
|
||||
<strong>Avant de commencer</strong>
|
||||
<ul>
|
||||
<li>Une installation neuve, ou un profil neuf : la liste de démarrage n'apparaît que dans un profil sans transaction.</li>
|
||||
<li>Pour l'étape Budget, une clé Base ou Premium ; sans clé, l'étape se présente verrouillée « Base et plus » et le total passe à 5 — note-le dans le champ, c'est attendu.</li>
|
||||
<li>La page garde tes réponses dans ce navigateur. Rien n'est envoyé : c'est toi qui colles le rapport dans l'application.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2>Étapes</h2>
|
||||
<ol class="steps" id="steps"></ol>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="primary" id="copy">Copier le rapport</button>
|
||||
<button type="button" id="reset">Réinitialiser</button>
|
||||
<span class="status" id="status" role="status" aria-live="polite"></span>
|
||||
</div>
|
||||
|
||||
<div class="fallback" id="fallback" hidden>
|
||||
<p>Le presse-papiers n'est pas accessible depuis ce fichier : sélectionne le texte ci-dessous (il est déjà sélectionné) et copie-le avec Ctrl+C, puis colle-le dans Paramètres → Systèmes → Rétroaction.</p>
|
||||
<textarea id="report" readonly aria-label="Rapport à copier"></textarea>
|
||||
</div>
|
||||
|
||||
<p class="note">Les noms entre guillemets sont ceux des boutons tels qu'ils apparaissent en français. « Exemple Desjardins » et « Exemple Tangerine » sont des relevés inventés : aucune donnée réelle.</p>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var STORAGE_KEY = "simpl-resultat.qa-onboarding.v1";
|
||||
|
||||
// One row per step of the issue. `expect` is what the tester should see.
|
||||
var STEPS = [
|
||||
{ id: "install", title: "Installer et lancer l'application",
|
||||
expect: "L'installateur s'exécute sans avertissement bloquant et l'application s'ouvre sur la sélection de profil." },
|
||||
{ id: "profile", title: "Créer un profil",
|
||||
expect: "Le profil s'ouvre directement après sa création (NIP facultatif)." },
|
||||
{ id: "dashboard-empty", title: "Tableau de bord vide avec la liste de démarrage à 1 / 6",
|
||||
expect: "Une carte d'accueil remplace les graphiques, la liste montre six lignes, seule « Créer un profil » est cochée, le compteur indique 1 / 6 (ou 1 / 5 sans clé)." },
|
||||
{ id: "cta-folder", title: "Cliquer « Choisir un dossier d'import »",
|
||||
expect: "Le bouton de la carte d'accueil mène à la page Import." },
|
||||
{ id: "import-no-folder", title: "Page Import sans dossier",
|
||||
expect: "Un état vide « Aucun dossier d'import » avec le bouton « Choisir un dossier » et le lien « Comment exporter un CSV depuis ma banque ? »." },
|
||||
{ id: "pick-folder", title: "Cliquer « Choisir un dossier » et prendre un dossier vide",
|
||||
expect: "La fenêtre du système s'ouvre ; une fois le dossier choisi, l'état vide devient « Aucune source trouvée » et nomme le dossier." },
|
||||
{ id: "create-sample", title: "Cliquer « Créer un dossier d'exemple »",
|
||||
expect: "Un message annonce les deux dossiers créés et invite à cliquer sur l'un d'eux. Dans l'explorateur de fichiers, les deux sous-dossiers contiennent un fichier releve-AAAA-MM.csv." },
|
||||
{ id: "two-sources", title: "Deux sources apparaissent",
|
||||
expect: "« Exemple Desjardins » et « Exemple Tangerine » sont listées comme sources, sans actualiser à la main." },
|
||||
{ id: "import-desjardins", title: "Importer « Exemple Desjardins » : aperçu, signes, doublons, rapport",
|
||||
expect: "Le format est détecté (bandeau Desjardins), l'aperçu montre des sorties et des entrées dans le bon sens (pas besoin d'« Inverser les signes »), aucun doublon, puis le rapport d'import indique le nombre de transactions importées." },
|
||||
{ id: "dashboard-compact", title: "Retour au tableau de bord : liste compacte à 4 / 6",
|
||||
expect: "Les graphiques sont là, la liste est devenue une barre compacte au-dessus des filtres et affiche 4 / 6 (ou 4 / 5 sans clé) : dossier, source et premier import sont cochés." },
|
||||
{ id: "manual-category", title: "Transactions : classer « GARDERIE LES PETITS PAS » à la main",
|
||||
expect: "La transaction est sans catégorie après l'import ; lui en donner une à la main la garde classée." },
|
||||
{ id: "five-of-six", title: "Liste à 5 / 6",
|
||||
expect: "De retour sur le tableau de bord, « Classer une transaction à la main » est cochée." },
|
||||
{ id: "budget", title: "Budget (clé Base ou Premium) : saisir un montant",
|
||||
expect: "La page Budget montre la grille (pas l'état vide, puisqu'il y a des transactions) ; un montant saisi dans une cellule est conservé." },
|
||||
{ id: "six-of-six", title: "Liste à 6 / 6, puis disparue",
|
||||
expect: "Le tableau de bord affiche brièvement 6 / 6 ou plus aucune liste : une liste complète ne s'affiche plus." },
|
||||
{ id: "hide-reshow", title: "« Masquer » sur un second profil, puis réaffichage depuis Paramètres",
|
||||
expect: "Créer un second profil (clé Base ou plus) : sa liste est à 1 / 6 ; « Masquer » la retire de ce profil seulement, le premier profil n'est pas touché ; Paramètres → Utilisateurs montre « Afficher la liste de démarrage », qui la ramène." },
|
||||
{ id: "guide-link", title: "Ouvrir le guide « Exporter un CSV depuis votre banque » depuis l'état vide d'Import",
|
||||
expect: "Sur un profil sans dossier ou sans source, le lien de l'état vide ouvre le guide directement sur la sous-section, avec les procédures par banque." }
|
||||
];
|
||||
|
||||
var state = load();
|
||||
|
||||
function load() {
|
||||
var fresh = { meta: { tester: "", version: "", os: "" }, steps: {} };
|
||||
try {
|
||||
var raw = window.localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return fresh;
|
||||
var parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== "object") return fresh;
|
||||
return {
|
||||
meta: Object.assign(fresh.meta, parsed.meta || {}),
|
||||
steps: parsed.steps && typeof parsed.steps === "object" ? parsed.steps : {}
|
||||
};
|
||||
} catch (e) {
|
||||
return fresh;
|
||||
}
|
||||
}
|
||||
|
||||
function save() {
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
} catch (e) {
|
||||
setStatus("Impossible d'enregistrer dans ce navigateur : le rapport reste copiable, mais l'état sera perdu au rechargement.");
|
||||
}
|
||||
}
|
||||
|
||||
function stepState(id) {
|
||||
if (!state.steps[id]) state.steps[id] = { done: false, note: "" };
|
||||
return state.steps[id];
|
||||
}
|
||||
|
||||
function setStatus(text) {
|
||||
document.getElementById("status").textContent = text;
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
var done = 0;
|
||||
STEPS.forEach(function (s) { if (stepState(s.id).done) done += 1; });
|
||||
document.getElementById("progress").textContent = done + " / " + STEPS.length + " étapes cochées";
|
||||
}
|
||||
|
||||
function render() {
|
||||
var list = document.getElementById("steps");
|
||||
list.textContent = "";
|
||||
STEPS.forEach(function (step) {
|
||||
var st = stepState(step.id);
|
||||
var li = document.createElement("li");
|
||||
li.className = st.done ? "done" : "";
|
||||
|
||||
var check = document.createElement("div");
|
||||
check.className = "check";
|
||||
var box = document.createElement("input");
|
||||
box.type = "checkbox";
|
||||
box.id = "done-" + step.id;
|
||||
box.checked = !!st.done;
|
||||
box.addEventListener("change", function () {
|
||||
st.done = box.checked;
|
||||
li.className = st.done ? "done" : "";
|
||||
save();
|
||||
updateProgress();
|
||||
});
|
||||
check.appendChild(box);
|
||||
|
||||
var title = document.createElement("label");
|
||||
title.className = "title";
|
||||
title.htmlFor = box.id;
|
||||
title.textContent = step.title;
|
||||
|
||||
var expect = document.createElement("div");
|
||||
expect.className = "expect";
|
||||
expect.textContent = step.expect;
|
||||
|
||||
var where = document.createElement("div");
|
||||
where.className = "where";
|
||||
var noteLabel = document.createElement("label");
|
||||
noteLabel.htmlFor = "note-" + step.id;
|
||||
noteLabel.textContent = "Où as-tu hésité ?";
|
||||
var note = document.createElement("input");
|
||||
note.type = "text";
|
||||
note.id = noteLabel.htmlFor;
|
||||
note.value = st.note || "";
|
||||
note.autocomplete = "off";
|
||||
note.addEventListener("input", function () {
|
||||
st.note = note.value;
|
||||
save();
|
||||
});
|
||||
where.appendChild(noteLabel);
|
||||
where.appendChild(note);
|
||||
|
||||
li.appendChild(check);
|
||||
li.appendChild(title);
|
||||
li.appendChild(expect);
|
||||
li.appendChild(where);
|
||||
list.appendChild(li);
|
||||
});
|
||||
|
||||
["tester", "version", "os"].forEach(function (key) {
|
||||
var input = document.getElementById(key);
|
||||
input.value = state.meta[key] || "";
|
||||
input.oninput = function () {
|
||||
state.meta[key] = input.value;
|
||||
save();
|
||||
};
|
||||
});
|
||||
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
function pad(n) { return n < 10 ? "0" + n : String(n); }
|
||||
|
||||
function buildReport() {
|
||||
var now = new Date();
|
||||
var date = now.getFullYear() + "-" + pad(now.getMonth() + 1) + "-" + pad(now.getDate()) +
|
||||
" " + pad(now.getHours()) + ":" + pad(now.getMinutes());
|
||||
var done = 0;
|
||||
var lines = [];
|
||||
lines.push("QA parcours d'accueil — " + date);
|
||||
if (state.meta.tester) lines.push("Testeur : " + state.meta.tester);
|
||||
if (state.meta.version) lines.push("Version : " + state.meta.version);
|
||||
if (state.meta.os) lines.push("Système : " + state.meta.os);
|
||||
lines.push("");
|
||||
STEPS.forEach(function (step, i) {
|
||||
var st = stepState(step.id);
|
||||
if (st.done) done += 1;
|
||||
lines.push((st.done ? "[x] " : "[ ] ") + (i + 1) + ". " + step.title);
|
||||
if (st.note && st.note.trim()) lines.push(" Hésitation : " + st.note.trim());
|
||||
});
|
||||
lines.splice(lines.indexOf("") , 0, "Étapes cochées : " + done + " / " + STEPS.length);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function showFallback(text) {
|
||||
var box = document.getElementById("fallback");
|
||||
var area = document.getElementById("report");
|
||||
area.value = text;
|
||||
box.hidden = false;
|
||||
area.focus();
|
||||
area.select();
|
||||
}
|
||||
|
||||
document.getElementById("copy").addEventListener("click", function () {
|
||||
var text = buildReport();
|
||||
document.getElementById("fallback").hidden = true;
|
||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
setStatus("Rapport copié. Colle-le dans Paramètres → Systèmes → Rétroaction.");
|
||||
}, function () {
|
||||
setStatus("Copie automatique refusée : utilise la zone ci-dessous.");
|
||||
showFallback(text);
|
||||
});
|
||||
} else {
|
||||
setStatus("Copie automatique indisponible : utilise la zone ci-dessous.");
|
||||
showFallback(text);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("reset").addEventListener("click", function () {
|
||||
if (!window.confirm("Effacer toutes les cases et tous les commentaires ?")) return;
|
||||
state = { meta: { tester: "", version: "", os: "" }, steps: {} };
|
||||
try { window.localStorage.removeItem(STORAGE_KEY); } catch (e) { /* nothing to remove */ }
|
||||
document.getElementById("fallback").hidden = true;
|
||||
setStatus("Checklist réinitialisée.");
|
||||
render();
|
||||
});
|
||||
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
// dispatches), and never touched by an error — which also leaves the scanned
|
||||
// sources exactly as they were. Plus the local-date helper handed to Rust.
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
// The hook module imports the import services (→ db → tauri plugins). Stub the
|
||||
// connection so importing the module never touches a real database.
|
||||
|
|
@ -111,11 +111,23 @@ describe("wizardReducer — sampleCreated (#356)", () => {
|
|||
});
|
||||
|
||||
describe("formatLocalDate (#356)", () => {
|
||||
// Pin a timezone west of Greenwich: under TZ=UTC (the CI runner) local and
|
||||
// UTC dates coincide, so a `toISOString()` regression would pass unnoticed.
|
||||
const originalTz = process.env.TZ;
|
||||
beforeAll(() => {
|
||||
process.env.TZ = "America/Toronto";
|
||||
});
|
||||
afterAll(() => {
|
||||
if (originalTz === undefined) delete process.env.TZ;
|
||||
else process.env.TZ = originalTz;
|
||||
});
|
||||
|
||||
it("formats the LOCAL calendar date, zero-padded", () => {
|
||||
// Built from local components, so the expectation holds in every timezone —
|
||||
// unlike `toISOString()`, which would shift this evening date to the 6th
|
||||
// east of Greenwich and to the 4th west of it.
|
||||
// in Toronto (04:30Z the next day).
|
||||
expect(formatLocalDate(new Date(2026, 0, 5, 23, 30))).toBe("2026-01-05");
|
||||
expect(new Date(2026, 0, 5, 23, 30).toISOString().slice(0, 10)).not.toBe("2026-01-05");
|
||||
expect(formatLocalDate(new Date(2026, 11, 31, 0, 0))).toBe("2026-12-31");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -841,13 +841,12 @@
|
|||
"Data export and import with optional AES-256 encryption"
|
||||
],
|
||||
"steps": [
|
||||
"On first launch, choose or create a profile — each profile has its own database",
|
||||
"Go to Settings and set your import folder — create one subfolder per bank account",
|
||||
"Place your CSV bank statements in the corresponding subfolder",
|
||||
"Open the Import page and configure your source (column mapping, delimiter, date format)",
|
||||
"Import your transactions, then head to Categories to set up keyword rules",
|
||||
"Use Auto-categorize on the Transactions page to apply your rules in bulk",
|
||||
"Set up your Budget and track progress via Reports"
|
||||
"Create a profile — each profile has its own database",
|
||||
"Choose an import folder — the folder where you will drop your statements, one subfolder per bank account",
|
||||
"Set up an import source — opening a subfolder for the first time detects the file format",
|
||||
"Import a first file — after reading the preview and its signed recap",
|
||||
"Categorize a transaction by hand — the keyword seed does most of the work, a manual correction refines the rest",
|
||||
"Enter a first budget — Base edition and up; then track your progress through Reports"
|
||||
],
|
||||
"tips": [
|
||||
"You can switch between English and French using the language selector in the sidebar",
|
||||
|
|
@ -887,9 +886,13 @@
|
|||
"Budget vs Actual table for the current month (variance in $ and %)",
|
||||
"Stacked bar chart of expenses by category and month",
|
||||
"Adjustable time period selector (default: year to date)",
|
||||
"Context menu (right-click) to hide a category or view its transactions"
|
||||
"Context menu (right-click) to hide a category or view its transactions",
|
||||
"Getting-started checklist for a new profile: six boxes that tick themselves from your data, with a \"Hide\" button"
|
||||
],
|
||||
"steps": [
|
||||
"In a profile without transactions, the dashboard shows a welcome card and the getting-started checklist: create a profile (already ticked), choose an import folder, set up an import source, import a first file, categorize a transaction by hand, enter a first budget. Each unticked row leads to the page where it is done; the Budget box is marked \"Base and up\" in the Free edition and does not count towards the total",
|
||||
"After your first import, the list shrinks to a compact bar above the filters, with its counter (for instance 4 / 6), and disappears on its own once every box is ticked",
|
||||
"\"Hide\" removes the list from this profile only; you can show it again from Settings → Users",
|
||||
"Use the period selector in the top-right to choose a time range (month, 3 months, year, etc.)",
|
||||
"Review the summary cards for your balance, total income, and total expenses",
|
||||
"Check the pie chart to see how your spending is distributed across categories",
|
||||
|
|
@ -901,7 +904,8 @@
|
|||
"The default period is year to date for an annual overview when you open the app",
|
||||
"The balance is calculated as income minus expenses for the selected period",
|
||||
"Hidden categories appear as dismissible chips above the chart — click Show All to restore them",
|
||||
"SVG patterns (lines, dots, crosshatch) help distinguish categories beyond just color"
|
||||
"SVG patterns (lines, dots, crosshatch) help distinguish categories beyond just color",
|
||||
"The checklist boxes tick on your data, not on a click: \"Categorize a transaction by hand\" expects a transaction whose category you changed yourself; the automatic categorization of the import does not tick it"
|
||||
]
|
||||
},
|
||||
"import": {
|
||||
|
|
@ -920,7 +924,7 @@
|
|||
],
|
||||
"steps": [
|
||||
"Set your import folder via the folder picker at the top of the page",
|
||||
"Create a subfolder for each bank/source and place CSV files inside",
|
||||
"Create a subfolder for each bank/source and place CSV files inside — while the folder is empty, the \"Create a sample folder\" button writes two fictitious subfolders (\"Exemple Desjardins\", \"Exemple Tangerine\") so you can try the wizard without waiting for your real statements; delete them afterwards",
|
||||
"Click on a source to open the import wizard — the first time, the format is detected automatically and a banner announces the result",
|
||||
"Check the proposed configuration (delimiter, encoding, date format, column mapping) and correct it if the banner reports an uncertain format",
|
||||
"Select which files to import",
|
||||
|
|
@ -992,7 +996,7 @@
|
|||
{
|
||||
"id": "import-bmo",
|
||||
"title": "BMO",
|
||||
"status": "Procedure reconstructed (provided by Max on 2026-10-03 from a general description), to be confirmed on a real account; file format not recognised yet (#371)",
|
||||
"status": "Procedure reconstructed from a general description, to be confirmed on a real account; file format not recognised yet",
|
||||
"steps": [
|
||||
"Sign in to BMO Online Banking at bmo.com (the website, not the app)",
|
||||
"In the summary, click the account you want (chequing, savings or Mastercard)",
|
||||
|
|
@ -1207,7 +1211,8 @@
|
|||
"Data export (transactions, categories, or both) in JSON or CSV format",
|
||||
"Data import from a previously exported file",
|
||||
"Optional AES-256-GCM encryption for exported files",
|
||||
"Optional feedback submission to feedback.lacompagniemaximus.com (explicit exception to the 100% local operation — prompts for consent)"
|
||||
"Optional feedback submission to feedback.lacompagniemaximus.com (explicit exception to the 100% local operation — prompts for consent)",
|
||||
"Show the dashboard's getting-started checklist again (Settings → Users) once it was hidden"
|
||||
],
|
||||
"steps": [
|
||||
"Click User Guide to access the full documentation",
|
||||
|
|
@ -1216,7 +1221,8 @@
|
|||
"Use the Data Management section to export or import your data",
|
||||
"When exporting, choose what to include and optionally set a password for encryption",
|
||||
"When importing, select a previously exported file — encrypted files will prompt for the password",
|
||||
"Click Send feedback in the Logs section to share a suggestion, comment, or issue — the identify and context/logs checkboxes are unchecked by default"
|
||||
"Click Send feedback in the Logs section to share a suggestion, comment, or issue — the identify and context/logs checkboxes are unchecked by default",
|
||||
"To see the getting-started checklist again after hiding it, open Settings → Users and click \"Show the getting-started checklist\"; it reappears on this profile's dashboard. The button is only visible once the list was hidden"
|
||||
],
|
||||
"tips": [
|
||||
"Updates only replace the app binary — your database is never modified",
|
||||
|
|
|
|||
|
|
@ -841,13 +841,12 @@
|
|||
"Export et import de données avec chiffrement AES-256 optionnel"
|
||||
],
|
||||
"steps": [
|
||||
"Au premier lancement, choisissez ou créez un profil — chaque profil a sa propre base de données",
|
||||
"Allez dans Paramètres et définissez votre dossier d'import — créez un sous-dossier par compte bancaire",
|
||||
"Placez vos relevés bancaires CSV dans le sous-dossier correspondant",
|
||||
"Ouvrez la page Import et configurez votre source (mapping des colonnes, délimiteur, format de date)",
|
||||
"Importez vos transactions, puis allez dans Catégories pour configurer les règles de mots-clés",
|
||||
"Utilisez l'auto-catégorisation sur la page Transactions pour appliquer vos règles en masse",
|
||||
"Configurez votre Budget et suivez votre progression via les Rapports"
|
||||
"Créer un profil — chaque profil a sa propre base de données",
|
||||
"Choisir un dossier d'import — le dossier où vous déposerez vos relevés, un sous-dossier par compte bancaire",
|
||||
"Configurer une source d'import — la première ouverture d'un sous-dossier détecte le format du fichier",
|
||||
"Importer un premier fichier — après avoir lu l'aperçu et son récapitulatif signé",
|
||||
"Classer une transaction à la main — le seed de mots-clés fait le gros du travail, une correction manuelle affine le reste",
|
||||
"Saisir un premier budget — édition Base et plus ; suivez ensuite votre progression via les Rapports"
|
||||
],
|
||||
"tips": [
|
||||
"Vous pouvez basculer entre le français et l'anglais via le sélecteur de langue dans la barre latérale",
|
||||
|
|
@ -887,9 +886,13 @@
|
|||
"Tableau Budget vs Réel du mois courant (écart en $ et %)",
|
||||
"Histogramme empilé des dépenses par catégorie et par mois",
|
||||
"Sélecteur de période ajustable (par défaut : année à ce jour)",
|
||||
"Menu contextuel (clic droit) pour masquer une catégorie ou voir ses transactions"
|
||||
"Menu contextuel (clic droit) pour masquer une catégorie ou voir ses transactions",
|
||||
"Liste de démarrage pour un profil neuf : six cases qui se cochent d'elles-mêmes selon vos données, avec un bouton « Masquer »"
|
||||
],
|
||||
"steps": [
|
||||
"Dans un profil sans transaction, le tableau de bord affiche une carte d'accueil et la liste de démarrage : créer un profil (déjà cochée), choisir un dossier d'import, configurer une source d'import, importer un premier fichier, classer une transaction à la main, saisir un premier budget. Chaque ligne non cochée mène à la page où la faire ; la case Budget est marquée « Base et plus » en édition Gratuite et ne compte pas dans le total",
|
||||
"Après votre premier import, la liste se réduit à une barre compacte au-dessus des filtres, avec son compteur (par exemple 4 / 6), et disparaît d'elle-même quand toutes les cases sont cochées",
|
||||
"« Masquer » retire la liste de ce profil seulement ; vous pouvez la réafficher depuis Paramètres → Utilisateurs",
|
||||
"Utilisez le sélecteur de période en haut à droite pour choisir une plage de temps (mois, 3 mois, année, etc.)",
|
||||
"Consultez les cartes résumées pour votre solde, revenus totaux et dépenses totales",
|
||||
"Vérifiez le graphique circulaire pour voir comment vos dépenses sont réparties par catégorie",
|
||||
|
|
@ -901,7 +904,8 @@
|
|||
"La période par défaut est « année à ce jour » pour un aperçu annuel dès l'ouverture",
|
||||
"Le solde est calculé comme les revenus moins les dépenses pour la période sélectionnée",
|
||||
"Les catégories masquées apparaissent sous forme de pastilles au-dessus du graphique — cliquez sur Tout afficher pour les restaurer",
|
||||
"Les motifs SVG (lignes, points, hachures) aident à distinguer les catégories au-delà des couleurs"
|
||||
"Les motifs SVG (lignes, points, hachures) aident à distinguer les catégories au-delà des couleurs",
|
||||
"Les cases de la liste de démarrage se cochent sur vos données, pas sur un clic : « Classer une transaction à la main » attend une transaction dont vous avez changé la catégorie vous-même, la catégorisation automatique de l'import ne la coche pas"
|
||||
]
|
||||
},
|
||||
"import": {
|
||||
|
|
@ -920,7 +924,7 @@
|
|||
],
|
||||
"steps": [
|
||||
"Définissez votre dossier d'import via le sélecteur de dossier en haut de la page",
|
||||
"Créez un sous-dossier pour chaque banque/source et placez-y les fichiers CSV",
|
||||
"Créez un sous-dossier pour chaque banque/source et placez-y les fichiers CSV — tant que le dossier est vide, le bouton « Créer un dossier d'exemple » dépose deux sous-dossiers fictifs (« Exemple Desjardins », « Exemple Tangerine ») pour essayer l'assistant sans attendre vos vrais relevés ; supprimez-les ensuite",
|
||||
"Cliquez sur une source pour ouvrir l'assistant d'import — la première fois, le format est détecté automatiquement et un bandeau annonce le résultat",
|
||||
"Vérifiez la configuration proposée (délimiteur, encodage, format de date, mapping des colonnes) et corrigez-la si le bandeau signale un format incertain",
|
||||
"Sélectionnez les fichiers à importer",
|
||||
|
|
@ -992,7 +996,7 @@
|
|||
{
|
||||
"id": "import-bmo",
|
||||
"title": "BMO",
|
||||
"status": "Procédure reconstituée (fournie par Max le 2026-10-03, d'après une description générale), à confirmer sur un compte réel ; format de fichier non encore reconnu (#371)",
|
||||
"status": "Procédure reconstituée d'après une description générale, à confirmer sur un compte réel ; format de fichier non encore reconnu",
|
||||
"steps": [
|
||||
"Connectez-vous aux Services bancaires en ligne BMO sur bmo.com (le site, pas l'application)",
|
||||
"Dans le sommaire, cliquez sur le compte voulu (chèques, épargne ou Mastercard)",
|
||||
|
|
@ -1207,7 +1211,8 @@
|
|||
"Export des données (transactions, catégories, ou les deux) en format JSON ou CSV",
|
||||
"Import des données depuis un fichier exporté précédemment",
|
||||
"Chiffrement AES-256-GCM optionnel pour les fichiers exportés",
|
||||
"Envoi de feedback optionnel vers feedback.lacompagniemaximus.com (exception explicite au fonctionnement 100% local — déclenche une demande de consentement)"
|
||||
"Envoi de feedback optionnel vers feedback.lacompagniemaximus.com (exception explicite au fonctionnement 100% local — déclenche une demande de consentement)",
|
||||
"Réaffichage de la liste de démarrage du tableau de bord (Paramètres → Utilisateurs) quand elle a été masquée"
|
||||
],
|
||||
"steps": [
|
||||
"Cliquez sur Guide d'utilisation pour accéder à la documentation complète",
|
||||
|
|
@ -1216,7 +1221,8 @@
|
|||
"Utilisez la section Gestion des données pour exporter ou importer vos données",
|
||||
"Lors de l'export, choisissez ce qu'il faut inclure et définissez optionnellement un mot de passe pour le chiffrement",
|
||||
"Lors de l'import, sélectionnez un fichier exporté précédemment — les fichiers chiffrés demanderont le mot de passe",
|
||||
"Cliquez sur Envoyer un feedback dans la section Journaux pour partager une suggestion, un commentaire ou un problème — la case d'identification et d'envoi du contexte/logs sont décochées par défaut"
|
||||
"Cliquez sur Envoyer un feedback dans la section Journaux pour partager une suggestion, un commentaire ou un problème — la case d'identification et d'envoi du contexte/logs sont décochées par défaut",
|
||||
"Pour revoir la liste de démarrage après l'avoir masquée, ouvrez Paramètres → Utilisateurs et cliquez sur « Afficher la liste de démarrage » ; elle réapparaît sur le tableau de bord de ce profil. Le bouton n'est visible que si la liste a été masquée"
|
||||
],
|
||||
"tips": [
|
||||
"Les mises à jour ne remplacent que le programme — votre base de données n'est jamais modifiée",
|
||||
|
|
|
|||
|
|
@ -109,7 +109,11 @@ export default function DashboardPage() {
|
|||
);
|
||||
}
|
||||
|
||||
const showCompactChecklist = checklistReady && !onboarding.hidden && !checklist.complete;
|
||||
// `complete` is computed with the fail-closed entitlement (Budget locked → 5/5)
|
||||
// until the licence resolves; gate on `budgetReady` so a Base+ profile with the
|
||||
// five other items done does not see the list vanish, then reappear as 5/6.
|
||||
const showCompactChecklist =
|
||||
checklistReady && !onboarding.hidden && !(onboarding.budgetReady && checklist.complete);
|
||||
|
||||
return (
|
||||
<div className={isLoading ? "opacity-50 pointer-events-none" : ""}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue