From 6454c10e63e83073253184a9d9853418473a5e00 Mon Sep 17 00:00:00 2001 From: Michael Clark <5285928+MikesGlitch@users.noreply.github.com> Date: Mon, 2 Sep 2024 17:23:22 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=90=9B=20=20Fix=20tooltip=20when=20us?= =?UTF-8?q?ing=20touch=20devices=20(#3342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix tooltip when using touch devices * release notes --- packages/desktop-client/src/components/common/Tooltip.tsx | 4 ++-- upcoming-release-notes/3342.md | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 upcoming-release-notes/3342.md diff --git a/packages/desktop-client/src/components/common/Tooltip.tsx b/packages/desktop-client/src/components/common/Tooltip.tsx index 6b6e3d28d5f..62a5e4614e6 100644 --- a/packages/desktop-client/src/components/common/Tooltip.tsx +++ b/packages/desktop-client/src/components/common/Tooltip.tsx @@ -54,8 +54,8 @@ export const Tooltip = ({ Date: Tue, 3 Sep 2024 18:02:45 +0100 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=94=96=20(24.9.0)=20(#3348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/package.json | 2 +- packages/desktop-client/package.json | 2 +- packages/desktop-electron/package.json | 2 +- upcoming-release-notes/2892.md | 6 ------ upcoming-release-notes/2923.md | 6 ------ upcoming-release-notes/2970.md | 6 ------ upcoming-release-notes/2974.md | 6 ------ upcoming-release-notes/2984.md | 6 ------ upcoming-release-notes/2991.md | 6 ------ upcoming-release-notes/3018.md | 6 ------ upcoming-release-notes/3036.md | 6 ------ upcoming-release-notes/3044.md | 6 ------ upcoming-release-notes/3093.md | 6 ------ upcoming-release-notes/3095.md | 6 ------ upcoming-release-notes/3097.md | 6 ------ upcoming-release-notes/3111.md | 6 ------ upcoming-release-notes/3114.md | 6 ------ upcoming-release-notes/3115.md | 6 ------ upcoming-release-notes/3122.md | 6 ------ upcoming-release-notes/3140.md | 6 ------ upcoming-release-notes/3156.md | 6 ------ upcoming-release-notes/3159.md | 6 ------ upcoming-release-notes/3166.md | 6 ------ upcoming-release-notes/3178.md | 6 ------ upcoming-release-notes/3180.md | 6 ------ upcoming-release-notes/3181.md | 6 ------ upcoming-release-notes/3183.md | 6 ------ upcoming-release-notes/3185.md | 6 ------ upcoming-release-notes/3186.md | 6 ------ upcoming-release-notes/3188.md | 6 ------ upcoming-release-notes/3198.md | 6 ------ upcoming-release-notes/3200.md | 6 ------ upcoming-release-notes/3203.md | 6 ------ upcoming-release-notes/3205.md | 6 ------ upcoming-release-notes/3206.md | 6 ------ upcoming-release-notes/3209.md | 6 ------ upcoming-release-notes/3212.md | 6 ------ upcoming-release-notes/3215.md | 6 ------ upcoming-release-notes/3219.md | 6 ------ upcoming-release-notes/3220.md | 6 ------ upcoming-release-notes/3221.md | 6 ------ upcoming-release-notes/3231.md | 6 ------ upcoming-release-notes/3232.md | 6 ------ upcoming-release-notes/3234.md | 6 ------ upcoming-release-notes/3236.md | 6 ------ upcoming-release-notes/3237.md | 6 ------ upcoming-release-notes/3238.md | 6 ------ upcoming-release-notes/3239.md | 6 ------ upcoming-release-notes/3241.md | 6 ------ upcoming-release-notes/3242.md | 6 ------ upcoming-release-notes/3246.md | 6 ------ upcoming-release-notes/3250.md | 6 ------ upcoming-release-notes/3251.md | 6 ------ upcoming-release-notes/3257.md | 6 ------ upcoming-release-notes/3258.md | 6 ------ upcoming-release-notes/3262.md | 6 ------ upcoming-release-notes/3270.md | 6 ------ upcoming-release-notes/3271.md | 6 ------ upcoming-release-notes/3275.md | 6 ------ upcoming-release-notes/3278.md | 6 ------ upcoming-release-notes/3279.md | 6 ------ upcoming-release-notes/3280.md | 6 ------ upcoming-release-notes/3283.md | 6 ------ upcoming-release-notes/3284.md | 6 ------ upcoming-release-notes/3285.md | 6 ------ upcoming-release-notes/3287.md | 6 ------ upcoming-release-notes/3289.md | 6 ------ upcoming-release-notes/3290.md | 6 ------ upcoming-release-notes/3295.md | 6 ------ upcoming-release-notes/3296.md | 6 ------ upcoming-release-notes/3299.md | 6 ------ upcoming-release-notes/3300.md | 6 ------ upcoming-release-notes/3302.md | 6 ------ upcoming-release-notes/3308.md | 6 ------ upcoming-release-notes/3318.md | 6 ------ upcoming-release-notes/3323.md | 6 ------ upcoming-release-notes/3324.md | 6 ------ upcoming-release-notes/3333.md | 6 ------ upcoming-release-notes/3337.md | 6 ------ upcoming-release-notes/3338.md | 6 ------ upcoming-release-notes/3340.md | 6 ------ upcoming-release-notes/3342.md | 6 ------ 82 files changed, 3 insertions(+), 477 deletions(-) delete mode 100644 upcoming-release-notes/2892.md delete mode 100644 upcoming-release-notes/2923.md delete mode 100644 upcoming-release-notes/2970.md delete mode 100644 upcoming-release-notes/2974.md delete mode 100644 upcoming-release-notes/2984.md delete mode 100644 upcoming-release-notes/2991.md delete mode 100644 upcoming-release-notes/3018.md delete mode 100644 upcoming-release-notes/3036.md delete mode 100644 upcoming-release-notes/3044.md delete mode 100644 upcoming-release-notes/3093.md delete mode 100644 upcoming-release-notes/3095.md delete mode 100644 upcoming-release-notes/3097.md delete mode 100644 upcoming-release-notes/3111.md delete mode 100644 upcoming-release-notes/3114.md delete mode 100644 upcoming-release-notes/3115.md delete mode 100644 upcoming-release-notes/3122.md delete mode 100644 upcoming-release-notes/3140.md delete mode 100644 upcoming-release-notes/3156.md delete mode 100644 upcoming-release-notes/3159.md delete mode 100644 upcoming-release-notes/3166.md delete mode 100644 upcoming-release-notes/3178.md delete mode 100644 upcoming-release-notes/3180.md delete mode 100644 upcoming-release-notes/3181.md delete mode 100644 upcoming-release-notes/3183.md delete mode 100644 upcoming-release-notes/3185.md delete mode 100644 upcoming-release-notes/3186.md delete mode 100644 upcoming-release-notes/3188.md delete mode 100644 upcoming-release-notes/3198.md delete mode 100644 upcoming-release-notes/3200.md delete mode 100644 upcoming-release-notes/3203.md delete mode 100644 upcoming-release-notes/3205.md delete mode 100644 upcoming-release-notes/3206.md delete mode 100644 upcoming-release-notes/3209.md delete mode 100644 upcoming-release-notes/3212.md delete mode 100644 upcoming-release-notes/3215.md delete mode 100644 upcoming-release-notes/3219.md delete mode 100644 upcoming-release-notes/3220.md delete mode 100644 upcoming-release-notes/3221.md delete mode 100644 upcoming-release-notes/3231.md delete mode 100644 upcoming-release-notes/3232.md delete mode 100644 upcoming-release-notes/3234.md delete mode 100644 upcoming-release-notes/3236.md delete mode 100644 upcoming-release-notes/3237.md delete mode 100644 upcoming-release-notes/3238.md delete mode 100644 upcoming-release-notes/3239.md delete mode 100644 upcoming-release-notes/3241.md delete mode 100644 upcoming-release-notes/3242.md delete mode 100644 upcoming-release-notes/3246.md delete mode 100644 upcoming-release-notes/3250.md delete mode 100644 upcoming-release-notes/3251.md delete mode 100644 upcoming-release-notes/3257.md delete mode 100644 upcoming-release-notes/3258.md delete mode 100644 upcoming-release-notes/3262.md delete mode 100644 upcoming-release-notes/3270.md delete mode 100644 upcoming-release-notes/3271.md delete mode 100644 upcoming-release-notes/3275.md delete mode 100644 upcoming-release-notes/3278.md delete mode 100644 upcoming-release-notes/3279.md delete mode 100644 upcoming-release-notes/3280.md delete mode 100644 upcoming-release-notes/3283.md delete mode 100644 upcoming-release-notes/3284.md delete mode 100644 upcoming-release-notes/3285.md delete mode 100644 upcoming-release-notes/3287.md delete mode 100644 upcoming-release-notes/3289.md delete mode 100644 upcoming-release-notes/3290.md delete mode 100644 upcoming-release-notes/3295.md delete mode 100644 upcoming-release-notes/3296.md delete mode 100644 upcoming-release-notes/3299.md delete mode 100644 upcoming-release-notes/3300.md delete mode 100644 upcoming-release-notes/3302.md delete mode 100644 upcoming-release-notes/3308.md delete mode 100644 upcoming-release-notes/3318.md delete mode 100644 upcoming-release-notes/3323.md delete mode 100644 upcoming-release-notes/3324.md delete mode 100644 upcoming-release-notes/3333.md delete mode 100644 upcoming-release-notes/3337.md delete mode 100644 upcoming-release-notes/3338.md delete mode 100644 upcoming-release-notes/3340.md delete mode 100644 upcoming-release-notes/3342.md diff --git a/packages/api/package.json b/packages/api/package.json index 35df18ce32b..5873f406805 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@actual-app/api", - "version": "6.9.0", + "version": "6.10.0", "license": "MIT", "description": "An API for Actual", "engines": { diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 57a8c952bbc..da2b2e6790f 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -1,6 +1,6 @@ { "name": "@actual-app/web", - "version": "24.8.0", + "version": "24.9.0", "license": "MIT", "files": [ "build" diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 49d3f64c675..c561de77249 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -3,7 +3,7 @@ "author": "Actual", "productName": "Actual", "description": "A simple and powerful personal finance system", - "version": "24.8.3", + "version": "24.9.0", "scripts": { "clean": "rm -rf dist", "update-client": "bin/update-client", diff --git a/upcoming-release-notes/2892.md b/upcoming-release-notes/2892.md deleted file mode 100644 index 41392a9cef6..00000000000 --- a/upcoming-release-notes/2892.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Features -authors: [joel-jeremy] ---- - -Long press transactions in mobile account view to reveal action bar with more actions. diff --git a/upcoming-release-notes/2923.md b/upcoming-release-notes/2923.md deleted file mode 100644 index 3fffdc22d4d..00000000000 --- a/upcoming-release-notes/2923.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [jfdoming] ---- - -Show split transactions in schedule previews. diff --git a/upcoming-release-notes/2970.md b/upcoming-release-notes/2970.md deleted file mode 100644 index 43d444d73fb..00000000000 --- a/upcoming-release-notes/2970.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [scivarolo] ---- - -Fix false positives for duplicate filters error when saving a new filter. diff --git a/upcoming-release-notes/2974.md b/upcoming-release-notes/2974.md deleted file mode 100644 index ec647222032..00000000000 --- a/upcoming-release-notes/2974.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [psybers] ---- - -Fix: Automatically focus inputs, or the primary button, in modals. diff --git a/upcoming-release-notes/2984.md b/upcoming-release-notes/2984.md deleted file mode 100644 index 9dae3c74012..00000000000 --- a/upcoming-release-notes/2984.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [joel-jeremy] ---- - -Use new react-aria-components based Button on sidebar, notifications, transactions, recurring schedule picker, etc. diff --git a/upcoming-release-notes/2991.md b/upcoming-release-notes/2991.md deleted file mode 100644 index 3681a1e521d..00000000000 --- a/upcoming-release-notes/2991.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [ttlgeek, strazto, pmoon00] ---- - -Prevent transaction deduplication for imported transactions diff --git a/upcoming-release-notes/3018.md b/upcoming-release-notes/3018.md deleted file mode 100644 index 320c1a0fc01..00000000000 --- a/upcoming-release-notes/3018.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [matt-fidd] ---- - -Add imported payee tooltip to transaction tables diff --git a/upcoming-release-notes/3036.md b/upcoming-release-notes/3036.md deleted file mode 100644 index d0eb9ba00cb..00000000000 --- a/upcoming-release-notes/3036.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [julianwachholz] ---- - -Introduce i18n framework to prepare for translations. diff --git a/upcoming-release-notes/3044.md b/upcoming-release-notes/3044.md deleted file mode 100644 index bf910bfb4b7..00000000000 --- a/upcoming-release-notes/3044.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [youngcw, wdpk] ---- - -Fix decimal comma parsing for ofx files diff --git a/upcoming-release-notes/3093.md b/upcoming-release-notes/3093.md deleted file mode 100644 index 10a21f26ecc..00000000000 --- a/upcoming-release-notes/3093.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [jfdoming] ---- - -Support type-checking on spreadsheet fields (part 1) diff --git a/upcoming-release-notes/3095.md b/upcoming-release-notes/3095.md deleted file mode 100644 index d31dd21d246..00000000000 --- a/upcoming-release-notes/3095.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [jfdoming] ---- - -Support type-checking on spreadsheet fields (part 2) diff --git a/upcoming-release-notes/3097.md b/upcoming-release-notes/3097.md deleted file mode 100644 index 46315010044..00000000000 --- a/upcoming-release-notes/3097.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [jfdoming] ---- - -Support type-checking on spreadsheet fields (part 3) diff --git a/upcoming-release-notes/3111.md b/upcoming-release-notes/3111.md deleted file mode 100644 index 5faa6916c20..00000000000 --- a/upcoming-release-notes/3111.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Highlight current month in budgets. diff --git a/upcoming-release-notes/3114.md b/upcoming-release-notes/3114.md deleted file mode 100644 index b77be48569f..00000000000 --- a/upcoming-release-notes/3114.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [jfdoming] ---- - -Disable typography linter in tests diff --git a/upcoming-release-notes/3115.md b/upcoming-release-notes/3115.md deleted file mode 100644 index fd2b7fa1fd9..00000000000 --- a/upcoming-release-notes/3115.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [matt-fidd] ---- - -Hide the target category from the cover overspending category list diff --git a/upcoming-release-notes/3122.md b/upcoming-release-notes/3122.md deleted file mode 100644 index b60b2e12dcf..00000000000 --- a/upcoming-release-notes/3122.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [alcroito] ---- - -Shorten hidden category names imported from YNAB4. diff --git a/upcoming-release-notes/3140.md b/upcoming-release-notes/3140.md deleted file mode 100644 index badd3c96ae2..00000000000 --- a/upcoming-release-notes/3140.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [rodriguestiago0] ---- - -Add `reset-hold` and `hold-for-next-month` methods to the API diff --git a/upcoming-release-notes/3156.md b/upcoming-release-notes/3156.md deleted file mode 100644 index 3d7c8823366..00000000000 --- a/upcoming-release-notes/3156.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [joel-jeremy] ---- - -Use new react-aria-components based Button on desktop and mobile budget pages. diff --git a/upcoming-release-notes/3159.md b/upcoming-release-notes/3159.md deleted file mode 100644 index 5694a09958c..00000000000 --- a/upcoming-release-notes/3159.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [joel-jeremy] ---- - -Use new react-aria-components based Button on reports page. diff --git a/upcoming-release-notes/3166.md b/upcoming-release-notes/3166.md deleted file mode 100644 index a14093b7c79..00000000000 --- a/upcoming-release-notes/3166.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [carkom] ---- - -Tweaking the UI of spending report to make it more consistent with other reports. diff --git a/upcoming-release-notes/3178.md b/upcoming-release-notes/3178.md deleted file mode 100644 index 7a5c23de418..00000000000 --- a/upcoming-release-notes/3178.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MatissJanis] ---- - -Custom reports: unify `selectedCategories` and `conditions` data source. diff --git a/upcoming-release-notes/3180.md b/upcoming-release-notes/3180.md deleted file mode 100644 index 1af6363525d..00000000000 --- a/upcoming-release-notes/3180.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MatissJanis] ---- - -TypeScript: make category and rule entities stricter. diff --git a/upcoming-release-notes/3181.md b/upcoming-release-notes/3181.md deleted file mode 100644 index 1353b8f8ba4..00000000000 --- a/upcoming-release-notes/3181.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [TimQuelch] ---- - -Update option name of experimental Monthly Spending Report diff --git a/upcoming-release-notes/3183.md b/upcoming-release-notes/3183.md deleted file mode 100644 index 7d783c71e90..00000000000 --- a/upcoming-release-notes/3183.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [ACWalker] ---- - -Add unit tests for the existing goal template types. diff --git a/upcoming-release-notes/3185.md b/upcoming-release-notes/3185.md deleted file mode 100644 index 4c82469281b..00000000000 --- a/upcoming-release-notes/3185.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MikesGlitch] ---- - -Package Electron app as Appx for use in the Windows Store. diff --git a/upcoming-release-notes/3186.md b/upcoming-release-notes/3186.md deleted file mode 100644 index 7d06c3c91cb..00000000000 --- a/upcoming-release-notes/3186.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MatissJanis] ---- - -Improve VRT test stability. diff --git a/upcoming-release-notes/3188.md b/upcoming-release-notes/3188.md deleted file mode 100644 index 6ce11140b21..00000000000 --- a/upcoming-release-notes/3188.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Filter by account when linking schedules and add shortcut "S" to link schedule. diff --git a/upcoming-release-notes/3198.md b/upcoming-release-notes/3198.md deleted file mode 100644 index 0e682ecc04e..00000000000 --- a/upcoming-release-notes/3198.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MatissJanis] ---- - -Reports: improve `useReports` data fetching hook to return the loading state. diff --git a/upcoming-release-notes/3200.md b/upcoming-release-notes/3200.md deleted file mode 100644 index ea72dae5cb1..00000000000 --- a/upcoming-release-notes/3200.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MatissJanis] ---- - -Reports: add `showTooltip` prop for controlling tooltip visibility. diff --git a/upcoming-release-notes/3203.md b/upcoming-release-notes/3203.md deleted file mode 100644 index 1b0f473f501..00000000000 --- a/upcoming-release-notes/3203.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [spalmurray] ---- - -Identify Payee and Notes fields by name if they exist in CSV import diff --git a/upcoming-release-notes/3205.md b/upcoming-release-notes/3205.md deleted file mode 100644 index 547801e3083..00000000000 --- a/upcoming-release-notes/3205.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [matt-fidd] ---- - -Fix typo in error message diff --git a/upcoming-release-notes/3206.md b/upcoming-release-notes/3206.md deleted file mode 100644 index bdd4a5df294..00000000000 --- a/upcoming-release-notes/3206.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [psybers] ---- - -Fix mobile account status indicators cutting off. diff --git a/upcoming-release-notes/3209.md b/upcoming-release-notes/3209.md deleted file mode 100644 index 43c7d6d7aea..00000000000 --- a/upcoming-release-notes/3209.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [carkom] ---- - -Adjusting UI so that spending report works on mobile. diff --git a/upcoming-release-notes/3212.md b/upcoming-release-notes/3212.md deleted file mode 100644 index 21391953f91..00000000000 --- a/upcoming-release-notes/3212.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MikesGlitch] ---- - -Fix gocardless "Linking back account" integration in Desktop app. diff --git a/upcoming-release-notes/3215.md b/upcoming-release-notes/3215.md deleted file mode 100644 index 85523f4c903..00000000000 --- a/upcoming-release-notes/3215.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Add rule actions to prepend/append to transaction notes. diff --git a/upcoming-release-notes/3219.md b/upcoming-release-notes/3219.md deleted file mode 100644 index 89d32e4f353..00000000000 --- a/upcoming-release-notes/3219.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MikesGlitch] ---- - -Making Server logs visible in devtools on Electron diff --git a/upcoming-release-notes/3220.md b/upcoming-release-notes/3220.md deleted file mode 100644 index 2baac8d1398..00000000000 --- a/upcoming-release-notes/3220.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MikesGlitch] ---- - -Fix electron builds throwing "We had an unknown problem opening file" diff --git a/upcoming-release-notes/3221.md b/upcoming-release-notes/3221.md deleted file mode 100644 index 6328751b22e..00000000000 --- a/upcoming-release-notes/3221.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [ACWalker] ---- - -Extract, refactor and test note handling logic from `goaltemplates.ts` file. diff --git a/upcoming-release-notes/3231.md b/upcoming-release-notes/3231.md deleted file mode 100644 index 38245cc9058..00000000000 --- a/upcoming-release-notes/3231.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Features -authors: [MatissJanis] ---- - -Customizable dashboard for reports page - drag-able and resizable widgets. diff --git a/upcoming-release-notes/3232.md b/upcoming-release-notes/3232.md deleted file mode 100644 index 4cbbb00b51e..00000000000 --- a/upcoming-release-notes/3232.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [pmoon00] ---- - -Fix import transaction issue introduced by strict id checking feature diff --git a/upcoming-release-notes/3234.md b/upcoming-release-notes/3234.md deleted file mode 100644 index 12dafb98122..00000000000 --- a/upcoming-release-notes/3234.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Features -authors: [Horizon0156] ---- - -Added an optional configuration value to skip one or more heading lines (added by some banks, like ING) during the CSV transactions import. \ No newline at end of file diff --git a/upcoming-release-notes/3236.md b/upcoming-release-notes/3236.md deleted file mode 100644 index 4df9a67999e..00000000000 --- a/upcoming-release-notes/3236.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [Matissjanis] ---- - -Separate `LocalPrefs` interface out into `LocalPrefs` (eventually using local storage), `SyncedPrefs` (eventually using the cross-device database) and `MetadataPrefs` (eventually using the `metadata.json` file). diff --git a/upcoming-release-notes/3237.md b/upcoming-release-notes/3237.md deleted file mode 100644 index 9f4a10697e6..00000000000 --- a/upcoming-release-notes/3237.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [jfdoming] ---- - -Fix crash when visiting later months diff --git a/upcoming-release-notes/3238.md b/upcoming-release-notes/3238.md deleted file mode 100644 index 9ffe939d899..00000000000 --- a/upcoming-release-notes/3238.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [jfdoming] ---- - -Remove some `any` types from the API diff --git a/upcoming-release-notes/3239.md b/upcoming-release-notes/3239.md deleted file mode 100644 index 45cbcf6434e..00000000000 --- a/upcoming-release-notes/3239.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [jfdoming] ---- - -Fix transfer category in temporary transactions diff --git a/upcoming-release-notes/3241.md b/upcoming-release-notes/3241.md deleted file mode 100644 index 8da4780f026..00000000000 --- a/upcoming-release-notes/3241.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [JL102] ---- - -Fixed category appearing in last slot when you drag it to the second-to-last slot diff --git a/upcoming-release-notes/3242.md b/upcoming-release-notes/3242.md deleted file mode 100644 index ccd0bd01f3e..00000000000 --- a/upcoming-release-notes/3242.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [julianwachholz] ---- - -Fixed translation keys being shown verbatim without interpolation diff --git a/upcoming-release-notes/3246.md b/upcoming-release-notes/3246.md deleted file mode 100644 index 6a03af438ff..00000000000 --- a/upcoming-release-notes/3246.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Allow escaping tags with double ##. diff --git a/upcoming-release-notes/3250.md b/upcoming-release-notes/3250.md deleted file mode 100644 index 454d994318f..00000000000 --- a/upcoming-release-notes/3250.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MikesGlitch] ---- - -Fix Export on Mac desktop app diff --git a/upcoming-release-notes/3251.md b/upcoming-release-notes/3251.md deleted file mode 100644 index 37997163292..00000000000 --- a/upcoming-release-notes/3251.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [eireksten] ---- - -Fix issue with importing transactions failing on new accounts (issue #3211). diff --git a/upcoming-release-notes/3257.md b/upcoming-release-notes/3257.md deleted file mode 100644 index 5588203d107..00000000000 --- a/upcoming-release-notes/3257.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [carkom] ---- - -Fix regression in button color for spending graph. \ No newline at end of file diff --git a/upcoming-release-notes/3258.md b/upcoming-release-notes/3258.md deleted file mode 100644 index 17d64d3438d..00000000000 --- a/upcoming-release-notes/3258.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [jfdoming] ---- - -Show category for on-to-off-budget transfers diff --git a/upcoming-release-notes/3262.md b/upcoming-release-notes/3262.md deleted file mode 100644 index 51cfaaee1cb..00000000000 --- a/upcoming-release-notes/3262.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [Matissjanis] ---- - -Cleanup `iterableTopologicalSort` feature flag. diff --git a/upcoming-release-notes/3270.md b/upcoming-release-notes/3270.md deleted file mode 100644 index 85a2c1b255e..00000000000 --- a/upcoming-release-notes/3270.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Support translations in desktop-client/components/filters. diff --git a/upcoming-release-notes/3271.md b/upcoming-release-notes/3271.md deleted file mode 100644 index 6089872efda..00000000000 --- a/upcoming-release-notes/3271.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Features -authors: [julianwachholz] ---- - -Update README to add Weblate project, a crowd-sourced translation tool. diff --git a/upcoming-release-notes/3275.md b/upcoming-release-notes/3275.md deleted file mode 100644 index e74cc174436..00000000000 --- a/upcoming-release-notes/3275.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Support translations in desktop-client/components/autocomplete. diff --git a/upcoming-release-notes/3278.md b/upcoming-release-notes/3278.md deleted file mode 100644 index 49cbfcaf6e3..00000000000 --- a/upcoming-release-notes/3278.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [lelemm] ---- - -Filter fix when alternating all <-> any diff --git a/upcoming-release-notes/3279.md b/upcoming-release-notes/3279.md deleted file mode 100644 index c72004b85c1..00000000000 --- a/upcoming-release-notes/3279.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [matt-fidd] ---- - -Optimise GoCardless sync to reduce API usage by removing balance information when unneeded diff --git a/upcoming-release-notes/3280.md b/upcoming-release-notes/3280.md deleted file mode 100644 index b71a90deeef..00000000000 --- a/upcoming-release-notes/3280.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Support translations in desktop-client/components/budget/report. diff --git a/upcoming-release-notes/3283.md b/upcoming-release-notes/3283.md deleted file mode 100644 index c140ab9d227..00000000000 --- a/upcoming-release-notes/3283.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [MatissJanis] ---- - -Added feedback links besides the experimental feature flags. diff --git a/upcoming-release-notes/3284.md b/upcoming-release-notes/3284.md deleted file mode 100644 index c6738ce3165..00000000000 --- a/upcoming-release-notes/3284.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [Matissjanis] ---- - -Dashboards: ability to rename all the widgets. diff --git a/upcoming-release-notes/3285.md b/upcoming-release-notes/3285.md deleted file mode 100644 index fa0c39c0997..00000000000 --- a/upcoming-release-notes/3285.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [Matissjanis] ---- - -TypeScript: migrate report cards to TS. diff --git a/upcoming-release-notes/3287.md b/upcoming-release-notes/3287.md deleted file mode 100644 index a1b272403c4..00000000000 --- a/upcoming-release-notes/3287.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [psybers, jameshurst] ---- - -Apply regular expression conditions to imported transactions. diff --git a/upcoming-release-notes/3289.md b/upcoming-release-notes/3289.md deleted file mode 100644 index 3af5a6040eb..00000000000 --- a/upcoming-release-notes/3289.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MatissJanis] ---- - -Upgrade `TypeScript`, `eslint` and `prettier`. diff --git a/upcoming-release-notes/3290.md b/upcoming-release-notes/3290.md deleted file mode 100644 index 5cbcce408cd..00000000000 --- a/upcoming-release-notes/3290.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [lelemm] ---- - -Add new 'has tag(s)' filter to filter note tags. diff --git a/upcoming-release-notes/3295.md b/upcoming-release-notes/3295.md deleted file mode 100644 index 6a69f4a575b..00000000000 --- a/upcoming-release-notes/3295.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [Crazypkr1099] ---- - -Fix incorrect month on spendingcard diff --git a/upcoming-release-notes/3296.md b/upcoming-release-notes/3296.md deleted file mode 100644 index 335dab70389..00000000000 --- a/upcoming-release-notes/3296.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [psybers] ---- - -Better debug logs for bank sync errors. diff --git a/upcoming-release-notes/3299.md b/upcoming-release-notes/3299.md deleted file mode 100644 index 713830845b2..00000000000 --- a/upcoming-release-notes/3299.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Support translations in desktop-client/components/reports/graphs. diff --git a/upcoming-release-notes/3300.md b/upcoming-release-notes/3300.md deleted file mode 100644 index 36eb58a2850..00000000000 --- a/upcoming-release-notes/3300.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MikesGlitch] ---- - -Sign the Mac desktop app to resolve damaged file errors diff --git a/upcoming-release-notes/3302.md b/upcoming-release-notes/3302.md deleted file mode 100644 index cf5de6fcd38..00000000000 --- a/upcoming-release-notes/3302.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Enhancements -authors: [psybers] ---- - -Support translations in desktop-client/components/sidebar. diff --git a/upcoming-release-notes/3308.md b/upcoming-release-notes/3308.md deleted file mode 100644 index e2b62d70c42..00000000000 --- a/upcoming-release-notes/3308.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Maintenance -authors: [MikesGlitch] ---- - -Support servers with self signed certificates in the Desktop app diff --git a/upcoming-release-notes/3318.md b/upcoming-release-notes/3318.md deleted file mode 100644 index 7dbb8e41cb7..00000000000 --- a/upcoming-release-notes/3318.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [qedi-r] ---- - -Fix display of deleted payees in suggested payee list diff --git a/upcoming-release-notes/3323.md b/upcoming-release-notes/3323.md deleted file mode 100644 index 8807a5a0998..00000000000 --- a/upcoming-release-notes/3323.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MatissJanis] ---- - -Dashboards: add back spending report if dashboards are not enabled diff --git a/upcoming-release-notes/3324.md b/upcoming-release-notes/3324.md deleted file mode 100644 index e72d02a5761..00000000000 --- a/upcoming-release-notes/3324.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MatissJanis] ---- - -Fix "s" hotkey breaking in transaction table. diff --git a/upcoming-release-notes/3333.md b/upcoming-release-notes/3333.md deleted file mode 100644 index 9af85abeb24..00000000000 --- a/upcoming-release-notes/3333.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [tim-smart] ---- - -Fix toggleSpentColumn being called on every render on mobile diff --git a/upcoming-release-notes/3337.md b/upcoming-release-notes/3337.md deleted file mode 100644 index 9079c082c76..00000000000 --- a/upcoming-release-notes/3337.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MatissJanis] ---- - -Fix schedules modal closing when selecting transactions to link. diff --git a/upcoming-release-notes/3338.md b/upcoming-release-notes/3338.md deleted file mode 100644 index 7460cfc33b1..00000000000 --- a/upcoming-release-notes/3338.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MatissJanis] ---- - -Fix reconciliation closing on `enter` click. diff --git a/upcoming-release-notes/3340.md b/upcoming-release-notes/3340.md deleted file mode 100644 index 0d29a8deb82..00000000000 --- a/upcoming-release-notes/3340.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MatissJanis] ---- - -Fix long payee names overflowing in transaction table. diff --git a/upcoming-release-notes/3342.md b/upcoming-release-notes/3342.md deleted file mode 100644 index d145087ab8d..00000000000 --- a/upcoming-release-notes/3342.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -category: Bugfix -authors: [MikesGlitch] ---- - -Prevent tooltips showing on budget notes when using touch devices From 1aa65946c2e57f94c265a1c52ff235ade36df162 Mon Sep 17 00:00:00 2001 From: Matt Fiddaman Date: Tue, 3 Sep 2024 19:20:55 +0100 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=8C=8D=20add=20translations=20for=20`?= =?UTF-8?q?desktop-client/components/accounts`=20(#3277)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add translations for `desktop-client/components/accounts` * release note * fix lint * fix quotes * feedback * Update 3277.md --- .../src/components/accounts/Account.jsx | 38 ++++++++---- .../components/accounts/AccountSyncCheck.jsx | 43 +++++++++----- .../src/components/accounts/Balance.jsx | 15 +++-- .../src/components/accounts/Header.jsx | 59 +++++++++++-------- .../src/components/accounts/Reconcile.jsx | 44 +++++++++----- upcoming-release-notes/3277.md | 6 ++ 6 files changed, 134 insertions(+), 71 deletions(-) create mode 100644 upcoming-release-notes/3277.md diff --git a/packages/desktop-client/src/components/accounts/Account.jsx b/packages/desktop-client/src/components/accounts/Account.jsx index b946bcbabae..ece252ea1b1 100644 --- a/packages/desktop-client/src/components/accounts/Account.jsx +++ b/packages/desktop-client/src/components/accounts/Account.jsx @@ -1,8 +1,10 @@ import React, { PureComponent, createRef, useMemo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { Navigate, useParams, useLocation } from 'react-router-dom'; import { debounce } from 'debounce'; +import { t } from 'i18next'; import { v4 as uuidv4 } from 'uuid'; import { validForTransfer } from 'loot-core/client/transfer'; @@ -70,9 +72,11 @@ function EmptyMessage({ onAdd }) { }} > - For Actual to be useful, you need to add an account. - You can link an account to automatically download transactions, or - manage it locally yourself. + + For Actual to be useful, you need to add an account + . You can link an account to automatically download transactions, or + manage it locally yourself. + - In the future, you can add accounts from the sidebar. + In the future, you can add accounts from the sidebar. @@ -456,6 +460,8 @@ class AccountInternal extends PureComponent { }; onImport = async () => { + const { t } = useTranslation(); + const accountId = this.props.accountId; const account = this.props.accounts.find(acct => acct.id === accountId); const categories = await this.props.getCategories(); @@ -464,7 +470,7 @@ class AccountInternal extends PureComponent { const res = await window.Actual?.openFileDialog({ filters: [ { - name: 'Financial Files', + name: t('Financial Files'), extensions: ['qif', 'ofx', 'qfx', 'csv', 'tsv', 'xml'], }, ], @@ -486,6 +492,8 @@ class AccountInternal extends PureComponent { }; onExport = async accountName => { + const { t } = useTranslation(); + const exportedTransactions = await send('transactions-export-query', { query: this.currentQuery.serialize(), }); @@ -496,7 +504,7 @@ class AccountInternal extends PureComponent { window.Actual?.saveFile( exportedTransactions, filename, - 'Export Transactions', + t('Export Transactions'), ); }; @@ -676,13 +684,13 @@ class AccountInternal extends PureComponent { if (!account) { if (id === 'budgeted') { - return 'Budgeted Accounts'; + return t('Budgeted Accounts'); } else if (id === 'offbudget') { - return 'Off Budget Accounts'; + return t('Off Budget Accounts'); } else if (id === 'uncategorized') { - return 'Uncategorized'; + return t('Uncategorized'); } else if (!id) { - return 'All Accounts'; + return t('All Accounts'); } return null; } @@ -793,6 +801,8 @@ class AccountInternal extends PureComponent { }; onCreateReconciliationTransaction = async diff => { + const { t } = useTranslation(); + // Create a new reconciliation transaction const reconciliationTransactions = realizeTempTransactions([ { @@ -802,7 +812,7 @@ class AccountInternal extends PureComponent { reconciled: false, amount: diff, date: currentDay(), - notes: 'Reconciliation balance adjustment', + notes: t('Reconciliation balance adjustment'), }, ]); @@ -819,8 +829,10 @@ class AccountInternal extends PureComponent { }; onShowTransactions = async ids => { + const { t } = useTranslation(); + this.onApplyFilter({ - customName: 'Selected transactions', + customName: t('Selected transactions'), queryFilter: { id: { $oneof: ids } }, }); }; diff --git a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx index e8812ecadac..c08b49792e2 100644 --- a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx +++ b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx @@ -1,7 +1,10 @@ import React, { useRef, useState } from 'react'; +import { Trans } from 'react-i18next'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; +import { t } from 'i18next'; + import { authorizeBank } from '../../gocardless'; import { useAccounts } from '../../hooks/useAccounts'; import { useActions } from '../../hooks/useActions'; @@ -17,9 +20,13 @@ function getErrorMessage(type, code) { case 'ITEM_ERROR': switch (code.toUpperCase()) { case 'NO_ACCOUNTS': - return 'No open accounts could be found. Did you close the account? If so, unlink the account.'; + return t( + 'No open accounts could be found. Did you close the account? If so, unlink the account.', + ); case 'ITEM_LOGIN_REQUIRED': - return 'Your password or something else has changed with your bank and you need to login again.'; + return t( + 'Your password or something else has changed with your bank and you need to login again.', + ); default: } break; @@ -27,39 +34,41 @@ function getErrorMessage(type, code) { case 'INVALID_INPUT': switch (code.toUpperCase()) { case 'INVALID_ACCESS_TOKEN': - return 'Item is no longer authorized. You need to login again.'; + return t('Item is no longer authorized. You need to login again.'); default: } break; case 'RATE_LIMIT_EXCEEDED': - return 'Rate limit exceeded for this item. Please try again later.'; + return t('Rate limit exceeded for this item. Please try again later.'); case 'INVALID_ACCESS_TOKEN': - return 'Your SimpleFIN Access Token is no longer valid. Please reset and generate a new token.'; + return t( + 'Your SimpleFIN Access Token is no longer valid. Please reset and generate a new token.', + ); case 'ACCOUNT_NEEDS_ATTENTION': return ( - <> + The account needs your attention at{' '} SimpleFIN . - + ); default: } return ( - <> + An internal error occurred. Try to login again, or get{' '} in touch {' '} for support. - + ); } @@ -116,7 +125,9 @@ export function AccountSyncCheck() { {' '} - This account is experiencing connection problems. Let’s fix it. + + This account is experiencing connection problems. Let’s fix it. +
- The server returned the following error: + The server returned the following error:
@@ -137,18 +148,22 @@ export function AccountSyncCheck() { {showAuth ? ( <> - + ) : ( - + )} diff --git a/packages/desktop-client/src/components/accounts/Balance.jsx b/packages/desktop-client/src/components/accounts/Balance.jsx index 91a50086626..3422add9161 100644 --- a/packages/desktop-client/src/components/accounts/Balance.jsx +++ b/packages/desktop-client/src/components/accounts/Balance.jsx @@ -1,4 +1,5 @@ import React, { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { useHover } from 'usehooks-ts'; @@ -42,6 +43,8 @@ function DetailedBalance({ name, balance, isExactBalance = true }) { } function SelectedBalance({ selectedItems, account }) { + const { t } = useTranslation(); + const name = `selected-balance-${[...selectedItems].join('-')}`; const rows = useSheetValue({ @@ -99,7 +102,7 @@ function SelectedBalance({ selectedItems, account }) { return ( @@ -107,9 +110,11 @@ function SelectedBalance({ selectedItems, account }) { } function FilteredBalance({ filteredAmount }) { + const { t } = useTranslation(); + return ( @@ -117,6 +122,8 @@ function FilteredBalance({ filteredAmount }) { } function MoreBalances({ balanceQuery }) { + const { t } = useTranslation(); + const cleared = useSheetValue({ name: balanceQuery.name + '-cleared', query: balanceQuery.query.filter({ cleared: true }), @@ -128,8 +135,8 @@ function MoreBalances({ balanceQuery }) { return ( - - + + ); } diff --git a/packages/desktop-client/src/components/accounts/Header.jsx b/packages/desktop-client/src/components/accounts/Header.jsx index 91204e6b3bd..f8aa6b492d8 100644 --- a/packages/desktop-client/src/components/accounts/Header.jsx +++ b/packages/desktop-client/src/components/accounts/Header.jsx @@ -1,5 +1,6 @@ import React, { useState, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; +import { Trans, useTranslation } from 'react-i18next'; import { useLocalPref } from '../../hooks/useLocalPref'; import { useSplitsExpanded } from '../../hooks/useSplitsExpanded'; @@ -88,6 +89,8 @@ export function AccountHeader({ onMakeAsSplitTransaction, onMakeAsNonSplitTransactions, }) { + const { t } = useTranslation(); + const [menuOpen, setMenuOpen] = useState(false); const searchInput = useRef(null); const triggerRef = useRef(null); @@ -231,7 +234,7 @@ export function AccountHeader({ data-testid="account-name" > {account && account.closed - ? 'Closed: ' + accountName + ? t('Closed: {{ accountName }}', { accountName }) : accountName} @@ -243,7 +246,7 @@ export function AccountHeader({ )} )} {!showEmptyMessage && ( )} @@ -327,7 +330,7 @@ export function AccountHeader({ 0} style={{ padding: 6, marginLeft: 10 }} @@ -369,8 +372,8 @@ export function AccountHeader({ {splitsExpanded.state.mode === 'collapse' ? ( @@ -432,9 +435,9 @@ export function AccountHeader({ items={[ isSorted && { name: 'remove-sorting', - text: 'Remove all sorting', + text: t('Remove all sorting'), }, - { name: 'export', text: 'Export' }, + { name: 'export', text: t('Export') }, ]} /> @@ -480,6 +483,8 @@ function AccountMenu({ onReconcile, onMenuSelect, }) { + const { t } = useTranslation(); + const [tooltip, setTooltip] = useState('default'); const syncServerStatus = useSyncServerStatus(); @@ -501,36 +506,42 @@ function AccountMenu({ items={[ isSorted && { name: 'remove-sorting', - text: 'Remove all sorting', + text: t('Remove all sorting'), }, canShowBalances && { name: 'toggle-balance', - text: (showBalances ? 'Hide' : 'Show') + ' running balance', + text: showBalances + ? t('Hide running balance') + : t('Show running balance'), }, { name: 'toggle-cleared', - text: (showCleared ? 'Hide' : 'Show') + ' “cleared” checkboxes', + text: showCleared + ? t('Hide “cleared” checkboxes') + : t('Show “cleared” checkboxes'), }, { name: 'toggle-reconciled', - text: (showReconciled ? 'Hide' : 'Show') + ' reconciled transactions', + text: showReconciled + ? t('Hide reconciled transactions') + : t('Show reconciled transactions'), }, - { name: 'export', text: 'Export' }, - { name: 'reconcile', text: 'Reconcile' }, + { name: 'export', text: t('Export') }, + { name: 'reconcile', text: t('Reconcile') }, account && !account.closed && (canSync ? { name: 'unlink', - text: 'Unlink account', + text: t('Unlink account'), } : syncServerStatus === 'online' && { name: 'link', - text: 'Link account', + text: t('Link account'), }), account.closed - ? { name: 'reopen', text: 'Reopen account' } - : { name: 'close', text: 'Close account' }, + ? { name: 'reopen', text: t('Reopen account') } + : { name: 'close', text: t('Close account') }, ].filter(x => x)} /> ); diff --git a/packages/desktop-client/src/components/accounts/Reconcile.jsx b/packages/desktop-client/src/components/accounts/Reconcile.jsx index ab7127858ef..0a4ac13b16c 100644 --- a/packages/desktop-client/src/components/accounts/Reconcile.jsx +++ b/packages/desktop-client/src/components/accounts/Reconcile.jsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { Trans } from 'react-i18next'; import * as queries from 'loot-core/src/client/queries'; import { currencyToInteger } from 'loot-core/src/shared/util'; @@ -59,33 +60,42 @@ export function ReconcilingMessage({ marginRight: 3, }} /> - All reconciled! + All reconciled! ) : ( - Your cleared balance{' '} - {format(cleared, 'financial')} needs{' '} - - {(targetDiff > 0 ? '+' : '') + format(targetDiff, 'financial')} - {' '} - to match -
your bank’s balance of{' '} - - {format(targetBalance, 'financial')} - + + Your cleared balance{' '} + + {{ clearedBalance: format(cleared, 'financial') }} + {' '} + needs{' '} + + {{ + difference: + (targetDiff > 0 ? '+' : '') + + format(targetDiff, 'financial'), + }} + {' '} + to match +
your bank's balance of{' '} + + {{ bankBalance: format(targetBalance, 'financial') }} + +
)} {targetDiff !== 0 && ( )} @@ -121,8 +131,10 @@ export function ReconcileMenu({ account, onReconcile, onClose }) { return ( - Enter the current balance of your bank account that you want to - reconcile with: + + Enter the current balance of your bank account that you want to + reconcile with: + {clearedBalance != null && ( @@ -136,7 +148,7 @@ export function ReconcileMenu({ account, onReconcile, onClose }) { )} ); diff --git a/upcoming-release-notes/3277.md b/upcoming-release-notes/3277.md new file mode 100644 index 00000000000..201f78a71f7 --- /dev/null +++ b/upcoming-release-notes/3277.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Support translations in desktop-client/components/accounts. From 3f8963273be0c737da78ea47e4bcf7adabd9ca5f Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Tue, 3 Sep 2024 13:23:15 -0500 Subject: [PATCH 4/7] Translation: desktop-client/components/schedules (#3313) * Translation: desktop-client/components/schedules * add release note * better handling of plural * clean plural use * add review suggestions * more review suggestions * change to t() syntax * use basic list format * eslint no longer needs disabled * Fix interpolation of payees list. Co-authored-by: Julian Dominguez-Schatz * fix linter * fix typecheck * tighten the types * move to hook * memoize the hook --------- Co-authored-by: Julian Dominguez-Schatz --- .../schedules/DiscoverSchedules.tsx | 39 ++++++--- .../schedules/PostsOfflineNotification.jsx | 77 +++++++++-------- .../components/schedules/ScheduleDetails.jsx | 84 +++++++++++-------- .../src/components/schedules/ScheduleLink.tsx | 18 ++-- .../components/schedules/SchedulesTable.tsx | 67 ++++++++++----- .../src/components/schedules/index.tsx | 13 ++- .../desktop-client/src/hooks/useFormatList.ts | 32 +++++++ upcoming-release-notes/3313.md | 6 ++ 8 files changed, 223 insertions(+), 113 deletions(-) create mode 100644 packages/desktop-client/src/hooks/useFormatList.ts create mode 100644 upcoming-release-notes/3313.md diff --git a/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx b/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx index 31d7fc10a24..dd7aee0011b 100644 --- a/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx +++ b/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import React, { useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -35,6 +36,8 @@ function DiscoverSchedulesTable({ schedules: DiscoverScheduleEntity[]; loading: boolean; }) { + const { t } = useTranslation(); + const selectedItems = useSelectedItems(); const dispatchSelected = useSelectedDispatch(); const dateFormat = useDateFormat() || 'MM/dd/yyyy'; @@ -107,13 +110,17 @@ function DiscoverSchedulesTable({ dispatchSelected({ type: 'select-all', isRangeSelect: e.shiftKey }) } /> - Payee - Account + + Payee + + + Account + - When + When - Amount + Amount selectedItems.has(String(id))} renderItem={renderItem} - renderEmpty="No schedules found" + renderEmpty={t('No schedules found')} /> ); } export function DiscoverSchedules() { + const { t } = useTranslation(); + const { data, isLoading } = useSendPlatformRequest('schedule/discover'); const schedules = data || []; @@ -185,18 +194,22 @@ export function DiscoverSchedules() { {({ state: { close } }) => ( <> } /> - We found some possible schedules in your current transactions. - Select the ones you want to create. + + We found some possible schedules in your current transactions. + Select the ones you want to create. + - If you expected a schedule here and don’t see it, it might be - because the payees of the transactions don’t match. Make sure you - rename payees on all transactions for a schedule to be the same - payee. + + If you expected a schedule here and don’t see it, it might be + because the payees of the transactions don’t match. Make sure you + rename payees on all transactions for a schedule to be the same + payee. + @@ -221,7 +234,7 @@ export function DiscoverSchedules() { close(); }} > - Create schedules + Create schedules diff --git a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx index 29ad0f5da38..a1ba84dd3a4 100644 --- a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx +++ b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.jsx @@ -1,10 +1,12 @@ import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { popModal } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; +import { useFormatList } from '../../hooks/useFormatList'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; @@ -14,63 +16,66 @@ import { Text } from '../common/Text'; import { DisplayId } from '../util/DisplayId'; export function PostsOfflineNotification() { + const { t } = useTranslation(); + const location = useLocation(); const dispatch = useDispatch(); const payees = (location.state && location.state.payees) || []; - const plural = payees.length > 1; async function onPost() { await send('schedule/force-run-service'); dispatch(popModal()); } + const payeesList = payees.map(id => ( + + + + )); + const payeeNamesList = useFormatList(payeesList, t.language); + return ( {({ state: { close } }) => ( <> } /> - {payees.length > 0 ? ( - - The {plural ? 'payees ' : 'payee '} - {payees.map((id, idx) => ( - - - - - {idx === payees.length - 1 - ? ' ' - : idx === payees.length - 2 - ? ', and ' - : ', '} - - ))} - - ) : ( - There {plural ? 'are payees ' : 'is a payee '} that - )} - - {plural ? 'have ' : 'has '} schedules that are due today. Usually - we automatically post transactions for these, but you are offline - or syncing failed. In order to avoid duplicate transactions, we - let you choose whether or not to create transactions for these - schedules. + {payees.length > 0 ? ( + + The payees {payeeNamesList} have schedules that + are due today. + + ) : ( + t('There are payees that have schedules that are due today.', { + count: payees.length, + }) + )}{' '} + + Usually we automatically post transactions for these, but you + are offline or syncing failed. In order to avoid duplicate + transactions, we let you choose whether or not to create + transactions for these schedules. + - Be aware that other devices may have already created these - transactions. If you have multiple devices, make sure you only do - this on one device or you will have duplicate transactions. + + Be aware that other devices may have already created these + transactions. If you have multiple devices, make sure you only do + this on one device or you will have duplicate transactions. + - You can always manually post a transaction later for a due schedule - by selecting the schedule and clicking “Post transaction” in the - action menu. + + You can always manually post a transaction later for a due + schedule by selecting the schedule and clicking “Post transaction” + in the action menu. + - + diff --git a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx index 67900bf27b9..f9419ffdf8d 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx +++ b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useReducer } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { t } from 'i18next'; @@ -49,11 +50,11 @@ function updateScheduleConditions(schedule, fields) { // Validate if (fields.date == null) { - return { error: 'Date is required' }; + return { error: t('Date is required') }; } if (fields.amount == null) { - return { error: 'A valid amount is required' }; + return { error: t('A valid amount is required') }; } return { @@ -73,6 +74,8 @@ function updateScheduleConditions(schedule, fields) { } export function ScheduleDetails({ id, transaction }) { + const { t } = useTranslation(); + const adding = id == null; const fromTrans = transaction != null; const payees = getPayeesById(usePayees()); @@ -365,7 +368,7 @@ export function ScheduleDetails({ id, transaction }) { if (sameName.length > 0 && sameName[0].id !== state.schedule.id) { dispatch({ type: 'form-error', - error: 'There is already a schedule with this name', + error: t('There is already a schedule with this name'), }); return; } @@ -396,8 +399,9 @@ export function ScheduleDetails({ id, transaction }) { if (res.error) { dispatch({ type: 'form-error', - error: + error: t( 'An error occurred while saving. Please visit https://actualbudget.org/contact/ for support.', + ), }); return; } @@ -457,12 +461,16 @@ export function ScheduleDetails({ id, transaction }) { {({ state: { close } }) => ( <> } /> - + - + dispatch({ type: 'set-field', field: 'payee', value: id }) } @@ -491,7 +503,7 @@ export function ScheduleDetails({ id, transaction }) { @@ -499,7 +511,7 @@ export function ScheduleDetails({ id, transaction }) { includeClosedAccounts={false} value={state.fields.account} labelProps={{ id: 'account-label' }} - inputProps={{ id: 'account-field', placeholder: '(none)' }} + inputProps={{ id: 'account-field', placeholder: t('(none)') }} onSelect={id => dispatch({ type: 'set-field', field: 'account', value: id }) } @@ -509,7 +521,7 @@ export function ScheduleDetails({ id, transaction }) { @@ -519,11 +531,11 @@ export function ScheduleDetails({ id, transaction }) { formatOp={op => { switch (op) { case 'is': - return 'is exactly'; + return t('is exactly'); case 'isapprox': - return 'is approximately'; + return t('is approximately'); case 'isbetween': - return 'is between'; + return t('is between'); default: throw new Error('Invalid op for select: ' + op); } @@ -570,7 +582,7 @@ export function ScheduleDetails({ id, transaction }) { - + @@ -595,7 +607,7 @@ export function ScheduleDetails({ id, transaction }) { {state.upcomingDates && ( - Upcoming dates + Upcoming dates @@ -657,7 +669,7 @@ export function ScheduleDetails({ id, transaction }) { htmlFor="form_posts_transaction" style={{ userSelect: 'none' }} > - Automatically add transaction + Automatically add transaction @@ -671,8 +683,10 @@ export function ScheduleDetails({ id, transaction }) { lineHeight: '1.4em', }} > - If checked, the schedule will automatically create transactions - for you in the specified account + + If checked, the schedule will automatically create + transactions for you in the specified account + {!adding && state.schedule.rule && ( @@ -686,11 +700,13 @@ export function ScheduleDetails({ id, transaction }) { width: 350, }} > - This schedule has custom conditions and actions + + This schedule has custom conditions and actions + )} )} @@ -702,11 +718,11 @@ export function ScheduleDetails({ id, transaction }) { {adding ? ( - These transactions match this schedule: + These transactions match this schedule: - Select transactions to link on save + Select transactions to link on save ) : ( @@ -723,7 +739,7 @@ export function ScheduleDetails({ id, transaction }) { }} onPress={() => onSwitchTransactions('linked')} > - Linked transactions + Linked transactions {' '} t('{{count}} transactions', { count })} items={ state.transactionsMode === 'linked' - ? [{ name: 'unlink', text: 'Unlink from schedule' }] - : [{ name: 'link', text: 'Link to schedule' }] + ? [{ name: 'unlink', text: t('Unlink from schedule') }] + : [{ name: 'link', text: t('Link to schedule') }] } onSelect={(name, ids) => { switch (name) { @@ -792,7 +808,7 @@ export function ScheduleDetails({ id, transaction }) { {state.error} )} @@ -810,6 +826,8 @@ export function ScheduleDetails({ id, transaction }) { } function NoTransactionsMessage(props) { + const { t } = useTranslation(); + return ( {props.error ? ( - Could not search: {props.error} + Could not search: {props.error} ) : props.transactionsMode === 'matched' ? ( - 'No matching transactions' + t('No matching transactions') ) : ( - 'No linked transactions' + t('No linked transactions') )} ); diff --git a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx index 15b3409a75f..7b3a3e40677 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx +++ b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import React, { useCallback, useRef, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; @@ -32,6 +33,8 @@ export function ScheduleLink({ accountName?: string; onScheduleLinked?: (schedule: ScheduleEntity) => void; }) { + const { t } = useTranslation(); + const dispatch = useDispatch(); const [filter, setFilter] = useState(accountName || ''); @@ -76,7 +79,7 @@ export function ScheduleLink({ {({ state: { close } }) => ( <> } /> - Choose the schedule{' '} - {ids?.length > 1 - ? `these ${ids.length} transactions belong` - : `this transaction belongs`}{' '} - to: + {t( + 'Choose the schedule these {{ count }} transactions belong to:', + { count: ids?.length ?? 0 }, + )} @@ -114,7 +116,7 @@ export function ScheduleLink({ }} > - Create New + Create New )} diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx index 66bc8fa8269..25d97f9e398 100644 --- a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx +++ b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import React, { useRef, useState, useMemo, type CSSProperties } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { type ScheduleStatusType, @@ -61,6 +62,8 @@ function OverflowMenu({ status: ScheduleStatusType; onAction: SchedulesTableProps['onAction']; }) { + const { t } = useTranslation(); + const triggerRef = useRef(null); const [open, setOpen] = useState(false); @@ -69,28 +72,28 @@ function OverflowMenu({ menuItems.push({ name: 'post-transaction', - text: 'Post transaction', + text: t('Post transaction'), }); if (status === 'completed') { menuItems.push({ name: 'restart', - text: 'Restart', + text: t('Restart'), }); } else { menuItems.push( { name: 'skip', - text: 'Skip next date', + text: t('Skip next date'), }, { name: 'complete', - text: 'Complete', + text: t('Complete'), }, ); } - menuItems.push({ name: 'delete', text: 'Delete' }); + menuItems.push({ name: 'delete', text: t('Delete') }); return menuItems; }; @@ -100,7 +103,7 @@ function OverflowMenu({ + diff --git a/packages/desktop-client/src/hooks/useFormatList.ts b/packages/desktop-client/src/hooks/useFormatList.ts new file mode 100644 index 00000000000..251df4fb1bc --- /dev/null +++ b/packages/desktop-client/src/hooks/useFormatList.ts @@ -0,0 +1,32 @@ +import { useMemo, type ReactNode } from 'react'; + +const interleaveArrays = (...arrays: ReactNode[][]) => + Array.from( + { + length: Math.max(...arrays.map(array => array.length)), + }, + (_, i) => arrays.map(array => array[i]), + ).flat(); + +export function useFormatList(values: ReactNode[], lng: string, opt = {}) { + const formatter = useMemo( + () => + new Intl.ListFormat(lng, { + style: 'long', + type: 'conjunction', + ...opt, + }), + [lng, opt], + ); + + const parts = useMemo(() => { + const placeholders = Array.from( + { length: values.length }, + (_, i) => `<${i}>`, + ); + const formatted = formatter.format(placeholders); + return formatted.split(/<\d+>/g); + }, [values.length, formatter]); + + return interleaveArrays(parts, values); +} diff --git a/upcoming-release-notes/3313.md b/upcoming-release-notes/3313.md new file mode 100644 index 00000000000..555b2ab24d2 --- /dev/null +++ b/upcoming-release-notes/3313.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [psybers] +--- + +Support translations in desktop-client/components/schedules. From 5cfa2cf577d94a99ddc55b519847fd79547e881b Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 3 Sep 2024 19:35:02 +0100 Subject: [PATCH 5/7] :recycle: (typescript) moving account component to TS (#3311) --- packages/desktop-client/package.json | 1 + .../accounts/{Account.jsx => Account.tsx} | 523 ++++++++++++------ .../src/components/filters/FiltersStack.tsx | 2 +- .../filters/SavedFilterMenuButton.tsx | 4 +- .../desktop-client/src/hooks/useSelected.tsx | 6 +- .../src/client/data-hooks/filters.ts | 2 +- packages/loot-core/src/client/queries.ts | 4 +- .../src/client/state-types/modals.d.ts | 6 +- .../src/client/state-types/queries.d.ts | 8 +- packages/loot-core/src/types/models/rule.d.ts | 3 +- .../src/types/models/transaction-filter.d.ts | 6 +- .../src/types/models/transaction.d.ts | 2 + packages/loot-core/src/types/prefs.d.ts | 1 + upcoming-release-notes/3311.md | 6 + yarn.lock | 8 + 15 files changed, 405 insertions(+), 177 deletions(-) rename packages/desktop-client/src/components/accounts/{Account.jsx => Account.tsx} (77%) create mode 100644 upcoming-release-notes/3311.md diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index da2b2e6790f..112df73b7aa 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -20,6 +20,7 @@ "@swc/plugin-react-remove-properties": "^1.5.121", "@testing-library/react": "14.1.2", "@testing-library/user-event": "14.5.2", + "@types/debounce": "^1.2.4", "@types/lodash": "^4", "@types/promise-retry": "^1.1.6", "@types/react": "^18.2.0", diff --git a/packages/desktop-client/src/components/accounts/Account.jsx b/packages/desktop-client/src/components/accounts/Account.tsx similarity index 77% rename from packages/desktop-client/src/components/accounts/Account.jsx rename to packages/desktop-client/src/components/accounts/Account.tsx index ece252ea1b1..9c5c9387789 100644 --- a/packages/desktop-client/src/components/accounts/Account.jsx +++ b/packages/desktop-client/src/components/accounts/Account.tsx @@ -1,4 +1,11 @@ -import React, { PureComponent, createRef, useMemo } from 'react'; +// @ts-strict-ignore +import React, { + PureComponent, + type MutableRefObject, + createRef, + useMemo, + type ReactElement, +} from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { Navigate, useParams, useLocation } from 'react-router-dom'; @@ -8,6 +15,7 @@ import { t } from 'i18next'; import { v4 as uuidv4 } from 'uuid'; import { validForTransfer } from 'loot-core/client/transfer'; +import { type UndoState } from 'loot-core/server/undo'; import { useFilters } from 'loot-core/src/client/data-hooks/filters'; import { SchedulesProvider, @@ -17,7 +25,7 @@ import * as queries from 'loot-core/src/client/queries'; import { runQuery, pagedQuery } from 'loot-core/src/client/query-helpers'; import { send, listen } from 'loot-core/src/platform/client/fetch'; import { currentDay } from 'loot-core/src/shared/months'; -import { q } from 'loot-core/src/shared/query'; +import { q, type Query } from 'loot-core/src/shared/query'; import { getScheduledAmount } from 'loot-core/src/shared/schedules'; import { updateTransaction, @@ -28,15 +36,28 @@ import { makeAsNonChildTransactions, } from 'loot-core/src/shared/transactions'; import { applyChanges, groupById } from 'loot-core/src/shared/util'; +import { + type NewRuleEntity, + type RuleActionEntity, + type AccountEntity, + type PayeeEntity, + type RuleConditionEntity, + type TransactionEntity, + type TransactionFilterEntity, +} from 'loot-core/src/types/models'; import { useAccounts } from '../../hooks/useAccounts'; import { useActions } from '../../hooks/useActions'; import { useCategories } from '../../hooks/useCategories'; import { useDateFormat } from '../../hooks/useDateFormat'; import { useFailedAccounts } from '../../hooks/useFailedAccounts'; +import { useLocalPref } from '../../hooks/useLocalPref'; import { usePayees } from '../../hooks/usePayees'; import { usePreviewTransactions } from '../../hooks/usePreviewTransactions'; -import { SelectedProviderWithItems } from '../../hooks/useSelected'; +import { + SelectedProviderWithItems, + type Actions, +} from '../../hooks/useSelected'; import { SplitsExpandedProvider, useSplitsExpanded, @@ -51,7 +72,19 @@ import { TransactionList } from '../transactions/TransactionList'; import { AccountHeader } from './Header'; -function EmptyMessage({ onAdd }) { +type ConditionEntity = Partial | TransactionFilterEntity; + +function isTransactionFilterEntity( + filter: ConditionEntity, +): filter is TransactionFilterEntity { + return 'id' in filter; +} + +type EmptyMessageProps = { + onAdd: () => void; +}; + +function EmptyMessage({ onAdd }: EmptyMessageProps) { return ( | null; + showBalances?: boolean; + filtered?: boolean; + children: ( + transactions: TransactionEntity[], + balances: Record | null, + ) => ReactElement; + collapseTransactions: (ids: string[]) => void; +}; + function AllTransactions({ - account = {}, + account, transactions, balances, showBalances, filtered, children, collapseTransactions, -}) { - const accountId = account.id; - const prependTransactions = usePreviewTransactions(collapseTransactions).map( - trans => ({ +}: AllTransactionsProps) { + const accountId = account?.id; + const prependTransactions: (TransactionEntity & { _inverse?: boolean })[] = + usePreviewTransactions(collapseTransactions).map(trans => ({ ...trans, _inverse: accountId ? accountId !== trans.account : false, - }), - ); + })); transactions ??= []; @@ -140,6 +185,8 @@ function AllTransactions({ (scheduledTransaction._inverse ? -1 : 1) * getScheduledAmount(scheduledTransaction.amount); return { + // TODO: fix me + // eslint-disable-next-line react-hooks/exhaustive-deps balance: (runningBalance += amount), id: scheduledTransaction.id, }; @@ -169,7 +216,11 @@ function AllTransactions({ return children(allTransactions, allBalances); } -function getField(field) { +function getField(field?: string) { + if (!field) { + return 'date'; + } + switch (field) { case 'account': return 'account.name'; @@ -186,37 +237,120 @@ function getField(field) { } } -class AccountInternal extends PureComponent { - constructor(props) { +type AccountInternalProps = { + accountId?: string; + filterConditions: RuleConditionEntity[]; + showBalances?: boolean; + setShowBalances: (newValue: boolean) => void; + showCleared?: boolean; + setShowCleared: (newValue: boolean) => void; + showReconciled: boolean; + setShowReconciled: (newValue: boolean) => void; + showExtraBalances?: boolean; + setShowExtraBalances: (newValue: boolean) => void; + modalShowing?: boolean; + setLastUndoState: (state: null) => void; + lastUndoState: { current: UndoState | null }; + accounts: AccountEntity[]; + getPayees: () => Promise; + updateAccount: (newAccount: AccountEntity) => void; + newTransactions: string[]; + matchedTransactions: string[]; + splitsExpandedDispatch: ReturnType['dispatch']; + expandSplits?: boolean; + savedFilters: TransactionFilterEntity[]; + onBatchEdit: ReturnType['onBatchEdit']; + onBatchDuplicate: ReturnType< + typeof useTransactionBatchActions + >['onBatchDuplicate']; + onBatchLinkSchedule: ReturnType< + typeof useTransactionBatchActions + >['onBatchLinkSchedule']; + onBatchUnlinkSchedule: ReturnType< + typeof useTransactionBatchActions + >['onBatchUnlinkSchedule']; + onBatchDelete: ReturnType['onBatchDelete']; + categoryId?: string; + location: ReturnType; + failedAccounts: ReturnType; + dateFormat: ReturnType; + payees: ReturnType; + categoryGroups: ReturnType['grouped']; + hideFraction: boolean; + accountsSyncing: string[]; +} & ReturnType; +type AccountInternalState = { + search: string; + filterConditions: ConditionEntity[]; + filterId: Record; + filterConditionsOp: 'and' | 'or'; + loading: boolean; + workingHard: boolean; + reconcileAmount: null | number; + transactions: TransactionEntity[]; + transactionCount: number; + transactionsFiltered?: boolean; + showBalances?: boolean; + balances: Record | null; + showCleared?: boolean; + prevShowCleared?: boolean; + showReconciled: boolean; + editingName: boolean; + isAdding: boolean; + modalShowing?: boolean; + sort: { + ascDesc: 'asc' | 'desc'; + field: string; + prevField?: string; + prevAscDesc?: 'asc' | 'desc'; + } | null; + filteredAmount: null | number; +}; + +class AccountInternal extends PureComponent< + AccountInternalProps, + AccountInternalState +> { + paged: ReturnType | null; + rootQuery: Query; + currentQuery: Query; + table: MutableRefObject<{ + edit: (updatedId: string | null, op?: string, someBool?: boolean) => void; + setRowAnimation: (animation: boolean) => void; + scrollTo: (focusId: string) => void; + scrollToTop: () => void; + } | null>; + unlisten?: () => void; + dispatchSelected?: (action: Actions) => void; + + constructor(props: AccountInternalProps) { super(props); this.paged = null; this.table = createRef(); - this.animated = true; this.state = { search: '', filterConditions: props.filterConditions || [], - filterId: [], + filterId: {}, filterConditionsOp: 'and', loading: true, workingHard: false, reconcileAmount: null, transactions: [], - transactionsCount: 0, + transactionCount: 0, showBalances: props.showBalances, balances: null, showCleared: props.showCleared, showReconciled: props.showReconciled, editingName: false, isAdding: false, - latestDate: null, - sort: [], + sort: null, filteredAmount: null, }; } async componentDidMount() { - const maybeRefetch = tables => { + const maybeRefetch = (tables: string[]) => { if ( tables.includes('transactions') || tables.includes('category_mapping') || @@ -226,14 +360,14 @@ class AccountInternal extends PureComponent { } }; - const onUndo = async ({ tables, messages }) => { + const onUndo = async ({ tables, messages }: UndoState) => { await maybeRefetch(tables); // If all the messages are dealing with transactions, find the // first message referencing a non-deleted row so that we can // highlight the row // - let focusId; + let focusId: null | string; if ( messages.every(msg => msg.dataset === 'transactions') && !messages.find(msg => msg.column === 'tombstone') @@ -282,7 +416,7 @@ class AccountInternal extends PureComponent { } } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: AccountInternalProps) { // If the active account changes - close the transaction entry mode if (this.state.isAdding && this.props.accountId !== prevProps.accountId) { this.setState({ isAdding: false }); @@ -302,7 +436,7 @@ class AccountInternal extends PureComponent { //Resest sort/filter/search on account change if (this.props.accountId !== prevProps.accountId) { - this.setState({ sort: [], search: '', filterConditions: [] }); + this.setState({ sort: null, search: '', filterConditions: [] }); } } @@ -316,12 +450,12 @@ class AccountInternal extends PureComponent { } fetchAllIds = async () => { - const { data } = await runQuery(this.paged.getQuery().select('id')); + const { data } = await runQuery(this.paged?.getQuery().select('id')); // Remember, this is the `grouped` split type so we need to deal // with the `subtransactions` property - return data.reduce((arr, t) => { + return data.reduce((arr: string[], t: TransactionEntity) => { arr.push(t.id); - t.subtransactions.forEach(sub => arr.push(sub.id)); + t.subtransactions?.forEach(sub => arr.push(sub.id)); return arr; }, []); }; @@ -330,7 +464,7 @@ class AccountInternal extends PureComponent { this.paged?.run(); }; - fetchTransactions = filterConditions => { + fetchTransactions = (filterConditions?: ConditionEntity[]) => { const query = this.makeRootQuery(); this.rootQuery = this.currentQuery = query; if (filterConditions) this.applyFilters(filterConditions); @@ -347,7 +481,7 @@ class AccountInternal extends PureComponent { return queries.makeTransactionsQuery(accountId); }; - updateQuery(query, isFiltered) { + updateQuery(query: Query, isFiltered: boolean = false) { if (this.paged) { this.paged.unsubscribe(); } @@ -359,7 +493,10 @@ class AccountInternal extends PureComponent { this.paged = pagedQuery( query.select('*'), - async (data, prevData) => { + async ( + data: TransactionEntity[], + prevData: TransactionEntity[] | null, + ) => { const firstLoad = prevData == null; if (firstLoad) { @@ -381,7 +518,7 @@ class AccountInternal extends PureComponent { this.setState( { transactions: data, - transactionCount: this.paged.getTotalCount(), + transactionCount: this.paged?.getTotalCount(), transactionsFiltered: isFiltered, loading: false, workingHard: false, @@ -409,7 +546,7 @@ class AccountInternal extends PureComponent { ); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: AccountInternalProps) { if (this.props.accountId !== nextProps.accountId) { this.setState( { @@ -429,8 +566,8 @@ class AccountInternal extends PureComponent { } } - onSearch = value => { - this.paged.unsubscribe(); + onSearch = (value: string) => { + this.paged?.unsubscribe(); this.setState({ search: value }, this.onSearchDone); }; @@ -481,7 +618,7 @@ class AccountInternal extends PureComponent { accountId, categories, filename: res[0], - onImported: didChange => { + onImported: (didChange: boolean) => { if (didChange) { this.fetchTransactions(); } @@ -491,7 +628,7 @@ class AccountInternal extends PureComponent { } }; - onExport = async accountName => { + onExport = async (accountName: string) => { const { t } = useTranslation(); const exportedTransactions = await send('transactions-export-query', { @@ -508,10 +645,13 @@ class AccountInternal extends PureComponent { ); }; - onTransactionsChange = (newTransaction, data) => { + onTransactionsChange = ( + newTransaction: TransactionEntity, + data: TransactionEntity[], + ) => { // Apply changes to pagedQuery data - this.paged.optimisticUpdate( - data => { + this.paged?.optimisticUpdate( + (data: TransactionEntity[]) => { if (newTransaction._deleted) { return data.filter(t => t.id !== newTransaction.id); } else { @@ -537,7 +677,7 @@ class AccountInternal extends PureComponent { account && this.state.search === '' && this.state.filterConditions.length === 0 && - (this.state.sort.length === 0 || + (this.state.sort === null || (this.state.sort.field === 'date' && this.state.sort.ascDesc === 'desc')) ); @@ -550,45 +690,53 @@ class AccountInternal extends PureComponent { const { data } = await runQuery( this.paged - .getQuery() + ?.getQuery() .options({ splits: 'none' }) .select([{ balance: { $sumOver: '$amount' } }]), ); - return groupById(data); + return groupById<{ id: string; balance: number }>(data); } onAddTransaction = () => { this.setState({ isAdding: true }); }; - onExposeName = flag => { + onExposeName = (flag: boolean) => { this.setState({ editingName: flag }); }; - onSaveName = name => { + onSaveName = (name: string) => { if (name.trim().length) { const accountId = this.props.accountId; const account = this.props.accounts.find( account => account.id === accountId, - ); + )!; this.props.updateAccount({ ...account, name }); this.setState({ editingName: false }); } }; onToggleExtraBalances = () => { - const { accountId, showExtraBalances } = this.props; - const key = 'show-extra-balances-' + accountId || 'all-accounts'; - - this.props.savePrefs({ [key]: !showExtraBalances }); + this.props.setShowExtraBalances(!this.props.showExtraBalances); }; - onMenuSelect = async item => { - const accountId = this.props.accountId; + onMenuSelect = async ( + item: + | 'link' + | 'unlink' + | 'close' + | 'reopen' + | 'export' + | 'toggle-balance' + | 'remove-sorting' + | 'toggle-cleared' + | 'toggle-reconciled', + ) => { + const accountId = this.props.accountId!; const account = this.props.accounts.find( account => account.id === accountId, - ); + )!; switch (item) { case 'link': @@ -616,17 +764,17 @@ class AccountInternal extends PureComponent { break; case 'toggle-balance': if (this.state.showBalances) { - this.props.savePrefs({ ['show-balances-' + accountId]: false }); + this.props.setShowBalances(false); this.setState({ showBalances: false, balances: null }); } else { - this.props.savePrefs({ ['show-balances-' + accountId]: true }); + this.props.setShowBalances(true); this.setState( { transactions: [], transactionCount: 0, filterConditions: [], search: '', - sort: [], + sort: null, showBalances: true, }, () => { @@ -636,7 +784,7 @@ class AccountInternal extends PureComponent { } break; case 'remove-sorting': { - this.setState({ sort: [] }, () => { + this.setState({ sort: null }, () => { const filterConditions = this.state.filterConditions; if (filterConditions.length > 0) { this.applyFilters([...filterConditions]); @@ -651,21 +799,21 @@ class AccountInternal extends PureComponent { } case 'toggle-cleared': if (this.state.showCleared) { - this.props.savePrefs({ ['hide-cleared-' + accountId]: true }); + this.props.setShowCleared(false); this.setState({ showCleared: false }); } else { - this.props.savePrefs({ ['hide-cleared-' + accountId]: false }); + this.props.setShowCleared(true); this.setState({ showCleared: true }); } break; case 'toggle-reconciled': if (this.state.showReconciled) { - this.props.savePrefs({ ['hide-reconciled-' + accountId]: true }); + this.props.setShowReconciled(false); this.setState({ showReconciled: false }, () => this.fetchTransactions(this.state.filterConditions), ); } else { - this.props.savePrefs({ ['hide-reconciled-' + accountId]: false }); + this.props.setShowReconciled(true); this.setState({ showReconciled: true }, () => this.fetchTransactions(this.state.filterConditions), ); @@ -675,7 +823,7 @@ class AccountInternal extends PureComponent { } }; - getAccountTitle(account, id) { + getAccountTitle(account?: AccountEntity, id?: string) { const { filterName } = this.props.location.state || {}; if (filterName) { @@ -698,7 +846,7 @@ class AccountInternal extends PureComponent { return account.name; } - getBalanceQuery(account, id) { + getBalanceQuery(id?: string) { return { name: `balance-query-${id}`, query: this.makeRootQuery().calculate({ $sum: '$amount' }), @@ -707,20 +855,20 @@ class AccountInternal extends PureComponent { getFilteredAmount = async () => { const { data: amount } = await runQuery( - this.paged.getQuery().calculate({ $sum: '$amount' }), + this.paged?.getQuery().calculate({ $sum: '$amount' }), ); return amount; }; - isNew = id => { + isNew = (id: string) => { return this.props.newTransactions.includes(id); }; - isMatched = id => { + isMatched = (id: string) => { return this.props.matchedTransactions.includes(id); }; - onCreatePayee = name => { + onCreatePayee = (name: string) => { const trimmed = name.trim(); if (trimmed !== '') { return this.props.createPayee(name); @@ -741,7 +889,9 @@ class AccountInternal extends PureComponent { ); let transactions = ungroupTransactions(data); - const changes = { updated: [] }; + const changes: { updated: Array> } = { + updated: [], + }; transactions.forEach(trans => { const { diff } = updateTransaction(transactions, { @@ -760,7 +910,7 @@ class AccountInternal extends PureComponent { await this.refetchTransactions(); }; - onReconcile = async balance => { + onReconcile = async (balance: number) => { this.setState(({ showCleared }) => ({ reconcileAmount: balance, showCleared: true, @@ -788,7 +938,7 @@ class AccountInternal extends PureComponent { } }); - const targetDiff = reconcileAmount - cleared; + const targetDiff = (reconcileAmount || 0) - cleared; if (targetDiff === 0) { await this.lockTransactions(); @@ -800,14 +950,14 @@ class AccountInternal extends PureComponent { }); }; - onCreateReconciliationTransaction = async diff => { + onCreateReconciliationTransaction = async (diff: number) => { const { t } = useTranslation(); // Create a new reconciliation transaction const reconciliationTransactions = realizeTempTransactions([ { id: 'temp', - account: this.props.accountId, + account: this.props.accountId!, cleared: true, reconciled: false, amount: diff, @@ -828,7 +978,7 @@ class AccountInternal extends PureComponent { await this.refetchTransactions(); }; - onShowTransactions = async ids => { + onShowTransactions = async (ids: string[]) => { const { t } = useTranslation(); this.onApplyFilter({ @@ -837,7 +987,7 @@ class AccountInternal extends PureComponent { }); }; - onBatchEdit = (name, ids) => { + onBatchEdit = (name: keyof TransactionEntity, ids: string[]) => { this.props.onBatchEdit({ name, ids, @@ -851,24 +1001,26 @@ class AccountInternal extends PureComponent { }); }; - onBatchDuplicate = ids => { + onBatchDuplicate = (ids: string[]) => { this.props.onBatchDuplicate({ ids, onSuccess: this.refetchTransactions }); }; - onBatchDelete = ids => { + onBatchDelete = (ids: string[]) => { this.props.onBatchDelete({ ids, onSuccess: this.refetchTransactions }); }; - onMakeAsSplitTransaction = async ids => { + onMakeAsSplitTransaction = async (ids: string[]) => { this.setState({ workingHard: true }); - const { data: transactions } = await runQuery( + const { data } = await runQuery( q('transactions') .filter({ id: { $oneof: ids } }) .select('*') .options({ splits: 'none' }), ); + const transactions: TransactionEntity[] = data; + if (!transactions || transactions.length === 0) { return; } @@ -896,17 +1048,22 @@ class AccountInternal extends PureComponent { this.refetchTransactions(); }; - onMakeAsNonSplitTransactions = async ids => { + onMakeAsNonSplitTransactions = async (ids: string[]) => { this.setState({ workingHard: true }); - const { data: groupedTransactions } = await runQuery( + const { data } = await runQuery( q('transactions') .filter({ id: { $oneof: ids } }) .select('*') .options({ splits: 'grouped' }), ); - let changes = { + const groupedTransactions: TransactionEntity[] = data; + + let changes: { + updated: TransactionEntity[]; + deleted: TransactionEntity[]; + } = { updated: [], deleted: [], }; @@ -961,13 +1118,17 @@ class AccountInternal extends PureComponent { this.refetchTransactions(); const transactionsToSelect = changes.updated.map(t => t.id); - this.dispatchSelected({ + this.dispatchSelected?.({ type: 'select-all', ids: transactionsToSelect, }); }; - checkForReconciledTransactions = async (ids, confirmReason, onConfirm) => { + checkForReconciledTransactions = async ( + ids: string[], + confirmReason: string, + onConfirm: (ids: string[]) => void, + ) => { const { data } = await runQuery( q('transactions') .filter({ id: { $oneof: ids }, reconciled: true }) @@ -987,7 +1148,7 @@ class AccountInternal extends PureComponent { } }; - onBatchLinkSchedule = ids => { + onBatchLinkSchedule = (ids: string[]) => { this.props.onBatchLinkSchedule({ ids, account: this.props.accounts.find(a => a.id === this.props.accountId), @@ -995,14 +1156,14 @@ class AccountInternal extends PureComponent { }); }; - onBatchUnlinkSchedule = ids => { + onBatchUnlinkSchedule = (ids: string[]) => { this.props.onBatchUnlinkSchedule({ ids, onSuccess: this.refetchTransactions, }); }; - onCreateRule = async ids => { + onCreateRule = async (ids: string[]) => { const { data } = await runQuery( q('transactions') .filter({ id: { $oneof: ids } }) @@ -1017,27 +1178,27 @@ class AccountInternal extends PureComponent { ); const payeeCondition = ruleTransaction.imported_payee - ? { + ? ({ field: 'imported_payee', op: 'is', value: ruleTransaction.imported_payee, type: 'string', - } - : { + } satisfies RuleConditionEntity) + : ({ field: 'payee', op: 'is', - value: ruleTransaction.payee, + value: ruleTransaction.payee!, type: 'id', - }; + } satisfies RuleConditionEntity); const amountCondition = { field: 'amount', op: 'isapprox', value: ruleTransaction.amount, type: 'number', - }; + } satisfies RuleConditionEntity; const rule = { - stage: null, + stage: 'default', conditionsOp: 'and', conditions: [payeeCondition, amountCondition], actions: [ @@ -1051,7 +1212,7 @@ class AccountInternal extends PureComponent { options: { splitIndex: 0, }, - }, + } satisfies RuleActionEntity, ] : []), ...childTransactions.flatMap((sub, index) => [ @@ -1062,7 +1223,7 @@ class AccountInternal extends PureComponent { splitIndex: index + 1, method: 'fixed-amount', }, - }, + } satisfies RuleActionEntity, { op: 'set', field: 'category', @@ -1071,16 +1232,16 @@ class AccountInternal extends PureComponent { options: { splitIndex: index + 1, }, - }, + } satisfies RuleActionEntity, ]), ], - }; + } satisfies NewRuleEntity; this.props.pushModal('edit-rule', { rule }); }; - onSetTransfer = async ids => { - const onConfirmTransfer = async ids => { + onSetTransfer = async (ids: string[]) => { + const onConfirmTransfer = async (ids: string[]) => { this.setState({ workingHard: true }); const payees = await this.props.getPayees(); @@ -1101,12 +1262,12 @@ class AccountInternal extends PureComponent { updated: [ { ...fromTrans, - payee: toPayee.id, + payee: toPayee?.id, transfer_id: toTrans.id, }, { ...toTrans, - payee: fromPayee.id, + payee: fromPayee?.id, transfer_id: fromTrans.id, }, ], @@ -1125,7 +1286,7 @@ class AccountInternal extends PureComponent { ); }; - onConditionsOpChange = value => { + onConditionsOpChange = (value: 'and' | 'or') => { this.setState({ filterConditionsOp: value }); this.setState({ filterId: { ...this.state.filterId, status: 'changed' } }); this.applyFilters([...this.state.filterConditions]); @@ -1134,16 +1295,21 @@ class AccountInternal extends PureComponent { } }; - onReloadSavedFilter = (savedFilter, item) => { + onReloadSavedFilter = ( + savedFilter: TransactionFilterEntity & { status?: string }, + item: string, + ) => { if (item === 'reload') { const [savedFilter] = this.props.savedFilters.filter( f => f.id === this.state.filterId.id, ); - this.setState({ filterConditionsOp: savedFilter.conditionsOp }); + this.setState({ filterConditionsOp: savedFilter.conditionsOp ?? 'and' }); this.applyFilters([...savedFilter.conditions]); } else { if (savedFilter.status) { - this.setState({ filterConditionsOp: savedFilter.conditionsOp }); + this.setState({ + filterConditionsOp: savedFilter.conditionsOp ?? 'and', + }); this.applyFilters([...savedFilter.conditions]); } } @@ -1152,14 +1318,17 @@ class AccountInternal extends PureComponent { onClearFilters = () => { this.setState({ filterConditionsOp: 'and' }); - this.setState({ filterId: [] }); + this.setState({ filterId: {} }); this.applyFilters([]); if (this.state.search !== '') { this.onSearch(this.state.search); } }; - onUpdateFilter = (oldCondition, updatedCondition) => { + onUpdateFilter = ( + oldCondition: RuleConditionEntity, + updatedCondition: RuleConditionEntity, + ) => { this.applyFilters( this.state.filterConditions.map(c => c === oldCondition ? updatedCondition : c, @@ -1176,10 +1345,10 @@ class AccountInternal extends PureComponent { } }; - onDeleteFilter = condition => { + onDeleteFilter = (condition: RuleConditionEntity) => { this.applyFilters(this.state.filterConditions.filter(c => c !== condition)); if (this.state.filterConditions.length === 1) { - this.setState({ filterId: [] }); + this.setState({ filterId: {} }); this.setState({ filterConditionsOp: 'and' }); } else { this.setState({ @@ -1194,14 +1363,9 @@ class AccountInternal extends PureComponent { } }; - onApplyFilter = async conditionOrSavedFilter => { + onApplyFilter = async (conditionOrSavedFilter: ConditionEntity) => { let filterConditions = this.state.filterConditions; - if (conditionOrSavedFilter.customName) { - filterConditions = filterConditions.filter( - c => c.customName !== conditionOrSavedFilter.customName, - ); - } - if (conditionOrSavedFilter.conditions) { + if (isTransactionFilterEntity(conditionOrSavedFilter)) { // A saved filter was passed in. const savedFilter = conditionOrSavedFilter; this.setState({ @@ -1210,6 +1374,12 @@ class AccountInternal extends PureComponent { this.setState({ filterConditionsOp: savedFilter.conditionsOp }); this.applyFilters([...savedFilter.conditions]); } else { + filterConditions = filterConditions.filter( + c => + !isTransactionFilterEntity(c) && + c.customName !== conditionOrSavedFilter.customName, + ); + // A condition was passed in. const condition = conditionOrSavedFilter; this.setState({ @@ -1220,12 +1390,16 @@ class AccountInternal extends PureComponent { }); this.applyFilters([...filterConditions, condition]); } + if (this.state.search !== '') { this.onSearch(this.state.search); } }; - onScheduleAction = async (name, ids) => { + onScheduleAction = async ( + name: 'skip' | 'post-transaction', + ids: string[], + ) => { switch (name) { case 'post-transaction': for (const id of ids) { @@ -1244,15 +1418,19 @@ class AccountInternal extends PureComponent { } }; - applyFilters = async conditions => { + applyFilters = async (conditions: ConditionEntity[]) => { if (conditions.length > 0) { - const customQueryFilters = conditions - .filter(cond => !!cond.customName) - .map(f => f.queryFilter); + const filteredCustomQueryFilters: Partial[] = + conditions.filter(cond => !isTransactionFilterEntity(cond)); + const customQueryFilters = filteredCustomQueryFilters.map( + f => f.queryFilter, + ); const { filters: queryFilters } = await send( 'make-filters-from-conditions', { - conditions: conditions.filter(cond => !cond.customName), + conditions: conditions.filter( + cond => isTransactionFilterEntity(cond) || !cond.customName, + ), }, ); const conditionsOpKey = @@ -1282,24 +1460,33 @@ class AccountInternal extends PureComponent { ); } - if (this.state.sort.length !== 0) { + if (this.state.sort !== null) { this.applySort(); } }; - applySort = (field, ascDesc, prevField, prevAscDesc) => { + applySort = ( + field?: string, + ascDesc?: 'asc' | 'desc', + prevField?: string, + prevAscDesc?: 'asc' | 'desc', + ) => { const filterConditions = this.state.filterConditions; const isFiltered = filterConditions.length > 0; - const sortField = getField(!field ? this.state.sort.field : field); - const sortAscDesc = !ascDesc ? this.state.sort.ascDesc : ascDesc; + const sortField = getField(!field ? this.state.sort?.field : field); + const sortAscDesc = !ascDesc ? this.state.sort?.ascDesc : ascDesc; const sortPrevField = getField( - !prevField ? this.state.sort.prevField : prevField, + !prevField ? this.state.sort?.prevField : prevField, ); const sortPrevAscDesc = !prevField - ? this.state.sort.prevAscDesc + ? this.state.sort?.prevAscDesc : prevAscDesc; - const sortCurrentQuery = function (that, sortField, sortAscDesc) { + const sortCurrentQuery = function ( + that: AccountInternal, + sortField: string, + sortAscDesc?: 'asc' | 'desc', + ) { if (sortField === 'cleared') { that.currentQuery = that.currentQuery.orderBy({ reconciled: sortAscDesc, @@ -1311,7 +1498,11 @@ class AccountInternal extends PureComponent { }); }; - const sortRootQuery = function (that, sortField, sortAscDesc) { + const sortRootQuery = function ( + that: AccountInternal, + sortField: string, + sortAscDesc?: 'asc' | 'desc', + ) { if (sortField === 'cleared') { that.currentQuery = that.rootQuery.orderBy({ reconciled: sortAscDesc, @@ -1328,9 +1519,9 @@ class AccountInternal extends PureComponent { // sort by previously used sort field, if any const maybeSortByPreviousField = function ( - that, - sortPrevField, - sortPrevAscDesc, + that: AccountInternal, + sortPrevField: string, + sortPrevAscDesc?: 'asc' | 'desc', ) { if (!sortPrevField) { return; @@ -1373,12 +1564,12 @@ class AccountInternal extends PureComponent { this.updateQuery(this.currentQuery, isFiltered); }; - onSort = (headerClicked, ascDesc) => { - let prevField; - let prevAscDesc; + onSort = (headerClicked: string, ascDesc: 'asc' | 'desc') => { + let prevField: string | undefined; + let prevAscDesc: 'asc' | 'desc' | undefined; //if staying on same column but switching asc/desc //then keep prev the same - if (headerClicked === this.state.sort.field) { + if (headerClicked === this.state.sort?.field) { prevField = this.state.sort.prevField; prevAscDesc = this.state.sort.prevAscDesc; this.setState({ @@ -1390,14 +1581,14 @@ class AccountInternal extends PureComponent { } else { //if switching to new column then capture state //of current sort column as prev - prevField = this.state.sort.field; - prevAscDesc = this.state.sort.ascDesc; + prevField = this.state.sort?.field; + prevAscDesc = this.state.sort?.ascDesc; this.setState({ sort: { field: headerClicked, ascDesc, - prevField: this.state.sort.field, - prevAscDesc: this.state.sort.ascDesc, + prevField: this.state.sort?.field, + prevAscDesc: this.state.sort?.ascDesc, }, }); } @@ -1449,7 +1640,7 @@ class AccountInternal extends PureComponent { const category = categoryGroups .flatMap(g => g.categories) - .find(category => category.id === categoryId); + .find(category => category?.id === categoryId); const showEmptyMessage = !loading && !accountId && accounts.length === 0; @@ -1459,7 +1650,7 @@ class AccountInternal extends PureComponent { accountId !== 'offbudget' && accountId !== 'uncategorized'; - const balanceQuery = this.getBalanceQuery(account, accountId); + const balanceQuery = this.getBalanceQuery(accountId); return ( this.paged && this.paged.fetchNext() } @@ -1590,16 +1779,10 @@ class AccountInternal extends PureComponent { ) : null } onSort={this.onSort} - sortField={this.state.sort.field} - ascDesc={this.state.sort.ascDesc} + sortField={this.state.sort?.field} + ascDesc={this.state.sort?.ascDesc} onChange={this.onTransactionsChange} onRefetch={this.refetchTransactions} - onRefetchUpToRow={row => - this.paged.refetchUpToRow(row, { - field: 'date', - order: 'desc', - }) - } onCloseAddTransaction={() => this.setState({ isAdding: false }) } @@ -1615,7 +1798,17 @@ class AccountInternal extends PureComponent { } } -function AccountHack(props) { +type AccountHackProps = Omit< + AccountInternalProps, + | 'splitsExpandedDispatch' + | 'onBatchEdit' + | 'onBatchDuplicate' + | 'onBatchLinkSchedule' + | 'onBatchUnlinkSchedule' + | 'onBatchDelete' +>; + +function AccountHack(props: AccountHackProps) { const { dispatch: splitsExpandedDispatch } = useSplitsExpanded(); const { onBatchEdit, @@ -1652,11 +1845,17 @@ export function Account() { const failedAccounts = useFailedAccounts(); const dateFormat = useDateFormat() || 'MM/dd/yyyy'; const [hideFraction = false] = useSyncedPref('hideFraction'); - const [expandSplits] = useSyncedPref('expand-splits'); - const [showBalances] = useSyncedPref(`show-balances-${params.id}`); - const [hideCleared] = useSyncedPref(`hide-cleared-${params.id}`); - const [hideReconciled] = useSyncedPref(`hide-reconciled-${params.id}`); - const [showExtraBalances] = useSyncedPref( + const [expandSplits] = useLocalPref('expand-splits'); + const [showBalances, setShowBalances] = useSyncedPref( + `show-balances-${params.id}`, + ); + const [hideCleared, setHideCleared] = useSyncedPref( + `hide-cleared-${params.id}`, + ); + const [hideReconciled, setHideReconciled] = useSyncedPref( + `hide-reconciled-${params.id}`, + ); + const [showExtraBalances, setShowExtraBalances] = useSyncedPref( `show-extra-balances-${params.id || 'all-accounts'}`, ); const modalShowing = useSelector(state => state.modals.modalStack.length > 0); @@ -1683,9 +1882,13 @@ export function Account() { hideFraction={hideFraction} expandSplits={expandSplits} showBalances={showBalances} + setShowBalances={setShowBalances} showCleared={!hideCleared} + setShowCleared={val => setHideCleared(!val)} showReconciled={!hideReconciled} + setShowReconciled={val => setHideReconciled(!val)} showExtraBalances={showExtraBalances} + setShowExtraBalances={setShowExtraBalances} payees={payees} modalShowing={modalShowing} accountsSyncing={accountsSyncing} diff --git a/packages/desktop-client/src/components/filters/FiltersStack.tsx b/packages/desktop-client/src/components/filters/FiltersStack.tsx index 02576c5de1d..ec639437e0a 100644 --- a/packages/desktop-client/src/components/filters/FiltersStack.tsx +++ b/packages/desktop-client/src/components/filters/FiltersStack.tsx @@ -24,7 +24,7 @@ export function FiltersStack({ onConditionsOpChange, }: { conditions: RuleConditionEntity[]; - conditionsOp: string; + conditionsOp: 'and' | 'or'; onUpdateFilter: ( filter: RuleConditionEntity, newFilter: RuleConditionEntity, diff --git a/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx b/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx index 1a2d68e525b..3d4b3d701c0 100644 --- a/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx +++ b/packages/desktop-client/src/components/filters/SavedFilterMenuButton.tsx @@ -16,7 +16,7 @@ import { NameFilter } from './NameFilter'; export type SavedFilter = { conditions?: RuleConditionEntity[]; - conditionsOp?: string; + conditionsOp?: 'and' | 'or'; id?: string; name: string; status?: string; @@ -31,7 +31,7 @@ export function SavedFilterMenuButton({ savedFilters, }: { conditions: RuleConditionEntity[]; - conditionsOp: string; + conditionsOp: 'and' | 'or'; filterId: SavedFilter; onClearFilters: () => void; onReloadSavedFilter: (savedFilter: SavedFilter, value?: string) => void; diff --git a/packages/desktop-client/src/hooks/useSelected.tsx b/packages/desktop-client/src/hooks/useSelected.tsx index 15c944e60c3..a2052e333db 100644 --- a/packages/desktop-client/src/hooks/useSelected.tsx +++ b/packages/desktop-client/src/hooks/useSelected.tsx @@ -48,7 +48,7 @@ type SelectAllAction = { isRangeSelect?: boolean; }; -type Actions = SelectAction | SelectNoneAction | SelectAllAction; +export type Actions = SelectAction | SelectNoneAction | SelectAllAction; export function useSelected( name: string, @@ -310,7 +310,7 @@ export function SelectedProvider({ type SelectedProviderWithItemsProps = { name: string; items: T[]; - initialSelectedIds: string[]; + initialSelectedIds?: string[]; fetchAllIds: () => Promise; registerDispatch?: (dispatch: Dispatch) => void; selectAllFilter?: (item: T) => boolean; @@ -322,7 +322,7 @@ type SelectedProviderWithItemsProps = { export function SelectedProviderWithItems({ name, items, - initialSelectedIds, + initialSelectedIds = [], fetchAllIds, registerDispatch, selectAllFilter, diff --git a/packages/loot-core/src/client/data-hooks/filters.ts b/packages/loot-core/src/client/data-hooks/filters.ts index e26d6fa7ec9..5b77204a6b1 100644 --- a/packages/loot-core/src/client/data-hooks/filters.ts +++ b/packages/loot-core/src/client/data-hooks/filters.ts @@ -14,7 +14,7 @@ function toJS(rows) { tombstone: row.tombstone, conditionsOp: row.conditions_op, conditions: row.conditions, - }; + } satisfies TransactionFilterEntity; }); return filters; } diff --git a/packages/loot-core/src/client/queries.ts b/packages/loot-core/src/client/queries.ts index ec5e124977c..807ee0eca21 100644 --- a/packages/loot-core/src/client/queries.ts +++ b/packages/loot-core/src/client/queries.ts @@ -28,7 +28,7 @@ const accountParametrizedField = parametrizedField<'account'>(); const rolloverParametrizedField = parametrizedField<'rollover-budget'>(); const reportParametrizedField = parametrizedField<'report-budget'>(); -export function getAccountFilter(accountId: string, field = 'account') { +export function getAccountFilter(accountId?: string, field = 'account') { if (accountId) { if (accountId === 'budgeted') { return { @@ -64,7 +64,7 @@ export function getAccountFilter(accountId: string, field = 'account') { return null; } -export function makeTransactionsQuery(accountId: string) { +export function makeTransactionsQuery(accountId?: string) { let query = q('transactions').options({ splits: 'grouped' }); const filter = getAccountFilter(accountId); diff --git a/packages/loot-core/src/client/state-types/modals.d.ts b/packages/loot-core/src/client/state-types/modals.d.ts index 078996b3cb9..785ba31cc56 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -54,7 +54,7 @@ type FinanceModals = { 'manage-rules': { payeeId?: string }; 'edit-rule': { rule: RuleEntity | NewRuleEntity; - onSave: (rule: RuleEntity) => void; + onSave?: (rule: RuleEntity) => void; }; 'merge-unused-payees': { payeeIds: string[]; @@ -270,6 +270,10 @@ type FinanceModals = { message?: string; onConfirm: () => void; }; + 'confirm-unlink-account': { + accountName: string; + onUnlink: () => void; + }; }; export type PushModalAction = { diff --git a/packages/loot-core/src/client/state-types/queries.d.ts b/packages/loot-core/src/client/state-types/queries.d.ts index 2b5138f4167..a4a32432c5e 100644 --- a/packages/loot-core/src/client/state-types/queries.d.ts +++ b/packages/loot-core/src/client/state-types/queries.d.ts @@ -3,8 +3,8 @@ import { type AccountEntity } from '../../types/models'; import type * as constants from '../constants'; export type QueriesState = { - newTransactions: unknown[]; - matchedTransactions: unknown[]; + newTransactions: string[]; + matchedTransactions: string[]; lastTransaction: unknown | null; updatedAccounts: string[]; accounts: AccountEntity[]; @@ -20,8 +20,8 @@ export type QueriesState = { type SetNewTransactionsAction = { type: typeof constants.SET_NEW_TRANSACTIONS; - newTransactions?: unknown[]; - matchedTransactions?: unknown[]; + newTransactions?: string[]; + matchedTransactions?: string[]; updatedAccounts?: string[]; }; diff --git a/packages/loot-core/src/types/models/rule.d.ts b/packages/loot-core/src/types/models/rule.d.ts index bacb94ca552..55fbda9c44c 100644 --- a/packages/loot-core/src/types/models/rule.d.ts +++ b/packages/loot-core/src/types/models/rule.d.ts @@ -55,8 +55,9 @@ type BaseConditionEntity< year?: boolean; }; conditionsOp?: string; - type?: 'id' | 'boolean' | 'date' | 'number'; + type?: 'id' | 'boolean' | 'date' | 'number' | 'string'; customName?: string; + queryFilter?: Record; }; export type RuleConditionEntity = diff --git a/packages/loot-core/src/types/models/transaction-filter.d.ts b/packages/loot-core/src/types/models/transaction-filter.d.ts index e43d50e21e3..c688cc6fb45 100644 --- a/packages/loot-core/src/types/models/transaction-filter.d.ts +++ b/packages/loot-core/src/types/models/transaction-filter.d.ts @@ -1,7 +1,9 @@ +import { type RuleConditionEntity } from './rule'; + export interface TransactionFilterEntity { id: string; name: string; - conditions_op: string; - conditions: unknown; + conditionsOp: 'and' | 'or'; + conditions: RuleConditionEntity[]; tombstone: boolean; } diff --git a/packages/loot-core/src/types/models/transaction.d.ts b/packages/loot-core/src/types/models/transaction.d.ts index 9cb097fec87..648eec97495 100644 --- a/packages/loot-core/src/types/models/transaction.d.ts +++ b/packages/loot-core/src/types/models/transaction.d.ts @@ -24,4 +24,6 @@ export interface TransactionEntity { tombstone?: boolean; schedule?: ScheduleEntity['id']; subtransactions?: TransactionEntity[]; + _unmatched?: boolean; + _deleted?: boolean; } diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index 29f3486313e..a9dd5235cdb 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -24,6 +24,7 @@ export type SyncedPrefs = Partial< numberFormat: (typeof numberFormats)[number]['value']; hideFraction: boolean; isPrivacyEnabled: boolean; + [key: `show-balances-${string}`]: boolean; [key: `show-extra-balances-${string}`]: boolean; [key: `hide-cleared-${string}`]: boolean; [key: `hide-reconciled-${string}`]: boolean; diff --git a/upcoming-release-notes/3311.md b/upcoming-release-notes/3311.md new file mode 100644 index 00000000000..c7eabab267a --- /dev/null +++ b/upcoming-release-notes/3311.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +TypeScript: migrate Account component. diff --git a/yarn.lock b/yarn.lock index 179f6284b9e..ed7ca65ea40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -73,6 +73,7 @@ __metadata: "@swc/plugin-react-remove-properties": "npm:^1.5.121" "@testing-library/react": "npm:14.1.2" "@testing-library/user-event": "npm:14.5.2" + "@types/debounce": "npm:^1.2.4" "@types/lodash": "npm:^4" "@types/promise-retry": "npm:^1.1.6" "@types/react": "npm:^18.2.0" @@ -5031,6 +5032,13 @@ __metadata: languageName: node linkType: hard +"@types/debounce@npm:^1.2.4": + version: 1.2.4 + resolution: "@types/debounce@npm:1.2.4" + checksum: 10/decef3eee65d681556d50f7fac346f1b33134f6b21f806d41326f9dfb362fa66b0282ff0640ae6791b690694c9dc3dad4e146e909e707e6f96650f3aa325b9da + languageName: node + linkType: hard + "@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.6": version: 4.1.8 resolution: "@types/debug@npm:4.1.8" From 61f5dcfd02741fbdde9bffba93e4c41820ad22fe Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 3 Sep 2024 20:42:33 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20(dashboards)=20text=20widget=20?= =?UTF-8?q?(#3288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../desktop-client/src/components/Notes.tsx | 3 + .../src/components/reports/Overview.tsx | 71 +++++---- .../src/components/reports/ReportCardName.tsx | 4 +- .../reports/reports/MarkdownCard.tsx | 141 ++++++++++++++++++ .../loot-core/src/server/dashboard/app.ts | 1 + .../loot-core/src/types/models/dashboard.d.ts | 10 +- upcoming-release-notes/3288.md | 6 + 7 files changed, 202 insertions(+), 34 deletions(-) create mode 100644 packages/desktop-client/src/components/reports/reports/MarkdownCard.tsx create mode 100644 upcoming-release-notes/3288.md diff --git a/packages/desktop-client/src/components/Notes.tsx b/packages/desktop-client/src/components/Notes.tsx index 4e8e4b7243f..06dd4c5ff31 100644 --- a/packages/desktop-client/src/components/Notes.tsx +++ b/packages/desktop-client/src/components/Notes.tsx @@ -75,6 +75,9 @@ const markdownStyles = css({ '& td': { padding: '0.25rem 0.75rem', }, + '& h3': { + fontSize: 15, + }, }); type NotesProps = { diff --git a/packages/desktop-client/src/components/reports/Overview.tsx b/packages/desktop-client/src/components/reports/Overview.tsx index faec350ce6f..c775077666f 100644 --- a/packages/desktop-client/src/components/reports/Overview.tsx +++ b/packages/desktop-client/src/components/reports/Overview.tsx @@ -15,6 +15,7 @@ import { send } from 'loot-core/src/platform/client/fetch'; import { type CustomReportWidget, type ExportImportDashboard, + type MarkdownWidget, type Widget, } from 'loot-core/src/types/models'; @@ -35,6 +36,7 @@ import { NON_DRAGGABLE_AREA_CLASS_NAME } from './constants'; import { LoadingIndicator } from './LoadingIndicator'; import { CashFlowCard } from './reports/CashFlowCard'; import { CustomReportListCards } from './reports/CustomReportListCards'; +import { MarkdownCard } from './reports/MarkdownCard'; import { NetWorthCard } from './reports/NetWorthCard'; import { SpendingCard } from './reports/SpendingCard'; @@ -46,22 +48,6 @@ function isCustomReportWidget(widget: Widget): widget is CustomReportWidget { return widget.type === 'custom-report'; } -type LayoutWidget = Layout & Pick; - -function useWidgetLayout(widgets: Widget[]): LayoutWidget[] { - return widgets.map(widget => ({ - i: widget.id, - type: widget.type, - x: widget.x, - y: widget.y, - w: widget.width, - h: widget.height, - minW: isCustomReportWidget(widget) ? 2 : 3, - minH: isCustomReportWidget(widget) ? 1 : 2, - meta: widget.meta, - })); -} - export function Overview() { const { t } = useTranslation(); const dispatch = useDispatch(); @@ -96,7 +82,17 @@ export function Overview() { const isDashboardsFeatureEnabled = useFeatureFlag('dashboards'); const spendingReportFeatureFlag = useFeatureFlag('spendingReport'); - const baseLayout = useWidgetLayout(widgets); + const baseLayout = widgets.map(widget => ({ + i: widget.id, + w: widget.width, + h: widget.height, + minW: + isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 2 : 3, + minH: + isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 1 : 2, + ...widget, + })); + const layout = spendingReportFeatureFlag && !isDashboardsFeatureEnabled && @@ -308,10 +304,7 @@ export function Overview() { ); }; - const onMetaChange = ( - widget: T, - newMeta: T['meta'], - ) => { + const onMetaChange = (widget: { i: string }, newMeta: Widget['meta']) => { send('dashboard-update-widget', { id: widget.i, meta: newMeta, @@ -384,7 +377,18 @@ export function Overview() { } if (isExistingCustomReport(item)) { const [, reportId] = item.split('custom-report-'); - onAddWidget('custom-report', { id: reportId }); + onAddWidget('custom-report', { + id: reportId, + }); + return; + } + + if (item === 'markdown-card') { + onAddWidget(item, { + content: t( + '### Text Widget\n\nEdit this widget to change the **markdown** content.', + ), + }); return; } @@ -407,6 +411,10 @@ export function Overview() { }, ] : []), + { + name: 'markdown-card' as const, + text: t('Text widget'), + }, { name: 'custom-report' as const, text: t('New custom report'), @@ -522,32 +530,35 @@ export function Overview() { onMetaChange(item, newMeta)} onRemove={() => onRemoveWidget(item.i)} /> ) : item.type === 'cash-flow-card' ? ( onMetaChange(item, newMeta)} onRemove={() => onRemoveWidget(item.i)} /> ) : item.type === 'spending-card' ? ( onMetaChange(item, newMeta)} + onRemove={() => onRemoveWidget(item.i)} + /> + ) : item.type === 'markdown-card' ? ( + onMetaChange(item, newMeta)} onRemove={() => onRemoveWidget(item.i)} /> ) : item.type === 'custom-report' ? ( onRemoveWidget(item.i)} /> ) : null} diff --git a/packages/desktop-client/src/components/reports/ReportCardName.tsx b/packages/desktop-client/src/components/reports/ReportCardName.tsx index 3eac1ece293..670ac0a9987 100644 --- a/packages/desktop-client/src/components/reports/ReportCardName.tsx +++ b/packages/desktop-client/src/components/reports/ReportCardName.tsx @@ -30,8 +30,7 @@ export const ReportCardName = ({ onUpdate={onChange} onEscape={onClose} style={{ - fontSize: 15, - fontWeight: 500, + ...styles.mediumText, marginTop: -6, marginBottom: -1, marginLeft: -6, @@ -46,7 +45,6 @@ export const ReportCardName = ({ void; + onRemove: () => void; +}; + +export function MarkdownCard({ + isEditing, + meta, + onMetaChange, + onRemove, +}: MarkdownCardProps) { + const { t } = useTranslation(); + + const [isVisibleTextArea, setIsVisibleTextArea] = useState(false); + + return ( + { + switch (item) { + case 'text-left': + onMetaChange({ + ...meta, + text_align: 'left', + }); + break; + case 'text-center': + onMetaChange({ + ...meta, + text_align: 'center', + }); + break; + case 'text-right': + onMetaChange({ + ...meta, + text_align: 'right', + }); + break; + case 'edit': + setIsVisibleTextArea(true); + break; + case 'remove': + onRemove(); + break; + default: + throw new Error(`Unrecognized selection: ${item}`); + } + }} + > + + {isVisibleTextArea ? ( +