Compare commits
16 Commits
QueryState
...
move-redux
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a61d66f23f | ||
|
|
c1b475777b | ||
|
|
b1a83123f5 | ||
|
|
71eb54e9f3 | ||
|
|
3e2f96a32c | ||
|
|
a91a8859ab | ||
|
|
a3256f5686 | ||
|
|
715bc00e3b | ||
|
|
4e07357221 | ||
|
|
03f2cabc18 | ||
|
|
259beb7665 | ||
|
|
0f3efde855 | ||
|
|
9aac44c58f | ||
|
|
0d9528e22c | ||
|
|
3f31d19d8a | ||
|
|
225c93914c |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
14
packages/desktop-client/globals.d.ts
vendored
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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]),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||