diff --git a/.eslintrc.js b/.eslintrc.js index b2d03eb5b43..fe5a15013b9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -82,7 +82,6 @@ module.exports = { // TODO: re-enable these rules 'react-hooks/exhaustive-deps': 'off', - 'react/no-children-prop': 'off', 'react/display-name': 'off', 'react/react-in-jsx-scope': 'off', // 'react-hooks/exhaustive-deps': [ @@ -240,7 +239,27 @@ module.exports = { './packages/api/app/**/*', './packages/crdt/**/*', './packages/desktop-client/src/*', - // './packages/desktop-client/src/components/**/*', + './packages/desktop-client/src/components/*', + './packages/desktop-client/src/components/accounts/**/*', + './packages/desktop-client/src/components/autocomplete/**/*', + './packages/desktop-client/src/components/budget/**/*', + './packages/desktop-client/src/components/common/**/*', + './packages/desktop-client/src/components/filters/**/*', + './packages/desktop-client/src/components/gocardless/**/*', + './packages/desktop-client/src/components/manager/**/*', + './packages/desktop-client/src/components/mobile/**/*', + './packages/desktop-client/src/components/modals/**/*', + './packages/desktop-client/src/components/payees/**/*', + './packages/desktop-client/src/components/reports/**/*', + './packages/desktop-client/src/components/responsive/**/*', + './packages/desktop-client/src/components/rules/**/*', + './packages/desktop-client/src/components/schedules/**/*', + './packages/desktop-client/src/components/select/**/*', + './packages/desktop-client/src/components/settings/**/*', + './packages/desktop-client/src/components/sidebar/**/*', + './packages/desktop-client/src/components/spreadsheet/**/*', + './packages/desktop-client/src/components/transactions/**/*', + './packages/desktop-client/src/components/util/**/*', './packages/desktop-client/src/hooks/**/*', './packages/desktop-client/src/icons/**/*', './packages/desktop-client/src/style/**/*', diff --git a/packages/api/methods.test.ts b/packages/api/methods.test.ts index f5284966b98..3453975fe0f 100644 --- a/packages/api/methods.test.ts +++ b/packages/api/methods.test.ts @@ -57,6 +57,109 @@ describe('API CRUD operations', () => { await api.loadBudget(budgetName); }); + // apis: createCategoryGroup, updateCategoryGroup, deleteCategoryGroup + test('CategoryGroups: successfully update category groups', async () => { + const month = '2023-10'; + global.currentMonth = month; + + // create our test category group + const mainGroupId = await api.createCategoryGroup({ + name: 'test-group', + }); + + let budgetMonth = await api.getBudgetMonth(month); + expect(budgetMonth.categoryGroups).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: mainGroupId, + }), + ]), + ); + + // update group + await api.updateCategoryGroup(mainGroupId, { + name: 'update-tests', + }); + + budgetMonth = await api.getBudgetMonth(month); + expect(budgetMonth.categoryGroups).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: mainGroupId, + }), + ]), + ); + + // delete group + await api.deleteCategoryGroup(mainGroupId); + + budgetMonth = await api.getBudgetMonth(month); + expect(budgetMonth.categoryGroups).toEqual( + expect.arrayContaining([ + expect.not.objectContaining({ + id: mainGroupId, + }), + ]), + ); + }); + + // apis: createCategory, getCategories, updateCategory, deleteCategory + test('Categories: successfully update categories', async () => { + const month = '2023-10'; + global.currentMonth = month; + + // create our test category group + const mainGroupId = await api.createCategoryGroup({ + name: 'test-group', + }); + const secondaryGroupId = await api.createCategoryGroup({ + name: 'test-secondary-group', + }); + const categoryId = await api.createCategory({ + name: 'test-budget', + group_id: mainGroupId, + }); + + let categories = await api.getCategories(); + expect(categories).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: categoryId, + name: 'test-budget', + group_id: mainGroupId, + }), + ]), + ); + + // update/move category + await api.updateCategory(categoryId, { + name: 'updated-budget', + group_id: secondaryGroupId, + }); + + categories = await api.getCategories(); + expect(categories).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: categoryId, + name: 'updated-budget', + group_id: secondaryGroupId, + }), + ]), + ); + + // delete categories + await api.deleteCategory(categoryId); + + expect(categories).toEqual( + expect.arrayContaining([ + expect.not.objectContaining({ + id: categoryId, + }), + ]), + ); + }); + // apis: setBudgetAmount, setBudgetCarryover, getBudgetMonth test('Budgets: successfully update budgets', async () => { const month = '2023-10'; diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png index 70c2c6fe0d5..b818ec3f62d 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png index bc44b2a6e53..4ebc64bbc49 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-3-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-3-chromium-linux.png index 7a1444dea13..493376e5c64 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-3-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-4-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-4-chromium-linux.png index ea8585ee1b8..980c2a92586 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-4-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-checks-that-settings-page-can-be-opened-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png index c06b43e4a84..109397af4d8 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png index 965e6a84b20..d0990083a99 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png index c5647b3882d..98d8921bfd4 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png index efcb1309229..ff9a2444f99 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png index 6b193821e39..27df8c3e591 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png index 9d023dffd95..ff627b5a0a6 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png index 03e16755164..ee3954dd3f7 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-5-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png index 6486bdb5c33..136f760a312 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-6-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index 653a64dc020..fa4ff40ee97 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png index dce7a9084f9..a7a17f6d1c4 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png index f1610b7cbdb..b5c82933d0b 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png index eada35d3782..198991d5651 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png index 5bd758cae7e..3ddbfa413cd 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-4-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-4-chromium-linux.png index df4f77574d6..24c7723cb49 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-4-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-4-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-5-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-5-chromium-linux.png index 549b92b5663..eec9c1637fb 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-5-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-5-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-6-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-6-chromium-linux.png index 77cf5ef886d..ce8318f02dc 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-6-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-6-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png index 29cfcf12498..b2608bff6b0 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png index b472dcb1c3e..42087af04b1 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-the-accounts-page-and-asserts-on-balances-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png index 28ec9aa70b1..08d86651f31 100644 Binary files a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png index 0297a4fee67..139d9920565 100644 Binary files a/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/settings.test.js-snapshots/Settings-checks-the-page-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/AnimatedRefresh.tsx b/packages/desktop-client/src/components/AnimatedRefresh.tsx index 4daa0782473..c6e613c956e 100644 --- a/packages/desktop-client/src/components/AnimatedRefresh.tsx +++ b/packages/desktop-client/src/components/AnimatedRefresh.tsx @@ -7,7 +7,7 @@ import { type CSSProperties } from '../style'; import View from './common/View'; -let spin = keyframes({ +const spin = keyframes({ '0%': { transform: 'rotateZ(0deg)' }, '100%': { transform: 'rotateZ(360deg)' }, }); diff --git a/packages/desktop-client/src/components/App.tsx b/packages/desktop-client/src/components/App.tsx index bdfdd3841a6..bf5334c42d7 100644 --- a/packages/desktop-client/src/components/App.tsx +++ b/packages/desktop-client/src/components/App.tsx @@ -68,7 +68,7 @@ function App({ // don't block on this in case they are offline or something) send('get-remote-files').then(files => { if (files) { - let remoteFile = files.find(f => f.fileId === cloudFileId); + const remoteFile = files.find(f => f.fileId === cloudFileId); if (remoteFile && remoteFile.deleted) { closeBudget(); } @@ -122,14 +122,14 @@ function ErrorFallback({ error }: FallbackProps) { } function AppWrapper() { - let budgetId = useSelector( + const budgetId = useSelector( state => state.prefs.local && state.prefs.local.id, ); - let cloudFileId = useSelector( + const cloudFileId = useSelector( state => state.prefs.local && state.prefs.local.cloudFileId, ); - let loadingText = useSelector(state => state.app.loadingText); - let { loadBudget, closeBudget, loadGlobalPrefs, sync } = useActions(); + const loadingText = useSelector(state => state.app.loadingText); + const { loadBudget, closeBudget, loadGlobalPrefs, sync } = useActions(); const [hiddenScrollbars, setHiddenScrollbars] = useState( hasHiddenScrollbars(), ); diff --git a/packages/desktop-client/src/components/BackgroundImage.tsx b/packages/desktop-client/src/components/BackgroundImage.tsx index dd8ee5c38e1..7a64bddfc40 100644 --- a/packages/desktop-client/src/components/BackgroundImage.tsx +++ b/packages/desktop-client/src/components/BackgroundImage.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import { theme } from '../style'; -let linesTop = theme.pageBackgroundLineTop; // lines top -let linesMid = theme.pageBackgroundLineMid; // lines mid -let linesBottom = theme.pageBackgroundLineBottom; // lines bottom +const linesTop = theme.pageBackgroundLineTop; // lines top +const linesMid = theme.pageBackgroundLineMid; // lines mid +const linesBottom = theme.pageBackgroundLineBottom; // lines bottom export function BackgroundImage() { return ( diff --git a/packages/desktop-client/src/components/BankSyncStatus.tsx b/packages/desktop-client/src/components/BankSyncStatus.tsx index 8095d406706..96a0390b4b4 100644 --- a/packages/desktop-client/src/components/BankSyncStatus.tsx +++ b/packages/desktop-client/src/components/BankSyncStatus.tsx @@ -9,9 +9,9 @@ import Text from './common/Text'; import View from './common/View'; export default function BankSyncStatus() { - let accountsSyncing = useSelector(state => state.account.accountsSyncing); + const accountsSyncing = useSelector(state => state.account.accountsSyncing); - let name = accountsSyncing + const name = accountsSyncing ? accountsSyncing === '__all' ? 'accounts' : accountsSyncing diff --git a/packages/desktop-client/src/components/FatalError.tsx b/packages/desktop-client/src/components/FatalError.tsx index 1d8a7a70b20..25702958ad5 100644 --- a/packages/desktop-client/src/components/FatalError.tsx +++ b/packages/desktop-client/src/components/FatalError.tsx @@ -113,8 +113,8 @@ function RenderUIError() { } function SharedArrayBufferOverride() { - let [expanded, setExpanded] = useState(false); - let [understand, setUnderstand] = useState(false); + const [expanded, setExpanded] = useState(false); + const [understand, setUnderstand] = useState(false); return expanded ? ( <> @@ -152,7 +152,7 @@ function SharedArrayBufferOverride() { } function FatalError({ buttonText, error }: FatalErrorProps) { - let [showError, setShowError] = useState(false); + const [showError, setShowError] = useState(false); const showSimpleRender = 'type' in error && error.type === 'app-init-failure'; diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index 58792c219c6..3ca0b40c00e 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -71,7 +71,7 @@ function WideNotSupported({ children, redirectTo = '/budget' }) { } function RouterBehaviors({ getAccounts }) { - let navigate = useNavigate(); + const navigate = useNavigate(); useEffect(() => { // Get the accounts and check if any exist. If there are no // accounts, we want to redirect the user to the All Accounts @@ -83,8 +83,8 @@ function RouterBehaviors({ getAccounts }) { }); }, []); - let location = useLocation(); - let href = useHref(location); + const location = useLocation(); + const href = useHref(location); useEffect(() => { undo.setUndoState('url', href); }, [href]); @@ -93,7 +93,7 @@ function RouterBehaviors({ getAccounts }) { } function FinancesApp() { - let actions = useActions(); + const actions = useActions(); useEffect(() => { // The default key handler scope hotkeys.setScope('app'); @@ -251,7 +251,7 @@ function FinancesApp() { } export default function FinancesAppWithContext() { - let app = useMemo(() => , []); + const app = useMemo(() => , []); return ( diff --git a/packages/desktop-client/src/components/FixedSizeList.js b/packages/desktop-client/src/components/FixedSizeList.js index e99d0971a64..ce5cb4b4e60 100644 --- a/packages/desktop-client/src/components/FixedSizeList.js +++ b/packages/desktop-client/src/components/FixedSizeList.js @@ -11,7 +11,7 @@ const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; const defaultItemKey = (index, data) => index; function ResizeObserver({ onResize, children }) { - let ref = useResizeObserver(onResize); + const ref = useResizeObserver(onResize); return children(ref); } @@ -92,8 +92,8 @@ export default class FixedSizeList extends PureComponent { getAnchoredScrollPos() { if (this.anchored && this.props.indexForKey && this._outerRef != null) { - let index = this.props.indexForKey(this.anchored.key); - let baseOffset = this.getOffsetForIndexAndAlignment(index, 'start'); + const index = this.props.indexForKey(this.anchored.key); + const baseOffset = this.getOffsetForIndexAndAlignment(index, 'start'); return baseOffset + this.anchored.offset; } return null; @@ -102,7 +102,7 @@ export default class FixedSizeList extends PureComponent { componentDidUpdate() { const { scrollOffset, scrollUpdateWasRequested } = this.state; - let anchoredPos = this.getAnchoredScrollPos(); + const anchoredPos = this.getAnchoredScrollPos(); if (anchoredPos != null) { const outerRef = this._outerRef; outerRef.scrollTop = anchoredPos; @@ -157,9 +157,9 @@ export default class FixedSizeList extends PureComponent { const items = []; if (itemCount > 0) { for (let index = startIndex; index <= stopIndex; index++) { - let key = itemKey(index); + const key = itemKey(index); let style = this._getItemStyle(index); - let lastPosition = this.lastPositions.current.get(key); + const lastPosition = this.lastPositions.current.get(key); let animating = false; positions.set(key, style.top); @@ -191,10 +191,10 @@ export default class FixedSizeList extends PureComponent { // Read this value AFTER items have been created, // So their actual sizes (if variable) are taken into consideration. - let estimatedTotalSize = this.getEstimatedTotalSize(); + const estimatedTotalSize = this.getEstimatedTotalSize(); - let OuterElement = outerElementType || outerTagName || 'div'; - let InnerElement = innerElementType || innerTagName || 'div'; + const OuterElement = outerElementType || outerTagName || 'div'; + const InnerElement = innerElementType || innerTagName || 'div'; return ( @@ -231,7 +231,7 @@ export default class FixedSizeList extends PureComponent { setRowAnimation = flag => { this.animationEnabled = flag; - let outerRef = this._outerRef; + const outerRef = this._outerRef; if (outerRef) { if (this.animationEnabled) { outerRef.classList.add('animated'); @@ -246,12 +246,12 @@ export default class FixedSizeList extends PureComponent { }; anchor() { - let itemKey = this.props.itemKey || defaultItemKey; + const itemKey = this.props.itemKey || defaultItemKey; - let outerRef = this._outerRef; - let scrollOffset = outerRef ? outerRef.scrollTop : 0; - let index = this.getStartIndexForOffset(scrollOffset); - let key = itemKey(index); + const outerRef = this._outerRef; + const scrollOffset = outerRef ? outerRef.scrollTop : 0; + const index = this.getStartIndexForOffset(scrollOffset); + const key = itemKey(index); this.anchored = { key, @@ -434,10 +434,15 @@ export default class FixedSizeList extends PureComponent { _getItemStyleCache = memoizeOne((_, __, ___) => ({})); _getRangeToRender() { - let { itemCount, overscanCount } = this.props; - let { isScrolling, scrollDirection, scrollOffset } = this.state; + const { itemCount, overscanCount } = this.props; + const { + isScrolling, + scrollDirection, + scrollOffset: originalScrollOffset, + } = this.state; - let anchoredPos = this.getAnchoredScrollPos(); + const anchoredPos = this.getAnchoredScrollPos(); + let scrollOffset = originalScrollOffset; if (anchoredPos != null) { scrollOffset = anchoredPos; } @@ -469,7 +474,7 @@ export default class FixedSizeList extends PureComponent { } _onScrollVertical = event => { - let { scrollTop } = event.currentTarget; + const { scrollTop } = event.currentTarget; this.setState(prevState => { if (prevState.scrollOffset === scrollTop) { @@ -479,7 +484,7 @@ export default class FixedSizeList extends PureComponent { return null; } - let scrollOffset = scrollTop; + const scrollOffset = scrollTop; return { isScrolling: true, diff --git a/packages/desktop-client/src/components/GlobalKeys.tsx b/packages/desktop-client/src/components/GlobalKeys.tsx index cff3d38ae2f..edcfe148aab 100644 --- a/packages/desktop-client/src/components/GlobalKeys.tsx +++ b/packages/desktop-client/src/components/GlobalKeys.tsx @@ -5,7 +5,7 @@ import * as Platform from 'loot-core/src/client/platform'; import useNavigate from '../hooks/useNavigate'; export default function GlobalKeys() { - let navigate = useNavigate(); + const navigate = useNavigate(); useEffect(() => { const handleKeys = e => { if (Platform.isBrowser) { diff --git a/packages/desktop-client/src/components/KeyHandlers.tsx b/packages/desktop-client/src/components/KeyHandlers.tsx index 1a38701b5dd..3a674e5f4b3 100644 --- a/packages/desktop-client/src/components/KeyHandlers.tsx +++ b/packages/desktop-client/src/components/KeyHandlers.tsx @@ -2,11 +2,11 @@ import React, { createContext, useEffect, useContext } from 'react'; import hotkeys, { type KeyHandler as HotKeyHandler } from 'hotkeys-js'; -let KeyScopeContext = createContext('app'); +const KeyScopeContext = createContext('app'); hotkeys.filter = event => { - let target = (event.target || event.srcElement) as HTMLElement; - let tagName = target.tagName; + const target = (event.target || event.srcElement) as HTMLElement; + const tagName = target.tagName; // This is the default behavior of hotkeys, except we only suppress // key presses if the meta key is not pressed @@ -34,7 +34,7 @@ function KeyHandler({ eventType = 'keydown', handler, }: KeyHandlerProps) { - let scope = useContext(KeyScopeContext); + const scope = useContext(KeyScopeContext); if (eventType !== 'keyup' && eventType !== 'keydown') { throw new Error('KeyHandler: unknown event type: ' + eventType); @@ -70,7 +70,7 @@ type KeyHandlersProps = { keys: Record; }; export function KeyHandlers({ eventType, keys = {} }: KeyHandlersProps) { - let handlers = Object.keys(keys).map(key => { + const handlers = Object.keys(keys).map(key => { return ( state.user.data); - let { getUserData, signOut, closeBudget } = useActions(); - let [loading, setLoading] = useState(true); - let [menuOpen, setMenuOpen] = useState(false); + const userData = useSelector(state => state.user.data); + const { getUserData, signOut, closeBudget } = useActions(); + const [loading, setLoading] = useState(true); + const [menuOpen, setMenuOpen] = useState(false); const serverUrl = useServerURL(); useEffect(() => { diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx index 1d6ea7d0a76..36928d65520 100644 --- a/packages/desktop-client/src/components/ManageRules.tsx +++ b/packages/desktop-client/src/components/ManageRules.tsx @@ -51,14 +51,14 @@ function mapValue(field, value, { payees, categories, accounts }) { } function ruleToString(rule, data) { - let conditions = rule.conditions.flatMap(cond => [ + const conditions = rule.conditions.flatMap(cond => [ mapField(cond.field), friendlyOp(cond.op), cond.op === 'oneOf' || cond.op === 'notOneOf' ? cond.value.map(v => mapValue(cond.field, v, data)).join(', ') : mapValue(cond.field, cond.value, data), ]); - let actions = rule.actions.flatMap(action => { + const actions = rule.actions.flatMap(action => { if (action.op === 'set') { return [ friendlyOp(action.op), @@ -67,7 +67,7 @@ function ruleToString(rule, data) { mapValue(action.field, action.value, data), ]; } else if (action.op === 'link-schedule') { - let schedule = data.schedules.find(s => s.id === action.value); + const schedule = data.schedules.find(s => s.id === action.value); return [ friendlyOp(action.op), describeSchedule( @@ -95,19 +95,19 @@ function ManageRulesContent({ payeeId, setLoading, }: ManageRulesContentProps) { - let [allRules, setAllRules] = useState(null); - let [rules, setRules] = useState(null); - let [filter, setFilter] = useState(''); - let dispatch = useDispatch(); - - let { data: schedules } = SchedulesQuery.useQuery(); - let { list: categories } = useCategories(); - let state = useSelector(state => ({ + const [allRules, setAllRules] = useState(null); + const [rules, setRules] = useState(null); + const [filter, setFilter] = useState(''); + const dispatch = useDispatch(); + + const { data: schedules } = SchedulesQuery.useQuery(); + const { list: categories } = useCategories(); + const state = useSelector(state => ({ payees: state.queries.payees, accounts: state.queries.accounts, schedules, })); - let filterData = useMemo( + const filterData = useMemo( () => ({ ...state, categories, @@ -115,7 +115,7 @@ function ManageRulesContent({ [state, categories], ); - let filteredRules = useMemo( + const filteredRules = useMemo( () => filter === '' || !rules ? rules @@ -126,8 +126,8 @@ function ManageRulesContent({ ), [rules, filter, filterData], ); - let selectedInst = useSelected('manage-rules', allRules, []); - let [hoveredRule, setHoveredRule] = useState(null); + const selectedInst = useSelected('manage-rules', allRules, []); + const [hoveredRule, setHoveredRule] = useState(null); async function loadRules() { setLoading(true); @@ -147,7 +147,7 @@ function ManageRulesContent({ useEffect(() => { async function loadData() { - let loadedRules = await loadRules(); + const loadedRules = await loadRules(); setRules(loadedRules.slice(0, 100)); setLoading(false); @@ -171,7 +171,7 @@ function ManageRulesContent({ async function onDeleteSelected() { setLoading(true); - let { someDeletionsFailed } = await send('rule-delete-all', [ + const { someDeletionsFailed } = await send('rule-delete-all', [ ...selectedInst.items, ]); @@ -179,7 +179,7 @@ function ManageRulesContent({ alert('Some rules were not deleted because they are linked to schedules'); } - let newRules = await loadRules(); + const newRules = await loadRules(); setRules(rules => { return newRules.slice(0, rules.length); }); @@ -187,15 +187,15 @@ function ManageRulesContent({ setLoading(false); } - let onEditRule = useCallback(rule => { + const onEditRule = useCallback(rule => { dispatch( pushModal('edit-rule', { rule, onSave: async newRule => { - let newRules = await loadRules(); + const newRules = await loadRules(); setRules(rules => { - let newIdx = newRules.findIndex(rule => rule.id === newRule.id); + const newIdx = newRules.findIndex(rule => rule.id === newRule.id); if (newIdx > rules.length) { return newRules.slice(0, newIdx + 75); @@ -211,7 +211,7 @@ function ManageRulesContent({ }, []); function onCreateRule() { - let rule: RuleEntity = { + const rule: RuleEntity = { stage: null, conditionsOp: 'and', conditions: [ @@ -236,10 +236,10 @@ function ManageRulesContent({ pushModal('edit-rule', { rule, onSave: async newRule => { - let newRules = await loadRules(); + const newRules = await loadRules(); setRules(rules => { - let newIdx = newRules.findIndex(rule => rule.id === newRule.id); + const newIdx = newRules.findIndex(rule => rule.id === newRule.id); return newRules.slice(0, newIdx + 75); }); @@ -249,7 +249,7 @@ function ManageRulesContent({ ); } - let onHover = useCallback(id => { + const onHover = useCallback(id => { setHoveredRule(id); }, []); diff --git a/packages/desktop-client/src/components/MobileBackButton.tsx b/packages/desktop-client/src/components/MobileBackButton.tsx new file mode 100644 index 00000000000..6ea749fdbbd --- /dev/null +++ b/packages/desktop-client/src/components/MobileBackButton.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import useNavigate from '../hooks/useNavigate'; +import CheveronLeft from '../icons/v1/CheveronLeft'; +import { type CSSProperties, styles, theme } from '../style'; + +import Button from './common/Button'; +import Text from './common/Text'; + +type MobileBackButtonProps = { + style?: CSSProperties; +}; + +export default function MobileBackButton({ style }: MobileBackButtonProps) { + const navigate = useNavigate(); + return ( + + ); +} diff --git a/packages/desktop-client/src/components/MobileWebMessage.tsx b/packages/desktop-client/src/components/MobileWebMessage.tsx index d68b927f0cc..cf93cbd13de 100644 --- a/packages/desktop-client/src/components/MobileWebMessage.tsx +++ b/packages/desktop-client/src/components/MobileWebMessage.tsx @@ -11,7 +11,7 @@ import Text from './common/Text'; import View from './common/View'; import { Checkbox } from './forms'; -let buttonStyle = { border: 0, fontSize: 15, padding: '10px 13px' }; +const buttonStyle = { border: 0, fontSize: 15, padding: '10px 13px' }; export default function MobileWebMessage() { const hideMobileMessagePref = useSelector(state => { @@ -20,14 +20,14 @@ export default function MobileWebMessage() { const { isNarrowWidth } = useResponsive(); - let [show, setShow] = useState( + const [show, setShow] = useState( isNarrowWidth && !hideMobileMessagePref && !document.cookie.match(/hideMobileMessage=true/), ); - let [requestDontRemindMe, setRequestDontRemindMe] = useState(false); + const [requestDontRemindMe, setRequestDontRemindMe] = useState(false); - let dispatch = useDispatch(); + const dispatch = useDispatch(); function onTry() { setShow(false); @@ -37,7 +37,7 @@ export default function MobileWebMessage() { dispatch(savePrefs({ hideMobileMessage: true })); } else { // Set a cookie for 5 minutes - let d = new Date(); + const d = new Date(); d.setTime(d.getTime() + 1000 * 60 * 5); document.cookie = 'hideMobileMessage=true;path=/;expires=' + d.toUTCString(); diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 643ef4a0899..6c9238faf2a 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -54,7 +54,7 @@ export default function Modals() { const syncServerStatus = useSyncServerStatus(); - let modals = modalStack + const modals = modalStack .map(({ name, options }, idx) => { const modalProps: CommonModalProps = { onClose: actions.popModal, diff --git a/packages/desktop-client/src/components/NotesButton.tsx b/packages/desktop-client/src/components/NotesButton.tsx index 59ab1112259..fb2770d8400 100644 --- a/packages/desktop-client/src/components/NotesButton.tsx +++ b/packages/desktop-client/src/components/NotesButton.tsx @@ -95,8 +95,8 @@ function NotesTooltip({ position = 'bottom-left', onClose, }: NotesTooltipProps) { - let [notes, setNotes] = useState(defaultNotes); - let inputRef = createRef(); + const [notes, setNotes] = useState(defaultNotes); + const inputRef = createRef(); useEffect(() => { if (editable) { @@ -124,11 +124,9 @@ function NotesTooltip({ /> ) : ( - + + {notes} + )} @@ -151,11 +149,11 @@ export default function NotesButton({ tooltipPosition, style, }: NotesButtonProps) { - let [hover, setHover] = useState(false); - let tooltip = useTooltip(); - let data = useLiveQuery(() => q('notes').filter({ id }).select('*'), [id]); - let note = data && data.length > 0 ? data[0].note : null; - let hasNotes = note && note !== ''; + const [hover, setHover] = useState(false); + const tooltip = useTooltip(); + const data = useLiveQuery(() => q('notes').filter({ id }).select('*'), [id]); + const note = data && data.length > 0 ? data[0].note : null; + const hasNotes = note && note !== ''; function onClose(notes) { send('notes-save', { id, note: notes }); diff --git a/packages/desktop-client/src/components/Notifications.tsx b/packages/desktop-client/src/components/Notifications.tsx index a56c529187d..8b16e7e86aa 100644 --- a/packages/desktop-client/src/components/Notifications.tsx +++ b/packages/desktop-client/src/components/Notifications.tsx @@ -29,17 +29,17 @@ function compileMessage( return ( {message.split(/\n\n/).map((paragraph, idx) => { - let parts = paragraph.split(/(\[[^\]]*\]\([^)]*\))/g); + const parts = paragraph.split(/(\[[^\]]*\]\([^)]*\))/g); return ( {parts.map((part, idx) => { - let match = part.match(/\[([^\]]*)\]\(([^)]*)\)/); + const match = part.match(/\[([^\]]*)\]\(([^)]*)\)/); if (match) { - let [_, text, href] = match; + const [_, text, href] = match; if (href[0] === '#') { - let actionName = href.slice(1); + const actionName = href.slice(1); return ( void; }) { - let { type, title, message, pre, messageActions, sticky, internal, button } = - notification; + const { + type, + title, + message, + pre, + messageActions, + sticky, + internal, + button, + } = notification; - let [loading, setLoading] = useState(false); - let [overlayLoading, setOverlayLoading] = useState(false); + const [loading, setLoading] = useState(false); + const [overlayLoading, setOverlayLoading] = useState(false); useEffect(() => { if (type === 'error' && internal) { @@ -95,10 +103,10 @@ function Notification({ } }, []); - let positive = type === 'message'; - let error = type === 'error'; + const positive = type === 'message'; + const error = type === 'error'; - let processedMessage = useMemo( + const processedMessage = useMemo( () => compileMessage(message, messageActions, setOverlayLoading, onRemove), [message, messageActions], ); @@ -228,8 +236,8 @@ function Notification({ } export default function Notifications({ style }: { style?: CSSProperties }) { - let { removeNotification } = useActions(); - let notifications = useSelector(state => state.notifications.notifications); + const { removeNotification } = useActions(); + const notifications = useSelector(state => state.notifications.notifications); return ( ; style?: CSSProperties; + leftContentContainerProps?: ComponentPropsWithoutRef; leftContent?: ReactNode; + rightContentContainerProps?: ComponentPropsWithoutRef; rightContent?: ReactNode; }; const HEADER_HEIGHT = 50; function PageHeader({ - name, + title, + titleContainerProps, style, + leftContentContainerProps, leftContent, + rightContentContainerProps, rightContent, }: PageHeaderProps) { const { isNarrowWidth } = useResponsive(); @@ -28,8 +34,8 @@ function PageHeader({ {leftContent} - {name} + {title} {rightContent} @@ -80,53 +94,88 @@ function PageHeader({ ...style, }} > - {name} + {title} ); } type PageProps = { - title: ReactNode; - titleStyle?: CSSProperties; - headerLeftContent?: ReactNode; - headerRightContent?: ReactNode; + titleContainerProps?: PageHeaderProps['titleContainerProps']; + title: PageHeaderProps['title']; + headerStyle?: CSSProperties; + headerLeftContentContainerProps?: PageHeaderProps['leftContentContainerProps']; + headerLeftContent?: PageHeaderProps['leftContent']; + headerRightContentContainerProps?: PageHeaderProps['rightContentContainerProps']; + headerRightContent?: PageHeaderProps['rightContent']; + style?: CSSProperties; + padding?: number; + childrenContainerProps?: ComponentPropsWithoutRef; children: ReactNode; + footer?: ReactNode; }; export function Page({ + titleContainerProps, title, - titleStyle, + headerStyle, + headerLeftContentContainerProps, headerLeftContent, + headerRightContentContainerProps, headerRightContent, + style, + padding, + childrenContainerProps, children, + footer, }: PageProps) { - let { isNarrowWidth } = useResponsive(); - let HORIZONTAL_PADDING = isNarrowWidth ? 10 : 20; + const { isNarrowWidth } = useResponsive(); + const _padding = padding != null ? padding : isNarrowWidth ? 10 : 20; return ( - + - - {children} - + {isNarrowWidth ? ( + + {children} + + ) : ( + + {children} + + )} + {footer} ); } diff --git a/packages/desktop-client/src/components/PrivacyFilter.tsx b/packages/desktop-client/src/components/PrivacyFilter.tsx index ac8fd543a75..5e5d9d0edec 100644 --- a/packages/desktop-client/src/components/PrivacyFilter.tsx +++ b/packages/desktop-client/src/components/PrivacyFilter.tsx @@ -22,7 +22,7 @@ export function ConditionalPrivacyFilter({ privacyFilter, defaultPrivacyFilterProps, }: ConditionalPrivacyFilterProps) { - let renderPrivacyFilter = (children, mergedProps) => ( + const renderPrivacyFilter = (children, mergedProps) => ( {children} ); return privacyFilter ? ( @@ -52,10 +52,10 @@ export default function PrivacyFilter({ children, ...props }: PrivacyFilterProps) { - let privacyMode = usePrivacyMode(); + const privacyMode = usePrivacyMode(); // Limit mobile support for now. - let { isNarrowWidth } = useResponsive(); - let activate = + const { isNarrowWidth } = useResponsive(); + const activate = privacyMode && !isNarrowWidth && (!activationFilters || @@ -63,7 +63,7 @@ export default function PrivacyFilter({ typeof value === 'boolean' ? value : value(), )); - let blurAmount = blurIntensity != null ? `${blurIntensity}px` : '3px'; + const blurAmount = blurIntensity != null ? `${blurIntensity}px` : '3px'; return !activate ? ( <>{Children.toArray(children)} @@ -75,11 +75,11 @@ export default function PrivacyFilter({ } function BlurredOverlay({ blurIntensity, children, ...props }) { - let [hovered, setHovered] = useState(false); - let onHover = useCallback(() => setHovered(true), [setHovered]); - let onHoverEnd = useCallback(() => setHovered(false), [setHovered]); + const [hovered, setHovered] = useState(false); + const onHover = useCallback(() => setHovered(true), [setHovered]); + const onHoverEnd = useCallback(() => setHovered(false), [setHovered]); - let blurStyle = { + const blurStyle = { ...(!hovered && { filter: `blur(${blurIntensity})`, WebkitFilter: `blur(${blurIntensity})`, @@ -89,7 +89,7 @@ function BlurredOverlay({ blurIntensity, children, ...props }) { }), }; - let { style, ...restProps } = props; + const { style, ...restProps } = props; return ( + + {children} + ); } diff --git a/packages/desktop-client/src/components/ServerContext.tsx b/packages/desktop-client/src/components/ServerContext.tsx index 86c0bf8d653..795a62b794d 100644 --- a/packages/desktop-client/src/components/ServerContext.tsx +++ b/packages/desktop-client/src/components/ServerContext.tsx @@ -29,7 +29,7 @@ export const useServerVersion = () => useContext(ServerContext).version; export const useSetServerURL = () => useContext(ServerContext).setURL; async function getServerVersion() { - let result = await send('get-server-version'); + const result = await send('get-server-version'); if ('version' in result) { return result.version; } @@ -37,8 +37,8 @@ async function getServerVersion() { } export function ServerProvider({ children }: { children: ReactNode }) { - let [serverURL, setServerURL] = useState(''); - let [version, setVersion] = useState(''); + const [serverURL, setServerURL] = useState(''); + const [version, setVersion] = useState(''); useEffect(() => { async function run() { @@ -48,9 +48,9 @@ export function ServerProvider({ children }: { children: ReactNode }) { run(); }, []); - let setURL = useCallback( + const setURL = useCallback( async (url: string, opts: { validate?: boolean } = {}) => { - let { error } = await send('set-server-url', { ...opts, url }); + const { error } = await send('set-server-url', { ...opts, url }); if (!error) { setServerURL(await send('get-server-url')); setVersion(await getServerVersion()); diff --git a/packages/desktop-client/src/components/SyncRefresh.tsx b/packages/desktop-client/src/components/SyncRefresh.tsx index ec95d7ea628..714ba0f6fee 100644 --- a/packages/desktop-client/src/components/SyncRefresh.tsx +++ b/packages/desktop-client/src/components/SyncRefresh.tsx @@ -9,7 +9,7 @@ type SyncRefreshProps = { children: (props: ChildrenProps) => ReactNode; }; export default function SyncRefresh({ onSync, children }: SyncRefreshProps) { - let [syncing, setSyncing] = useState(false); + const [syncing, setSyncing] = useState(false); async function onSync_() { setSyncing(true); diff --git a/packages/desktop-client/src/components/ThemeSelector.tsx b/packages/desktop-client/src/components/ThemeSelector.tsx index e780bfe296d..e909e4d0e61 100644 --- a/packages/desktop-client/src/components/ThemeSelector.tsx +++ b/packages/desktop-client/src/components/ThemeSelector.tsx @@ -13,10 +13,10 @@ type ThemeSelectorProps = { }; export function ThemeSelector({ style }: ThemeSelectorProps) { - let theme = useTheme(); - let { saveGlobalPrefs } = useActions(); + const theme = useTheme(); + const { saveGlobalPrefs } = useActions(); - let { isNarrowWidth } = useResponsive(); + const { isNarrowWidth } = useResponsive(); return isNarrowWidth ? null : ( - - - - {account.name} - - - - - - - - - + ); } diff --git a/packages/desktop-client/src/components/accounts/MobileAccounts.js b/packages/desktop-client/src/components/accounts/MobileAccounts.js index 59c9db5d172..ac71f9231e0 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccounts.js +++ b/packages/desktop-client/src/components/accounts/MobileAccounts.js @@ -158,32 +158,28 @@ function AccountList({ }; return ( - - - - - } - > - {accounts.length === 0 && } - + + + + } + padding={0} + style={{ flex: 1, backgroundColor: theme.mobilePageBackground }} + > + {accounts.length === 0 && } + + {budgetedAccounts.length > 0 && ( )} @@ -213,25 +209,25 @@ function AccountList({ onSelect={onSelectAccount} /> ))} - - - + + + ); } export default function Accounts() { - let accounts = useSelector(state => state.queries.accounts); - let newTransactions = useSelector(state => state.queries.newTransactions); - let updatedAccounts = useSelector(state => state.queries.updatedAccounts); - let numberFormat = useSelector( + const accounts = useSelector(state => state.queries.accounts); + const newTransactions = useSelector(state => state.queries.newTransactions); + const updatedAccounts = useSelector(state => state.queries.updatedAccounts); + const numberFormat = useSelector( state => state.prefs.local.numberFormat || 'comma-dot', ); - let hideFraction = useSelector( + const hideFraction = useSelector( state => state.prefs.local.hideFraction || false, ); const { list: categories } = useCategories(); - let { getAccounts, replaceModal, syncAndDownload } = useActions(); + const { getAccounts, replaceModal, syncAndDownload } = useActions(); const transactions = useState({}); const navigate = useNavigate(); diff --git a/packages/desktop-client/src/components/accounts/Reconcile.js b/packages/desktop-client/src/components/accounts/Reconcile.js index 762a728f884..d01ad8ef3f2 100644 --- a/packages/desktop-client/src/components/accounts/Reconcile.js +++ b/packages/desktop-client/src/components/accounts/Reconcile.js @@ -20,13 +20,13 @@ export function ReconcilingMessage({ onDone, onCreateTransaction, }) { - let cleared = useSheetValue({ + const cleared = useSheetValue({ name: balanceQuery.name + '-cleared', value: 0, query: balanceQuery.query.filter({ cleared: true }), }); - let format = useFormat(); - let targetDiff = targetBalance - cleared; + const format = useFormat(); + const targetDiff = targetBalance - cleared; return ( item.id === value); + const idx = suggestions.findIndex(item => item.id === value); return idx === -1 ? null : suggestions[idx]; } @@ -70,7 +70,7 @@ function fireUpdate(onUpdate, strict, suggestions, index, value) { } else { if (index == null) { // If passing in a value directly, validate the id - let sug = suggestions.find(sug => sug.id === value); + const sug = suggestions.find(sug => sug.id === value); if (sug) { selected = sug.id; } @@ -90,7 +90,7 @@ function defaultRenderItems(items, getItemProps, highlightedIndex) { return (
{items.map((item, index) => { - let name = getItemName(item); + const name = getItemName(item); return (
{ + const defaultGetHighlightedIndex = filteredSuggestions => { return highlightFirst && filteredSuggestions.length ? 0 : null; }; - let highlightedIndex = ( + const highlightedIndex = ( getHighlightedIndex || defaultGetHighlightedIndex )(filteredSuggestions); // @ts-expect-error Types say there is no type @@ -415,7 +415,7 @@ function SingleAutocomplete({ // If not using table behavior, reset the input on blur. Tables // handle saving the value on blur. - let value = selectedItem ? getItemId(selectedItem) : null; + const value = selectedItem ? getItemId(selectedItem) : null; resetState(value); } else { @@ -423,7 +423,7 @@ function SingleAutocomplete({ } }, onKeyDown: (e: KeyboardEvent) => { - let { onKeyDown } = inputProps || {}; + const { onKeyDown } = inputProps || {}; // If the dropdown is open, an item is highlighted, and the user // pressed enter, always capture that and handle it ourselves @@ -561,15 +561,15 @@ function MultiAutocomplete({ strict, ...props }: MultiAutocompleteProps) { - let [focused, setFocused] = useState(false); - let lastSelectedItems = useRef(); + const [focused, setFocused] = useState(false); + const lastSelectedItems = useRef(); useEffect(() => { lastSelectedItems.current = selectedItems; }); function onRemoveItem(id) { - let items = selectedItems.filter(i => i !== id); + const items = selectedItems.filter(i => i !== id); onSelect(items); } diff --git a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx index 1ed1009f978..b8c07ba36db 100644 --- a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx @@ -15,6 +15,7 @@ import { } from 'loot-core/src/types/models'; import Split from '../../icons/v0/Split'; +import { useResponsive } from '../../ResponsiveProvider'; import { type CSSProperties, theme } from '../../style'; import Text from '../common/Text'; import View from '../common/View'; @@ -116,7 +117,7 @@ export default function CategoryAutocomplete({ renderCategoryItem, ...props }: CategoryAutocompleteProps) { - let categorySuggestions: Array< + const categorySuggestions: Array< CategoryEntity & { group?: CategoryGroupEntity } > = useMemo( () => @@ -220,6 +221,7 @@ export function SplitTransactionButton({ style, ...props }: SplitTransactionButtonProps) { + const { isNarrowWidth } = useResponsive(); return ( { - let type = item.transfer_acct ? 'account' : 'payee'; + const type = item.transfer_acct ? 'account' : 'payee'; let title; if (type === 'payee' && lastType !== type) { title = 'Payees'; } else if (type === 'account' && lastType !== type) { title = 'Transfer To/From'; } - let showMoreMessage = idx === items.length - 1 && isFiltered; + const showMoreMessage = idx === items.length - 1 && isFiltered; lastType = type; return ( @@ -184,20 +185,20 @@ export default function PayeeAutocomplete({ payees, ...props }: PayeeAutocompleteProps) { - let cachedPayees = useCachedPayees(); + const cachedPayees = useCachedPayees(); if (!payees) { payees = cachedPayees; } - let cachedAccounts = useCachedAccounts(); + const cachedAccounts = useCachedAccounts(); if (!accounts) { accounts = cachedAccounts; } - let [focusTransferPayees, setFocusTransferPayees] = useState(false); - let [rawPayee, setRawPayee] = useState(''); - let hasPayeeInput = !!rawPayee; - let payeeSuggestions = useMemo(() => { + const [focusTransferPayees, setFocusTransferPayees] = useState(false); + const [rawPayee, setRawPayee] = useState(''); + const hasPayeeInput = !!rawPayee; + const payeeSuggestions = useMemo(() => { const suggestions = getPayeeSuggestions( payees, focusTransferPayees, @@ -210,13 +211,13 @@ export default function PayeeAutocomplete({ return [{ id: 'new', name: '' }, ...suggestions]; }, [payees, focusTransferPayees, accounts, hasPayeeInput]); - let dispatch = useDispatch(); + const dispatch = useDispatch(); async function handleSelect(value, rawInputValue) { if (tableBehavior) { onSelect?.(makeNew(value, rawInputValue)); } else { - let create = () => dispatch(createPayee(rawInputValue)); + const create = () => dispatch(createPayee(rawInputValue)); if (Array.isArray(value)) { value = await Promise.all(value.map(v => (v === 'new' ? create() : v))); @@ -278,10 +279,10 @@ export default function PayeeAutocomplete({ }); filtered.sort((p1, p2) => { - let r1 = p1.name.toLowerCase().startsWith(value.toLowerCase()); - let r2 = p2.name.toLowerCase().startsWith(value.toLowerCase()); - let r1exact = p1.name.toLowerCase() === value.toLowerCase(); - let r2exact = p2.name.toLowerCase() === value.toLowerCase(); + const r1 = p1.name.toLowerCase().startsWith(value.toLowerCase()); + const r2 = p2.name.toLowerCase().startsWith(value.toLowerCase()); + const r1exact = p1.name.toLowerCase() === value.toLowerCase(); + const r2exact = p2.name.toLowerCase() === value.toLowerCase(); // (maniacal laughter) mwahaHAHAHAHAH if (p1.id === 'new') { @@ -305,7 +306,7 @@ export default function PayeeAutocomplete({ } }); - let isf = filtered.length > 100; + const isf = filtered.length > 100; filtered = filtered.slice(0, 100); filtered.filtered = isf; @@ -373,19 +374,21 @@ export function CreatePayeeButton({ style, ...props }: CreatePayeeButtonProps) { + const { isNarrowWidth } = useResponsive(); return ( { - let items = useMemo(() => { - let [expenseGroups, incomeGroup] = separateGroups(categoryGroups); + const items = useMemo(() => { + const [expenseGroups, incomeGroup] = separateGroups(categoryGroups); let items = Array.prototype.concat.apply( [], @@ -53,7 +53,7 @@ const BudgetCategories = memo( cat => showHiddenCategories || !cat.hidden, ); - let items = [{ type: 'expense-group', value: { ...group } }]; + const items = [{ type: 'expense-group', value: { ...group } }]; if (newCategoryForGroup === group.id) { items.push({ type: 'new-category' }); @@ -103,13 +103,13 @@ const BudgetCategories = memo( showHiddenCategories, ]); - let [dragState, setDragState] = useState(null); - let [savedCollapsed, setSavedCollapsed] = useState(null); + const [dragState, setDragState] = useState(null); + const [savedCollapsed, setSavedCollapsed] = useState(null); // TODO: If we turn this into a reducer, we could probably memoize // each item in the list for better perf function onDragChange(newDragState) { - let { state } = newDragState; + const { state } = newDragState; if (state === 'start-preview') { setDragState({ @@ -283,7 +283,7 @@ const BudgetCategories = memo( throw new Error('Unknown item type: ' + item.type); } - let pos = + const pos = idx === 0 ? 'first' : idx === items.length - 1 ? 'last' : null; return ( diff --git a/packages/desktop-client/src/components/budget/BudgetMonthCountContext.tsx b/packages/desktop-client/src/components/budget/BudgetMonthCountContext.tsx index 4a825f4ec20..a69e40fb69f 100644 --- a/packages/desktop-client/src/components/budget/BudgetMonthCountContext.tsx +++ b/packages/desktop-client/src/components/budget/BudgetMonthCountContext.tsx @@ -12,7 +12,8 @@ type BudgetMonthCountContextValue = { setDisplayMax: Dispatch>; }; -let BudgetMonthCountContext = createContext(null); +const BudgetMonthCountContext = + createContext(null); type BudgetMonthCountProviderProps = { children: ReactNode; @@ -21,7 +22,7 @@ type BudgetMonthCountProviderProps = { export function BudgetMonthCountProvider({ children, }: BudgetMonthCountProviderProps) { - let [displayMax, setDisplayMax] = useState(1); + const [displayMax, setDisplayMax] = useState(1); return ( diff --git a/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx b/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx index c5f3576c70b..919793bc3f6 100644 --- a/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx +++ b/packages/desktop-client/src/components/budget/BudgetPageHeader.tsx @@ -17,8 +17,8 @@ type BudgetPageHeaderProps = { const BudgetPageHeader = memo( ({ startMonth, onMonthSelect, numMonths, monthBounds }) => { function getValidMonth(month) { - let start = monthBounds.start; - let end = monthUtils.subMonths(monthBounds.end, numMonths - 1); + const start = monthBounds.start; + const end = monthUtils.subMonths(monthBounds.end, numMonths - 1); if (month < start) { return start; diff --git a/packages/desktop-client/src/components/budget/BudgetSummaries.tsx b/packages/desktop-client/src/components/budget/BudgetSummaries.tsx index 11dc77f5bc2..1367005cc11 100644 --- a/packages/desktop-client/src/components/budget/BudgetSummaries.tsx +++ b/packages/desktop-client/src/components/budget/BudgetSummaries.tsx @@ -25,37 +25,37 @@ type BudgetSummariesProps = { export default function BudgetSummaries({ SummaryComponent, }: BudgetSummariesProps) { - let { months } = useContext(MonthsContext); + const { months } = useContext(MonthsContext); - let [widthState, setWidthState] = useState(0); - let [styles, spring] = useSpring(() => ({ + const [widthState, setWidthState] = useState(0); + const [styles, spring] = useSpring(() => ({ x: 0, config: { mass: 3, tension: 600, friction: 80 }, })); - let containerRef = useResizeObserver( + const containerRef = useResizeObserver( useCallback(rect => { setWidthState(rect.width); }, []), ); - let prevMonth0 = useRef(months[0]); - let allMonths = [...months]; + const prevMonth0 = useRef(months[0]); + const allMonths = [...months]; allMonths.unshift(subMonths(months[0], 1)); allMonths.push(addMonths(months[months.length - 1], 1)); - let monthWidth = widthState / months.length; + const monthWidth = widthState / months.length; useLayoutEffect(() => { - let prevMonth = prevMonth0.current; - let reversed = prevMonth > months[0]; - let offsetX = monthWidth; + const prevMonth = prevMonth0.current; + const reversed = prevMonth > months[0]; + const offsetX = monthWidth; let from = reversed ? -offsetX * 2 : 0; if (prevMonth !== allMonths[0] && prevMonth !== allMonths[2]) { from = -offsetX; } - let to = -offsetX; + const to = -offsetX; spring.start({ from: { x: from }, x: to }); }, [months[0]]); diff --git a/packages/desktop-client/src/components/budget/BudgetTable.js b/packages/desktop-client/src/components/budget/BudgetTable.js index 46568a28847..1059c0633fd 100644 --- a/packages/desktop-client/src/components/budget/BudgetTable.js +++ b/packages/desktop-client/src/components/budget/BudgetTable.js @@ -33,16 +33,20 @@ class BudgetTable extends Component { }; onReorderCategory = (id, dropPos, targetId) => { - let { categoryGroups } = this.props; + const { categoryGroups } = this.props; - let isGroup = !!categoryGroups.find(g => g.id === targetId); + const isGroup = !!categoryGroups.find(g => g.id === targetId); if (isGroup) { - let { targetId: groupId } = findSortUp(categoryGroups, dropPos, targetId); - let group = categoryGroups.find(g => g.id === groupId); + const { targetId: groupId } = findSortUp( + categoryGroups, + dropPos, + targetId, + ); + const group = categoryGroups.find(g => g.id === groupId); if (group) { - let { categories } = group; + const { categories } = group; this.props.onReorderCategory({ id, groupId: group.id, @@ -55,7 +59,7 @@ class BudgetTable extends Component { } else { let targetGroup; - for (let group of categoryGroups) { + for (const group of categoryGroups) { if (group.categories.find(cat => cat.id === targetId)) { targetGroup = group; break; @@ -71,7 +75,7 @@ class BudgetTable extends Component { }; onReorderGroup = (id, dropPos, targetId) => { - let { categoryGroups } = this.props; + const { categoryGroups } = this.props; this.props.onReorderGroup({ id, @@ -80,8 +84,8 @@ class BudgetTable extends Component { }; moveVertically = dir => { - let { editing } = this.state; - let { type, categoryGroups, collapsed } = this.props; + const { editing } = this.state; + const { type, categoryGroups, collapsed } = this.props; const flattened = categoryGroups.reduce((all, group) => { if (collapsed.includes(group.id)) { @@ -151,12 +155,12 @@ class BudgetTable extends Component { }; collapseAllCategories = () => { - let { setCollapsed, categoryGroups } = this.props; + const { setCollapsed, categoryGroups } = this.props; setCollapsed(categoryGroups.map(g => g.id)); }; render() { - let { + const { type, categoryGroups, prewarmStartMonth, @@ -177,7 +181,7 @@ class BudgetTable extends Component { onShowNewGroup, onHideNewGroup, } = this.props; - let { editing, draggingState, showHiddenCategories } = this.state; + const { editing, draggingState, showHiddenCategories } = this.state; return ( { - let prefs = useSelector(state => state.prefs.local); - let { setDisplayMax } = useBudgetMonthCount(); - let actions = useActions(); + const prefs = useSelector(state => state.prefs.local); + const { setDisplayMax } = useBudgetMonthCount(); + const actions = useActions(); - let numPossible = getNumPossibleMonths(width); - let numMonths = Math.min(numPossible, maxMonths); - let maxWidth = 200 + 500 * numMonths; + const numPossible = getNumPossibleMonths(width); + const numMonths = Math.min(numPossible, maxMonths); + const maxWidth = 200 + 500 * numMonths; useEffect(() => { setDisplayMax(numPossible); diff --git a/packages/desktop-client/src/components/budget/ExpenseCategory.tsx b/packages/desktop-client/src/components/budget/ExpenseCategory.tsx index af20de7e541..cc131396687 100644 --- a/packages/desktop-client/src/components/budget/ExpenseCategory.tsx +++ b/packages/desktop-client/src/components/budget/ExpenseCategory.tsx @@ -52,14 +52,14 @@ function ExpenseCategory({ dragging = true; } - let { dragRef } = useDraggable({ + const { dragRef } = useDraggable({ type: 'category', onDragChange, item: cat, canDrag: editingCell === null, }); - let { dropRef, dropPos } = useDroppable({ + const { dropRef, dropPos } = useDroppable({ types: 'category', id: cat.id, onDrop: onReorder, diff --git a/packages/desktop-client/src/components/budget/ExpenseGroup.tsx b/packages/desktop-client/src/components/budget/ExpenseGroup.tsx index 6104e012545..7c1bfd8983f 100644 --- a/packages/desktop-client/src/components/budget/ExpenseGroup.tsx +++ b/packages/desktop-client/src/components/budget/ExpenseGroup.tsx @@ -48,22 +48,22 @@ function ExpenseGroup({ onToggleCollapse, onShowNewCategory, }: ExpenseGroupProps) { - let dragging = dragState && dragState.item === group; + const dragging = dragState && dragState.item === group; - let { dragRef } = useDraggable({ + const { dragRef } = useDraggable({ type: 'group', onDragChange, item: group, canDrag: editingCell === null, }); - let { dropRef, dropPos } = useDroppable({ + const { dropRef, dropPos } = useDroppable({ types: 'group', id: group.id, onDrop: onReorderGroup, }); - let { dropRef: catDropRef, dropPos: catDropPos } = useDroppable({ + const { dropRef: catDropRef, dropPos: catDropPos } = useDroppable({ types: 'category', id: group.id, onDrop: onReorderCategory, diff --git a/packages/desktop-client/src/components/budget/IncomeCategory.tsx b/packages/desktop-client/src/components/budget/IncomeCategory.tsx index 110da55231e..0d5625186da 100644 --- a/packages/desktop-client/src/components/budget/IncomeCategory.tsx +++ b/packages/desktop-client/src/components/budget/IncomeCategory.tsx @@ -43,14 +43,14 @@ function IncomeCategory({ onReorder, onShowActivity, }: IncomeCategoryProps) { - let { dragRef } = useDraggable({ + const { dragRef } = useDraggable({ type: 'income-category', onDragChange, item: cat, canDrag: editingCell === null, }); - let { dropRef, dropPos } = useDroppable({ + const { dropRef, dropPos } = useDroppable({ types: 'income-category', id: cat.id, onDrop: onReorder, diff --git a/packages/desktop-client/src/components/budget/MobileBudget.js b/packages/desktop-client/src/components/budget/MobileBudget.js index 4f9e1601c18..8c3e7327c65 100644 --- a/packages/desktop-client/src/components/budget/MobileBudget.js +++ b/packages/desktop-client/src/components/budget/MobileBudget.js @@ -41,7 +41,7 @@ class Budget extends Component { } async loadCategories() { - let result = await this.props.getCategories(); + const result = await this.props.getCategories(); this.setState({ categoryGroups: result.grouped }); } @@ -63,7 +63,7 @@ class Budget extends Component { this.setState({ initialized: true }); - let unlisten = listen('sync-event', ({ type, tables }) => { + const unlisten = listen('sync-event', ({ type, tables }) => { if ( type === 'success' && (tables.includes('categories') || @@ -145,14 +145,14 @@ class Budget extends Component { }; onDeleteGroup = async groupId => { - let group = this.state.categoryGroups?.find(g => g.id === groupId); + const group = this.state.categoryGroups?.find(g => g.id === groupId); if (!group) { return; } let mustTransfer = false; - for (let category of group.categories) { + for (const category of group.categories) { if (await send('must-category-transfer', { id: category.id })) { mustTransfer = true; break; @@ -210,21 +210,22 @@ class Budget extends Component { }; onReorderCategory = (id, { inGroup, aroundCategory }) => { - let { categoryGroups } = this.state; + const { categoryGroups } = this.state; let groupId, targetId; if (inGroup) { groupId = inGroup; } else if (aroundCategory) { - let { id: catId, position } = aroundCategory; + const { id: originalCatId, position } = aroundCategory; - let group = categoryGroups.find(group => + let catId = originalCatId; + const group = categoryGroups.find(group => group.categories.find(cat => cat.id === catId), ); if (position === 'bottom') { - let { categories } = group; - let idx = categories.findIndex(cat => cat.id === catId); + const { categories } = group; + const idx = categories.findIndex(cat => cat.id === catId); catId = idx < categories.length - 1 ? categories[idx + 1].id : null; } @@ -240,10 +241,10 @@ class Budget extends Component { }; onReorderGroup = (id, targetId, position) => { - let { categoryGroups } = this.state; + const { categoryGroups } = this.state; if (position === 'bottom') { - let idx = categoryGroups.findIndex(group => group.id === targetId); + const idx = categoryGroups.findIndex(group => group.id === targetId); targetId = idx < categoryGroups.length - 1 ? categoryGroups[idx + 1].id : null; } @@ -266,23 +267,23 @@ class Budget extends Component { }; onPrevMonth = async () => { - let { spreadsheet, budgetType } = this.props; - let month = monthUtils.subMonths(this.state.currentMonth, 1); + const { spreadsheet, budgetType } = this.props; + const month = monthUtils.subMonths(this.state.currentMonth, 1); await prewarmMonth(budgetType, spreadsheet, month); this.setState({ currentMonth: month, initialized: true }); }; onNextMonth = async () => { - let { spreadsheet, budgetType } = this.props; - let month = monthUtils.addMonths(this.state.currentMonth, 1); + const { spreadsheet, budgetType } = this.props; + const month = monthUtils.addMonths(this.state.currentMonth, 1); await prewarmMonth(budgetType, spreadsheet, month); this.setState({ currentMonth: month, initialized: true }); }; onOpenActionSheet = () => { - let { budgetType } = this.props; + const { budgetType } = this.props; - let options = [ + const options = [ 'Edit Categories', 'Copy last month’s budget', 'Set budgets to zero', @@ -352,8 +353,8 @@ class Budget extends Component { applyBudgetAction, pushModal, } = this.props; - let numberFormat = prefs.numberFormat || 'comma-dot'; - let hideFraction = prefs.hideFraction || false; + const numberFormat = prefs.numberFormat || 'comma-dot'; + const hideFraction = prefs.hideFraction || false; if (!categoryGroups || !initialized) { return ( @@ -414,14 +415,14 @@ class Budget extends Component { } export default function BudgetWrapper() { - let { list: categories, grouped: categoryGroups } = useCategories(); - let budgetType = useSelector( + const { list: categories, grouped: categoryGroups } = useCategories(); + const budgetType = useSelector( state => state.prefs.local.budgetType || 'rollover', ); - let prefs = useSelector(state => state.prefs.local); + const prefs = useSelector(state => state.prefs.local); - let actions = useActions(); - let spreadsheet = useSpreadsheet(); + const actions = useActions(); + const spreadsheet = useSpreadsheet(); useSetThemeColor(theme.mobileViewTheme); return ( { if (isBudgetActionMenuOpen) { @@ -277,7 +276,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ } }, [isEditing, tooltip]); - let onSubmit = () => { + const onSubmit = () => { if (categoryName) { onSave?.({ ...category, @@ -289,7 +288,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ onEdit?.(null); }; - let onMenuSelect = type => { + const onMenuSelect = type => { onEdit?.(null); switch (type) { case 'toggle-visibility': @@ -307,10 +306,10 @@ const ExpenseCategory = memo(function ExpenseCategory({ } }; - let listItemRef = useRef(); - let inputRef = useRef(); + const listItemRef = useRef(); + const inputRef = useRef(); - let _onBudgetAction = (monthIndex, action, arg) => { + const _onBudgetAction = (monthIndex, action, arg) => { onBudgetAction?.( monthUtils.getMonthFromIndex(monthUtils.getYear(month), monthIndex), action, @@ -318,7 +317,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ ); }; - let content = ( + const content = ( { if (!isEditing && tooltip.isOpen) { @@ -570,7 +569,7 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ } }, [isEditing]); - let onSubmit = () => { + const onSubmit = () => { if (groupName) { onSave?.({ ...group, @@ -582,7 +581,7 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ onEdit?.(null); }; - let onMenuSelect = type => { + const onMenuSelect = type => { onEdit?.(null); switch (type) { case 'add-category': @@ -603,10 +602,10 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ } }; - let listItemRef = useRef(); - let inputRef = useRef(); + const listItemRef = useRef(); + const inputRef = useRef(); - let content = ( + const content = ( { if (!isEditing && tooltip.isOpen) { @@ -824,7 +823,7 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({ } }, [isEditing]); - let onSubmit = () => { + const onSubmit = () => { if (groupName) { onSave?.({ ...group, @@ -836,7 +835,7 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({ onEdit?.(null); }; - let onMenuSelect = type => { + const onMenuSelect = type => { onEdit?.(null); switch (type) { case 'add-category': @@ -857,8 +856,8 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({ } }; - let listItemRef = useRef(); - let inputRef = useRef(); + const listItemRef = useRef(); + const inputRef = useRef(); return ( { if (!isEditing && tooltip.isOpen) { @@ -1032,7 +1031,7 @@ const IncomeCategory = memo(function IncomeCategory({ } }, [isEditing]); - let onSubmit = () => { + const onSubmit = () => { if (categoryName) { onSave?.({ ...category, @@ -1044,7 +1043,7 @@ const IncomeCategory = memo(function IncomeCategory({ onEdit?.(null); }; - let onMenuSelect = type => { + const onMenuSelect = type => { onEdit?.(null); switch (type) { case 'toggle-visibility': @@ -1062,8 +1061,8 @@ const IncomeCategory = memo(function IncomeCategory({ } }; - let listItemRef = useRef(); - let inputRef = useRef(); + const listItemRef = useRef(); + const inputRef = useRef(); return ( = 360; // let editMode = false; // neuter editMode -- sorry, not rewriting drag-n-drop right now - let currentMonth = monthUtils.currentMonth(); - let format = useFormat(); + const format = useFormat(); const mobileShowBudgetedColPref = useSelector(state => { return state.prefs?.local?.toggleMobileDisplayPref || true; @@ -1706,7 +1704,7 @@ export function BudgetTable(props) { return state.prefs?.local?.['budget.showHiddenCategories'] || false; }); - let [showBudgetedCol, setShowBudgetedCol] = useState( + const [showBudgetedCol, setShowBudgetedCol] = useState( !mobileShowBudgetedColPref && !document.cookie.match(/mobileShowBudgetedColPref=true/), ); @@ -1718,33 +1716,68 @@ export function BudgetTable(props) { } } - let buttonStyle = { + const buttonStyle = { padding: 0, backgroundColor: 'transparent', borderRadius: 'unset', }; + const _onSwitchBudgetType = () => { + pushModal('switch-budget-type', { + onSwitch: onSwitchBudgetType, + }); + }; + + const onToggleHiddenCategories = () => { + savePrefs({ + 'budget.showHiddenCategories': !showHiddenCategories, + }); + }; + return ( - - + + } + headerRightContent={ + !editMode ? ( + + ) : ( + + ) + } + style={{ flex: 1 }} + > {type === 'report' ? ( = currentMonth} + projected={month >= monthUtils.currentMonth()} onClick={onShowBudgetSummary} /> ) : ( @@ -1873,308 +1906,222 @@ export function BudgetTable(props) { /> - - - {!editMode ? ( - // (this.list = el)} - // keyboardShouldPersistTaps="always" - // refreshControl={refreshControl} - // style={{ backgroundColor: colors.n10 }} - // automaticallyAdjustContentInsets={false} - // > - - - - ) : ( - // - // - // {({ - // dragging, - // onGestureEvent, - // onHandlerStateChange, - // scrollRef, - // onScroll - // }) => ( - - - + + {!editMode ? ( + // (this.list = el)} + // keyboardShouldPersistTaps="always" + // refreshControl={refreshControl} + // style={{ backgroundColor: colors.n10 }} + // automaticallyAdjustContentInsets={false} + // > + + + + ) : ( + // + // + // {({ + // dragging, + // onGestureEvent, + // onHandlerStateChange, + // scrollRef, + // onScroll + // }) => ( + + + - // - // - )} - - - + // + // + )} + + ); } -const LEFT_RIGHT_FLEX_WIDTH = 80; -const BUDGET_HEADER_HEIGHT = 50; - -function BudgetHeader({ - currentMonth, - monthBounds, - onPrevMonth, - onNextMonth, - editMode, +function BudgetMenu({ onEditMode, - showHiddenCategories, - savePrefs, - pushModal, + onToggleHiddenCategories, onSwitchBudgetType, }) { - let serverURL = useServerURL(); + const tooltip = useTooltip(); + const isReportBudgetEnabled = useFeatureFlag('reportBudget'); - let prevEnabled = currentMonth > monthBounds.start; - let nextEnabled = currentMonth < monthUtils.subMonths(monthBounds.end, 1); - - let buttonStyle = { - padding: 10, - margin: 2, - }; - - let toggleHiddenCategories = () => { - savePrefs({ - 'budget.showHiddenCategories': !showHiddenCategories, - }); - }; - - let tooltip = useTooltip(); - let isReportBudgetEnabled = useFeatureFlag('reportBudget'); - - let onMenuSelect = name => { + const onMenuSelect = name => { tooltip.close(); switch (name) { case 'edit-mode': onEditMode?.(true); break; case 'toggle-hidden-categories': - toggleHiddenCategories(); + onToggleHiddenCategories?.(); break; case 'switch-budget-type': - pushModal('switch-budget-type', { - onSwitch: onSwitchBudgetType, - }); + onSwitchBudgetType?.(); break; default: throw new Error(`Unrecognized menu option: ${name}`); } }; + return ( + <> + + {tooltip.isOpen && ( + +