Compare commits

...

72 Commits

Author SHA1 Message Date
Leandro Menezes
83cd364c5f Changed to remove sessionStorage 2025-06-15 13:56:29 -03:00
Crazypkr1099
b61b1758d6 Merge branch 'master' into scrollToLocationBudget 2024-06-20 21:53:37 -04:00
Crazypkr
dbc434c84e Renamed scrollToPosition to setScrollPosition 2024-06-20 21:50:53 -04:00
Crazypkr
b1ea639e11 Merge branch 'scrollToLocationBudget' of https://github.com/Crazypkr1099/actual into scrollToLocationBudget 2024-06-20 21:47:07 -04:00
Crazypkr
bc6098fbb3 Remove intersectionboundary 2024-06-20 21:47:04 -04:00
lelemm
7f658691bb Fix: column titles incorrect when selecting transactions and resizing window (#2867) 2024-06-20 21:49:33 +01:00
Crazypkr1099
15869eca61 Merge branch 'master' into scrollToLocationBudget 2024-06-19 21:59:42 -04:00
Matiss Janis Aboltins
5b1a730f11 💬 (bank-sync) update sync button label to improve discoverability (#2899) 2024-06-19 20:19:48 +01:00
Neil
0c14eb17c4 Unflag custom reports (#2554)
* remove featureflag

* notes

* merge fixes

* vrt

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-06-19 06:57:46 +01:00
dymanoid
7bb0425c81 Consider account sort order in autocomplete (#2896)
* Consider account sort order in autocomplete

* Add release notes
2024-06-18 15:44:44 -07:00
Crazypkr1099
c5c098ea0c Merge branch 'master' into scrollToLocationBudget 2024-06-18 18:38:02 -04:00
DJ Mountney
8832c2b234 Trafico embedded action (#2895)
* Embed trafico as an action

* Add all the event types from trafico

* Swith to main trafico branch

* Add release note
2024-06-18 15:30:24 -07:00
Crazypkr1099
437e202d27 Add filtering of group name to autocomplete for transactions (#2861)
* Delete packages/desktop-client/e2e directory

* Delete packages/desktop-client/e2e directory

* Add toggleable option to autoCompleteCategories

* Create 2861.md

* Fixing typescript error & removing console.log

* Added themesetting back in

* Added filtering and separation for group name autocomplete

* removed typescript error

* another typescript error fixed

* ts error

* removed styling

* Update 2861.md

* Update prefs.d.ts

* Update index.tsx

removed toggleable settings

* Update index.tsx

* removed toggleablesettings

* changed test tobe     items = screen
      .getByTestId('autocomplete')
      .querySelectorAll('[data-testid$="category-item"]');
    expect(items.length).toBe(3);
  });

* removed isChanged passdown

* Added string followed by category name capability, moved sorting logic to categoryautocomplete, used toSorted, used Usememo, added TS.

* Fix unnecessary changes

* Apply suggestions from code review

Co-authored-by: Robert Dyer <rdyer@unl.edu>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>

* Fixed TS, sort, and memo to useCallback

* fix tosorted -> sort

* Apply suggestions from code review

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>

* fix typescript issue

---------

Co-authored-by: Robert Dyer <rdyer@unl.edu>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-06-18 13:27:49 -07:00
Crazypkr1099
f6b88cc1ba Merge branch 'master' into scrollToLocationBudget 2024-06-18 16:03:47 -04:00
Robert Dyer
d34f5eccb6 Timeout SimpleFIN sync calls after 60 seconds (#2891) 2024-06-18 20:55:09 +01:00
Robert Dyer
f1d3902e3e Rework bank secrets reset (#2870)
* Rework bank secrets reset

* Fix alignment

* Fix lint errors

* add release note

* Fix typo

* fix typo
2024-06-18 11:33:51 -07:00
Crazypkr1099
ca1d067921 Merge branch 'master' into scrollToLocationBudget 2024-06-18 13:55:14 -04:00
lelemm
8b6ef7b325 Removed recursion in place for iterable solution to prevent stack overflow (#2848) 2024-06-18 18:39:24 +01:00
Crazypkr1099
18e55800e4 Merge branch 'master' into scrollToLocationBudget 2024-06-18 12:36:52 -04:00
Neil
6ad0b47c7c Custom Reports: Include current (#2684)
* include current

* notes

* label change

* add migration and fix merge issues

* fix extra year/month

* default and disabled lists

* default on

* fixes

* rerun
2024-06-17 20:09:38 +01:00
Matiss Janis Aboltins
96964224f4 🐛 fix budget tooltip showing if panel is not collapsed (#2887) 2024-06-17 18:22:20 +01:00
Joel Jeremy Marquez
0ed5e3ebe6 Revert scheduled transaction date filter causing weekly scheduled transactions as Paid (#2890)
* Revert scheduled transaction date filter

* Release notes
2024-06-17 08:03:40 -07:00
Matiss Janis Aboltins
64cd6ee3c9 ♻️ (tooltip) refactoring to react-aria (vol.9) (#2826) 2024-06-16 14:31:10 +01:00
Crazypkr1099
abc4636662 Bugfix : Account Nav Padding Bottom containing ; (#2881)
* Added underline & Fixed Capitalization in Account Header

* Create 2847.md

* Changed border pixel size, made it more opaque.

* removed comments

* Update E2E

* Required to run E2E in local...

* Revert "Required to run E2E in local..."

This reverts commit e73ac75cb3.

* Revert "Update E2E"

This reverts commit 56990fd6fe.

* testing...

* testing...

* Update 2847.md

* Underline no longer goes to edge

* vrt

* vrt

* fix bad screenshot

* bug fix

* Create 2881.md

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-06-15 09:38:31 -07:00
Crazypkr1099
18314acd25 Merge branch 'master' into scrollToLocationBudget 2024-06-14 18:44:32 -04:00
aaimio
ade25b3304 fix: link schedules modal - table width (#2863)
* fix: remove negative margin on ScheduleLink

* chore: add note for upcoming release notes

* feat: increase width of <ScheduleLink>

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-06-13 14:43:19 -07:00
Neil
b192ad955e Custom Reports: fix card menus regression (#2878)
* fix menus

* notes
2024-06-13 16:45:57 +01:00
Joel Jeremy Marquez
e9da476b51 Fix mobile budget and spent column auto sizing (#2875)
* Fix mobile budget and spent column auto sizing

* Release notes
2024-06-13 07:35:32 -07:00
Crazypkr1099
042058ec7b Merge branch 'scrollToLocationBudget' of https://github.com/Crazypkr1099/actual into scrollToLocationBudget 2024-06-12 22:43:01 -04:00
Crazypkr1099
9580be7bc4 Mobile Support 2024-06-12 22:42:58 -04:00
Crazypkr1099
569b995278 Update 2859.md 2024-06-12 19:12:39 -04:00
Crazypkr1099
9590a93e9f Merge branch 'master' into scrollToLocationBudget 2024-06-12 19:12:01 -04:00
Crazypkr1099
c20ebd9dbd Added rollover and reset when leaving budget page 2024-06-12 19:10:50 -04:00
lelemm
12719e3049 FIX: For Report Budget, the income categories are negative when using 'Set budgets to 3 month average' (#2862)
* FIX: For Report Budget, the income categories are negative when using 'Set budgets to 3 month average'. This fix solves this issue

* added 2862.md

* Ajustments for category menu not closing properly and fix to negative values for income category copy budget

* fix lint

* changed variable name without changing references after lint ajust

* retrigger checks

* smaller 2862.md
2024-06-12 07:50:39 -07:00
aaimio
e178d9914d fix: schedules are missing highlight color (#2864)
* fix: remove redundant style

* chore: add note for upcoming release notes

* chore: fix typo
2024-06-11 12:21:00 -07:00
lelemm
6fd728aa2d fix: Account names in side nav squashed (#2866)
* Fix: Account names in side nav squashed

* add 2866.md

* lint
2024-06-11 12:20:00 -07:00
Crazypkr1099
bf26ca4eb9 Add Underline in Account Header (#2847)
* Added underline & Fixed Capitalization in Account Header

* Create 2847.md

* Changed border pixel size, made it more opaque.

* removed comments

* Update E2E

* Required to run E2E in local...

* Revert "Required to run E2E in local..."

This reverts commit e73ac75cb3.

* Revert "Update E2E"

This reverts commit 56990fd6fe.

* testing...

* testing...

* Update 2847.md

* Underline no longer goes to edge

* vrt

* vrt

* fix bad screenshot

---------

Co-authored-by: youngcw <calebyoung94@gmail.com>
2024-06-11 20:07:19 +01:00
lelemm
f606d92c5c Changing the autocomplete search to accept text without accents to match options with accents (#2842)
* Changing the autocomplete search to accept text without accents to match options with accents

* lint fix

* Added upcoming-release-notes
2024-06-11 07:53:10 -07:00
Dan D
8b850f1410 Handle Null Org Domain in SimpleFIN for bank_id (#2836)
* Update main.ts - Handle Null Org Domain in SimpleFIN

SimpleFIN (currently) uses the org domain as the bank ID. According to SimpleFIN docs, this can possibly be a null value, as can the org name. However, at least one of the two must be present.

In Actual, org name is set to "Unknown" if it comes in as null, but the bank_id (previously just set to the org domain) was not, resulting in a null bank_id value at times.

* Create 2836.md

* Fix release notes format.

* Small change to release note.

* Change to use new org Id as fallback for bank_id

* Add orgId property, for new org id from SimpleFIN
2024-06-10 15:42:45 -07:00
Joel Jeremy Marquez
c992e340ca Cover overbudgeted action + make balance movement menus only appear on relevant conditions (#2850)
* Make balance movement menus only appear on relevant conditions

* Release notes

* Hide to be budgeted when covering overbudgeted

* Fix typecheck error
2024-06-10 13:16:27 -07:00
Robert Dyer
06f9db06b0 Fix amounts over 1 million cutting off (#2812)
* Fix amounts over 1 million cutting off.

* Update VRT

* Update VRT

* update VRT
2024-06-10 11:44:38 -07:00
Crazypkr1099
2b96bb3d52 Added Year comparison feature. (#2806)
* Added Year comparsion feature. Also fixed bug with greater than 28 days.

* Removed comments and console.logs

* Create 2806.md

* Cleaned up code

* Hide's graph if no data, and hides average, last month or last year if no data.

* Apply suggestions from code review

Co-authored-by: Neil <55785687+carkom@users.noreply.github.com>

* Fixed spent MTD and last MTD. Added in all suggestions from carkom.

* Update 2806.md

* Added changes required by carkom #2

* Couple more fixes, only show graph if have data for last month (as requested by carkom)

* Removed console.log that was mistakenly added.

* removed useEffect

* Add files via upload

* Remove async function

* lint fix

* fixed carkom requests & added in fix for YAxis issues

* Fixed couple of mistakes. Removed Y Axis fix (new PR will be created)

* Cleanup code

* Fix mode buttons

* Removed console.log...

* Update showAverage Logic

* Update switch logic

default should be default for everyone other part of the graph.

* Add Math.abs

* lint fix

---------

Co-authored-by: Neil <55785687+carkom@users.noreply.github.com>
2024-06-10 12:41:45 +01:00
Crazypkr
112f066b8b Fix typescript mistake and changed to sessionStorage 2024-06-09 07:53:45 -04:00
Crazypkr
cf6825a541 bug fixes 2024-06-08 12:27:37 -04:00
Crazypkr
9ec0bdec33 fixing more issues 2024-06-08 12:10:31 -04:00
Crazypkr
4c57596117 linting fix 2024-06-08 12:04:26 -04:00
Crazypkr1099
9e7ebb405f Merge branch 'master' into scrollToLocationBudget 2024-06-08 11:57:25 -04:00
Crazypkr
7ba3a37ead Merge branch 'scrollToLocationBudget' of https://github.com/Crazypkr1099/actual into scrollToLocationBudget 2024-06-08 11:55:13 -04:00
Crazypkr
201e1dab54 Revert "Delete packages/desktop-client/e2e directory"
This reverts commit 2476e45735.
2024-06-08 11:55:09 -04:00
Crazypkr
ab124105c2 Revert "Delete packages/desktop-client/e2e directory"
This reverts commit bbec585305.
2024-06-08 11:53:54 -04:00
Crazypkr1099
129b2c3061 Create 2859.md 2024-06-08 11:39:30 -04:00
Crazypkr
9d6b574708 Add ability to scroll to current position in table when leaving budgetable 2024-06-08 11:34:57 -04:00
Matiss Janis Aboltins
ebb9452b8f 🔖 (api) 6.8.1: fix #2829 (#2853) 2024-06-07 21:33:23 +01:00
Matiss Janis Aboltins
196f03b84e ♻️ (tooltip) refactoring to react-aria (vol.8) (#2822) 2024-06-07 19:21:09 +01:00
Matthew Strasiotto
93e784a0fe fix(#2562): Prevent transaction deduplication for imported transactions (#2770)
* fix(#2562): Prevent transaction deduplication for imported transactions

* chore(): eslint fixes

* chore(): Add release note file

* fix(#2562): Allow transaction deduplication if transaction being imported is null

* chore: Rename release note, add strazto as author

* test(loot-core): Add test case for new logic

* docs(release-notes.loot-core): Add pmoon00 as author

* test(loot-core): Update test case to not be affected by unrelated bug

* test(loot-core): fix linter

---------

Co-authored-by: Mohamed El Mahdali <mohamed.elmahdali.developer@gmail.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-06-07 10:47:16 -07:00
lichen
026194e5e2 Enable compress without dropping debugger to avoid SyntaxError in safari 16 {to #1766} (#2825) 2024-06-07 08:11:52 -07:00
Crazypkr1099
bbec585305 Delete packages/desktop-client/e2e directory 2024-06-06 23:51:10 -04:00
Crazypkr1099
2476e45735 Delete packages/desktop-client/e2e directory 2024-06-06 23:45:55 -04:00
Joel Jeremy Marquez
98341b440a [Mobile] Use AmountInput on mobile transfer and hold buffer modals (#2837)
* Use AmountInput on mobile transfer and hold buffer modals

* Release notes

* VRT + typecheck error fixes

* VRT

* VRT
2024-06-06 17:12:59 -07:00
Joel Jeremy Marquez
417f5805a8 [Mobile] Add loading indicator on mobile transactions list (#2831)
* Add loading indicator on mobile transactions list

* Release notes
2024-06-06 17:12:47 -07:00
Neil
094f0b8a91 Spending Report: filter out offbudget items (#2840)
* filter out offBudget

* notes
2024-06-06 18:47:34 +01:00
Julian Dominguez-Schatz
b89a32025a Use amount input on rules page instead of plain text field (#2566)
* Use amount input on rules page instead of plain text field

* Add release notes

* Remove unneeded attributes

* Support percent formatting

* Lint + typecheck

* Fix latent bug

* Handle existing data correctly

* PR feedback: naming

* PR feedback: force percent to a positive number

* PR feedback: reset percent to 100 upon changing input type

* Fix input clamping behaviour

* Empty commit to bump ci

* PR feedback: prop cleanup

* PR feedback: no default number format

* PR feedback: cosmetic refactor
2024-06-05 08:25:37 -07:00
Robert Dyer
d62919a357 [WIP] Don't show account tooltips during VRT (#2838)
* Don't show account tooltips during VRT

* add release note
2024-06-04 21:46:10 +01:00
Joel Jeremy Marquez
1c7d9bf141 [Mobile] Budget table revamp (#2642)
* Initial

* Add cheveron to logo

* Updates

* Release notes

* Fix typecheck error

* Fix build error

* Align budgeted

* Fix tests

* Dynamic font sizes in budget table cells

* VRT

* Apply AutoTextSize

* Layout updates

* VRT updates

* Resize text on value update + fix lint/typecheck  errors

* Category/groups take full width

* Cleanup + VRT

* Feedback

* VRT

* Change column visibility indicator

* VRT

* Use SvgExpandArrow

* VRT

* Zero balance contrast on light mode

* Update AutoTextSize key

* Color updates

* VRT
2024-06-04 13:16:09 -07:00
Stefan Hall
6d117f44de Support nynab imports that have (#2817)
- category groups called income
- categories called income
2024-06-04 10:33:28 -07:00
Joel Jeremy Marquez
64821e6a64 [Mobile] Auto create 2 child transactions on split (#2821)
* Auto create 2 child transactions on split

* Release notes

* Fix typecheck error
2024-06-03 19:52:35 -07:00
Neil
e7c6611c88 Custom Reports show activity fix (#2785)
* onShow Activity fix with Weekly Interval fix

* notes

* remove scrollbar

* notes update

* updates
2024-06-03 17:23:17 +01:00
Matt Fiddaman
6220aadb2d Fix CSV import not matching category is (nothing) rules (#2790)
* Fix CSV import not matching category is (nothing) rules

* release note
2024-06-03 08:11:02 -07:00
Sreetam Das
2959054d0c Update regex for looselyParseAmount for 5-9 decimal places (#2799)
* Update regex for `looselyParseAmount` for 5-9 decimal places

* Add release note

* Update comment, add some more tests
2024-06-03 07:52:01 -07:00
Julian Dominguez-Schatz
7d960579f9 Release 'Splits in rules' feature (#2789)
* Release 'Splits in rules' feature

* Add release notes

* Add missed comment

* Remove unused import

* Fix failing test
2024-06-03 07:39:57 -07:00
Robert Dyer
5fd1d05670 Show account notes in tooltip on sidebar (#2796)
* Show account notes in tooltip on sidebar

* add release note

* Only show tooltip for accounts

* Split account name and note into separate nodes

* Add padding inside tooltip

* Move tooltip to the right

* remove commented code

* Tweak the visual design

* Fix lint

* adjust padding

* Update packages/desktop-client/src/components/sidebar/Account.tsx

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>

* refactor to avoid function

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-06-03 07:39:33 -07:00
Matiss Janis Aboltins
0e86dea544 ♻️ (tooltip) refactoring to react-aria (vol.6) (#2771)
* ♻️ (tooltip) refactoring to react-aria (vol.6)

* Release notes
2024-06-03 11:56:39 +01:00
257 changed files with 3654 additions and 1987 deletions

39
.github/workflows/trafico.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
##########################################################################################
# WARNING! This workflow uses the 'pull_request_target' event. That mans that it will #
# always run in the context of the main actualbudget/actual repo, even if the PR is from #
# a fork. This is necessary to get access to a GitHub token that can modify the PR. #
# Be VERY CAREFUL about adding things to this workflow, since forks can inject #
# arbitrary code into their branch, and can pollute the artifacts we download. Arbitrary #
# code execution in this workflow could lead to a compromise of the main repo. #
##########################################################################################
# See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests #
##########################################################################################
name: Trafico Reviews
on:
pull_request_target:
types:
- opened
- closed
- reopened
- synchronize
- edited
- review_requested
- review_request_removed
pull_request_review:
types: [submitted, edited, dismissed]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
manage-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actualbudget/trafico@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/api",
"version": "6.8.0",
"version": "6.8.1",
"license": "MIT",
"description": "An API for Actual",
"engines": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -102,10 +102,22 @@
"name": "Store",
"transfer_account_id": null,
"deleted": false
},
{
"id": "620e85b1-2ae7-45b1-bb3e-b875ea5c553a",
"name": "Work",
"transfer_account_id": null,
"deleted": false
}
],
"payee_locations": [],
"category_groups": [
{
"id": "a5c355c2-3b77-4a7f-b8b3-c832b10cfec8",
"name": "Income",
"hidden": false,
"deleted": false
},
{
"id": "d5c355c2-3b77-4a7f-b8b3-c832b10cfec9",
"name": "Internal Master Category",
@@ -611,6 +623,30 @@
"goal_overall_funded": null,
"goal_overall_left": null,
"deleted": false
},
{
"id": "1429f287-50aa-49d8-a89c-752cbd167d6c",
"category_group_id": "a5c355c2-3b77-4a7f-b8b3-c832b10cfec8",
"name": "Income",
"hidden": false,
"original_category_group_id": null,
"note": null,
"budgeted": 0,
"activity": 0,
"balance": 0,
"goal_type": "NEED",
"goal_day": null,
"goal_cadence": 1,
"goal_cadence_frequency": 1,
"goal_creation_month": null,
"goal_target": 0,
"goal_target_month": null,
"goal_percentage_complete": null,
"goal_months_to_budget": null,
"goal_under_funded": null,
"goal_overall_funded": null,
"goal_overall_left": null,
"deleted": false
}
],
"months": [
@@ -1711,6 +1747,26 @@
"import_payee_name_original": null,
"debt_transaction_type": null,
"deleted": false
},
{
"id": "9a22f287-f1e0-4667-9fc0-91e4a4262193",
"date": "2024-02-02",
"amount": 2000000,
"memo": "Paycheck",
"cleared": "cleared",
"approved": true,
"flag_color": null,
"account_id": "bc1d862f-bab0-41c3-bd1e-6cee8c688e32",
"payee_id": "620e85b1-2ae7-45b1-bb3e-b875ea5c553a",
"category_id": "1429f287-50aa-49d8-a89c-752cbd167d6c",
"transfer_account_id": null,
"transfer_transaction_id": null,
"matched_transaction_id": null,
"import_id": null,
"import_payee_name": null,
"import_payee_name_original": null,
"debt_transaction_type": null,
"deleted": false
}
],
"subtransactions": [

View File

@@ -42,6 +42,9 @@ test.describe('Mobile', () => {
'Mortgage',
'Water',
'Power',
'Starting Balances',
'Misc',
'Income',
]);
await expect(page).toMatchThemeScreenshots();
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -59,7 +59,7 @@ test.describe('Onboarding', () => {
await expect(budgetPage.budgetTable).toBeVisible({ timeout: 30000 });
const accountPage = await navigation.goToAccountPage('Checking');
await expect(accountPage.accountBalance).toHaveText('600.00');
await expect(accountPage.accountBalance).toHaveText('2,600.00');
await navigation.goToAccountPage('Saving');
await expect(accountPage.accountBalance).toHaveText('250.00');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -69,11 +69,6 @@ test.describe('Rules', () => {
});
test('creates a split transaction rule and makes sure it is applied when creating a transaction', async () => {
const settingsPage = await navigation.goToSettingsPage();
await settingsPage.enableExperimentalFeature('splits in rules');
await expect(settingsPage.page.getByLabel('splits in rules')).toBeChecked();
rulesPage = await navigation.goToRulesPage();
await rulesPage.createRule({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Submodule packages/desktop-client/locale added at a0e122296a

View File

@@ -32,6 +32,7 @@
"@use-gesture/react": "^10.3.0",
"@vitejs/plugin-basic-ssl": "^1.1.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"auto-text-size": "^0.2.3",
"chokidar": "^3.5.3",
"cross-env": "^7.0.3",
"date-fns": "^2.30.0",

View File

@@ -544,6 +544,7 @@ export function Modals() {
<RolloverToBudgetMenuModal
modalProps={modalProps}
onTransfer={options.onTransfer}
onCover={options.onCover}
onHoldBuffer={options.onHoldBuffer}
onResetHoldBuffer={options.onResetHoldBuffer}
/>
@@ -596,8 +597,9 @@ export function Modals() {
<CoverModal
key={name}
modalProps={modalProps}
categoryId={options.categoryId}
title={options.title}
month={options.month}
showToBeBudgeted={options.showToBeBudgeted}
onSubmit={options.onSubmit}
/>
);

View File

@@ -35,6 +35,12 @@ function getErrorMessage(type, code) {
case 'RATE_LIMIT_EXCEEDED':
return '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.';
case 'ACCOUNT_NEEDS_ATTENTION':
return 'The account needs your attention at [SimpleFIN](https://beta-bridge.simplefin.org/auth/login).';
default:
}

View File

@@ -19,7 +19,7 @@ import { InitialFocus } from '../common/InitialFocus';
import { Input } from '../common/Input';
import { Menu } from '../common/Menu';
import { MenuButton } from '../common/MenuButton';
import { MenuTooltip } from '../common/MenuTooltip';
import { Popover } from '../common/Popover';
import { Search } from '../common/Search';
import { Stack } from '../common/Stack';
import { View } from '../common/View';
@@ -29,7 +29,7 @@ import { NotesButton } from '../NotesButton';
import { SelectedTransactionsButton } from '../transactions/SelectedTransactions';
import { Balances } from './Balance';
import { ReconcilingMessage, ReconcileTooltip } from './Reconcile';
import { ReconcilingMessage, ReconcileMenu } from './Reconcile';
export function AccountHeader({
filteredAmount,
@@ -86,6 +86,7 @@ export function AccountHeader({
}) {
const [menuOpen, setMenuOpen] = useState(false);
const searchInput = useRef(null);
const triggerRef = useRef(null);
const splitsExpanded = useSplitsExpanded();
const syncServerStatus = useSyncServerStatus();
const isUsingServer = syncServerStatus !== 'no-server';
@@ -270,7 +271,7 @@ export function AccountHeader({
}
style={{ marginRight: 4 }}
/>{' '}
{isServerOffline ? 'Sync offline' : 'Sync'}
{isServerOffline ? 'Bank Sync Offline' : 'Bank Sync'}
</>
) : (
<>
@@ -338,9 +339,14 @@ export function AccountHeader({
</Button>
{account ? (
<View>
<MenuButton onClick={() => setMenuOpen(true)} />
<MenuButton ref={triggerRef} onClick={() => setMenuOpen(true)} />
{menuOpen && (
<Popover
triggerRef={triggerRef}
style={{ width: 275 }}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<AccountMenu
account={account}
canSync={canSync}
@@ -356,22 +362,31 @@ export function AccountHeader({
onReconcile={onReconcile}
onClose={() => setMenuOpen(false)}
/>
)}
</Popover>
</View>
) : (
<View>
<MenuButton onClick={() => setMenuOpen(true)} />
<MenuButton ref={triggerRef} onClick={() => setMenuOpen(true)} />
{menuOpen && (
<CategoryMenu
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
setMenuOpen(false);
onMenuSelect(item);
}}
onClose={() => setMenuOpen(false)}
isSorted={isSorted}
items={[
isSorted && {
name: 'remove-sorting',
text: 'Remove all sorting',
},
{ name: 'export', text: 'Export' },
]}
/>
)}
</Popover>
</View>
)}
</Stack>
@@ -418,76 +433,54 @@ function AccountMenu({
const syncServerStatus = useSyncServerStatus();
return tooltip === 'reconcile' ? (
<ReconcileTooltip
<ReconcileMenu
account={account}
onClose={onClose}
onReconcile={onReconcile}
/>
) : (
<MenuTooltip width={200} onClose={onClose}>
<Menu
onMenuSelect={item => {
if (item === 'reconcile') {
setTooltip('reconcile');
} else {
onMenuSelect(item);
}
}}
items={[
isSorted && {
name: 'remove-sorting',
text: 'Remove all sorting',
},
canShowBalances && {
name: 'toggle-balance',
text: (showBalances ? 'Hide' : 'Show') + ' running balance',
},
{
name: 'toggle-cleared',
text: (showCleared ? 'Hide' : 'Show') + ' “cleared” checkboxes',
},
{
name: 'toggle-reconciled',
text:
(showReconciled ? 'Hide' : 'Show') + ' reconciled transactions',
},
{ name: 'export', text: 'Export' },
{ name: 'reconcile', text: 'Reconcile' },
account &&
!account.closed &&
(canSync
? {
name: 'unlink',
text: 'Unlink account',
}
: syncServerStatus === 'online' && {
name: 'link',
text: 'Link account',
}),
account.closed
? { name: 'reopen', text: 'Reopen account' }
: { name: 'close', text: 'Close account' },
].filter(x => x)}
/>
</MenuTooltip>
);
}
function CategoryMenu({ onClose, onMenuSelect, isSorted }) {
return (
<MenuTooltip width={200} onClose={onClose}>
<Menu
onMenuSelect={item => {
<Menu
onMenuSelect={item => {
if (item === 'reconcile') {
setTooltip('reconcile');
} else {
onMenuSelect(item);
}}
items={[
isSorted && {
name: 'remove-sorting',
text: 'Remove all sorting',
},
{ name: 'export', text: 'Export' },
]}
/>
</MenuTooltip>
}
}}
items={[
isSorted && {
name: 'remove-sorting',
text: 'Remove all sorting',
},
canShowBalances && {
name: 'toggle-balance',
text: (showBalances ? 'Hide' : 'Show') + ' running balance',
},
{
name: 'toggle-cleared',
text: (showCleared ? 'Hide' : 'Show') + ' “cleared” checkboxes',
},
{
name: 'toggle-reconciled',
text: (showReconciled ? 'Hide' : 'Show') + ' reconciled transactions',
},
{ name: 'export', text: 'Export' },
{ name: 'reconcile', text: 'Reconcile' },
account &&
!account.closed &&
(canSync
? {
name: 'unlink',
text: 'Unlink account',
}
: syncServerStatus === 'online' && {
name: 'link',
text: 'Link account',
}),
account.closed
? { name: 'reopen', text: 'Reopen account' }
: { name: 'close', text: 'Close account' },
].filter(x => x)}
/>
);
}

View File

@@ -12,7 +12,6 @@ import { Text } from '../common/Text';
import { View } from '../common/View';
import { useFormat } from '../spreadsheet/useFormat';
import { useSheetValue } from '../spreadsheet/useSheetValue';
import { Tooltip } from '../tooltips';
export function ReconcilingMessage({
balanceQuery,
@@ -95,7 +94,7 @@ export function ReconcilingMessage({
);
}
export function ReconcileTooltip({ account, onReconcile, onClose }) {
export function ReconcileMenu({ account, onReconcile, onClose }) {
const balanceQuery = queries.accountBalance(account);
const clearedBalance = useSheetValue({
name: balanceQuery.name + '-cleared',
@@ -117,24 +116,22 @@ export function ReconcileTooltip({ account, onReconcile, onClose }) {
}
return (
<Tooltip position="bottom-right" width={275} onClose={onClose}>
<View style={{ padding: '5px 8px' }}>
<Text>
Enter the current balance of your bank account that you want to
reconcile with:
</Text>
<form onSubmit={onSubmit}>
{clearedBalance != null && (
<InitialFocus>
<Input
defaultValue={format(clearedBalance, 'financial')}
style={{ margin: '7px 0' }}
/>
</InitialFocus>
)}
<Button type="primary">Reconcile</Button>
</form>
</View>
</Tooltip>
<View style={{ padding: '5px 8px' }}>
<Text>
Enter the current balance of your bank account that you want to
reconcile with:
</Text>
<form onSubmit={onSubmit}>
{clearedBalance != null && (
<InitialFocus>
<Input
defaultValue={format(clearedBalance, 'financial')}
style={{ margin: '7px 0' }}
/>
</InitialFocus>
)}
<Button type="primary">Reconcile</Button>
</form>
</View>
);
}

View File

@@ -122,13 +122,12 @@ export function AccountAutocomplete({
.filter(item => {
return includeClosedAccounts ? item : !item.closed;
})
.sort((a, b) => {
if (a.closed === b.closed) {
return a.offbudget === b.offbudget ? 0 : a.offbudget ? 1 : -1;
} else {
return a.closed ? 1 : -1;
}
});
.sort(
(a, b) =>
a.closed - b.closed ||
a.offbudget - b.offbudget ||
a.sort_order - b.sort_order,
);
return (
<Autocomplete

View File

@@ -92,7 +92,16 @@ export function defaultFilterSuggestion<T extends Item>(
suggestion: T,
value: string,
) {
return getItemName(suggestion).toLowerCase().includes(value.toLowerCase());
return getItemName(suggestion)
.toLowerCase()
.normalize('NFD')
.replace(/\p{Diacritic}/gu, '')
.includes(
value
.toLowerCase()
.normalize('NFD')
.replace(/\p{Diacritic}/gu, ''),
);
}
function defaultFilterSuggestions<T extends Item>(

View File

@@ -7,6 +7,7 @@ import React, {
type ComponentType,
type ComponentPropsWithoutRef,
type ReactElement,
useCallback,
} from 'react';
import { css } from 'glamor';
@@ -135,6 +136,21 @@ function CategoryList({
);
}
function customSort(obj: CategoryAutocompleteItem, value: string): number {
const name = obj.name.toLowerCase();
const groupName = obj.group ? obj.group.name.toLowerCase() : '';
if (obj.id === 'split') {
return -2;
}
if (name.includes(value)) {
return -1;
}
if (groupName.includes(value)) {
return 0;
}
return 1;
}
type CategoryAutocompleteProps = ComponentProps<
typeof Autocomplete<CategoryAutocompleteItem>
> & {
@@ -183,6 +199,33 @@ export function CategoryAutocomplete({
[defaultCategoryGroups, categoryGroups, showSplitOption],
);
const filterSuggestions = useCallback(
(
suggestions: CategoryAutocompleteItem[],
value: string,
): CategoryAutocompleteItem[] => {
return suggestions
.filter(suggestion => {
return (
suggestion.id === 'split' ||
suggestion.group?.name
.toLowerCase()
.includes(value.toLowerCase()) ||
(suggestion.group?.name + ' ' + suggestion.name)
.toLowerCase()
.includes(value.toLowerCase()) ||
defaultFilterSuggestion(suggestion, value)
);
})
.sort(
(a, b) =>
customSort(a, value.toLowerCase()) -
customSort(b, value.toLowerCase()),
);
},
[],
);
return (
<Autocomplete
strict={true}
@@ -197,14 +240,7 @@ export function CategoryAutocomplete({
}
return 0;
}}
filterSuggestions={(suggestions, value) => {
return suggestions.filter(suggestion => {
return (
suggestion.id === 'split' ||
defaultFilterSuggestion(suggestion, value)
);
});
}}
filterSuggestions={filterSuggestions}
suggestions={categorySuggestions}
renderItems={(items, getItemProps, highlightedIndex) => (
<CategoryList

View File

@@ -1,23 +1,26 @@
// @ts-strict-ignore
import React, { type ComponentProps } from 'react';
import React, { type ComponentPropsWithoutRef } from 'react';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { SvgArrowThinRight } from '../../icons/v1';
import { useResponsive } from '../../ResponsiveProvider';
import { type CSSProperties } from '../../style';
import { View } from '../common/View';
import { type Binding } from '../spreadsheet';
import { CellValue } from '../spreadsheet/CellValue';
import { useSheetValue } from '../spreadsheet/useSheetValue';
import { makeBalanceAmountStyle } from './util';
type BalanceWithCarryoverProps = {
carryover: ComponentProps<typeof CellValue>['binding'];
balance: ComponentProps<typeof CellValue>['binding'];
goal?: ComponentProps<typeof CellValue>['binding'];
budgeted?: ComponentProps<typeof CellValue>['binding'];
type BalanceWithCarryoverProps = Omit<
ComponentPropsWithoutRef<typeof CellValue>,
'binding'
> & {
carryover: Binding;
balance: Binding;
goal?: Binding;
budgeted?: Binding;
disabled?: boolean;
balanceStyle?: CSSProperties;
carryoverStyle?: CSSProperties;
};
export function BalanceWithCarryover({
@@ -26,8 +29,8 @@ export function BalanceWithCarryover({
goal,
budgeted,
disabled,
balanceStyle,
carryoverStyle,
...props
}: BalanceWithCarryoverProps) {
const carryoverValue = useSheetValue(carryover);
const balanceValue = useSheetValue(balance);
@@ -40,6 +43,7 @@ export function BalanceWithCarryover({
return (
<>
<CellValue
{...props}
binding={balance}
type="financial"
getStyle={value =>
@@ -53,9 +57,8 @@ export function BalanceWithCarryover({
textAlign: 'right',
...(!disabled && {
cursor: 'pointer',
':hover': { textDecoration: 'underline' },
}),
...balanceStyle,
...props.style,
}}
/>
{carryoverValue && (

View File

@@ -37,7 +37,6 @@ export const BudgetCategories = memo(
function onCollapse(value) {
setCollapsedGroupIdsPref(value);
}
const [isAddingGroup, setIsAddingGroup] = useState(false);
const [newCategoryForGroup, setNewCategoryForGroup] = useState(null);
const items = useMemo(() => {

View File

@@ -1,10 +1,10 @@
import React, { useRef, useState } from 'react';
import React, { useRef, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useCategories } from '../../hooks/useCategories';
import { useLocalPref } from '../../hooks/useLocalPref';
import { theme, styles } from '../../style';
import { View } from '../common/View';
import { IntersectionBoundary } from '../tooltips';
import { BudgetCategories } from './BudgetCategories';
import { BudgetSummaries } from './BudgetSummaries';
@@ -31,12 +31,37 @@ export function BudgetTable(props) {
} = props;
const budgetCategoriesRef = useRef();
const scrollableDivRef = useRef();
const location = useLocation();
const { grouped: categoryGroups } = useCategories();
const [collapsedGroupIds = [], setCollapsedGroupIdsPref] =
useLocalPref('budget.collapsed');
const [showHiddenCategories, setShowHiddenCategoriesPef] = useLocalPref(
'budget.showHiddenCategories',
);
const getCurrentScrollPosition = () => {
return scrollableDivRef.current?.scrollTop || 0;
};
const onShowActivityWithScroll = (categoryId, month) => {
const scrollPosition = getCurrentScrollPosition();
onShowActivity(categoryId, month, scrollPosition);
};
useEffect(() => {
const savedScrollPosition = location.state?.scrollPosition;
if (savedScrollPosition && scrollableDivRef.current) {
// Use requestAnimationFrame to ensure the DOM is ready
requestAnimationFrame(() => {
if (scrollableDivRef.current) {
scrollableDivRef.current.scrollTop = savedScrollPosition;
}
});
}
}, [location.state?.scrollPosition]);
const [editing, setEditing] = useState(null);
const onEditMonth = (id, month) => {
@@ -202,41 +227,40 @@ export function BudgetTable(props) {
expandAllCategories={expandAllCategories}
collapseAllCategories={collapseAllCategories}
/>
<IntersectionBoundary.Provider value={budgetCategoriesRef}>
<View
id="scrollableDiv"
style={{
overflowY: 'scroll',
overflowAnchor: 'none',
flex: 1,
paddingLeft: 5,
paddingRight: 5,
}}
innerRef={scrollableDivRef}
>
<View
style={{
overflowY: 'scroll',
overflowAnchor: 'none',
flex: 1,
paddingLeft: 5,
paddingRight: 5,
flexShrink: 0,
}}
innerRef={budgetCategoriesRef}
onKeyDown={onKeyDown}
>
<View
style={{
flexShrink: 0,
}}
onKeyDown={onKeyDown}
>
<BudgetCategories
categoryGroups={categoryGroups}
editingCell={editing}
dataComponents={dataComponents}
onEditMonth={onEditMonth}
onEditName={onEditName}
onSaveCategory={onSaveCategory}
onSaveGroup={onSaveGroup}
onDeleteCategory={onDeleteCategory}
onDeleteGroup={onDeleteGroup}
onReorderCategory={_onReorderCategory}
onReorderGroup={_onReorderGroup}
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivity}
/>
</View>
<BudgetCategories
categoryGroups={categoryGroups}
editingCell={editing}
dataComponents={dataComponents}
onEditMonth={onEditMonth}
onEditName={onEditName}
onSaveCategory={onSaveCategory}
onSaveGroup={onSaveGroup}
onDeleteCategory={onDeleteCategory}
onDeleteGroup={onDeleteGroup}
onReorderCategory={_onReorderCategory}
onReorderGroup={_onReorderGroup}
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivityWithScroll}
/>
</View>
</IntersectionBoundary.Provider>
</View>
</MonthsProvider>
</View>
);

View File

@@ -276,7 +276,7 @@ function BudgetInner(props: BudgetInnerProps) {
dispatch(applyBudgetAction(month, type, args));
};
const onShowActivity = (categoryId, month) => {
const onShowActivity = (categoryId, month, scrollPosition) => {
const conditions = [
{ field: 'category', op: 'is', value: categoryId, type: 'id' },
{
@@ -287,6 +287,12 @@ function BudgetInner(props: BudgetInnerProps) {
type: 'date',
},
];
navigate('/budget', {
replace: true,
state: { scrollPosition }
});
navigate('/accounts', {
state: {
goBack: true,

View File

@@ -1,48 +0,0 @@
import React from 'react';
import { Tooltip } from '../../tooltips';
import { BalanceMenu } from './BalanceMenu';
type BalanceTooltipProps = {
categoryId: string;
tooltip: { close: () => void };
month: string;
onBudgetAction: (month: string, action: string, arg: unknown) => void;
onClose?: () => void;
};
export function BalanceTooltip({
categoryId,
tooltip,
month,
onBudgetAction,
onClose,
...tooltipProps
}: BalanceTooltipProps) {
const _onClose = () => {
tooltip.close();
onClose?.();
};
return (
<Tooltip
position="bottom-right"
width={200}
style={{ padding: 0 }}
onClose={_onClose}
{...tooltipProps}
>
<BalanceMenu
categoryId={categoryId}
onCarryover={carryover => {
onBudgetAction?.(month, 'carryover', {
category: categoryId,
flag: carryover,
});
_onClose();
}}
/>
</Tooltip>
);
}

View File

@@ -1,5 +1,5 @@
// @ts-strict-ignore
import React, { memo, useState } from 'react';
import React, { memo, useRef, useState } from 'react';
import { reportBudget } from 'loot-core/src/client/queries';
import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
@@ -8,16 +8,16 @@ import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util';
import { SvgCheveronDown } from '../../../icons/v1';
import { styles, theme, type CSSProperties } from '../../../style';
import { Button } from '../../common/Button';
import { Popover } from '../../common/Popover';
import { Text } from '../../common/Text';
import { View } from '../../common/View';
import { CellValue } from '../../spreadsheet/CellValue';
import { useFormat } from '../../spreadsheet/useFormat';
import { Field, SheetCell } from '../../table';
import { Tooltip, useTooltip } from '../../tooltips';
import { BalanceWithCarryover } from '../BalanceWithCarryover';
import { makeAmountGrey } from '../util';
import { BalanceTooltip } from './BalanceTooltip';
import { BalanceMenu } from './BalanceMenu';
import { BudgetMenu } from './BudgetMenu';
const headerLabelStyle: CSSProperties = {
@@ -156,9 +156,12 @@ export const CategoryMonth = memo(function CategoryMonth({
onBudgetAction,
onShowActivity,
}: CategoryMonthProps) {
const balanceTooltip = useTooltip();
const [menuOpen, setMenuOpen] = useState(false);
const [hover, setHover] = useState(false);
const triggerRef = useRef(null);
const [balanceMenuOpen, setBalanceMenuOpen] = useState(false);
const triggerBalanceMenuRef = useRef(null);
return (
<View
@@ -196,6 +199,7 @@ export const CategoryMonth = memo(function CategoryMonth({
}}
>
<Button
ref={triggerRef}
type="bare"
onClick={e => {
e.stopPropagation();
@@ -212,44 +216,42 @@ export const CategoryMonth = memo(function CategoryMonth({
style={menuOpen && { opacity: 1 }}
/>
</Button>
{menuOpen && (
<Tooltip
position="bottom-left"
width={200}
style={{ padding: 0 }}
onClose={() => setMenuOpen(false)}
>
<BudgetMenu
onCopyLastMonthAverage={() => {
onBudgetAction?.(month, 'copy-single-last', {
category: category.id,
});
}}
onSetMonthsAverage={numberOfMonths => {
if (
numberOfMonths !== 3 &&
numberOfMonths !== 6 &&
numberOfMonths !== 12
) {
return;
}
onBudgetAction?.(
month,
`set-single-${numberOfMonths}-avg`,
{
category: category.id,
},
);
}}
onApplyBudgetTemplate={() => {
onBudgetAction?.(month, 'apply-single-category-template', {
category: category.id,
});
}}
/>
</Tooltip>
)}
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
placement="bottom start"
>
<BudgetMenu
onCopyLastMonthAverage={() => {
onBudgetAction?.(month, 'copy-single-last', {
category: category.id,
});
setMenuOpen(false);
}}
onSetMonthsAverage={numberOfMonths => {
if (
numberOfMonths !== 3 &&
numberOfMonths !== 6 &&
numberOfMonths !== 12
) {
return;
}
onBudgetAction?.(month, `set-single-${numberOfMonths}-avg`, {
category: category.id,
});
setMenuOpen(false);
}}
onApplyBudgetTemplate={() => {
onBudgetAction?.(month, 'apply-single-category-template', {
category: category.id,
});
setMenuOpen(false);
}}
/>
</Popover>
</View>
)}
<SheetCell
@@ -300,7 +302,9 @@ export const CategoryMonth = memo(function CategoryMonth({
<Field name="spent" width="flex" style={{ textAlign: 'right' }}>
<span
data-testid="category-month-spent"
onClick={() => onShowActivity(category.id, month)}
onClick={() => {
onShowActivity(category.id, month);
}}
>
<CellValue
binding={reportBudget.catSumAmount(category.id)}
@@ -323,23 +327,41 @@ export const CategoryMonth = memo(function CategoryMonth({
width="flex"
style={{ paddingRight: styles.monthRightPadding, textAlign: 'right' }}
>
<span {...(category.is_income ? {} : balanceTooltip.getOpenEvents())}>
<span
ref={triggerBalanceMenuRef}
{...(category.is_income
? {}
: { onClick: () => setBalanceMenuOpen(true) })}
>
<BalanceWithCarryover
disabled={category.is_income}
carryover={reportBudget.catCarryover(category.id)}
balance={reportBudget.catBalance(category.id)}
goal={reportBudget.catGoal(category.id)}
budgeted={reportBudget.catBudgeted(category.id)}
style={{
':hover': { textDecoration: 'underline' },
}}
/>
</span>
{balanceTooltip.isOpen && (
<BalanceTooltip
<Popover
triggerRef={triggerBalanceMenuRef}
isOpen={balanceMenuOpen}
onOpenChange={() => setBalanceMenuOpen(false)}
placement="bottom end"
>
<BalanceMenu
categoryId={category.id}
tooltip={balanceTooltip}
month={month}
onBudgetAction={onBudgetAction}
onCarryover={carryover => {
onBudgetAction?.(month, 'carryover', {
category: category.id,
flag: carryover,
});
setBalanceMenuOpen(false);
}}
/>
)}
</Popover>
</Field>
)}
</View>

View File

@@ -1,5 +1,5 @@
// @ts-strict-ignore
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { css } from 'glamor';
@@ -9,11 +9,11 @@ import { SvgDotsHorizontalTriple } from '../../../../icons/v1';
import { SvgArrowButtonDown1, SvgArrowButtonUp1 } from '../../../../icons/v2';
import { theme, styles } from '../../../../style';
import { Button } from '../../../common/Button';
import { Popover } from '../../../common/Popover';
import { Stack } from '../../../common/Stack';
import { View } from '../../../common/View';
import { NotesButton } from '../../../NotesButton';
import { NamespaceContext } from '../../../spreadsheet/NamespaceContext';
import { Tooltip } from '../../../tooltips';
import { useReport } from '../ReportContext';
import { BudgetMonthMenu } from './BudgetMonthMenu';
@@ -33,6 +33,8 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
} = useReport();
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
function onMenuOpen() {
setMenuOpen(true);
}
@@ -129,48 +131,51 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
/>
</View>
<View style={{ userSelect: 'none' }}>
<Button type="bare" aria-label="Menu" onClick={onMenuOpen}>
<Button
ref={triggerRef}
type="bare"
aria-label="Menu"
onClick={onMenuOpen}
>
<SvgDotsHorizontalTriple
width={15}
height={15}
style={{ color: theme.pageTextLight }}
/>
</Button>
{menuOpen && (
<Tooltip
position="bottom-right"
width={200}
style={{ padding: 0 }}
onClose={onMenuClose}
>
<BudgetMonthMenu
onCopyLastMonthBudget={() => {
onBudgetAction(month, 'copy-last');
onMenuClose();
}}
onSetBudgetsToZero={() => {
onBudgetAction(month, 'set-zero');
onMenuClose();
}}
onSetMonthsAverage={numberOfMonths => {
onBudgetAction(month, `set-${numberOfMonths}-avg`);
onMenuClose();
}}
onCheckTemplates={() => {
onBudgetAction(month, 'check-templates');
onMenuClose();
}}
onApplyBudgetTemplates={() => {
onBudgetAction(month, 'apply-goal-template');
onMenuClose();
}}
onOverwriteWithBudgetTemplates={() => {
onBudgetAction(month, 'overwrite-goal-template');
onMenuClose();
}}
/>
</Tooltip>
)}
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={onMenuClose}
>
<BudgetMonthMenu
onCopyLastMonthBudget={() => {
onBudgetAction(month, 'copy-last');
onMenuClose();
}}
onSetBudgetsToZero={() => {
onBudgetAction(month, 'set-zero');
onMenuClose();
}}
onSetMonthsAverage={numberOfMonths => {
onBudgetAction(month, `set-${numberOfMonths}-avg`);
onMenuClose();
}}
onCheckTemplates={() => {
onBudgetAction(month, 'check-templates');
onMenuClose();
}}
onApplyBudgetTemplates={() => {
onBudgetAction(month, 'apply-goal-template');
onMenuClose();
}}
onOverwriteWithBudgetTemplates={() => {
onBudgetAction(month, 'overwrite-goal-template');
onMenuClose();
}}
/>
</Popover>
</View>
</View>
</View>

View File

@@ -6,13 +6,12 @@ import { reportBudget } from 'loot-core/src/client/queries';
import { theme, type CSSProperties, styles } from '../../../../style';
import { AlignedText } from '../../../common/AlignedText';
import { HoverTarget } from '../../../common/HoverTarget';
import { Text } from '../../../common/Text';
import { Tooltip } from '../../../common/Tooltip';
import { View } from '../../../common/View';
import { PrivacyFilter } from '../../../PrivacyFilter';
import { useFormat } from '../../../spreadsheet/useFormat';
import { useSheetValue } from '../../../spreadsheet/useSheetValue';
import { Tooltip } from '../../../tooltips';
import { makeAmountFullStyle } from '../../util';
type SavedProps = {
@@ -25,6 +24,7 @@ export function Saved({ projected, style }: SavedProps) {
const format = useFormat();
const saved = projected ? budgetedSaved : totalSaved;
const isNegative = saved < 0;
const diff = totalSaved - budgetedSaved;
return (
<View style={{ alignItems: 'center', fontSize: 14, ...style }}>
@@ -36,42 +36,36 @@ export function Saved({ projected, style }: SavedProps) {
</View>
)}
<HoverTarget
renderContent={() => {
if (!projected) {
const diff = totalSaved - budgetedSaved;
return (
<Tooltip
position="bottom-center"
style={{ padding: 10, fontSize: 14 }}
>
<AlignedText
left="Projected Savings:"
right={
<Text
style={{
...makeAmountFullStyle(budgetedSaved),
...styles.tnum,
}}
>
{format(budgetedSaved, 'financial-with-sign')}
</Text>
}
/>
<AlignedText
left="Difference:"
right={
<Text
style={{ ...makeAmountFullStyle(diff), ...styles.tnum }}
>
{format(diff, 'financial-with-sign')}
</Text>
}
/>
</Tooltip>
);
}
return null;
<Tooltip
style={{ ...styles.tooltip, fontSize: 14, padding: 10 }}
content={
<>
<AlignedText
left="Projected Savings:"
right={
<Text
style={{
...makeAmountFullStyle(budgetedSaved),
...styles.tnum,
}}
>
{format(budgetedSaved, 'financial-with-sign')}
</Text>
}
/>
<AlignedText
left="Difference:"
right={
<Text style={{ ...makeAmountFullStyle(diff), ...styles.tnum }}>
{format(diff, 'financial-with-sign')}
</Text>
}
/>
</>
}
placement="bottom"
triggerProps={{
isDisabled: Boolean(projected),
}}
>
<View
@@ -90,7 +84,7 @@ export function Saved({ projected, style }: SavedProps) {
{format(saved, 'financial')}
</PrivacyFilter>
</View>
</HoverTarget>
</Tooltip>
</View>
);
}

View File

@@ -43,16 +43,14 @@ export function BalanceMenu({
}
}}
items={[
{
name: 'transfer',
text: 'Transfer to another category',
},
{
name: 'carryover',
text: carryover
? 'Remove overspending rollover'
: 'Rollover overspending',
},
...(balance > 0
? [
{
name: 'transfer',
text: 'Transfer to another category',
},
]
: []),
...(balance < 0
? [
{
@@ -61,6 +59,12 @@ export function BalanceMenu({
},
]
: []),
{
name: 'carryover',
text: carryover
? 'Remove overspending rollover'
: 'Rollover overspending',
},
]}
/>
);

View File

@@ -60,7 +60,7 @@ export function BalanceMovementMenu({
<CoverMenu
onClose={onClose}
onSubmit={fromCategoryId => {
onBudgetAction(month, 'cover', {
onBudgetAction(month, 'cover-overspending', {
to: categoryId,
from: fromCategoryId,
});

View File

@@ -8,15 +8,21 @@ import { View } from '../../common/View';
import { addToBeBudgetedGroup } from '../util';
type CoverMenuProps = {
showToBeBudgeted?: boolean;
onSubmit: (categoryId: string) => void;
onClose: () => void;
};
export function CoverMenu({ onSubmit, onClose }: CoverMenuProps) {
export function CoverMenu({
showToBeBudgeted = true,
onSubmit,
onClose,
}: CoverMenuProps) {
const { grouped: originalCategoryGroups } = useCategories();
const categoryGroups = addToBeBudgetedGroup(
originalCategoryGroups.filter(g => !g.is_income),
);
let categoryGroups = originalCategoryGroups.filter(g => !g.is_income);
categoryGroups = showToBeBudgeted
? addToBeBudgetedGroup(categoryGroups)
: categoryGroups;
const [categoryId, setCategoryId] = useState<string | null>(null);
function submit() {

View File

@@ -295,7 +295,9 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({
<Field name="spent" width="flex" style={{ textAlign: 'right' }}>
<span
data-testid="category-month-spent"
onClick={() => onShowActivity(category.id, month)}
onClick={() => {
onShowActivity(category.id, month);
}}
>
<CellValue
binding={rolloverBudget.catSumAmount(category.id)}
@@ -323,6 +325,9 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({
balance={rolloverBudget.catBalance(category.id)}
goal={rolloverBudget.catGoal(category.id)}
budgeted={rolloverBudget.catBudgeted(category.id)}
style={{
':hover': { textDecoration: 'underline' },
}}
/>
</span>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { css } from 'glamor';
@@ -8,10 +8,10 @@ import { SvgDotsHorizontalTriple } from '../../../../icons/v1';
import { SvgArrowButtonDown1, SvgArrowButtonUp1 } from '../../../../icons/v2';
import { theme, styles } from '../../../../style';
import { Button } from '../../../common/Button';
import { Popover } from '../../../common/Popover';
import { View } from '../../../common/View';
import { NotesButton } from '../../../NotesButton';
import { NamespaceContext } from '../../../spreadsheet/NamespaceContext';
import { Tooltip } from '../../../tooltips';
import { useRollover } from '../RolloverContext';
import { BudgetMonthMenu } from './BudgetMonthMenu';
@@ -31,6 +31,8 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
} = useRollover();
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
function onMenuOpen() {
setMenuOpen(true);
}
@@ -131,52 +133,55 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
/>
</View>
<View style={{ userSelect: 'none', marginLeft: 2 }}>
<Button type="bare" aria-label="Menu" onClick={onMenuOpen}>
<Button
ref={triggerRef}
type="bare"
aria-label="Menu"
onClick={onMenuOpen}
>
<SvgDotsHorizontalTriple
width={15}
height={15}
style={{ color: theme.pageTextLight }}
/>
</Button>
{menuOpen && (
<Tooltip
position="bottom-right"
width={200}
style={{ padding: 0 }}
onClose={onMenuClose}
>
<BudgetMonthMenu
onCopyLastMonthBudget={() => {
onBudgetAction(month, 'copy-last');
onMenuClose();
}}
onSetBudgetsToZero={() => {
onBudgetAction(month, 'set-zero');
onMenuClose();
}}
onSetMonthsAverage={numberOfMonths => {
onBudgetAction(month, `set-${numberOfMonths}-avg`);
onMenuClose();
}}
onCheckTemplates={() => {
onBudgetAction(month, 'check-templates');
onMenuClose();
}}
onApplyBudgetTemplates={() => {
onBudgetAction(month, 'apply-goal-template');
onMenuClose();
}}
onOverwriteWithBudgetTemplates={() => {
onBudgetAction(month, 'overwrite-goal-template');
onMenuClose();
}}
onEndOfMonthCleanup={() => {
onBudgetAction(month, 'cleanup-goal-template');
onMenuClose();
}}
/>
</Tooltip>
)}
<Popover
triggerRef={triggerRef}
isOpen={menuOpen}
onOpenChange={onMenuClose}
>
<BudgetMonthMenu
onCopyLastMonthBudget={() => {
onBudgetAction(month, 'copy-last');
onMenuClose();
}}
onSetBudgetsToZero={() => {
onBudgetAction(month, 'set-zero');
onMenuClose();
}}
onSetMonthsAverage={numberOfMonths => {
onBudgetAction(month, `set-${numberOfMonths}-avg`);
onMenuClose();
}}
onCheckTemplates={() => {
onBudgetAction(month, 'check-templates');
onMenuClose();
}}
onApplyBudgetTemplates={() => {
onBudgetAction(month, 'apply-goal-template');
onMenuClose();
}}
onOverwriteWithBudgetTemplates={() => {
onBudgetAction(month, 'overwrite-goal-template');
onMenuClose();
}}
onEndOfMonthCleanup={() => {
onBudgetAction(month, 'cleanup-goal-template');
onMenuClose();
}}
/>
</Popover>
</View>
</View>
</View>
@@ -195,6 +200,7 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
prevMonthName={prevMonthName}
month={month}
onBudgetAction={onBudgetAction}
isCollapsed
/>
</View>
) : (

Some files were not shown because too many files have changed in this diff Show More