Compare commits

...

16 Commits

Author SHA1 Message Date
Joel Jeremy Marquez
a61d66f23f Modal migrations 2024-10-18 23:09:51 -07:00
Joel Jeremy Marquez
c1b475777b More modals migration 2024-10-18 17:43:40 -07:00
Joel Jeremy Marquez
b1a83123f5 Modal changes 2024-10-18 05:27:18 -07:00
Joel Jeremy Marquez
71eb54e9f3 More initial changes 2024-10-18 04:58:05 -07:00
Joel Jeremy Marquez
3e2f96a32c Initial commit - unfinished 2024-10-18 04:56:34 -07:00
Koen van Staveren
a91a8859ab feat: fix categories being set on offbudget transactions (#3705)
* chore: release note

* feat: fix categories being set on offbudget transactions

* fix: #2266

* fix: small mistake

* chore: update comment
2024-10-21 14:23:09 -07:00
deathblade666
a3256f5686 Adding a help modal for quick reference to goal template syntax (#3691)
* Adding a help modal for qucik reference to goal template syntax

* added release notes

* fixed misspelling of Enhancements

* fix lint errors

* Only show when Goal Template Flag is enabled

* fix lint

* Only show on Budget page

* fix lint

* Added Translation, change text formating to table (styling wip), change headers to use react-aria-component headings

* fix lint, made requested change to HelpMenuItem type

* stylized tables

* fixed type error

* Moved section headers to within Table Headers

* fix lint

* added space between last table and see more statement
2024-10-21 22:05:19 +01:00
joel-rich
715bc00e3b Fix incorrect cumulative totals for Days 28+ on the Spending Report (#3679)
* Fix bug with spending report cumulative totals

* release notes

---------

Co-authored-by: Joel Rich <joelrich@protonmail.com>
2024-10-21 06:59:41 +01:00
Vincenzo Di Biase
4e07357221 Add Reconcile button on the account page. (#3684)
* added reconcile button to account page

* add Reconcile Button in the account page

* added release note

* updated VRT snapshots

* removed Reconcile option from Account menu

* made the button minimal with tooltip and added vrt

* missing unused import

* fixed icon and button size

* fixed merge conflicts

* hide reconcile button on all account and for budget page

* fix lint from merged file

* changed reconcile button order

---------

Co-authored-by: vincenzo <dibiasev@gmail.com>
2024-10-20 13:11:45 -07:00
youngcw
03f2cabc18 clean up accidental push (#3695)
* clean up accidental commit

* move
2024-10-19 17:35:13 -07:00
Julian Dominguez-Schatz
259beb7665 Clarify logic to generate splits from rules (#3641)
* Add tests for bug

* Add tests for unexpected behaviour

* Refactor to consistently generate valid splits with no errors

* Add release notes

* Update test names
2024-10-19 19:35:52 -04:00
youngcw
0f3efde855 rabbit suggestion 2024-10-19 16:00:54 -07:00
Matiss Janis Aboltins
9aac44c58f ♻️ (typescript) migrated account header to TS (#3640)
* ♻️ (typescript) migrated account header to TS

* TS patches

* Patch types

* Update packages/desktop-client/src/components/accounts/Header.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-10-18 20:53:46 +01:00
Joel Jeremy Marquez
0d9528e22c @emotion/css as drop-in replacement for abandoned glamor library (#3471)
* Migrate to @emotion/css

* Remove custom CSSProperties

* Fix errors

* Fix typecheck error

* Fix lint error

* Fix typecheck error

* Fix typecheck error

* Fix typecheck error

* Release notes

* VRT

* VRT

* Revert VRT

* Fix typecheck error

* Fix glamor import

* yarn

* Fix lint error

* Dedupe

* Fix typecheck error

* Update @emotion/css

* Revert vrt screenshots

* Fix toggle content

* Fix content

* Fix lint error

* VRT

* Cleanup unneeded style
2024-10-17 17:35:53 -07:00
Samuel Barnes
3f31d19d8a Add Upcoming length adjustment feature flag (#3651)
* feature flag created

* feature flag implemented

* feature toggle setting added

* added release notes

* Update packages/desktop-client/src/components/settings/Experimental.tsx

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

* vrt

* vrt

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
2024-10-17 17:09:34 -07:00
youngcw
225c93914c allow 4 decimal places in file imports (#3676)
* allow 4 decimals

* note

* fix comment

* update test
2024-10-17 12:40:35 -07:00
369 changed files with 8373 additions and 4544 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,2 +1,16 @@
import { type CSSObject } from '@emotion/css/dist/declarations/src/create-instance';
import { type State } from './src/state';
// Allow images to be imported
declare module '*.png';
declare module 'react' {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-empty-object-type
interface CSSProperties extends CSSObject {}
}
declare module 'react-redux' {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/consistent-type-definitions
interface DefaultRootState extends State {}
}

View File

@@ -6,6 +6,7 @@
"build"
],
"devDependencies": {
"@emotion/css": "^11.13.4",
"@fontsource/redacted-script": "^5.0.21",
"@juggle/resize-observer": "^3.4.0",
"@playwright/test": "1.41.1",
@@ -36,7 +37,6 @@
"debounce": "^1.2.1",
"downshift": "7.6.2",
"focus-visible": "^4.1.5",
"glamor": "^2.20.40",
"i18next": "^23.11.5",
"i18next-parser": "^9.0.0",
"i18next-resources-to-backend": "^1.2.1",

View File

@@ -1,10 +1,9 @@
// @ts-strict-ignore
import React from 'react';
import React, { type CSSProperties } from 'react';
import { keyframes } from 'glamor';
import { keyframes } from '@emotion/css';
import { SvgRefresh } from '../icons/v1';
import { type CSSProperties } from '../style';
import { View } from './common/View';

View File

@@ -12,13 +12,6 @@ import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import {
closeBudget,
loadBudget,
loadGlobalPrefs,
setAppState,
sync,
} from 'loot-core/client/actions';
import { SpreadsheetProvider } from 'loot-core/client/SpreadsheetProvider';
import * as Platform from 'loot-core/src/client/platform';
import {
@@ -29,6 +22,13 @@ import {
import { useMetadataPref } from '../hooks/useMetadataPref';
import { installPolyfills } from '../polyfills';
import { ResponsiveProvider } from '../ResponsiveProvider';
import {
closeBudget,
loadBudget,
loadGlobalPrefs,
setAppState,
sync,
} from '../state/actions';
import { styles, hasHiddenScrollbars, ThemeStyle, useTheme } from '../style';
import { ExposeNavigate } from '../util/router-tools';

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { useTransition, animated } from 'react-spring';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { AnimatedLoading } from '../icons/AnimatedLoading';
import { theme } from '../style';
@@ -33,7 +33,7 @@ export function AppBackground({ isLoading }: AppBackgroundProps) {
transitions((style, item) => (
<animated.div key={item} style={style}>
<View
className={`${css({
className={css({
position: 'absolute',
top: 0,
left: 0,
@@ -42,7 +42,7 @@ export function AppBackground({ isLoading }: AppBackgroundProps) {
paddingTop: 200,
color: theme.pageText,
alignItems: 'center',
})}`}
})}
>
<Block style={{ marginBottom: 20, fontSize: 18 }}>
{loadingText}

View File

@@ -3,8 +3,7 @@ import { Trans } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useTransition, animated } from 'react-spring';
import { type State } from 'loot-core/src/client/state-types';
import { type State } from '../state';
import { theme, styles } from '../style';
import { AnimatedRefresh } from './AnimatedRefresh';

View File

@@ -10,8 +10,6 @@ import {
useHref,
} from 'react-router-dom';
import { addNotification, sync } from 'loot-core/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import * as undo from 'loot-core/src/platform/client/undo';
import { useAccounts } from '../hooks/useAccounts';
@@ -19,6 +17,8 @@ import { useLocalPref } from '../hooks/useLocalPref';
import { useMetaThemeColor } from '../hooks/useMetaThemeColor';
import { useNavigate } from '../hooks/useNavigate';
import { useResponsive } from '../ResponsiveProvider';
import { type State } from '../state';
import { addNotification, sync } from '../state/actions';
import { theme } from '../style';
import { getIsOutdated, getLatestVersion } from '../util/versions';

View File

@@ -6,12 +6,11 @@ import {
type Ref,
type MutableRefObject,
type UIEvent,
type CSSProperties,
} from 'react';
import memoizeOne from 'memoize-one';
import { type CSSProperties } from '../style';
import { View } from './common/View';
const IS_SCROLLING_DEBOUNCE_INTERVAL = 150;

View File

@@ -6,9 +6,9 @@ import { useLocation } from 'react-router-dom';
import { useToggle } from 'usehooks-ts';
import { pushModal } from 'loot-core/client/actions/modals';
import { useFeatureFlag } from '../hooks/useFeatureFlag';
import { SvgHelp } from '../icons/v2/Help';
import { pushModal } from '../state/actions';
import { openUrl } from '../util/router-tools';
import { Button } from './common/Button2';
@@ -16,7 +16,7 @@ import { Menu } from './common/Menu';
import { Popover } from './common/Popover';
import { SpaceBetween } from './common/SpaceBetween';
type HelpMenuItem = 'docs' | 'keyboard-shortcuts';
type HelpMenuItem = 'docs' | 'keyboard-shortcuts' | 'goal-templates';
type HelpButtonProps = {
onPress?: () => void;
@@ -66,6 +66,7 @@ const getPageDocs = (page: string) => {
};
export const HelpMenu = () => {
const showGoalTemplates = useFeatureFlag('goalTemplatesEnabled');
const { t } = useTranslation();
const [isMenuOpen, toggleMenuOpen, setMenuOpen] = useToggle();
const menuButtonRef = useRef(null);
@@ -81,6 +82,9 @@ export const HelpMenu = () => {
case 'keyboard-shortcuts':
dispatch(pushModal('keyboard-shortcuts'));
break;
case 'goal-templates':
dispatch(pushModal('goal-templates'));
break;
}
};
@@ -98,7 +102,7 @@ export const HelpMenu = () => {
onOpenChange={() => setMenuOpen(false)}
>
<Menu
onMenuSelect={item => {
onMenuSelect={(item: HelpMenuItem) => {
setMenuOpen(false);
handleItemSelect(item);
}}
@@ -108,6 +112,9 @@ export const HelpMenu = () => {
text: t('Documentation'),
},
{ name: 'keyboard-shortcuts', text: t('Keyboard shortcuts') },
...(showGoalTemplates && page === '/budget'
? [{ name: 'goal-templates', text: t('Goal templates') }]
: []),
]}
/>
</Popover>

View File

@@ -1,12 +1,11 @@
// @ts-strict-ignore
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, type CSSProperties } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
import { useActions } from '../hooks/useActions';
import { theme, styles, type CSSProperties } from '../style';
import { type State } from '../state';
import { theme, styles } from '../style';
import { Button } from './common/Button2';
import { Menu } from './common/Menu';

View File

@@ -9,8 +9,6 @@ import React, {
} from 'react';
import { useDispatch } from 'react-redux';
import { pushModal } from 'loot-core/src/client/actions/modals';
import { initiallyLoadPayees } from 'loot-core/src/client/actions/queries';
import { send } from 'loot-core/src/platform/client/fetch';
import * as undo from 'loot-core/src/platform/client/undo';
import { getNormalisedString } from 'loot-core/src/shared/normalisation';
@@ -23,6 +21,7 @@ import { useCategories } from '../hooks/useCategories';
import { usePayees } from '../hooks/usePayees';
import { useSchedules } from '../hooks/useSchedules';
import { useSelected, SelectedProvider } from '../hooks/useSelected';
import { initiallyLoadPayees, pushModal } from '../state/actions';
import { theme } from '../style';
import { Button } from './common/Button2';
@@ -117,17 +116,14 @@ export function ManageRules({
const { list: categories } = useCategories();
const payees = usePayees();
const accounts = useAccounts();
const state = {
payees,
accounts,
schedules,
};
const filterData = useMemo(
() => ({
...state,
payees,
accounts,
schedules,
categories,
}),
[state, categories],
[payees, accounts, schedules, categories],
);
const filteredRules = useMemo(

View File

@@ -3,14 +3,10 @@ import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { closeModal } from 'loot-core/client/actions';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useMetadataPref } from '../hooks/useMetadataPref';
import { useModalState } from '../hooks/useModalState';
import { closeModal } from '../state/actions';
import { ModalTitle, ModalHeader } from './common/Modal';
import { AccountAutocompleteModal } from './modals/AccountAutocompleteModal';
import { AccountMenuModal } from './modals/AccountMenuModal';
import { BudgetListModal } from './modals/BudgetListModal';
@@ -35,6 +31,7 @@ import { EnvelopeBudgetMonthMenuModal } from './modals/EnvelopeBudgetMonthMenuMo
import { EnvelopeBudgetSummaryModal } from './modals/EnvelopeBudgetSummaryModal';
import { EnvelopeToBudgetMenuModal } from './modals/EnvelopeToBudgetMenuModal';
import { FixEncryptionKeyModal } from './modals/FixEncryptionKeyModal';
import { GoalTemplateModal } from './modals/GoalTemplateModal';
import { GoCardlessExternalMsgModal } from './modals/GoCardlessExternalMsgModal';
import { GoCardlessInitialiseModal } from './modals/GoCardlessInitialiseModal';
import { HoldBufferModal } from './modals/HoldBufferModal';
@@ -66,7 +63,6 @@ import { DiscoverSchedules } from './schedules/DiscoverSchedules';
import { PostsOfflineNotification } from './schedules/PostsOfflineNotification';
import { ScheduleDetails } from './schedules/ScheduleDetails';
import { ScheduleLink } from './schedules/ScheduleLink';
import { NamespaceContext } from './spreadsheet/NamespaceContext';
export function Modals() {
const location = useLocation();
@@ -83,517 +79,244 @@ export function Modals() {
const modals = modalStack
.map(({ name, options }) => {
switch (name) {
case 'keyboard-shortcuts':
case GoalTemplateModal.modalName:
return budgetId ? (
<GoalTemplateModal key={name} name={name} {...options} />
) : null;
case KeyboardShortcutModal.modalName:
// don't show the hotkey help modal when a budget is not open
return budgetId ? <KeyboardShortcutModal key={name} /> : null;
return budgetId ? (
<KeyboardShortcutModal key={name} name={name} {...options} />
) : null;
// Must be `case ImportTransactionsModal.modalName` once component is migrated to TS
case 'import-transactions':
return <ImportTransactionsModal key={name} options={options} />;
case 'add-account':
return (
<CreateAccountModal
key={name}
upgradingAccountId={options?.upgradingAccountId}
/>
<ImportTransactionsModal key={name} name={name} {...options} />
);
case 'add-local-account':
return <CreateLocalAccountModal key={name} />;
case CreateAccountModal.modalName:
return <CreateAccountModal key={name} name={name} {...options} />;
case 'close-account':
case CreateLocalAccountModal.modalName:
return (
<CloseAccountModal
key={name}
account={options.account}
balance={options.balance}
canDelete={options.canDelete}
/>
<CreateLocalAccountModal key={name} name={name} {...options} />
);
case CloseAccountModal.modalName:
return <CloseAccountModal key={name} name={name} {...options} />;
// Must be `case SelectLinkedAccountsModal.modalName` once component is migrated to TS
case 'select-linked-accounts':
return (
<SelectLinkedAccountsModal
key={name}
externalAccounts={options.accounts}
requisitionId={options.requisitionId}
syncSource={options.syncSource}
/>
<SelectLinkedAccountsModal key={name} name={name} {...options} />
);
case 'confirm-category-delete':
case ConfirmCategoryDeleteModal.modalName:
return (
<ConfirmCategoryDeleteModal
key={name}
category={options.category}
group={options.group}
onDelete={options.onDelete}
/>
<ConfirmCategoryDeleteModal key={name} name={name} {...options} />
);
case 'confirm-unlink-account':
case ConfirmUnlinkAccountModal.modalName:
return (
<ConfirmUnlinkAccountModal
key={name}
accountName={options.accountName}
onUnlink={options.onUnlink}
/>
<ConfirmUnlinkAccountModal key={name} name={name} {...options} />
);
case 'confirm-transaction-edit':
case ConfirmTransactionEditModal.modalName:
return (
<ConfirmTransactionEditModal
key={name}
onCancel={options.onCancel}
onConfirm={options.onConfirm}
confirmReason={options.confirmReason}
/>
<ConfirmTransactionEditModal key={name} name={name} {...options} />
);
case 'confirm-transaction-delete':
case ConfirmTransactionDeleteModal.modalName:
return (
<ConfirmTransactionDeleteModal
key={name}
message={options.message}
onConfirm={options.onConfirm}
name={name}
{...options}
/>
);
case 'load-backup':
return (
<LoadBackupModal
key={name}
watchUpdates
budgetId={options.budgetId}
backupDisabled={false}
/>
);
case LoadBackupModal.modalName:
return <LoadBackupModal key={name} name={name} {...options} />;
case 'manage-rules':
return <ManageRulesModal key={name} payeeId={options?.payeeId} />;
case ManageRulesModal.modalName:
return <ManageRulesModal key={name} name={name} {...options} />;
// Must be `case EditRuleModal.modalName` once component is migrated to TS
case 'edit-rule':
return (
<EditRuleModal
key={name}
defaultRule={options.rule}
onSave={options.onSave}
/>
);
return <EditRuleModal key={name} name={name} {...options} />;
// Must be `case MergeUnusedPayeesModal.modalName` once component is migrated to TS
case 'merge-unused-payees':
return <MergeUnusedPayeesModal key={name} name={name} {...options} />;
case GoCardlessInitialiseModal.modalName:
return (
<MergeUnusedPayeesModal
key={name}
payeeIds={options.payeeIds}
targetPayeeId={options.targetPayeeId}
/>
<GoCardlessInitialiseModal key={name} name={name} {...options} />
);
case 'gocardless-init':
case SimpleFinInitialiseModal.modalName:
return (
<GoCardlessInitialiseModal
key={name}
onSuccess={options.onSuccess}
/>
<SimpleFinInitialiseModal key={name} name={name} {...options} />
);
case 'simplefin-init':
case GoCardlessExternalMsgModal.modalName:
return (
<SimpleFinInitialiseModal
key={name}
onSuccess={options.onSuccess}
/>
<GoCardlessExternalMsgModal key={name} name={name} {...options} />
);
case 'gocardless-external-msg':
case CreateEncryptionKeyModal.modalName:
return (
<GoCardlessExternalMsgModal
key={name}
onMoveExternal={options.onMoveExternal}
onClose={() => {
options.onClose?.();
send('gocardless-poll-web-token-stop');
}}
onSuccess={options.onSuccess}
/>
<CreateEncryptionKeyModal key={name} name={name} {...options} />
);
case 'create-encryption-key':
return <CreateEncryptionKeyModal key={name} options={options} />;
case 'fix-encryption-key':
return <FixEncryptionKeyModal key={name} options={options} />;
case FixEncryptionKeyModal.modalName:
return <FixEncryptionKeyModal key={name} name={name} {...options} />;
// Must be `case EditFieldModal.modalName` once component is migrated to TS
case 'edit-field':
return <EditFieldModal key={name} name={name} {...options} />;
case CategoryAutocompleteModal.modalName:
return (
<EditFieldModal
key={name}
name={options.name}
onSubmit={options.onSubmit}
onClose={options.onClose}
/>
<CategoryAutocompleteModal key={name} name={name} {...options} />
);
case 'category-autocomplete':
case AccountAutocompleteModal.modalName:
return (
<CategoryAutocompleteModal
key={name}
autocompleteProps={{
value: null,
onSelect: options.onSelect,
categoryGroups: options.categoryGroups,
showHiddenCategories: options.showHiddenCategories,
}}
month={options.month}
onClose={options.onClose}
/>
<AccountAutocompleteModal key={name} name={name} {...options} />
);
case 'account-autocomplete':
return (
<AccountAutocompleteModal
key={name}
autocompleteProps={{
value: null,
onSelect: options.onSelect,
includeClosedAccounts: options.includeClosedAccounts,
}}
onClose={options.onClose}
/>
);
case 'payee-autocomplete':
return (
<PayeeAutocompleteModal
key={name}
autocompleteProps={{
value: null,
onSelect: options.onSelect,
}}
onClose={options.onClose}
/>
);
case PayeeAutocompleteModal.modalName:
return <PayeeAutocompleteModal key={name} name={name} {...options} />;
// Create a new component for this modal
case 'new-category':
return (
<SingleInputModal
key={name}
name={name}
Header={props => (
<ModalHeader
{...props}
title={<ModalTitle title="New Category" shrinkOnOverflow />}
/>
)}
inputPlaceholder="Category name"
buttonText="Add"
onValidate={options.onValidate}
onSubmit={options.onSubmit}
/>
);
return <SingleInputModal key={name} name={name} {...options} />;
// Create a new component for this modal
case 'new-category-group':
return <SingleInputModal key={name} name={name} {...options} />;
case EnvelopeBudgetSummaryModal.modalName:
return (
<SingleInputModal
key={name}
name={name}
Header={props => (
<ModalHeader
{...props}
title={
<ModalTitle title="New Category Group" shrinkOnOverflow />
}
/>
)}
inputPlaceholder="Category group name"
buttonText="Add"
onValidate={options.onValidate}
onSubmit={options.onSubmit}
/>
<EnvelopeBudgetSummaryModal key={name} name={name} {...options} />
);
case 'envelope-budget-summary':
case TrackingBudgetSummaryModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBudgetSummaryModal
key={name}
month={options.month}
onBudgetAction={options.onBudgetAction}
/>
</NamespaceContext.Provider>
);
case 'tracking-budget-summary':
return (
<TrackingBudgetSummaryModal key={name} month={options.month} />
<TrackingBudgetSummaryModal key={name} name={name} {...options} />
);
// Must be `case ScheduleDetails.modalName` once component is migrated to TS
case 'schedule-edit':
return (
<ScheduleDetails
key={name}
id={options?.id || null}
transaction={options?.transaction || null}
/>
);
return <ScheduleDetails key={name} name={name} {...options} />;
case 'schedule-link':
return (
<ScheduleLink
key={name}
transactionIds={options?.transactionIds}
getTransaction={options?.getTransaction}
accountName={options?.accountName}
onScheduleLinked={options?.onScheduleLinked}
/>
);
case ScheduleLink.modalName:
return <ScheduleLink key={name} name={name} {...options} />;
case 'schedules-discover':
return <DiscoverSchedules key={name} />;
case DiscoverSchedules.modalName:
return <DiscoverSchedules key={name} name={name} {...options} />;
// Must be `case PostsOfflineNotification.modalName` once component is migrated to TS
case 'schedule-posts-offline-notification':
return <PostsOfflineNotification key={name} />;
return <PostsOfflineNotification key={name} name={name} />;
case 'account-menu':
case AccountMenuModal.modalName:
return <AccountMenuModal key={name} name={name} {...options} />;
case CategoryMenuModal.modalName:
return <CategoryMenuModal key={name} name={name} {...options} />;
case EnvelopeBudgetMenuModal.modalName:
return (
<AccountMenuModal
key={name}
accountId={options.accountId}
onSave={options.onSave}
onEditNotes={options.onEditNotes}
onCloseAccount={options.onCloseAccount}
onReopenAccount={options.onReopenAccount}
onClose={options.onClose}
/>
<EnvelopeBudgetMenuModal key={name} name={name} {...options} />
);
case 'category-menu':
case TrackingBudgetMenuModal.modalName:
return (
<CategoryMenuModal
key={name}
categoryId={options.categoryId}
onSave={options.onSave}
onEditNotes={options.onEditNotes}
onDelete={options.onDelete}
onToggleVisibility={options.onToggleVisibility}
onClose={options.onClose}
/>
<TrackingBudgetMenuModal key={name} name={name} {...options} />
);
case 'envelope-budget-menu':
case CategoryGroupMenuModal.modalName:
return <CategoryGroupMenuModal key={name} name={name} {...options} />;
case NotesModal.modalName:
return <NotesModal key={name} name={name} {...options} />;
case EnvelopeBalanceMenuModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBudgetMenuModal
categoryId={options.categoryId}
onUpdateBudget={options.onUpdateBudget}
onCopyLastMonthAverage={options.onCopyLastMonthAverage}
onSetMonthsAverage={options.onSetMonthsAverage}
onApplyBudgetTemplate={options.onApplyBudgetTemplate}
/>
</NamespaceContext.Provider>
<EnvelopeBalanceMenuModal key={name} name={name} {...options} />
);
case 'tracking-budget-menu':
case EnvelopeToBudgetMenuModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<TrackingBudgetMenuModal
categoryId={options.categoryId}
onUpdateBudget={options.onUpdateBudget}
onCopyLastMonthAverage={options.onCopyLastMonthAverage}
onSetMonthsAverage={options.onSetMonthsAverage}
onApplyBudgetTemplate={options.onApplyBudgetTemplate}
/>
</NamespaceContext.Provider>
<EnvelopeToBudgetMenuModal key={name} name={name} {...options} />
);
case 'category-group-menu':
case HoldBufferModal.modalName:
return <HoldBufferModal key={name} name={name} {...options} />;
case TrackingBalanceMenuModal.modalName:
return (
<CategoryGroupMenuModal
key={name}
groupId={options.groupId}
onSave={options.onSave}
onAddCategory={options.onAddCategory}
onEditNotes={options.onEditNotes}
onSaveNotes={options.onSaveNotes}
onDelete={options.onDelete}
onToggleVisibility={options.onToggleVisibility}
onClose={options.onClose}
/>
<TrackingBalanceMenuModal key={name} name={name} {...options} />
);
case 'notes':
return (
<NotesModal
key={name}
id={options.id}
name={options.name}
onSave={options.onSave}
/>
);
case TransferModal.modalName:
return <TransferModal key={name} name={name} {...options} />;
case 'envelope-balance-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBalanceMenuModal
categoryId={options.categoryId}
onCarryover={options.onCarryover}
onTransfer={options.onTransfer}
onCover={options.onCover}
/>
</NamespaceContext.Provider>
);
case CoverModal.modalName:
return <CoverModal key={name} name={name} {...options} />;
case 'envelope-summary-to-budget-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeToBudgetMenuModal
onTransfer={options.onTransfer}
onCover={options.onCover}
onHoldBuffer={options.onHoldBuffer}
onResetHoldBuffer={options.onResetHoldBuffer}
/>
</NamespaceContext.Provider>
);
case 'hold-buffer':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<HoldBufferModal
month={options.month}
onSubmit={options.onSubmit}
/>
</NamespaceContext.Provider>
);
case 'tracking-balance-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<TrackingBalanceMenuModal
categoryId={options.categoryId}
onCarryover={options.onCarryover}
/>
</NamespaceContext.Provider>
);
case 'transfer':
return (
<TransferModal
key={name}
title={options.title}
categoryId={options.categoryId}
month={options.month}
amount={options.amount}
onSubmit={options.onSubmit}
showToBeBudgeted={options.showToBeBudgeted}
/>
);
case 'cover':
return (
<CoverModal
key={name}
title={options.title}
categoryId={options.categoryId}
month={options.month}
showToBeBudgeted={options.showToBeBudgeted}
onSubmit={options.onSubmit}
/>
);
case 'scheduled-transaction-menu':
case ScheduledTransactionMenuModal.modalName:
return (
<ScheduledTransactionMenuModal
key={name}
transactionId={options.transactionId}
onPost={options.onPost}
onSkip={options.onSkip}
name={name}
{...options}
/>
);
case 'budget-page-menu':
case BudgetPageMenuModal.modalName:
return <BudgetPageMenuModal key={name} name={name} {...options} />;
case EnvelopeBudgetMonthMenuModal.modalName:
return (
<BudgetPageMenuModal
key={name}
onAddCategoryGroup={options.onAddCategoryGroup}
onToggleHiddenCategories={options.onToggleHiddenCategories}
onSwitchBudgetFile={options.onSwitchBudgetFile}
/>
<EnvelopeBudgetMonthMenuModal key={name} name={name} {...options} />
);
case 'envelope-budget-month-menu':
case TrackingBudgetMonthMenuModal.modalName:
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<EnvelopeBudgetMonthMenuModal
month={options.month}
onBudgetAction={options.onBudgetAction}
onEditNotes={options.onEditNotes}
/>
</NamespaceContext.Provider>
<TrackingBudgetMonthMenuModal key={name} name={name} {...options} />
);
case 'tracking-budget-month-menu':
return (
<NamespaceContext.Provider
key={name}
value={monthUtils.sheetForMonth(options.month)}
>
<TrackingBudgetMonthMenuModal
month={options.month}
onBudgetAction={options.onBudgetAction}
onEditNotes={options.onEditNotes}
/>
</NamespaceContext.Provider>
);
case 'budget-list':
return <BudgetListModal key={name} />;
case 'delete-budget':
return <DeleteFileModal key={name} file={options.file} />;
case 'import':
return <ImportModal key={name} />;
case 'files-settings':
return <FilesSettingsModal key={name} />;
case 'confirm-change-document-dir':
case BudgetListModal.modalName:
return <BudgetListModal key={name} name={name} {...options} />;
case DeleteFileModal.modalName:
return <DeleteFileModal key={name} name={name} {...options} />;
case ImportModal.modalName:
return <ImportModal key={name} name={name} />;
case FilesSettingsModal.modalName:
return <FilesSettingsModal key={name} name={name} {...options} />;
case ConfirmChangeDocumentDirModal.modalName:
return (
<ConfirmChangeDocumentDirModal
key={name}
currentBudgetDirectory={options.currentBudgetDirectory}
newDirectory={options.newDirectory}
name={name}
{...options}
/>
);
case 'import-ynab4':
return <ImportYNAB4Modal key={name} />;
case 'import-ynab5':
return <ImportYNAB5Modal key={name} />;
case 'import-actual':
return <ImportActualModal key={name} />;
case 'out-of-sync-migrations':
return <OutOfSyncMigrationsModal key={name} />;
case ImportYNAB4Modal.modalName:
return <ImportYNAB4Modal key={name} name={name} {...options} />;
case ImportYNAB5Modal.modalName:
return <ImportYNAB5Modal key={name} name={name} {...options} />;
case ImportActualModal.modalName:
return <ImportActualModal key={name} name={name} {...options} />;
case OutOfSyncMigrationsModal.modalName:
return (
<OutOfSyncMigrationsModal key={name} name={name} {...options} />
);
default:
throw new Error('Unknown modal');

View File

@@ -1,12 +1,12 @@
// @ts-strict-ignore
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, type CSSProperties } from 'react';
import ReactMarkdown from 'react-markdown';
import { css } from 'glamor';
import { css } from '@emotion/css';
import remarkGfm from 'remark-gfm';
import { useResponsive } from '../ResponsiveProvider';
import { type CSSProperties, theme } from '../style';
import { theme } from '../style';
import { remarkBreaks, sequentialNewlinesPlugin } from '../util/markdown';
import { Text } from './common/Text';
@@ -110,7 +110,7 @@ export function Notes({
return editable ? (
<textarea
ref={textAreaRef}
className={`${css({
className={css({
border: '1px solid ' + theme.buttonNormalBorder,
padding: 7,
...(!isNarrowWidth && { minWidth: 350, minHeight: 120 }),
@@ -118,14 +118,14 @@ export function Notes({
backgroundColor: theme.tableBackground,
color: theme.tableText,
...getStyle?.(editable),
})}`}
})}
value={notes || ''}
onChange={e => onChange?.(e.target.value)}
onBlur={e => onBlur?.(e.target.value)}
placeholder="Notes (markdown supported)"
/>
) : (
<Text {...markdownStyles} style={{ ...getStyle?.(editable) }}>
<Text className={css([markdownStyles, getStyle?.(editable)])}>
<ReactMarkdown remarkPlugins={remarkPlugins} linkTarget="_blank">
{notes}
</ReactMarkdown>

View File

@@ -1,10 +1,16 @@
import React, { useEffect, useRef, useState, type ComponentProps } from 'react';
import React, {
useEffect,
useRef,
useState,
type ComponentProps,
type CSSProperties,
} from 'react';
import { send } from 'loot-core/src/platform/client/fetch';
import { useNotes } from '../hooks/useNotes';
import { SvgCustomNotesPaper } from '../icons/v2';
import { type CSSProperties, theme } from '../style';
import { theme } from '../style';
import { Button } from './common/Button2';
import { Popover } from './common/Popover';

View File

@@ -4,19 +4,19 @@ import React, {
useEffect,
useMemo,
type SetStateAction,
type CSSProperties,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { css } from 'glamor';
import { removeNotification } from 'loot-core/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import type { NotificationWithId } from 'loot-core/src/client/state-types/notifications';
import { css } from '@emotion/css';
import { AnimatedLoading } from '../icons/AnimatedLoading';
import { SvgDelete } from '../icons/v0';
import { useResponsive } from '../ResponsiveProvider';
import { styles, theme, type CSSProperties } from '../style';
import { type State } from '../state';
import { removeNotification } from '../state/actions';
import type { NotificationWithId } from '../state/notifications';
import { styles, theme } from '../style';
import { Button, ButtonWithLoading } from './common/Button2';
import { Link } from './common/Link';
@@ -202,29 +202,27 @@ function Notification({
onRemove();
setLoading(false);
}}
className={String(
css({
backgroundColor: 'transparent',
border: `1px solid ${
positive
? theme.noticeBorder
: error
? theme.errorBorder
: theme.warningBorder
}`,
color: 'currentColor',
...styles.mediumText,
flexShrink: 0,
'&[data-hovered], &[data-pressed]': {
backgroundColor: positive
? theme.noticeBackground
: error
? theme.errorBackground
: theme.warningBackground,
},
...narrowStyle,
}),
)}
className={css({
backgroundColor: 'transparent',
border: `1px solid ${
positive
? theme.noticeBorder
: error
? theme.errorBorder
: theme.warningBorder
}`,
color: 'currentColor',
...styles.mediumText,
flexShrink: 0,
'&[data-hovered], &[data-pressed]': {
backgroundColor: positive
? theme.noticeBackground
: error
? theme.errorBackground
: theme.warningBackground,
},
...narrowStyle,
})}
>
{button.title}
</ButtonWithLoading>

View File

@@ -1,7 +1,7 @@
import React, { type ReactNode } from 'react';
import React, { type ReactNode, type CSSProperties } from 'react';
import { useResponsive } from '../ResponsiveProvider';
import { theme, styles, type CSSProperties } from '../style';
import { theme, styles } from '../style';
import { Text } from './common/Text';
import { View } from './common/View';

View File

@@ -5,7 +5,7 @@ import React, {
type ReactNode,
} from 'react';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { usePrivacyMode } from '../hooks/usePrivacyMode';
import { useResponsive } from '../ResponsiveProvider';
@@ -73,7 +73,7 @@ function PrivacyOverlay({ children, ...props }) {
return (
<View
className={`${css(
className={css(
[
{
display: 'inline-flex',
@@ -96,23 +96,23 @@ function PrivacyOverlay({ children, ...props }) {
},
],
style,
)}`}
)}
{...restProps}
>
<div
className={`${css([
className={css([
{
display: 'flex',
flexGrow: 1,
},
])}`}
])}
>
{children}
</div>
<div
aria-hidden="true"
className={`${css({
className={css({
flexDirection: 'column',
fontFamily: 'Redacted Script',
height: '100%',
@@ -121,7 +121,7 @@ function PrivacyOverlay({ children, ...props }) {
pointerEvents: 'none',
position: 'absolute',
width: '100%',
})}`}
})}
>
{children}
</div>

View File

@@ -1,10 +1,10 @@
import React, { useRef, useState } from 'react';
import React, { useRef, useState, type CSSProperties } from 'react';
import type { Theme } from 'loot-core/src/types/prefs';
import { SvgMoonStars, SvgSun, SvgSystem } from '../icons/v2';
import { useResponsive } from '../ResponsiveProvider';
import { type CSSProperties, themeOptions, useTheme } from '../style';
import { themeOptions, useTheme } from '../style';
import { Button } from './common/Button2';
import { Menu } from './common/Menu';

View File

@@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, type CSSProperties } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Routes, Route, useLocation } from 'react-router-dom';
import { css } from 'glamor';
import { css } from '@emotion/css';
import * as Platform from 'loot-core/src/client/platform';
import * as queries from 'loot-core/src/client/queries';
@@ -22,7 +22,7 @@ import {
SvgViewShow,
} from '../icons/v2';
import { useResponsive } from '../ResponsiveProvider';
import { theme, type CSSProperties, styles } from '../style';
import { theme, styles } from '../style';
import { AccountSyncCheck } from './accounts/AccountSyncCheck';
import { AnimatedRefresh } from './AnimatedRefresh';
@@ -204,23 +204,21 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
<Button
variant="bare"
aria-label="Sync"
className={String(
css({
...(isMobile
? {
...style,
WebkitAppRegion: 'none',
...mobileIconStyle,
}
: {
...style,
WebkitAppRegion: 'none',
color: desktopColor,
}),
'&[data-hovered]': hoveredStyle,
'&[data-pressed]': activeStyle,
}),
)}
className={css({
...(isMobile
? {
...style,
WebkitAppRegion: 'none',
...mobileIconStyle,
}
: {
...style,
WebkitAppRegion: 'none',
color: desktopColor,
}),
'&[data-hovered]': hoveredStyle,
'&[data-pressed]': activeStyle,
})}
onPress={sync}
>
{isMobile ? (

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { type State } from 'loot-core/src/client/state-types';
import { useActions } from '../hooks/useActions';
import { SvgClose } from '../icons/v1';
import { type State } from '../state';
import { theme } from '../style';
import { Button } from './common/Button2';

View File

@@ -15,7 +15,6 @@ import { t } from 'i18next';
import { v4 as uuidv4 } from 'uuid';
import { validForTransfer } from 'loot-core/client/transfer';
import { type UndoState } from 'loot-core/server/undo';
import { useFilters } from 'loot-core/src/client/data-hooks/filters';
import {
SchedulesProvider,
@@ -45,6 +44,7 @@ import {
type TransactionEntity,
type TransactionFilterEntity,
} from 'loot-core/src/types/models';
import { type UndoState } from 'loot-core/types/server-events';
import { useAccounts } from '../../hooks/useAccounts';
import { useActions } from '../../hooks/useActions';
@@ -68,6 +68,7 @@ import { styles, theme } from '../../style';
import { Button } from '../common/Button2';
import { Text } from '../common/Text';
import { View } from '../common/View';
import { type SavedFilter } from '../filters/SavedFilterMenuButton';
import { TransactionList } from '../transactions/TransactionList';
import { validateAccountName } from '../util/accountValidation';
@@ -283,7 +284,7 @@ type AccountInternalProps = {
type AccountInternalState = {
search: string;
filterConditions: ConditionEntity[];
filterId: Record<string, unknown>;
filterId?: SavedFilter;
filterConditionsOp: 'and' | 'or';
loading: boolean;
workingHard: boolean;
@@ -309,6 +310,14 @@ type AccountInternalState = {
filteredAmount: null | number;
};
export type TableRef = MutableRefObject<{
edit: (updatedId: string | null, op?: string, someBool?: boolean) => void;
setRowAnimation: (animation: boolean) => void;
scrollTo: (focusId: string) => void;
scrollToTop: () => void;
getScrolledItem: () => string;
} | null>;
class AccountInternal extends PureComponent<
AccountInternalProps,
AccountInternalState
@@ -316,12 +325,7 @@ class AccountInternal extends PureComponent<
paged: ReturnType<typeof pagedQuery> | null;
rootQuery: Query;
currentQuery: Query;
table: MutableRefObject<{
edit: (updatedId: string | null, op?: string, someBool?: boolean) => void;
setRowAnimation: (animation: boolean) => void;
scrollTo: (focusId: string) => void;
scrollToTop: () => void;
} | null>;
table: TableRef;
unlisten?: () => void;
dispatchSelected?: (action: Actions) => void;
@@ -333,7 +337,7 @@ class AccountInternal extends PureComponent<
this.state = {
search: '',
filterConditions: props.filterConditions || [],
filterId: {},
filterId: undefined,
filterConditionsOp: 'and',
loading: true,
workingHard: false,
@@ -859,7 +863,7 @@ class AccountInternal extends PureComponent<
return {
name: `balance-query-${id}`,
query: this.makeRootQuery().calculate({ $sum: '$amount' }),
};
} as const;
}
getFilteredAmount = async () => {
@@ -1307,13 +1311,10 @@ class AccountInternal extends PureComponent<
}
};
onReloadSavedFilter = (
savedFilter: TransactionFilterEntity & { status?: string },
item: string,
) => {
onReloadSavedFilter = (savedFilter: SavedFilter, item: string) => {
if (item === 'reload') {
const [savedFilter] = this.props.savedFilters.filter(
f => f.id === this.state.filterId.id,
f => f.id === this.state.filterId?.id,
);
this.setState({ filterConditionsOp: savedFilter.conditionsOp ?? 'and' });
this.applyFilters([...savedFilter.conditions]);
@@ -1330,7 +1331,7 @@ class AccountInternal extends PureComponent<
onClearFilters = () => {
this.setState({ filterConditionsOp: 'and' });
this.setState({ filterId: {} });
this.setState({ filterId: undefined });
this.applyFilters([]);
if (this.state.search !== '') {
this.onSearch(this.state.search);
@@ -1360,7 +1361,7 @@ class AccountInternal extends PureComponent<
onDeleteFilter = (condition: RuleConditionEntity) => {
this.applyFilters(this.state.filterConditions.filter(c => c !== condition));
if (this.state.filterConditions.length === 1) {
this.setState({ filterId: {} });
this.setState({ filterId: undefined });
this.setState({ filterConditionsOp: 'and' });
} else {
this.setState({
@@ -1715,9 +1716,9 @@ class AccountInternal extends PureComponent<
isSorted={this.state.sort !== null}
reconcileAmount={reconcileAmount}
search={this.state.search}
// @ts-expect-error fix me
filterConditions={this.state.filterConditions}
filterConditionsOp={this.state.filterConditionsOp}
pushModal={this.props.pushModal}
onSearch={this.onSearch}
onShowTransactions={this.onShowTransactions}
onMenuSelect={this.onMenuSelect}
@@ -1856,9 +1857,9 @@ export function Account() {
const location = useLocation();
const { grouped: categoryGroups } = useCategories();
const newTransactions = useSelector(state => state.queries.newTransactions);
const newTransactions = useSelector(state => state.account.newTransactions);
const matchedTransactions = useSelector(
state => state.queries.matchedTransactions,
state => state.account.matchedTransactions,
);
const accounts = useAccounts();
const payees = usePayees();

View File

@@ -5,11 +5,10 @@ import { useParams } from 'react-router-dom';
import { t } from 'i18next';
import { unlinkAccount } from 'loot-core/client/actions';
import { authorizeBank } from '../../gocardless';
import { useAccounts } from '../../hooks/useAccounts';
import { SvgExclamationOutline } from '../../icons/v1';
import { unlinkAccount } from '../../state/actions';
import { theme } from '../../style';
import { Button } from '../common/Button2';
import { Link } from '../common/Link';

View File

@@ -1,7 +1,20 @@
import React, { useState, useRef, Fragment } from 'react';
import React, {
useState,
useRef,
Fragment,
type ReactNode,
type ComponentProps,
} from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Trans, useTranslation } from 'react-i18next';
import {
type AccountEntity,
type RuleConditionEntity,
type TransactionEntity,
type TransactionFilterEntity,
} from 'loot-core/types/models';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useSplitsExpanded } from '../../hooks/useSplitsExpanded';
import { useSyncServerStatus } from '../../hooks/useSyncServerStatus';
@@ -11,6 +24,7 @@ import {
SvgArrowsExpand3,
SvgArrowsShrink3,
SvgDownloadThickBottom,
SvgLockClosed,
SvgPencil1,
} from '../../icons/v2';
import { theme, styles } from '../../style';
@@ -26,12 +40,89 @@ import { Stack } from '../common/Stack';
import { View } from '../common/View';
import { FilterButton } from '../filters/FiltersMenu';
import { FiltersStack } from '../filters/FiltersStack';
import { type SavedFilter } from '../filters/SavedFilterMenuButton';
import { NotesButton } from '../NotesButton';
import { SelectedTransactionsButton } from '../transactions/SelectedTransactionsButton';
import { type TableRef } from './Account';
import { Balances } from './Balance';
import { ReconcilingMessage, ReconcileMenu } from './Reconcile';
type AccountHeaderProps = {
tableRef: TableRef;
editingName: boolean;
isNameEditable: boolean;
workingHard: boolean;
accountName: string;
account: AccountEntity;
filterId?: SavedFilter;
savedFilters: TransactionFilterEntity[];
accountsSyncing: string[];
failedAccounts: AccountSyncSidebarProps['failedAccounts'];
accounts: AccountEntity[];
transactions: TransactionEntity[];
showBalances: boolean;
showExtraBalances: boolean;
showCleared: boolean;
showReconciled: boolean;
showEmptyMessage: boolean;
balanceQuery: ComponentProps<typeof ReconcilingMessage>['balanceQuery'];
reconcileAmount: number;
canCalculateBalance: () => boolean;
isFiltered: boolean;
filteredAmount: number;
isSorted: boolean;
search: string;
filterConditions: RuleConditionEntity[];
filterConditionsOp: 'and' | 'or';
onSearch: (newSearch: string) => void;
onAddTransaction: () => void;
onShowTransactions: ComponentProps<
typeof SelectedTransactionsButton
>['onShow'];
onDoneReconciling: ComponentProps<typeof ReconcilingMessage>['onDone'];
onCreateReconciliationTransaction: ComponentProps<
typeof ReconcilingMessage
>['onCreateTransaction'];
onToggleExtraBalances: ComponentProps<
typeof Balances
>['onToggleExtraBalances'];
onSaveName: AccountNameFieldProps['onSaveName'];
saveNameError: AccountNameFieldProps['saveNameError'];
onExposeName: (isExposed: boolean) => void;
onSync: () => void;
onImport: () => void;
onMenuSelect: AccountMenuProps['onMenuSelect'];
onReconcile: ComponentProps<typeof ReconcileMenu>['onReconcile'];
onBatchEdit: ComponentProps<typeof SelectedTransactionsButton>['onEdit'];
onBatchDelete: ComponentProps<typeof SelectedTransactionsButton>['onDelete'];
onBatchDuplicate: ComponentProps<
typeof SelectedTransactionsButton
>['onDuplicate'];
onBatchLinkSchedule: ComponentProps<
typeof SelectedTransactionsButton
>['onLinkSchedule'];
onBatchUnlinkSchedule: ComponentProps<
typeof SelectedTransactionsButton
>['onUnlinkSchedule'];
onApplyFilter: (filter: RuleConditionEntity) => void;
} & Pick<
ComponentProps<typeof SelectedTransactionsButton>,
| 'onCreateRule'
| 'onScheduleAction'
| 'onSetTransfer'
| 'onMakeAsSplitTransaction'
| 'onMakeAsNonSplitTransactions'
> &
Pick<
ComponentProps<typeof FiltersStack>,
| 'onUpdateFilter'
| 'onDeleteFilter'
| 'onConditionsOpChange'
| 'onClearFilters'
| 'onReloadSavedFilter'
>;
export function AccountHeader({
tableRef,
editingName,
@@ -59,7 +150,6 @@ export function AccountHeader({
search,
filterConditions,
filterConditionsOp,
pushModal,
onSearch,
onAddTransaction,
onShowTransactions,
@@ -89,18 +179,20 @@ export function AccountHeader({
onSetTransfer,
onMakeAsSplitTransaction,
onMakeAsNonSplitTransactions,
}) {
}: AccountHeaderProps) {
const { t } = useTranslation();
const [menuOpen, setMenuOpen] = useState(false);
const searchInput = useRef(null);
const [reconcileOpen, setReconcileOpen] = useState(false);
const searchInput = useRef<HTMLInputElement>(null);
const triggerRef = useRef(null);
const reconcileRef = useRef(null);
const splitsExpanded = useSplitsExpanded();
const syncServerStatus = useSyncServerStatus();
const isUsingServer = syncServerStatus !== 'no-server';
const isServerOffline = syncServerStatus === 'offline';
const [_, setExpandSplitsPref] = useLocalPref('expand-splits');
let canSync = account && account.account_id && isUsingServer;
let canSync = !!(account?.account_id && isUsingServer);
if (!account) {
// All accounts - check for any syncable account
canSync = !!accounts.find(account => !!account.account_id) && isUsingServer;
@@ -223,7 +315,6 @@ export function AccountHeader({
? accountsSyncing.includes(account.id)
: accountsSyncing.length > 0
}
style={{ marginRight: 4 }}
/>{' '}
{isServerOffline ? t('Bank Sync Offline') : t('Bank Sync')}
</Button>
@@ -247,7 +338,8 @@ export function AccountHeader({
</Button>
)}
<View style={{ flexShrink: 0 }}>
<FilterButton onApply={onApplyFilter} type="accounts" />
{/* @ts-expect-error fix me */}
<FilterButton onApply={onApplyFilter} />
</View>
<View style={{ flex: 1 }} />
<Search
@@ -262,7 +354,6 @@ export function AccountHeader({
</View>
) : (
<SelectedTransactionsButton
account={account}
getTransaction={id => transactions.find(t => t.id === id)}
onShow={onShowTransactions}
onDuplicate={onBatchDuplicate}
@@ -273,12 +364,43 @@ export function AccountHeader({
onCreateRule={onCreateRule}
onSetTransfer={onSetTransfer}
onScheduleAction={onScheduleAction}
pushModal={pushModal}
showMakeTransfer={showMakeTransfer}
onMakeAsSplitTransaction={onMakeAsSplitTransaction}
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions}
/>
)}
<View>
{account && (
<>
<Button
ref={reconcileRef}
variant="bare"
aria-label={t('Reconcile')}
style={{ padding: 6, marginLeft: 10 }}
onPress={() => {
setReconcileOpen(true);
}}
>
<View title={t('Reconcile')}>
<SvgLockClosed width={14} height={14} />
</View>
</Button>
<Popover
placement="bottom"
triggerRef={reconcileRef}
style={{ width: 275 }}
isOpen={reconcileOpen}
onOpenChange={() => setReconcileOpen(false)}
>
<ReconcileMenu
account={account}
onClose={() => setReconcileOpen(false)}
onReconcile={onReconcile}
/>
</Popover>
</>
)}
</View>
<Button
variant="bare"
aria-label={
@@ -287,7 +409,7 @@ export function AccountHeader({
: t('Expand split transactions')
}
isDisabled={search !== '' || filterConditions.length > 0}
style={{ padding: 6, marginLeft: 10 }}
style={{ padding: 6 }}
onPress={onToggleSplits}
>
<View
@@ -330,8 +452,6 @@ export function AccountHeader({
setMenuOpen(false);
onMenuSelect(item);
}}
onReconcile={onReconcile}
onClose={() => setMenuOpen(false)}
/>
</Popover>
</View>
@@ -354,10 +474,14 @@ export function AccountHeader({
onMenuSelect(item);
}}
items={[
isSorted && {
name: 'remove-sorting',
text: t('Remove all sorting'),
},
...(isSorted
? [
{
name: 'remove-sorting',
text: t('Remove all sorting'),
} as const,
]
: []),
{ name: 'export', text: t('Export') },
]}
/>
@@ -392,7 +516,23 @@ export function AccountHeader({
);
}
function AccountSyncSidebar({ account, failedAccounts, accountsSyncing }) {
type AccountSyncSidebarProps = {
account: AccountEntity;
failedAccounts: Map<
string,
{
type: string;
code: string;
}
>;
accountsSyncing: string[];
};
function AccountSyncSidebar({
account,
failedAccounts,
accountsSyncing,
}: AccountSyncSidebarProps) {
return (
<View
style={{
@@ -410,6 +550,16 @@ function AccountSyncSidebar({ account, failedAccounts, accountsSyncing }) {
);
}
type AccountNameFieldProps = {
account: AccountEntity;
accountName: string;
isNameEditable: boolean;
editingName: boolean;
saveNameError?: ReactNode;
onSaveName: (newName: string) => void;
onExposeName: (isExposed: boolean) => void;
};
function AccountNameField({
account,
accountName,
@@ -418,7 +568,7 @@ function AccountNameField({
saveNameError,
onSaveName,
onExposeName,
}) {
}: AccountNameFieldProps) {
const { t } = useTranslation();
if (editingName) {
@@ -427,7 +577,7 @@ function AccountNameField({
<InitialFocus>
<Input
defaultValue={accountName}
onEnter={e => onSaveName(e.target.value)}
onEnter={e => onSaveName(e.currentTarget.value)}
onBlur={e => onSaveName(e.target.value)}
onEscape={() => onExposeName(false)}
style={{
@@ -515,6 +665,28 @@ function AccountNameField({
}
}
type AccountMenuProps = {
account: AccountEntity;
canSync: boolean;
showBalances: boolean;
canShowBalances: boolean;
showCleared: boolean;
showReconciled: boolean;
isSorted: boolean;
onMenuSelect: (
item:
| 'link'
| 'unlink'
| 'close'
| 'reopen'
| 'export'
| 'toggle-balance'
| 'remove-sorting'
| 'toggle-cleared'
| 'toggle-reconciled',
) => void;
};
function AccountMenu({
account,
canSync,
@@ -522,41 +694,36 @@ function AccountMenu({
canShowBalances,
showCleared,
showReconciled,
onClose,
isSorted,
onReconcile,
onMenuSelect,
}) {
}: AccountMenuProps) {
const { t } = useTranslation();
const [tooltip, setTooltip] = useState('default');
const syncServerStatus = useSyncServerStatus();
return tooltip === 'reconcile' ? (
<ReconcileMenu
account={account}
onClose={onClose}
onReconcile={onReconcile}
/>
) : (
return (
<Menu
onMenuSelect={item => {
if (item === 'reconcile') {
setTooltip('reconcile');
} else {
onMenuSelect(item);
}
onMenuSelect(item);
}}
items={[
isSorted && {
name: 'remove-sorting',
text: t('Remove all sorting'),
},
canShowBalances && {
name: 'toggle-balance',
text: showBalances
? t('Hide running balance')
: t('Show running balance'),
},
...(isSorted
? [
{
name: 'remove-sorting',
text: t('Remove all sorting'),
} as const,
]
: []),
...(canShowBalances
? [
{
name: 'toggle-balance',
text: showBalances
? t('Hide running balance')
: t('Show running balance'),
} as const,
]
: []),
{
name: 'toggle-cleared',
text: showCleared
@@ -570,22 +737,28 @@ function AccountMenu({
: t('Show reconciled transactions'),
},
{ name: 'export', text: t('Export') },
{ name: 'reconcile', text: t('Reconcile') },
account &&
!account.closed &&
(canSync
? {
name: 'unlink',
text: t('Unlink account'),
}
: syncServerStatus === 'online' && {
name: 'link',
text: t('Link account'),
}),
account.closed
? { name: 'reopen', text: t('Reopen account') }
: { name: 'close', text: t('Close account') },
].filter(x => x)}
...(account && !account.closed
? canSync
? [
{
name: 'unlink',
text: t('Unlink account'),
} as const,
]
: syncServerStatus === 'online'
? [
{
name: 'link',
text: t('Link account'),
} as const,
]
: []
: []),
...(account.closed
? [{ name: 'reopen', text: t('Reopen account') } as const]
: [{ name: 'close', text: t('Close account') } as const]),
]}
/>
);
}

View File

@@ -1,8 +1,12 @@
// @ts-strict-ignore
import React, { type ComponentType, type ReactNode } from 'react';
import React, {
type ComponentType,
type ReactNode,
type CSSProperties,
} from 'react';
import { SvgExclamationOutline, SvgInformationOutline } from '../icons/v1';
import { styles, theme, type CSSProperties } from '../style';
import { styles, theme } from '../style';
import { Text } from './common/Text';
import { View } from './common/View';

View File

@@ -4,16 +4,17 @@ import React, {
type ComponentProps,
type ComponentPropsWithoutRef,
type ReactElement,
type CSSProperties,
} from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'glamor';
import { css, cx } from '@emotion/css';
import { type AccountEntity } from 'loot-core/src/types/models';
import { useAccounts } from '../../hooks/useAccounts';
import { useResponsive } from '../../ResponsiveProvider';
import { type CSSProperties, theme, styles } from '../../style';
import { theme, styles } from '../../style';
import { TextOneLine } from '../common/TextOneLine';
import { View } from '../common/View';
@@ -208,8 +209,9 @@ function AccountItem({
// * https://github.com/WebKit/WebKit/blob/58956cf59ba01267644b5e8fe766efa7aa6f0c5c/Source/WebCore/page/ios/ContentChangeObserver.cpp
// * https://github.com/WebKit/WebKit/blob/58956cf59ba01267644b5e8fe766efa7aa6f0c5c/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L783
role="button"
className={`${className} ${css([
{
className={cx(
className,
css({
backgroundColor: highlighted
? theme.menuAutoCompleteBackgroundHover
: 'transparent',
@@ -220,8 +222,8 @@ function AccountItem({
paddingLeft: 20,
borderRadius: embedded ? 4 : 0,
...narrowStyle,
},
])}`}
}),
)}
data-testid={`${item.name}-account-item`}
data-highlighted={highlighted || undefined}
{...props}

View File

@@ -11,8 +11,8 @@ import React, {
type ChangeEvent,
} from 'react';
import { css, cx } from '@emotion/css';
import Downshift, { type StateChangeTypes } from 'downshift';
import { css } from 'glamor';
import { getNormalisedString } from 'loot-core/src/shared/normalisation';
@@ -178,14 +178,14 @@ function defaultRenderItems<T extends Item>(
// * https://github.com/WebKit/WebKit/blob/58956cf59ba01267644b5e8fe766efa7aa6f0c5c/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L783
role="button"
key={name}
className={`${css({
className={css({
padding: 5,
cursor: 'default',
backgroundColor:
highlightedIndex === index
? theme.menuAutoCompleteBackgroundHover
: null,
})}`}
: undefined,
})}
>
{name}
</div>
@@ -443,7 +443,10 @@ function SingleAutocomplete<T extends Item>({
// Super annoying but it works best to return a div so we
// can't use a View here, but we can fake it be using the
// className
<div className={`view ${css({ display: 'flex' })}`} {...containerProps}>
<div
className={cx('view', css({ display: 'flex' }))}
{...containerProps}
>
<View ref={triggerRef} style={{ flexShrink: 0 }}>
{renderInput(
getInputProps({

View File

@@ -7,11 +7,12 @@ import React, {
type ComponentType,
type ComponentPropsWithoutRef,
type ReactElement,
type CSSProperties,
useCallback,
} from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { css } from 'glamor';
import { css, cx } from '@emotion/css';
import { trackingBudget, envelopeBudget } from 'loot-core/client/queries';
import { integerToCurrency } from 'loot-core/shared/util';
@@ -25,7 +26,7 @@ import { useCategories } from '../../hooks/useCategories';
import { useSyncedPref } from '../../hooks/useSyncedPref';
import { SvgSplit } from '../../icons/v0';
import { useResponsive } from '../../ResponsiveProvider';
import { type CSSProperties, theme, styles } from '../../style';
import { theme, styles } from '../../style';
import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponents';
import { makeAmountFullStyle } from '../budget/util';
import { Text } from '../common/Text';
@@ -398,8 +399,9 @@ function CategoryItem({
style={style}
// See comment above.
role="button"
className={`${className} ${css([
{
className={cx(
className,
css({
backgroundColor: highlighted
? theme.menuAutoCompleteBackgroundHover
: 'transparent',
@@ -410,8 +412,8 @@ function CategoryItem({
paddingLeft: 20,
borderRadius: embedded ? 4 : 0,
...narrowStyle,
},
])}`}
}),
)}
data-testid={`${item.name}-category-item`}
data-highlighted={highlighted || undefined}
{...props}

View File

@@ -1,8 +1,7 @@
import React from 'react';
import React, { type CSSProperties } from 'react';
import { useResponsive } from '../../ResponsiveProvider';
import { styles, theme } from '../../style';
import { type CSSProperties } from '../../style/types';
type ItemHeaderProps = {
title: string;

View File

@@ -9,14 +9,13 @@ import React, {
type SVGProps,
type ComponentPropsWithoutRef,
type ReactElement,
type CSSProperties,
} from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { css } from 'glamor';
import { css, cx } from '@emotion/css';
import { createPayee } from 'loot-core/src/client/actions/queries';
import { getActivePayees } from 'loot-core/src/client/reducers/queries';
import { getNormalisedString } from 'loot-core/src/shared/normalisation';
import {
type AccountEntity,
@@ -27,7 +26,9 @@ import { useAccounts } from '../../hooks/useAccounts';
import { useCommonPayees, usePayees } from '../../hooks/usePayees';
import { SvgAdd, SvgBookmark } from '../../icons/v1';
import { useResponsive } from '../../ResponsiveProvider';
import { type CSSProperties, theme, styles } from '../../style';
import { createPayee } from '../../state/actions';
import { getActivePayees } from '../../state/queries';
import { theme, styles } from '../../style';
import { Button } from '../common/Button';
import { TextOneLine } from '../common/TextOneLine';
import { View } from '../common/View';
@@ -605,8 +606,9 @@ function PayeeItem({
// * https://github.com/WebKit/WebKit/blob/58956cf59ba01267644b5e8fe766efa7aa6f0c5c/Source/WebCore/page/ios/ContentChangeObserver.cpp
// * https://github.com/WebKit/WebKit/blob/58956cf59ba01267644b5e8fe766efa7aa6f0c5c/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L783
role="button"
className={`${className} ${css([
{
className={cx(
className,
css({
backgroundColor: highlighted
? theme.menuAutoCompleteBackgroundHover
: 'transparent',
@@ -617,8 +619,8 @@ function PayeeItem({
padding: 4,
paddingLeft: paddingLeftOverFromIcon,
...narrowStyle,
},
])}`}
}),
)}
data-testid={`${item.name}-payee-item`}
data-highlighted={highlighted || undefined}
{...props}

View File

@@ -2,16 +2,17 @@
import React, {
type ComponentType,
type ComponentPropsWithoutRef,
type CSSProperties,
useCallback,
} from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { SvgArrowThinRight } from '../../icons/v1';
import { useResponsive } from '../../ResponsiveProvider';
import { type CSSProperties, theme, styles } from '../../style';
import { theme, styles } from '../../style';
import { Tooltip } from '../common/Tooltip';
import { View } from '../common/View';
import { type Binding } from '../spreadsheet';
@@ -124,18 +125,16 @@ export function BalanceWithCarryover({
const getDefaultClassName = useCallback(
(balanceValue: number) =>
String(
css({
...getBalanceAmountStyle(balanceValue),
overflow: 'hidden',
textOverflow: 'ellipsis',
textAlign: 'right',
...(!isDisabled && {
cursor: 'pointer',
}),
':hover': { textDecoration: 'underline' },
css({
...getBalanceAmountStyle(balanceValue),
overflow: 'hidden',
textOverflow: 'ellipsis',
textAlign: 'right',
...(!isDisabled && {
cursor: 'pointer',
}),
),
':hover': { textDecoration: 'underline' },
}),
[getBalanceAmountStyle, isDisabled],
);

View File

@@ -7,7 +7,7 @@ import React, {
} from 'react';
import { useSpring, animated } from 'react-spring';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { addMonths, subMonths } from 'loot-core/src/shared/months';
@@ -67,13 +67,13 @@ export function BudgetSummaries({ SummaryComponent }: BudgetSummariesProps) {
return (
<div
className={`${css([
className={css([
{ flex: 1, overflow: 'hidden' },
months.length === 1 && {
marginLeft: -4,
marginRight: -4,
},
])}`}
])}
ref={containerRef}
>
<animated.div

View File

@@ -1,7 +1,13 @@
import React, { type ComponentProps, memo, useRef, useState } from 'react';
import React, {
type ComponentProps,
type CSSProperties,
memo,
useRef,
useState,
} from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { envelopeBudget } from 'loot-core/src/client/queries';
import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
@@ -10,7 +16,7 @@ import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util';
import { useUndo } from '../../../hooks/useUndo';
import { SvgCheveronDown } from '../../../icons/v1';
import { styles, theme, type CSSProperties } from '../../../style';
import { styles, theme } from '../../../style';
import { Button } from '../../common/Button2';
import { Popover } from '../../common/Popover';
import { Text } from '../../common/Text';
@@ -366,13 +372,11 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({
{props => (
<CellValueText
{...props}
className={String(
css({
cursor: 'pointer',
':hover': { textDecoration: 'underline' },
...makeAmountGrey(props.value),
}),
)}
className={css({
cursor: 'pointer',
':hover': { textDecoration: 'underline' },
...makeAmountGrey(props.value),
})}
/>
)}
</EnvelopeCellValue>
@@ -476,12 +480,10 @@ export function IncomeCategoryMonth({
{props => (
<CellValueText
{...props}
className={String(
css({
cursor: 'pointer',
':hover': { textDecoration: 'underline' },
}),
)}
className={css({
cursor: 'pointer',
':hover': { textDecoration: 'underline' },
})}
/>
)}
</EnvelopeCellValue>

View File

@@ -1,6 +1,6 @@
import React, { useRef, useState } from 'react';
import { css } from 'glamor';
import { css } from '@emotion/css';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -103,7 +103,7 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
</View>
<div
className={`${css([
className={css([
{
textAlign: 'center',
marginTop: 3,
@@ -112,7 +112,7 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
textDecorationSkip: 'ink',
},
currentMonth === month && { fontWeight: 'bold' },
])}`}
])}
>
{monthUtils.format(month, 'MMMM')}
</div>

View File

@@ -1,8 +1,7 @@
import React, { useRef, useState } from 'react';
import React, { useRef, useState, type CSSProperties } from 'react';
import { envelopeBudget } from 'loot-core/src/client/queries';
import { type CSSProperties } from '../../../../style';
import { Popover } from '../../../common/Popover';
import { View } from '../../../common/View';
import { CoverMenu } from '../CoverMenu';

View File

@@ -1,10 +1,10 @@
import React from 'react';
import React, { type CSSProperties } from 'react';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { envelopeBudget } from 'loot-core/src/client/queries';
import { theme, styles, type CSSProperties } from '../../../../style';
import { theme, styles } from '../../../../style';
import { Block } from '../../../common/Block';
import { Tooltip } from '../../../common/Tooltip';
import { View } from '../../../common/View';
@@ -72,7 +72,7 @@ export function ToBudgetAmount({
<Block
onClick={onClick}
data-cellname={sheetName}
className={`${css([
className={css([
styles.veryLargeText,
{
fontWeight: 400,
@@ -88,7 +88,7 @@ export function ToBudgetAmount({
},
},
amountStyle,
])}`}
])}
>
{format(num, 'financial')}
</Block>

View File

@@ -1,8 +1,8 @@
import React from 'react';
import React, { type CSSProperties } from 'react';
import { envelopeBudget } from 'loot-core/src/client/queries';
import { styles, type CSSProperties } from '../../../../style';
import { styles } from '../../../../style';
import { AlignedText } from '../../../common/AlignedText';
import { Block } from '../../../common/Block';
import { Tooltip } from '../../../common/Tooltip';

View File

@@ -3,6 +3,15 @@ import React, { memo, useMemo, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useCategories } from '../../hooks/useCategories';
import { useGlobalPref } from '../../hooks/useGlobalPref';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useNavigate } from '../../hooks/useNavigate';
import { useSyncedPref } from '../../hooks/useSyncedPref';
import {
addNotification,
applyBudgetAction,
@@ -16,16 +25,7 @@ import {
pushModal,
updateCategory,
updateGroup,
} from 'loot-core/src/client/actions';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { useCategories } from '../../hooks/useCategories';
import { useGlobalPref } from '../../hooks/useGlobalPref';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useNavigate } from '../../hooks/useNavigate';
import { useSyncedPref } from '../../hooks/useSyncedPref';
} from '../../state/actions';
import { styles } from '../../style';
import { View } from '../common/View';
import { NamespaceContext } from '../spreadsheet/NamespaceContext';

View File

@@ -1,8 +1,14 @@
// @ts-strict-ignore
import React, { type ComponentProps, memo, useRef, useState } from 'react';
import React, {
type ComponentProps,
type CSSProperties,
memo,
useRef,
useState,
} from 'react';
import { Trans } from 'react-i18next';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { trackingBudget } from 'loot-core/src/client/queries';
import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
@@ -11,7 +17,7 @@ import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util';
import { useUndo } from '../../../hooks/useUndo';
import { SvgCheveronDown } from '../../../icons/v1';
import { styles, theme, type CSSProperties } from '../../../style';
import { styles, theme } from '../../../style';
import { Button } from '../../common/Button2';
import { Popover } from '../../common/Popover';
import { Text } from '../../common/Text';
@@ -367,15 +373,13 @@ export const CategoryMonth = memo(function CategoryMonth({
{props => (
<CellValueText
{...props}
className={String(
css({
cursor: 'pointer',
':hover': {
textDecoration: 'underline',
},
...makeAmountGrey(props.value),
}),
)}
className={css({
cursor: 'pointer',
':hover': {
textDecoration: 'underline',
},
...makeAmountGrey(props.value),
})}
/>
)}
</TrackingCellValue>

View File

@@ -2,7 +2,7 @@
import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'glamor';
import { css } from '@emotion/css';
import * as monthUtils from 'loot-core/src/shared/months';
@@ -108,15 +108,13 @@ export function BudgetSummary({ month }: BudgetSummaryProps) {
</View>
<div
className={`${css([
{
textAlign: 'center',
marginTop: 3,
fontSize: 18,
fontWeight: 500,
textDecorationSkip: 'ink',
},
])}`}
className={css({
textAlign: 'center',
marginTop: 3,
fontSize: 18,
fontWeight: 500,
textDecorationSkip: 'ink',
})}
>
{monthUtils.format(month, 'MMMM')}
</div>

View File

@@ -1,10 +1,8 @@
import React from 'react';
import React, { type CSSProperties } from 'react';
import { useTranslation } from 'react-i18next';
import { trackingBudget } from 'loot-core/src/client/queries';
import { type CSSProperties } from '../../../../style';
import { BudgetTotal } from './BudgetTotal';
import { ExpenseProgress } from './ExpenseProgress';

View File

@@ -1,10 +1,8 @@
import React from 'react';
import React, { type CSSProperties } from 'react';
import { useTranslation } from 'react-i18next';
import { trackingBudget } from 'loot-core/src/client/queries';
import { type CSSProperties } from '../../../../style';
import { BudgetTotal } from './BudgetTotal';
import { IncomeProgress } from './IncomeProgress';

View File

@@ -1,11 +1,11 @@
import React from 'react';
import React, { type CSSProperties } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { css } from 'glamor';
import { css } from '@emotion/css';
import { trackingBudget } from 'loot-core/src/client/queries';
import { theme, type CSSProperties, styles } from '../../../../style';
import { theme, styles } from '../../../../style';
import { AlignedText } from '../../../common/AlignedText';
import { Text } from '../../../common/Text';
import { Tooltip } from '../../../common/Tooltip';
@@ -74,16 +74,14 @@ export function Saved({ projected, style }: SavedProps) {
}}
>
<View
className={`${css([
{
fontSize: 25,
color: projected
? theme.warningText
: isNegative
? theme.errorTextDark
: theme.upcomingText,
},
])}`}
className={css({
fontSize: 25,
color: projected
? theme.warningText
: isNegative
? theme.errorTextDark
: theme.upcomingText,
})}
>
<PrivacyFilter>{format(saved, 'financial')}</PrivacyFilter>
</View>

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