Compare commits
115 Commits
v24.6.0
...
react-aria
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58b57aefe1 | ||
|
|
e25683f130 | ||
|
|
496c76c7f9 | ||
|
|
b7d4964539 | ||
|
|
7479df359a | ||
|
|
b1b14d0813 | ||
|
|
b710b9675e | ||
|
|
f8fb4a9ba7 | ||
|
|
9f738956d7 | ||
|
|
dc9ce974a5 | ||
|
|
27974c63fd | ||
|
|
f81c452ba5 | ||
|
|
7072674111 | ||
|
|
16e887c917 | ||
|
|
572033debe | ||
|
|
b85f9102ce | ||
|
|
942aebedd0 | ||
|
|
32d830440a | ||
|
|
4575616961 | ||
|
|
4a0e2ea306 | ||
|
|
14ec9a9089 | ||
|
|
e91b4070aa | ||
|
|
6dd34b0c63 | ||
|
|
ab4639f48f | ||
|
|
aa3cbd881b | ||
|
|
8681c9c3e6 | ||
|
|
9ec9aef632 | ||
|
|
3be7dd753d | ||
|
|
259e84cea5 | ||
|
|
f9014f0e19 | ||
|
|
e59f5c9af8 | ||
|
|
771c01c8b4 | ||
|
|
9f72b43826 | ||
|
|
ec3475d834 | ||
|
|
5ea9c587a8 | ||
|
|
1e38055376 | ||
|
|
0ee9126820 | ||
|
|
9e455e4c1e | ||
|
|
d77b54f27b | ||
|
|
ff36d1efbe | ||
|
|
cbbbaf65cf | ||
|
|
f129b07dc9 | ||
|
|
f1caf21deb | ||
|
|
a28ea6be8f | ||
|
|
f36c5e002b | ||
|
|
803289ee1f | ||
|
|
76cdad4fe6 | ||
|
|
d03b30bc00 | ||
|
|
710d9ab8ac | ||
|
|
d008944022 | ||
|
|
f18bce6094 | ||
|
|
31eb00a155 | ||
|
|
a67c969189 | ||
|
|
58e6c6f23a | ||
|
|
f046d75b75 | ||
|
|
30bcfedc86 | ||
|
|
866b4d6cd4 | ||
|
|
a42938fa64 | ||
|
|
e02b0f9bc7 | ||
|
|
049a41f366 | ||
|
|
7f30680fb3 | ||
|
|
2d4256b239 | ||
|
|
247e3e8d93 | ||
|
|
5951b92668 | ||
|
|
a9ee670eb4 | ||
|
|
3990aaf38f | ||
|
|
48f5880f1d | ||
|
|
3332f58376 | ||
|
|
46ea8fbf72 | ||
|
|
6a21f8e3de | ||
|
|
f02ca4e3d2 | ||
|
|
7f658691bb | ||
|
|
5b1a730f11 | ||
|
|
0c14eb17c4 | ||
|
|
7bb0425c81 | ||
|
|
8832c2b234 | ||
|
|
437e202d27 | ||
|
|
d34f5eccb6 | ||
|
|
f1d3902e3e | ||
|
|
8b6ef7b325 | ||
|
|
6ad0b47c7c | ||
|
|
96964224f4 | ||
|
|
0ed5e3ebe6 | ||
|
|
64cd6ee3c9 | ||
|
|
abc4636662 | ||
|
|
ade25b3304 | ||
|
|
b192ad955e | ||
|
|
e9da476b51 | ||
|
|
12719e3049 | ||
|
|
e178d9914d | ||
|
|
6fd728aa2d | ||
|
|
bf26ca4eb9 | ||
|
|
f606d92c5c | ||
|
|
8b850f1410 | ||
|
|
c992e340ca | ||
|
|
06f9db06b0 | ||
|
|
2b96bb3d52 | ||
|
|
ebb9452b8f | ||
|
|
196f03b84e | ||
|
|
93e784a0fe | ||
|
|
026194e5e2 | ||
|
|
98341b440a | ||
|
|
417f5805a8 | ||
|
|
094f0b8a91 | ||
|
|
b89a32025a | ||
|
|
d62919a357 | ||
|
|
1c7d9bf141 | ||
|
|
6d117f44de | ||
|
|
64821e6a64 | ||
|
|
e7c6611c88 | ||
|
|
6220aadb2d | ||
|
|
2959054d0c | ||
|
|
7d960579f9 | ||
|
|
5fd1d05670 | ||
|
|
0e86dea544 |
27
.github/workflows/wip.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Add WIP
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add_wip_prefix:
|
||||
if: |
|
||||
join(github.event.pull_request.requested_reviewers) == ''
|
||||
&& !contains(github.event.pull_request.title, 'WIP')
|
||||
&& !contains(github.event.pull_request.labels.*.name, 'WIP')
|
||||
&& github.event.pull_request.draft != true
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Add WIP
|
||||
env:
|
||||
TITLE: ${{ github.event.pull_request.title }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo ${{ secrets.GITHUB_TOKEN }} | gh auth login --with-token
|
||||
gh pr edit ${{ github.event.pull_request.number }} -t "[WIP] ${TITLE}"
|
||||
1
.gitignore
vendored
@@ -21,6 +21,7 @@ packages/api/dist
|
||||
packages/api/@types
|
||||
packages/crdt/dist
|
||||
packages/desktop-electron/client-build
|
||||
packages/desktop-electron/build
|
||||
packages/desktop-electron/.electron-symbols
|
||||
packages/desktop-electron/dist
|
||||
packages/desktop-electron/loot-core
|
||||
|
||||
@@ -58,6 +58,19 @@ describe('API CRUD operations', () => {
|
||||
await api.loadBudget(budgetName);
|
||||
});
|
||||
|
||||
// api: getBudgets
|
||||
test('getBudgets', async () => {
|
||||
const budgets = await api.getBudgets();
|
||||
expect(budgets).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'test-budget',
|
||||
name: 'Default Test Db',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
// apis: getCategoryGroups, createCategoryGroup, updateCategoryGroup, deleteCategoryGroup
|
||||
test('CategoryGroups: successfully update category groups', async () => {
|
||||
const month = '2023-10';
|
||||
@@ -251,7 +264,7 @@ describe('API CRUD operations', () => {
|
||||
);
|
||||
});
|
||||
|
||||
//apis: createAccount, getAccounts, updateAccount, closeAccount, deleteAccount, reopenAccount
|
||||
//apis: createAccount, getAccounts, updateAccount, closeAccount, deleteAccount, reopenAccount, getAccountBalance
|
||||
test('Accounts: successfully complete account operators', async () => {
|
||||
const accountId1 = await api.createAccount(
|
||||
{ name: 'test-account1', offbudget: true },
|
||||
@@ -272,6 +285,9 @@ describe('API CRUD operations', () => {
|
||||
]),
|
||||
);
|
||||
|
||||
expect(await api.getAccountBalance(accountId1)).toEqual(1000);
|
||||
expect(await api.getAccountBalance(accountId2)).toEqual(0);
|
||||
|
||||
await api.updateAccount(accountId1, { offbudget: false });
|
||||
await api.closeAccount(accountId1, accountId2, null);
|
||||
await api.deleteAccount(accountId2);
|
||||
@@ -569,6 +585,11 @@ describe('API CRUD operations', () => {
|
||||
});
|
||||
expect(addResult).toBe('ok');
|
||||
|
||||
expect(await api.getAccountBalance(accountId)).toEqual(200);
|
||||
expect(
|
||||
await api.getAccountBalance(accountId, new Date(2023, 10, 2)),
|
||||
).toEqual(0);
|
||||
|
||||
// confirm added transactions exist
|
||||
let transactions = await api.getTransactions(
|
||||
accountId,
|
||||
|
||||
@@ -31,6 +31,10 @@ export async function downloadBudget(syncId, { password }: { password? } = {}) {
|
||||
return send('api/download-budget', { syncId, password });
|
||||
}
|
||||
|
||||
export async function getBudgets() {
|
||||
return send('api/get-budgets');
|
||||
}
|
||||
|
||||
export async function sync() {
|
||||
return send('api/sync');
|
||||
}
|
||||
@@ -125,6 +129,10 @@ export function deleteAccount(id) {
|
||||
return send('api/account-delete', { id });
|
||||
}
|
||||
|
||||
export function getAccountBalance(id, cutoff?) {
|
||||
return send('api/account-balance', { id, cutoff });
|
||||
}
|
||||
|
||||
export function getCategoryGroups() {
|
||||
return send('api/category-groups-get');
|
||||
}
|
||||
@@ -173,6 +181,10 @@ export function deletePayee(id) {
|
||||
return send('api/payee-delete', { id });
|
||||
}
|
||||
|
||||
export function mergePayees(targetId, mergeIds) {
|
||||
return send('api/payees-merge', { targetId, mergeIds });
|
||||
}
|
||||
|
||||
export function getRules() {
|
||||
return send('api/rules-get');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actual-app/api",
|
||||
"version": "6.8.0",
|
||||
"version": "6.8.2",
|
||||
"license": "MIT",
|
||||
"description": "An API for Actual",
|
||||
"engines": {
|
||||
|
||||
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 83 KiB |
@@ -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": [
|
||||
|
||||
@@ -42,6 +42,9 @@ test.describe('Mobile', () => {
|
||||
'Mortgage',
|
||||
'Water',
|
||||
'Power',
|
||||
'Starting Balances',
|
||||
'Misc',
|
||||
'Income',
|
||||
]);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 32 KiB |
@@ -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');
|
||||
|
||||
@@ -44,7 +44,7 @@ export class RulesPage {
|
||||
.first()
|
||||
.click();
|
||||
await this.page
|
||||
.getByRole('option', { exact: true, name: data.conditionsOp })
|
||||
.getByRole('button', { exact: true, name: data.conditionsOp })
|
||||
.click();
|
||||
}
|
||||
|
||||
@@ -97,13 +97,13 @@ export class RulesPage {
|
||||
if (field) {
|
||||
await row.getByRole('button').first().click();
|
||||
await this.page
|
||||
.getByRole('option', { exact: true, name: field })
|
||||
.getByRole('button', { exact: true, name: field })
|
||||
.click();
|
||||
}
|
||||
|
||||
if (op) {
|
||||
await row.getByRole('button', { name: 'is' }).click();
|
||||
await this.page.getByRole('option', { name: op, exact: true }).click();
|
||||
await this.page.getByRole('button', { name: op, exact: true }).click();
|
||||
}
|
||||
|
||||
if (value) {
|
||||
|
||||
@@ -84,6 +84,10 @@ export class SchedulesPage {
|
||||
|
||||
if (data.amount) {
|
||||
await this.page.getByLabel('Amount').fill(String(data.amount));
|
||||
// For some readon, the input field does not trigger the change event on tests
|
||||
// but it works on the browser. We can revisit this once migration to
|
||||
// react aria components is complete.
|
||||
await this.page.keyboard.press('Enter');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 76 KiB |
@@ -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({
|
||||
@@ -94,7 +89,7 @@ test.describe('Rules', () => {
|
||||
splitActions: [
|
||||
[
|
||||
{
|
||||
field: 'a fixed percent',
|
||||
field: 'a fixed percent of the remainder',
|
||||
value: '90',
|
||||
},
|
||||
{
|
||||
@@ -125,7 +120,7 @@ test.describe('Rules', () => {
|
||||
});
|
||||
|
||||
const transaction = accountPage.getNthTransaction(0);
|
||||
await expect(transaction.payee).toHaveText('Ikea');
|
||||
await expect(transaction.payee).toHaveText('Split');
|
||||
await expect(transaction.notes).toHaveText('food / entertainment');
|
||||
await expect(transaction.category).toHaveText('Split');
|
||||
await expect(transaction.debit).toHaveText('100.00');
|
||||
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 82 KiB |
@@ -120,7 +120,7 @@ test.describe('Transactions', () => {
|
||||
]);
|
||||
|
||||
const firstTransaction = accountPage.getNthTransaction(0);
|
||||
await expect(firstTransaction.payee).toHaveText('Krogger');
|
||||
await expect(firstTransaction.payee).toHaveText('Split');
|
||||
await expect(firstTransaction.notes).toHaveText('Notes');
|
||||
await expect(firstTransaction.category).toHaveText('Split');
|
||||
await expect(firstTransaction.debit).toHaveText('333.33');
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actual-app/web",
|
||||
"version": "24.6.0",
|
||||
"version": "24.7.0",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"build"
|
||||
@@ -8,7 +8,6 @@
|
||||
"devDependencies": {
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@playwright/test": "1.41.1",
|
||||
"@reach/listbox": "^0.18.0",
|
||||
"@react-aria/focus": "^3.16.0",
|
||||
"@react-aria/listbox": "^3.11.3",
|
||||
"@react-aria/utils": "^3.23.0",
|
||||
@@ -32,6 +31,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",
|
||||
@@ -47,8 +47,9 @@
|
||||
"memoize-one": "^6.0.0",
|
||||
"pikaday": "1.8.2",
|
||||
"promise-retry": "^2.0.1",
|
||||
"re-resizable": "^6.9.17",
|
||||
"react": "18.2.0",
|
||||
"react-aria-components": "^1.1.1",
|
||||
"react-aria-components": "^1.2.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
3
packages/desktop-client/public/shortcut-accounts.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M1.857 7.612H.98a.98.98 0 0 0-.98.98v2.224c0 .449.184.857.51 1.163.612.551 1.816 1.49 3.735 2.368a.571.571 0 0 1 .306.346l.939 3.286a.484.484 0 0 0 .469.347h1.47a.479.479 0 0 0 .469-.367l.47-1.817a.262.262 0 0 1 .326-.183c.571.122 1.183.163 1.795.163.613 0 1.225-.061 1.796-.163.143-.02.286.06.327.183l.47 1.817a.502.502 0 0 0 .468.367h1.47a.484.484 0 0 0 .47-.347l1.224-4.245a.487.487 0 0 1 .122-.224c.919-.98 1.53-2.163 1.735-3.47h.49c.53 0 .959-.448.938-.979-.02-.51-.47-.918-.98-.918h-.448C18.06 4.673 14.632 2 10.489 2c-1.775 0-3.428.49-4.755 1.326-.591-.408-1.469-.734-2.693-.632-.49.04-.715.612-.388.959.408.429.796 1 .877 1.735L1.857 7.612Zm3.122.98a.862.862 0 0 1-.857-.858c0-.469.388-.857.857-.857.47 0 .858.388.858.857 0 .47-.388.858-.858.858Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 838 B |
3
packages/desktop-client/public/shortcut-reports.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M19 18h-1v-8c0-.6-.4-1-1-1s-1 .4-1 1v8h-3V1c0-.6-.4-1-1-1s-1 .4-1 1v17H8V7c0-.6-.4-1-1-1s-1 .4-1 1v11H3V3c0-.6-.4-1-1-1s-1 .4-1 1v15c-.6 0-1 .4-1 1s.4 1 1 1h18c.6 0 1-.4 1-1s-.4-1-1-1z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 268 B |
4
packages/desktop-client/public/shortcut-transaction.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M23 11.5a1.5 1.5 0 0 1-1.5 1.5h-20a1.5 1.5 0 0 1 0-3h20a1.5 1.5 0 0 1 1.5 1.5Z" />
|
||||
<path d="M11.5 23a1.5 1.5 0 0 1-1.5-1.5v-20a1.5 1.5 0 0 1 3 0v20a1.5 1.5 0 0 1-1.5 1.5Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 256 B |
@@ -28,6 +28,44 @@
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Add Transaction",
|
||||
"short_name": "Add Transaction",
|
||||
"description": "Add a new transaction",
|
||||
"url": "/transactions/new",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/shortcut-transaction.svg",
|
||||
"sizes": "150x150"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Accounts",
|
||||
"short_name": "Accounts",
|
||||
"description": "View all accounts",
|
||||
"url": "/accounts",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/shortcut-accounts.svg",
|
||||
"sizes": "150x150"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Reports",
|
||||
"short_name": "Reports",
|
||||
"description": "View reports",
|
||||
"url": "/reports",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/shortcut-reports.svg",
|
||||
"sizes": "150x150"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/screenshot_wide.png",
|
||||
@@ -43,7 +81,7 @@
|
||||
"type": "image/png",
|
||||
"sizes": "350x600"
|
||||
}
|
||||
],
|
||||
],
|
||||
"theme_color": "#8812E1",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone",
|
||||
|
||||
@@ -135,6 +135,14 @@ global.Actual = {
|
||||
},
|
||||
};
|
||||
|
||||
function inputFocused(e) {
|
||||
return (
|
||||
e.target.tagName === 'INPUT' ||
|
||||
e.target.tagName === 'TEXTAREA' ||
|
||||
e.target.isContentEditable
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
// Cmd/Ctrl+o
|
||||
@@ -144,11 +152,7 @@ document.addEventListener('keydown', e => {
|
||||
}
|
||||
// Cmd/Ctrl+z
|
||||
else if (e.key.toLowerCase() === 'z') {
|
||||
if (
|
||||
e.target.tagName === 'INPUT' ||
|
||||
e.target.tagName === 'TEXTAREA' ||
|
||||
e.target.isContentEditable
|
||||
) {
|
||||
if (inputFocused(e)) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
@@ -160,5 +164,10 @@ document.addEventListener('keydown', e => {
|
||||
window.__actionsForMenu.undo();
|
||||
}
|
||||
}
|
||||
} else if (e.key === '?') {
|
||||
if (inputFocused(e)) {
|
||||
return;
|
||||
}
|
||||
window.__actionsForMenu.pushModal('keyboard-shortcuts');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ import { ScrollProvider } from './ScrollProvider';
|
||||
import { Settings } from './settings';
|
||||
import { FloatableSidebar } from './sidebar';
|
||||
import { SidebarProvider } from './sidebar/SidebarProvider';
|
||||
import { Titlebar, TitlebarProvider } from './Titlebar';
|
||||
import { Titlebar } from './Titlebar';
|
||||
|
||||
function NarrowNotSupported({
|
||||
redirectTo = '/budget',
|
||||
@@ -246,15 +246,13 @@ export function FinancesApp() {
|
||||
|
||||
return (
|
||||
<SpreadsheetProvider>
|
||||
<TitlebarProvider>
|
||||
<SidebarProvider>
|
||||
<BudgetMonthCountProvider>
|
||||
<DndProvider backend={Backend}>
|
||||
<ScrollProvider>{app}</ScrollProvider>
|
||||
</DndProvider>
|
||||
</BudgetMonthCountProvider>
|
||||
</SidebarProvider>
|
||||
</TitlebarProvider>
|
||||
<SidebarProvider>
|
||||
<BudgetMonthCountProvider>
|
||||
<DndProvider backend={Backend}>
|
||||
<ScrollProvider>{app}</ScrollProvider>
|
||||
</DndProvider>
|
||||
</BudgetMonthCountProvider>
|
||||
</SidebarProvider>
|
||||
</SpreadsheetProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import { GoCardlessExternalMsg } from './modals/GoCardlessExternalMsg';
|
||||
import { GoCardlessInitialise } from './modals/GoCardlessInitialise';
|
||||
import { HoldBufferModal } from './modals/HoldBufferModal';
|
||||
import { ImportTransactions } from './modals/ImportTransactions';
|
||||
import { KeyboardShortcutModal } from './modals/KeyboardShortcutModal';
|
||||
import { LoadBackup } from './modals/LoadBackup';
|
||||
import { ManageRulesModal } from './modals/ManageRulesModal';
|
||||
import { MergeUnusedPayees } from './modals/MergeUnusedPayees';
|
||||
@@ -53,7 +54,6 @@ import { ScheduledTransactionMenuModal } from './modals/ScheduledTransactionMenu
|
||||
import { SelectLinkedAccounts } from './modals/SelectLinkedAccounts';
|
||||
import { SimpleFinInitialise } from './modals/SimpleFinInitialise';
|
||||
import { SingleInputModal } from './modals/SingleInputModal';
|
||||
import { SwitchBudgetTypeModal } from './modals/SwitchBudgetTypeModal';
|
||||
import { TransferModal } from './modals/TransferModal';
|
||||
import { DiscoverSchedules } from './schedules/DiscoverSchedules';
|
||||
import { PostsOfflineNotification } from './schedules/PostsOfflineNotification';
|
||||
@@ -96,6 +96,9 @@ export function Modals() {
|
||||
};
|
||||
|
||||
switch (name) {
|
||||
case 'keyboard-shortcuts':
|
||||
return <KeyboardShortcutModal modalProps={modalProps} />;
|
||||
|
||||
case 'import-transactions':
|
||||
return (
|
||||
<ImportTransactions
|
||||
@@ -173,6 +176,7 @@ export function Modals() {
|
||||
<ConfirmTransactionEdit
|
||||
key={name}
|
||||
modalProps={modalProps}
|
||||
onCancel={options.onCancel}
|
||||
onConfirm={options.onConfirm}
|
||||
confirmReason={options.confirmReason}
|
||||
/>
|
||||
@@ -401,7 +405,6 @@ export function Modals() {
|
||||
actions={actions}
|
||||
transactionIds={options?.transactionIds}
|
||||
getTransaction={options?.getTransaction}
|
||||
pushModal={options?.pushModal}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -423,15 +426,6 @@ export function Modals() {
|
||||
/>
|
||||
);
|
||||
|
||||
case 'switch-budget-type':
|
||||
return (
|
||||
<SwitchBudgetTypeModal
|
||||
key={name}
|
||||
modalProps={modalProps}
|
||||
onSwitch={options.onSwitch}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'account-menu':
|
||||
return (
|
||||
<AccountMenuModal
|
||||
@@ -544,6 +538,7 @@ export function Modals() {
|
||||
<RolloverToBudgetMenuModal
|
||||
modalProps={modalProps}
|
||||
onTransfer={options.onTransfer}
|
||||
onCover={options.onCover}
|
||||
onHoldBuffer={options.onHoldBuffer}
|
||||
onResetHoldBuffer={options.onResetHoldBuffer}
|
||||
/>
|
||||
@@ -596,8 +591,9 @@ export function Modals() {
|
||||
<CoverModal
|
||||
key={name}
|
||||
modalProps={modalProps}
|
||||
categoryId={options.categoryId}
|
||||
title={options.title}
|
||||
month={options.month}
|
||||
showToBeBudgeted={options.showToBeBudgeted}
|
||||
onSubmit={options.onSubmit}
|
||||
/>
|
||||
);
|
||||
@@ -621,7 +617,6 @@ export function Modals() {
|
||||
onAddCategoryGroup={options.onAddCategoryGroup}
|
||||
onToggleHiddenCategories={options.onToggleHiddenCategories}
|
||||
onSwitchBudgetFile={options.onSwitchBudgetFile}
|
||||
onSwitchBudgetType={options.onSwitchBudgetType}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { NotificationWithId } from 'loot-core/src/client/state-types/notifi
|
||||
import { useActions } from '../hooks/useActions';
|
||||
import { AnimatedLoading } from '../icons/AnimatedLoading';
|
||||
import { SvgDelete } from '../icons/v0';
|
||||
import { useResponsive } from '../ResponsiveProvider';
|
||||
import { styles, theme, type CSSProperties } from '../style';
|
||||
|
||||
import { Button, ButtonWithLoading } from './common/Button';
|
||||
@@ -245,6 +246,7 @@ function Notification({
|
||||
|
||||
export function Notifications({ style }: { style?: CSSProperties }) {
|
||||
const { removeNotification } = useActions();
|
||||
const { isNarrowWidth } = useResponsive();
|
||||
const notifications = useSelector(
|
||||
(state: State) => state.notifications.notifications,
|
||||
);
|
||||
@@ -254,6 +256,7 @@ export function Notifications({ style }: { style?: CSSProperties }) {
|
||||
position: 'fixed',
|
||||
bottom: 20,
|
||||
right: 13,
|
||||
left: isNarrowWidth ? 13 : undefined,
|
||||
zIndex: 10000,
|
||||
...style,
|
||||
}}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useContext,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||
|
||||
@@ -13,10 +6,8 @@ import * as Platform from 'loot-core/src/client/platform';
|
||||
import * as queries from 'loot-core/src/client/queries';
|
||||
import { listen } from 'loot-core/src/platform/client/fetch';
|
||||
import { isDevelopmentEnvironment } from 'loot-core/src/shared/environment';
|
||||
import { type LocalPrefs } from 'loot-core/src/types/prefs';
|
||||
|
||||
import { useActions } from '../hooks/useActions';
|
||||
import { useFeatureFlag } from '../hooks/useFeatureFlag';
|
||||
import { useGlobalPref } from '../hooks/useGlobalPref';
|
||||
import { useLocalPref } from '../hooks/useLocalPref';
|
||||
import { useNavigate } from '../hooks/useNavigate';
|
||||
@@ -33,10 +24,8 @@ import { theme, type CSSProperties, styles } from '../style';
|
||||
import { AccountSyncCheck } from './accounts/AccountSyncCheck';
|
||||
import { AnimatedRefresh } from './AnimatedRefresh';
|
||||
import { MonthCountSelector } from './budget/MonthCountSelector';
|
||||
import { Button, ButtonWithLoading } from './common/Button';
|
||||
import { Button } from './common/Button';
|
||||
import { Link } from './common/Link';
|
||||
import { Paragraph } from './common/Paragraph';
|
||||
import { Popover } from './common/Popover';
|
||||
import { Text } from './common/Text';
|
||||
import { View } from './common/View';
|
||||
import { LoggedInUser } from './LoggedInUser';
|
||||
@@ -45,55 +34,6 @@ import { useSidebar } from './sidebar/SidebarProvider';
|
||||
import { useSheetValue } from './spreadsheet/useSheetValue';
|
||||
import { ThemeSelector } from './ThemeSelector';
|
||||
|
||||
export const SWITCH_BUDGET_MESSAGE_TYPE = 'budget/switch-type';
|
||||
|
||||
type SwitchBudgetTypeMessage = {
|
||||
type: typeof SWITCH_BUDGET_MESSAGE_TYPE;
|
||||
payload: {
|
||||
newBudgetType: LocalPrefs['budgetType'];
|
||||
};
|
||||
};
|
||||
export type TitlebarMessage = SwitchBudgetTypeMessage;
|
||||
|
||||
type Listener = (msg: TitlebarMessage) => void;
|
||||
export type TitlebarContextValue = {
|
||||
sendEvent: (msg: TitlebarMessage) => void;
|
||||
subscribe: (listener: Listener) => () => void;
|
||||
};
|
||||
|
||||
export const TitlebarContext = createContext<TitlebarContextValue>({
|
||||
sendEvent() {
|
||||
throw new Error('TitlebarContext not initialized');
|
||||
},
|
||||
subscribe() {
|
||||
throw new Error('TitlebarContext not initialized');
|
||||
},
|
||||
});
|
||||
|
||||
type TitlebarProviderProps = {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export function TitlebarProvider({ children }: TitlebarProviderProps) {
|
||||
const listeners = useRef<Listener[]>([]);
|
||||
|
||||
function sendEvent(msg: TitlebarMessage) {
|
||||
listeners.current.forEach(func => func(msg));
|
||||
}
|
||||
|
||||
function subscribe(listener: Listener) {
|
||||
listeners.current.push(listener);
|
||||
return () =>
|
||||
(listeners.current = listeners.current.filter(func => func !== listener));
|
||||
}
|
||||
|
||||
return (
|
||||
<TitlebarContext.Provider value={{ sendEvent, subscribe }}>
|
||||
{children}
|
||||
</TitlebarContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function UncategorizedButton() {
|
||||
const count: number | null = useSheetValue(queries.uncategorizedCount());
|
||||
if (count === null || count <= 0) {
|
||||
@@ -287,31 +227,6 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
|
||||
|
||||
function BudgetTitlebar() {
|
||||
const [maxMonths, setMaxMonthsPref] = useGlobalPref('maxMonths');
|
||||
const [budgetType] = useLocalPref('budgetType');
|
||||
const { sendEvent } = useContext(TitlebarContext);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const triggerRef = useRef(null);
|
||||
|
||||
const reportBudgetEnabled = useFeatureFlag('reportBudget');
|
||||
|
||||
function onSwitchType() {
|
||||
setLoading(true);
|
||||
if (!loading) {
|
||||
const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
|
||||
sendEvent({
|
||||
type: SWITCH_BUDGET_MESSAGE_TYPE,
|
||||
payload: {
|
||||
newBudgetType,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(false);
|
||||
}, [budgetType]);
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
@@ -319,61 +234,6 @@ function BudgetTitlebar() {
|
||||
maxMonths={maxMonths || 1}
|
||||
onChange={value => setMaxMonthsPref(value)}
|
||||
/>
|
||||
{reportBudgetEnabled && (
|
||||
<View style={{ marginLeft: -5 }}>
|
||||
<ButtonWithLoading
|
||||
ref={triggerRef}
|
||||
type="bare"
|
||||
loading={loading}
|
||||
style={{
|
||||
alignSelf: 'flex-start',
|
||||
padding: '4px 7px',
|
||||
}}
|
||||
title="Learn more about budgeting"
|
||||
onClick={() => setShowPopover(true)}
|
||||
>
|
||||
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}
|
||||
</ButtonWithLoading>
|
||||
|
||||
<Popover
|
||||
triggerRef={triggerRef}
|
||||
placement="bottom start"
|
||||
isOpen={showPopover}
|
||||
onOpenChange={() => setShowPopover(false)}
|
||||
style={{
|
||||
padding: 10,
|
||||
maxWidth: 400,
|
||||
}}
|
||||
>
|
||||
<Paragraph>
|
||||
You are currently using a{' '}
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
|
||||
</Text>{' '}
|
||||
Switching will not lose any data and you can always switch back.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<ButtonWithLoading
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={onSwitchType}
|
||||
>
|
||||
Switch to a{' '}
|
||||
{budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
|
||||
</ButtonWithLoading>
|
||||
</Paragraph>
|
||||
<Paragraph isLast={true}>
|
||||
<Link
|
||||
variant="external"
|
||||
to="https://actualbudget.org/docs/experimental/report-budget"
|
||||
linkColor="muted"
|
||||
>
|
||||
How do these types of budgeting work?
|
||||
</Link>
|
||||
</Paragraph>
|
||||
</Popover>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -416,11 +276,6 @@ export function Titlebar({ style }: TitlebarProps) {
|
||||
sidebar.setHidden(false);
|
||||
}
|
||||
}}
|
||||
onPointerLeave={e => {
|
||||
if (e.pointerType === 'mouse') {
|
||||
sidebar.setHidden(true);
|
||||
}
|
||||
}}
|
||||
onPointerUp={e => {
|
||||
if (e.pointerType !== 'mouse') {
|
||||
sidebar.setHidden(!sidebar.hidden);
|
||||
|
||||