mirror of
https://github.com/actualbudget/actual.git
synced 2026-05-10 05:50:49 -05:00
Compare commits
5 Commits
mobile/lin
...
move-redux
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a61d66f23f | ||
|
|
c1b475777b | ||
|
|
b1a83123f5 | ||
|
|
71eb54e9f3 | ||
|
|
3e2f96a32c |
7
packages/desktop-client/globals.d.ts
vendored
7
packages/desktop-client/globals.d.ts
vendored
@@ -1,5 +1,7 @@
|
||||
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';
|
||||
|
||||
@@ -7,3 +9,8 @@ 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 {}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,10 +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';
|
||||
|
||||
@@ -3,9 +3,8 @@ 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 { type State } from '../state';
|
||||
import { theme, styles } from '../style';
|
||||
|
||||
import { Button } from './common/Button2';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
@@ -67,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();
|
||||
@@ -84,520 +79,244 @@ export function Modals() {
|
||||
const modals = modalStack
|
||||
.map(({ name, options }) => {
|
||||
switch (name) {
|
||||
case 'goal-templates':
|
||||
return budgetId ? <GoalTemplateModal key={name} /> : null;
|
||||
case GoalTemplateModal.modalName:
|
||||
return budgetId ? (
|
||||
<GoalTemplateModal key={name} name={name} {...options} />
|
||||
) : null;
|
||||
|
||||
case 'keyboard-shortcuts':
|
||||
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');
|
||||
|
||||
@@ -10,13 +10,12 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
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 { AnimatedLoading } from '../icons/AnimatedLoading';
|
||||
import { SvgDelete } from '../icons/v0';
|
||||
import { useResponsive } from '../ResponsiveProvider';
|
||||
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';
|
||||
|
||||
@@ -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';
|
||||
@@ -1857,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';
|
||||
|
||||
@@ -16,8 +16,6 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
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,
|
||||
@@ -28,6 +26,8 @@ import { useAccounts } from '../../hooks/useAccounts';
|
||||
import { useCommonPayees, usePayees } from '../../hooks/usePayees';
|
||||
import { SvgAdd, SvgBookmark } from '../../icons/v1';
|
||||
import { useResponsive } from '../../ResponsiveProvider';
|
||||
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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -2,16 +2,6 @@ import React, { useState, useRef, type CSSProperties } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
closeAndDownloadBudget,
|
||||
closeAndLoadBudget,
|
||||
createBudget,
|
||||
downloadBudget,
|
||||
getUserData,
|
||||
loadAllFiles,
|
||||
loadBudget,
|
||||
pushModal,
|
||||
} from 'loot-core/client/actions';
|
||||
import {
|
||||
isElectron,
|
||||
isNonProductionEnvironment,
|
||||
@@ -35,6 +25,16 @@ import {
|
||||
} from '../../icons/v1';
|
||||
import { SvgCloudUnknown, SvgKey, SvgRefreshArrow } from '../../icons/v2';
|
||||
import { useResponsive } from '../../ResponsiveProvider';
|
||||
import {
|
||||
closeAndDownloadBudget,
|
||||
closeAndLoadBudget,
|
||||
createBudget,
|
||||
downloadBudget,
|
||||
getUserData,
|
||||
loadAllFiles,
|
||||
loadBudget,
|
||||
pushModal,
|
||||
} from '../../state/actions';
|
||||
import { styles, theme } from '../../style';
|
||||
import { tokens } from '../../tokens';
|
||||
import { Button } from '../common/Button2';
|
||||
|
||||
@@ -2,14 +2,9 @@ import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
getUserData,
|
||||
loadAllFiles,
|
||||
setAppState,
|
||||
} from 'loot-core/client/actions';
|
||||
|
||||
import { useMetaThemeColor } from '../../hooks/useMetaThemeColor';
|
||||
import { useResponsive } from '../../ResponsiveProvider';
|
||||
import { getUserData, loadAllFiles, setAppState } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { tokens } from '../../tokens';
|
||||
import { AppBackground } from '../AppBackground';
|
||||
|
||||
@@ -3,10 +3,9 @@ import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createBudget } from 'loot-core/src/client/actions/budgets';
|
||||
import { loggedIn } from 'loot-core/src/client/actions/user';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
|
||||
import { createBudget, loggedIn } from '../../../state/actions';
|
||||
import { theme } from '../../../style';
|
||||
import { Button } from '../../common/Button2';
|
||||
import { Link } from '../../common/Link';
|
||||
|
||||
@@ -4,11 +4,10 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { createBudget } from 'loot-core/src/client/actions/budgets';
|
||||
import { loggedIn } from 'loot-core/src/client/actions/user';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
|
||||
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
|
||||
import { createBudget, loggedIn } from '../../../state/actions';
|
||||
import { theme } from '../../../style';
|
||||
import { Button, ButtonWithLoading } from '../../common/Button2';
|
||||
import { BigInput } from '../../common/Input';
|
||||
|
||||
@@ -10,16 +10,6 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { useDebounceCallback } from 'usehooks-ts';
|
||||
|
||||
import {
|
||||
collapseModals,
|
||||
getPayees,
|
||||
markAccountRead,
|
||||
openAccountCloseModal,
|
||||
pushModal,
|
||||
reopenAccount,
|
||||
syncAndDownload,
|
||||
updateAccount,
|
||||
} from 'loot-core/client/actions';
|
||||
import {
|
||||
SchedulesProvider,
|
||||
useDefaultSchedulesQueryTransform,
|
||||
@@ -38,6 +28,16 @@ import { useDateFormat } from '../../../hooks/useDateFormat';
|
||||
import { useFailedAccounts } from '../../../hooks/useFailedAccounts';
|
||||
import { useNavigate } from '../../../hooks/useNavigate';
|
||||
import { usePreviewTransactions } from '../../../hooks/usePreviewTransactions';
|
||||
import {
|
||||
collapseModals,
|
||||
getPayees,
|
||||
markAccountRead,
|
||||
openAccountCloseModal,
|
||||
pushModal,
|
||||
reopenAccount,
|
||||
syncAndDownload,
|
||||
updateAccount,
|
||||
} from '../../../state/actions';
|
||||
import { styles, theme } from '../../../style';
|
||||
import { Button } from '../../common/Button2';
|
||||
import { Text } from '../../common/Text';
|
||||
@@ -111,7 +111,7 @@ function AccountHeader({ account }: { readonly account: AccountEntity }) {
|
||||
dispatch(
|
||||
pushModal('notes', {
|
||||
id: `account-${id}`,
|
||||
name: account.name,
|
||||
title: account.name,
|
||||
onSave: onSaveNotes,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { replaceModal, syncAndDownload } from 'loot-core/src/client/actions';
|
||||
import * as queries from 'loot-core/src/client/queries';
|
||||
|
||||
import { useAccounts } from '../../../hooks/useAccounts';
|
||||
@@ -9,6 +8,7 @@ import { useFailedAccounts } from '../../../hooks/useFailedAccounts';
|
||||
import { useNavigate } from '../../../hooks/useNavigate';
|
||||
import { useSyncedPref } from '../../../hooks/useSyncedPref';
|
||||
import { SvgAdd } from '../../../icons/v1';
|
||||
import { replaceModal, syncAndDownload } from '../../../state/actions';
|
||||
import { theme, styles } from '../../../style';
|
||||
import { makeAmountFullStyle } from '../../budget/util';
|
||||
import { Button } from '../../common/Button2';
|
||||
@@ -229,7 +229,7 @@ function AccountList({
|
||||
export function Accounts() {
|
||||
const dispatch = useDispatch();
|
||||
const accounts = useAccounts();
|
||||
const updatedAccounts = useSelector(state => state.queries.updatedAccounts);
|
||||
const updatedAccounts = useSelector(state => state.account.updatedAccounts);
|
||||
const [_numberFormat] = useSyncedPref('numberFormat');
|
||||
const numberFormat = _numberFormat || 'comma-dot';
|
||||
const [hideFraction] = useSyncedPref('hideFraction');
|
||||
|
||||
@@ -5,7 +5,6 @@ import { css } from '@emotion/css';
|
||||
import { AutoTextSize } from 'auto-text-size';
|
||||
import memoizeOne from 'memoize-one';
|
||||
|
||||
import { collapseModals, pushModal } from 'loot-core/client/actions';
|
||||
import { groupById, integerToCurrency } from 'loot-core/shared/util';
|
||||
import {
|
||||
envelopeBudget,
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
} from '../../../icons/v1';
|
||||
import { SvgViewShow } from '../../../icons/v2';
|
||||
import { useResponsive } from '../../../ResponsiveProvider';
|
||||
import { collapseModals, pushModal } from '../../../state/actions';
|
||||
import { theme, styles } from '../../../style';
|
||||
import { BalanceWithCarryover } from '../../budget/BalanceWithCarryover';
|
||||
import { makeAmountGrey, makeBalanceAmountStyle } from '../../budget/util';
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useDebounceCallback } from 'usehooks-ts';
|
||||
|
||||
import { getPayees } from 'loot-core/client/actions';
|
||||
import * as queries from 'loot-core/client/queries';
|
||||
import { pagedQuery } from 'loot-core/client/query-helpers';
|
||||
import { listen } from 'loot-core/platform/client/fetch';
|
||||
@@ -13,6 +12,7 @@ import { isPreviewId } from 'loot-core/shared/transactions';
|
||||
|
||||
import { useDateFormat } from '../../../hooks/useDateFormat';
|
||||
import { useNavigate } from '../../../hooks/useNavigate';
|
||||
import { getPayees } from '../../../state/actions';
|
||||
import { TextOneLine } from '../../common/TextOneLine';
|
||||
import { View } from '../../common/View';
|
||||
import { MobilePageHeader, Page } from '../../Page';
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
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 { useLocalPref } from '../../../hooks/useLocalPref';
|
||||
import { useSyncedPref } from '../../../hooks/useSyncedPref';
|
||||
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
|
||||
import {
|
||||
applyBudgetAction,
|
||||
collapseModals,
|
||||
@@ -16,17 +24,10 @@ import {
|
||||
updateCategory,
|
||||
updateGroup,
|
||||
sync,
|
||||
} from 'loot-core/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 { useLocalPref } from '../../../hooks/useLocalPref';
|
||||
import { useSyncedPref } from '../../../hooks/useSyncedPref';
|
||||
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
|
||||
} from '../../../state/actions';
|
||||
import { theme } from '../../../style';
|
||||
import { prewarmMonth } from '../../budget/util';
|
||||
import { ModalHeader, ModalTitle } from '../../common/Modal';
|
||||
import { View } from '../../common/View';
|
||||
import { NamespaceContext } from '../../spreadsheet/NamespaceContext';
|
||||
import { SyncRefresh } from '../../SyncRefresh';
|
||||
@@ -111,6 +112,14 @@ export function Budget() {
|
||||
const onOpenNewCategoryGroupModal = useCallback(() => {
|
||||
dispatch(
|
||||
pushModal('new-category-group', {
|
||||
Header: props => (
|
||||
<ModalHeader
|
||||
{...props}
|
||||
title={<ModalTitle title="New Category Group" shrinkOnOverflow />}
|
||||
/>
|
||||
),
|
||||
inputPlaceholder: 'Category group name',
|
||||
buttonText: 'Add',
|
||||
onValidate: name => (!name ? 'Name is required.' : null),
|
||||
onSubmit: async name => {
|
||||
dispatch(collapseModals('budget-page-menu'));
|
||||
@@ -124,6 +133,14 @@ export function Budget() {
|
||||
(groupId, isIncome) => {
|
||||
dispatch(
|
||||
pushModal('new-category', {
|
||||
Header: props => (
|
||||
<ModalHeader
|
||||
{...props}
|
||||
title={<ModalTitle title="New Category" shrinkOnOverflow />}
|
||||
/>
|
||||
),
|
||||
inputPlaceholder: 'Category name',
|
||||
buttonText: 'Add',
|
||||
onValidate: name => (!name ? 'Name is required.' : null),
|
||||
onSubmit: async name => {
|
||||
dispatch(collapseModals('category-group-menu'));
|
||||
@@ -342,7 +359,7 @@ export function Budget() {
|
||||
dispatch(
|
||||
pushModal('notes', {
|
||||
id,
|
||||
name: group.name,
|
||||
title: group.name,
|
||||
onSave: onSaveNotes,
|
||||
}),
|
||||
);
|
||||
@@ -356,7 +373,7 @@ export function Budget() {
|
||||
dispatch(
|
||||
pushModal('notes', {
|
||||
id,
|
||||
name: category.name,
|
||||
title: category.name,
|
||||
onSave: onSaveNotes,
|
||||
}),
|
||||
);
|
||||
@@ -399,14 +416,12 @@ export function Budget() {
|
||||
onEditNotes: onOpenCategoryNotesModal,
|
||||
onDelete: onDeleteCategory,
|
||||
onToggleVisibility: onToggleCategoryVisibility,
|
||||
onBudgetAction,
|
||||
}),
|
||||
);
|
||||
},
|
||||
[
|
||||
categories,
|
||||
dispatch,
|
||||
onBudgetAction,
|
||||
onDeleteCategory,
|
||||
onOpenCategoryNotesModal,
|
||||
onSaveCategory,
|
||||
@@ -428,7 +443,7 @@ export function Budget() {
|
||||
dispatch(
|
||||
pushModal('notes', {
|
||||
id: `budget-${month}`,
|
||||
name: monthUtils.format(month, 'MMMM ‘yy'),
|
||||
title: monthUtils.format(month, 'MMMM ‘yy'),
|
||||
onSave: onSaveNotes,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
isValid as isValidDate,
|
||||
} from 'date-fns';
|
||||
|
||||
import { pushModal, setLastTransaction } from 'loot-core/client/actions';
|
||||
import { runQuery } from 'loot-core/src/client/query-helpers';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
@@ -54,6 +53,7 @@ import {
|
||||
import { SvgSplit } from '../../../icons/v0';
|
||||
import { SvgAdd, SvgPiggyBank, SvgTrash } from '../../../icons/v1';
|
||||
import { SvgPencilWriteAlternate } from '../../../icons/v2';
|
||||
import { pushModal, setLastTransaction } from '../../../state/actions';
|
||||
import { styles, theme } from '../../../style';
|
||||
import { Button } from '../../common/Button';
|
||||
import { Text } from '../../common/Text';
|
||||
@@ -628,11 +628,14 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
||||
case 'category':
|
||||
dispatch(
|
||||
pushModal('category-autocomplete', {
|
||||
categoryGroups,
|
||||
month: monthUtils.monthFromDate(unserializedTransaction.date),
|
||||
onSelect: categoryId => {
|
||||
onUpdateInner(transactionToEdit, name, categoryId);
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
categoryGroups,
|
||||
onSelect: categoryId => {
|
||||
onUpdateInner(transactionToEdit, name, categoryId);
|
||||
},
|
||||
},
|
||||
month: monthUtils.monthFromDate(unserializedTransaction.date),
|
||||
onClose: () => {
|
||||
onClearActiveEdit();
|
||||
},
|
||||
@@ -642,8 +645,11 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
||||
case 'account':
|
||||
dispatch(
|
||||
pushModal('account-autocomplete', {
|
||||
onSelect: accountId => {
|
||||
onUpdateInner(transactionToEdit, name, accountId);
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
onSelect: accountId => {
|
||||
onUpdateInner(transactionToEdit, name, accountId);
|
||||
},
|
||||
},
|
||||
onClose: () => {
|
||||
onClearActiveEdit();
|
||||
@@ -654,8 +660,11 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
||||
case 'payee':
|
||||
dispatch(
|
||||
pushModal('payee-autocomplete', {
|
||||
onSelect: payeeId => {
|
||||
onUpdateInner(transactionToEdit, name, payeeId);
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
onSelect: payeeId => {
|
||||
onUpdateInner(transactionToEdit, name, payeeId);
|
||||
},
|
||||
},
|
||||
onClose: () => {
|
||||
onClearActiveEdit();
|
||||
@@ -666,7 +675,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
||||
default:
|
||||
dispatch(
|
||||
pushModal('edit-field', {
|
||||
name,
|
||||
fieldName: name,
|
||||
month: monthUtils.monthFromDate(unserializedTransaction.date),
|
||||
onSubmit: (name, value) => {
|
||||
onUpdateInner(transactionToEdit, name, value);
|
||||
@@ -1278,7 +1287,7 @@ function TransactionEditUnconnected({
|
||||
export const TransactionEdit = props => {
|
||||
const { list: categories } = useCategories();
|
||||
const payees = usePayees();
|
||||
const lastTransaction = useSelector(state => state.queries.lastTransaction);
|
||||
const lastTransaction = useSelector(state => state.account.lastTransaction);
|
||||
const accounts = useAccounts();
|
||||
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import React, {
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Item, Section } from 'react-stately';
|
||||
|
||||
import { setNotificationInset } from 'loot-core/client/actions';
|
||||
import { groupById, integerToCurrency } from 'loot-core/shared/util';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import { isPreviewId } from 'loot-core/src/shared/transactions';
|
||||
@@ -26,6 +25,7 @@ import { useUndo } from '../../../hooks/useUndo';
|
||||
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
|
||||
import { SvgDelete } from '../../../icons/v0';
|
||||
import { SvgDotsHorizontalTriple } from '../../../icons/v1';
|
||||
import { setNotificationInset } from '../../../state/actions';
|
||||
import { styles, theme } from '../../../style';
|
||||
import { Button } from '../../common/Button2';
|
||||
import { Menu } from '../../common/Menu';
|
||||
|
||||
@@ -68,7 +68,7 @@ export function TransactionListWithBalances({
|
||||
onOpenTransaction,
|
||||
onRefresh,
|
||||
}) {
|
||||
const newTransactions = useSelector(state => state.queries.newTransactions);
|
||||
const newTransactions = useSelector(state => state.account.newTransactions);
|
||||
|
||||
const isNewTransaction = id => {
|
||||
return newTransactions.includes(id);
|
||||
|
||||
@@ -12,12 +12,16 @@ import {
|
||||
import { View } from '../common/View';
|
||||
import { SectionLabel } from '../forms';
|
||||
|
||||
const MODAL_NAME = 'account-autocomplete' as const;
|
||||
|
||||
type AccountAutocompleteModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
autocompleteProps: ComponentPropsWithoutRef<typeof AccountAutocomplete>;
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export function AccountAutocompleteModal({
|
||||
name = MODAL_NAME,
|
||||
autocompleteProps,
|
||||
onClose,
|
||||
}: AccountAutocompleteModalProps) {
|
||||
@@ -28,7 +32,7 @@ export function AccountAutocompleteModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="account-autocomplete"
|
||||
name={name}
|
||||
noAnimation={!isNarrowWidth}
|
||||
onClose={onClose}
|
||||
containerProps={{
|
||||
@@ -83,3 +87,4 @@ export function AccountAutocompleteModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
AccountAutocompleteModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -28,7 +28,10 @@ import { View } from '../common/View';
|
||||
import { Notes } from '../Notes';
|
||||
import { validateAccountName } from '../util/accountValidation';
|
||||
|
||||
const MODAL_NAME = 'account-menu' as const;
|
||||
|
||||
type AccountMenuModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
accountId: string;
|
||||
onSave: (account: AccountEntity) => void;
|
||||
onCloseAccount: (accountId: string) => void;
|
||||
@@ -38,6 +41,7 @@ type AccountMenuModalProps = {
|
||||
};
|
||||
|
||||
export function AccountMenuModal({
|
||||
name = MODAL_NAME,
|
||||
accountId,
|
||||
onSave,
|
||||
onCloseAccount,
|
||||
@@ -105,7 +109,7 @@ export function AccountMenuModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="account-menu"
|
||||
name={name}
|
||||
onClose={onClose}
|
||||
containerProps={{
|
||||
style: {
|
||||
@@ -193,6 +197,7 @@ export function AccountMenuModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
AccountMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
type AdditionalAccountMenuProps = {
|
||||
account: AccountEntity;
|
||||
|
||||
@@ -7,14 +7,20 @@ import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { BudgetList } from '../manager/BudgetList';
|
||||
|
||||
export function BudgetListModal() {
|
||||
const MODAL_NAME = 'budget-list' as const;
|
||||
|
||||
type BudgetListModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function BudgetListModal({ name = MODAL_NAME }: BudgetListModalProps) {
|
||||
const [id] = useMetadataPref('id');
|
||||
const currentFile = useSelector(state =>
|
||||
state.budgets.allFiles?.find(f => 'id' in f && f.id === id),
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal name="budget-list">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -41,3 +47,4 @@ export function BudgetListModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
BudgetListModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -8,9 +8,16 @@ import { theme, styles } from '../../style';
|
||||
import { Menu } from '../common/Menu';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
|
||||
type BudgetPageMenuModalProps = ComponentPropsWithoutRef<typeof BudgetPageMenu>;
|
||||
const MODAL_NAME = 'budget-page-menu' as const;
|
||||
|
||||
type BudgetPageMenuModalProps = ComponentPropsWithoutRef<
|
||||
typeof BudgetPageMenu
|
||||
> & {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function BudgetPageMenuModal({
|
||||
name = MODAL_NAME,
|
||||
onAddCategoryGroup,
|
||||
onToggleHiddenCategories,
|
||||
onSwitchBudgetFile,
|
||||
@@ -23,7 +30,7 @@ export function BudgetPageMenuModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="budget-page-menu">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -41,6 +48,7 @@ export function BudgetPageMenuModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
BudgetPageMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
type BudgetPageMenuProps = Omit<
|
||||
ComponentPropsWithoutRef<typeof Menu>,
|
||||
|
||||
@@ -15,13 +15,17 @@ import { View } from '../common/View';
|
||||
import { SectionLabel } from '../forms';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'category-autocomplete' as const;
|
||||
|
||||
type CategoryAutocompleteModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
autocompleteProps: ComponentPropsWithoutRef<typeof CategoryAutocomplete>;
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
month?: string;
|
||||
};
|
||||
|
||||
export function CategoryAutocompleteModal({
|
||||
name = MODAL_NAME,
|
||||
autocompleteProps,
|
||||
month,
|
||||
onClose,
|
||||
@@ -34,7 +38,7 @@ export function CategoryAutocompleteModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="category-autocomplete"
|
||||
name={name}
|
||||
noAnimation={!isNarrowWidth}
|
||||
onClose={onClose}
|
||||
containerProps={{
|
||||
@@ -94,3 +98,4 @@ export function CategoryAutocompleteModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CategoryAutocompleteModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -25,18 +25,21 @@ import { Popover } from '../common/Popover';
|
||||
import { View } from '../common/View';
|
||||
import { Notes } from '../Notes';
|
||||
|
||||
const MODAL_NAME = 'category-group-menu' as const;
|
||||
|
||||
type CategoryGroupMenuModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
groupId: string;
|
||||
onSave: (group: CategoryGroupEntity) => void;
|
||||
onAddCategory: (groupId: string, isIncome: boolean) => void;
|
||||
onEditNotes: (id: string) => void;
|
||||
onSaveNotes: (id: string, notes: string) => void;
|
||||
onDelete: (groupId: string) => void;
|
||||
onToggleVisibility: (groupId: string) => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export function CategoryGroupMenuModal({
|
||||
name = MODAL_NAME,
|
||||
groupId,
|
||||
onSave,
|
||||
onAddCategory,
|
||||
@@ -86,7 +89,7 @@ export function CategoryGroupMenuModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="category-group-menu"
|
||||
name={name}
|
||||
onClose={onClose}
|
||||
containerProps={{
|
||||
style: {
|
||||
@@ -168,6 +171,7 @@ export function CategoryGroupMenuModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CategoryGroupMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function AdditionalCategoryGroupMenu({ group, onDelete, onToggleVisibility }) {
|
||||
const triggerRef = useRef(null);
|
||||
|
||||
@@ -21,7 +21,10 @@ import { Popover } from '../common/Popover';
|
||||
import { View } from '../common/View';
|
||||
import { Notes } from '../Notes';
|
||||
|
||||
const MODAL_NAME = 'category-menu' as const;
|
||||
|
||||
type CategoryMenuModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
categoryId: string;
|
||||
onSave: (category: CategoryEntity) => void;
|
||||
onEditNotes: (categoryId: string) => void;
|
||||
@@ -31,6 +34,7 @@ type CategoryMenuModalProps = {
|
||||
};
|
||||
|
||||
export function CategoryMenuModal({
|
||||
name = MODAL_NAME,
|
||||
categoryId,
|
||||
onSave,
|
||||
onEditNotes,
|
||||
@@ -73,7 +77,7 @@ export function CategoryMenuModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="category-menu"
|
||||
name={name}
|
||||
onClose={onClose}
|
||||
containerProps={{
|
||||
style: { height: '45vh' },
|
||||
@@ -149,6 +153,7 @@ export function CategoryMenuModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CategoryMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function AdditionalCategoryMenu({
|
||||
category,
|
||||
|
||||
@@ -3,17 +3,17 @@ import React, { type FormEvent, useState, type CSSProperties } from 'react';
|
||||
import { Form } from 'react-aria-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import {
|
||||
closeAccount,
|
||||
forceCloseAccount,
|
||||
pushModal,
|
||||
} from 'loot-core/client/actions';
|
||||
import { integerToCurrency } from 'loot-core/src/shared/util';
|
||||
import { type AccountEntity } from 'loot-core/src/types/models';
|
||||
|
||||
import { useAccounts } from '../../hooks/useAccounts';
|
||||
import { useCategories } from '../../hooks/useCategories';
|
||||
import { useResponsive } from '../../ResponsiveProvider';
|
||||
import {
|
||||
closeAccount,
|
||||
forceCloseAccount,
|
||||
pushModal,
|
||||
} from '../../state/actions';
|
||||
import { styles, theme } from '../../style';
|
||||
import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete';
|
||||
import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete';
|
||||
@@ -25,6 +25,8 @@ import { Paragraph } from '../common/Paragraph';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'close-account' as const;
|
||||
|
||||
function needsCategory(
|
||||
account: AccountEntity,
|
||||
currentTransfer: string,
|
||||
@@ -39,12 +41,14 @@ function needsCategory(
|
||||
}
|
||||
|
||||
type CloseAccountModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
account: AccountEntity;
|
||||
balance: number;
|
||||
canDelete: boolean;
|
||||
};
|
||||
|
||||
export function CloseAccountModal({
|
||||
name = MODAL_NAME,
|
||||
account,
|
||||
balance,
|
||||
canDelete,
|
||||
@@ -105,7 +109,7 @@ export function CloseAccountModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="close-account"
|
||||
name={name}
|
||||
isLoading={loading}
|
||||
containerProps={{ style: { width: '30vw' } }}
|
||||
>
|
||||
@@ -160,8 +164,11 @@ export function CloseAccountModal({
|
||||
onClick: () => {
|
||||
dispatch(
|
||||
pushModal('account-autocomplete', {
|
||||
includeClosedAccounts: false,
|
||||
onSelect: onSelectAccount,
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
includeClosedAccounts: false,
|
||||
onSelect: onSelectAccount,
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
@@ -198,9 +205,12 @@ export function CloseAccountModal({
|
||||
onClick: () => {
|
||||
dispatch(
|
||||
pushModal('category-autocomplete', {
|
||||
categoryGroups,
|
||||
showHiddenCategories: true,
|
||||
onSelect: onSelectCategory,
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
categoryGroups,
|
||||
showHiddenCategories: true,
|
||||
onSelect: onSelectCategory,
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
@@ -272,3 +282,4 @@ export function CloseAccountModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CloseAccountModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -10,13 +10,17 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'confirm-category-delete' as const;
|
||||
|
||||
type ConfirmCategoryDeleteProps = {
|
||||
category: string;
|
||||
group: string;
|
||||
onDelete: (categoryId: string) => void;
|
||||
name: typeof MODAL_NAME;
|
||||
category?: string;
|
||||
group?: string;
|
||||
onDelete: (id: string) => void;
|
||||
};
|
||||
|
||||
export function ConfirmCategoryDeleteModal({
|
||||
name = MODAL_NAME,
|
||||
group: groupId,
|
||||
category: categoryId,
|
||||
onDelete,
|
||||
@@ -53,10 +57,7 @@ export function ConfirmCategoryDeleteModal({
|
||||
const isIncome = !!(category || group).is_income;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="confirm-category-delete"
|
||||
containerProps={{ style: { width: '30vw' } }}
|
||||
>
|
||||
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -144,3 +145,4 @@ export function ConfirmCategoryDeleteModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ConfirmCategoryDeleteModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -8,12 +8,16 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { Paragraph } from '../common/Paragraph';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'confirm-transaction-delete' as const;
|
||||
|
||||
type ConfirmTransactionDeleteProps = {
|
||||
name?: typeof MODAL_NAME;
|
||||
message?: string;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
export function ConfirmTransactionDeleteModal({
|
||||
name = MODAL_NAME,
|
||||
message = 'Are you sure you want to delete the transaction?',
|
||||
onConfirm,
|
||||
}: ConfirmTransactionDeleteProps) {
|
||||
@@ -25,7 +29,7 @@ export function ConfirmTransactionDeleteModal({
|
||||
: {};
|
||||
|
||||
return (
|
||||
<Modal name="confirm-transaction-delete">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -68,3 +72,4 @@ export function ConfirmTransactionDeleteModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ConfirmTransactionDeleteModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -7,22 +7,23 @@ import { InitialFocus } from '../common/InitialFocus';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'confirm-transaction-edit' as const;
|
||||
|
||||
type ConfirmTransactionEditProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
onCancel?: () => void;
|
||||
onConfirm: () => void;
|
||||
confirmReason: string;
|
||||
};
|
||||
|
||||
export function ConfirmTransactionEditModal({
|
||||
name = MODAL_NAME,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
confirmReason,
|
||||
}: ConfirmTransactionEditProps) {
|
||||
return (
|
||||
<Modal
|
||||
name="confirm-transaction-edit"
|
||||
containerProps={{ style: { width: '30vw' } }}
|
||||
>
|
||||
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -109,3 +110,4 @@ export function ConfirmTransactionEditModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ConfirmTransactionEditModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -6,20 +6,21 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { Paragraph } from '../common/Paragraph';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'confirm-unlink-account' as const;
|
||||
|
||||
type ConfirmUnlinkAccountProps = {
|
||||
name?: typeof MODAL_NAME;
|
||||
accountName: string;
|
||||
onUnlink: () => void;
|
||||
};
|
||||
|
||||
export function ConfirmUnlinkAccountModal({
|
||||
name = MODAL_NAME,
|
||||
accountName,
|
||||
onUnlink,
|
||||
}: ConfirmUnlinkAccountProps) {
|
||||
return (
|
||||
<Modal
|
||||
name="confirm-unlink-account"
|
||||
containerProps={{ style: { width: '30vw' } }}
|
||||
>
|
||||
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -63,3 +64,4 @@ export function ConfirmUnlinkAccountModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ConfirmUnlinkAccountModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,10 +2,10 @@ import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { type CategoryEntity } from 'loot-core/src/types/models';
|
||||
|
||||
import { useCategories } from '../../hooks/useCategories';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { styles } from '../../style';
|
||||
import {
|
||||
addToBeBudgetedGroup,
|
||||
@@ -16,7 +16,10 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { View } from '../common/View';
|
||||
import { FieldLabel, TapField } from '../mobile/MobileForms';
|
||||
|
||||
const MODAL_NAME = 'cover' as const;
|
||||
|
||||
type CoverModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
title: string;
|
||||
categoryId?: CategoryEntity['id'];
|
||||
month: string;
|
||||
@@ -25,6 +28,7 @@ type CoverModalProps = {
|
||||
};
|
||||
|
||||
export function CoverModal({
|
||||
name = MODAL_NAME,
|
||||
title,
|
||||
categoryId,
|
||||
month,
|
||||
@@ -54,11 +58,14 @@ export function CoverModal({
|
||||
const onCategoryClick = useCallback(() => {
|
||||
dispatch(
|
||||
pushModal('category-autocomplete', {
|
||||
categoryGroups,
|
||||
month,
|
||||
onSelect: categoryId => {
|
||||
setFromCategoryId(categoryId);
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
categoryGroups,
|
||||
onSelect: categoryId => {
|
||||
setFromCategoryId(categoryId);
|
||||
},
|
||||
},
|
||||
month,
|
||||
}),
|
||||
);
|
||||
}, [categoryGroups, dispatch, month]);
|
||||
@@ -72,7 +79,7 @@ export function CoverModal({
|
||||
const fromCategory = categories.find(c => c.id === fromCategoryId);
|
||||
|
||||
return (
|
||||
<Modal name="cover">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -111,3 +118,4 @@ export function CoverModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CoverModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
|
||||
import { authorizeBank } from '../../gocardless';
|
||||
@@ -10,6 +9,7 @@ import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus';
|
||||
import { useSimpleFinStatus } from '../../hooks/useSimpleFinStatus';
|
||||
import { useSyncServerStatus } from '../../hooks/useSyncServerStatus';
|
||||
import { SvgDotsHorizontalTriple } from '../../icons/v1';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Button, ButtonWithLoading } from '../common/Button2';
|
||||
import { InitialFocus } from '../common/InitialFocus';
|
||||
@@ -21,11 +21,17 @@ import { Popover } from '../common/Popover';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
type CreateAccountProps = {
|
||||
const MODAL_NAME = 'add-account' as const;
|
||||
|
||||
type CreateAccountModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
upgradingAccountId?: string;
|
||||
};
|
||||
|
||||
export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
|
||||
export function CreateAccountModal({
|
||||
name = MODAL_NAME,
|
||||
upgradingAccountId,
|
||||
}: CreateAccountModalProps) {
|
||||
const syncServerStatus = useSyncServerStatus();
|
||||
const dispatch = useDispatch();
|
||||
const [isGoCardlessSetupComplete, setIsGoCardlessSetupComplete] =
|
||||
@@ -95,7 +101,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
|
||||
|
||||
dispatch(
|
||||
pushModal('select-linked-accounts', {
|
||||
accounts: newAccounts,
|
||||
externalAccounts: newAccounts,
|
||||
syncSource: 'simpleFin',
|
||||
}),
|
||||
);
|
||||
@@ -180,7 +186,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="add-account">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -387,3 +393,4 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CreateAccountModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { loadAllFiles, loadGlobalPrefs, sync } from 'loot-core/client/actions';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import { getCreateKeyError } from 'loot-core/src/shared/errors';
|
||||
|
||||
import { useResponsive } from '../../ResponsiveProvider';
|
||||
import { loadAllFiles, loadGlobalPrefs, sync } from '../../state/actions';
|
||||
import { styles, theme } from '../../style';
|
||||
import { ButtonWithLoading } from '../common/Button2';
|
||||
import { InitialFocus } from '../common/InitialFocus';
|
||||
@@ -25,14 +25,16 @@ import { Paragraph } from '../common/Paragraph';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'create-encryption-key' as const;
|
||||
|
||||
type CreateEncryptionKeyModalProps = {
|
||||
options: {
|
||||
recreate?: boolean;
|
||||
};
|
||||
name: typeof MODAL_NAME;
|
||||
recreate?: boolean;
|
||||
};
|
||||
|
||||
export function CreateEncryptionKeyModal({
|
||||
options = {},
|
||||
name = MODAL_NAME,
|
||||
recreate: isRecreating,
|
||||
}: CreateEncryptionKeyModalProps) {
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -41,8 +43,6 @@ export function CreateEncryptionKeyModal({
|
||||
const { isNarrowWidth } = useResponsive();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isRecreating = options.recreate;
|
||||
|
||||
async function onCreateKey(close: () => void) {
|
||||
if (password !== '' && !loading) {
|
||||
setLoading(true);
|
||||
@@ -65,7 +65,7 @@ export function CreateEncryptionKeyModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="create-encryption-key">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -210,3 +210,4 @@ export function CreateEncryptionKeyModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CreateEncryptionKeyModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,11 +3,11 @@ import { type FormEvent, useState } from 'react';
|
||||
import { Form } from 'react-aria-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { closeModal, createAccount } from 'loot-core/client/actions';
|
||||
import { toRelaxedNumber } from 'loot-core/src/shared/util';
|
||||
|
||||
import * as useAccounts from '../../hooks/useAccounts';
|
||||
import { useNavigate } from '../../hooks/useNavigate';
|
||||
import { closeModal, createAccount } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Button } from '../common/Button2';
|
||||
import { FormError } from '../common/FormError';
|
||||
@@ -27,7 +27,15 @@ import { View } from '../common/View';
|
||||
import { Checkbox } from '../forms';
|
||||
import { validateAccountName } from '../util/accountValidation';
|
||||
|
||||
export function CreateLocalAccountModal() {
|
||||
const MODAL_NAME = 'add-local-account' as const;
|
||||
|
||||
type CreateLocalAccountModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function CreateLocalAccountModal({
|
||||
name: modalName = MODAL_NAME,
|
||||
}: CreateLocalAccountModalProps) {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const accounts = useAccounts.useAccounts();
|
||||
@@ -67,7 +75,7 @@ export function CreateLocalAccountModal() {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Modal name="add-local-account">
|
||||
<Modal name={modalName}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -189,3 +197,4 @@ export function CreateLocalAccountModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
CreateLocalAccountModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -15,23 +15,30 @@ import { View } from '../common/View';
|
||||
import { SectionLabel } from '../forms';
|
||||
import { DateSelect } from '../select/DateSelect';
|
||||
|
||||
export function EditFieldModal({ name, onSubmit, onClose }) {
|
||||
const MODAL_NAME = 'edit-field';
|
||||
|
||||
export function EditFieldModal({
|
||||
name: modalName = MODAL_NAME,
|
||||
fieldName,
|
||||
onSubmit,
|
||||
onClose = undefined,
|
||||
}) {
|
||||
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
|
||||
|
||||
function onSelectNote(value, mode) {
|
||||
if (value != null) {
|
||||
onSubmit(name, value, mode);
|
||||
onSubmit(fieldName, value, mode);
|
||||
}
|
||||
}
|
||||
|
||||
function onSelect(value) {
|
||||
if (value != null) {
|
||||
// Process the value if needed
|
||||
if (name === 'amount') {
|
||||
if (fieldName === 'amount') {
|
||||
value = amountToInteger(value);
|
||||
}
|
||||
|
||||
onSubmit(name, value);
|
||||
onSubmit(fieldName, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +58,7 @@ export function EditFieldModal({ name, onSubmit, onClose }) {
|
||||
|
||||
const [noteAmend, onChangeMode] = useState('replace');
|
||||
|
||||
switch (name) {
|
||||
switch (fieldName) {
|
||||
case 'date':
|
||||
const today = currentDay();
|
||||
label = 'Date';
|
||||
@@ -216,7 +223,7 @@ export function EditFieldModal({ name, onSubmit, onClose }) {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="edit-field"
|
||||
name={modalName}
|
||||
noAnimation={!isNarrowWidth}
|
||||
onClose={onClose}
|
||||
containerProps={{
|
||||
@@ -254,3 +261,4 @@ export function EditFieldModal({ name, onSubmit, onClose }) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
EditFieldModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -10,10 +10,6 @@ import { useDispatch } from 'react-redux';
|
||||
import { css } from '@emotion/css';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import {
|
||||
initiallyLoadPayees,
|
||||
setUndoEnabled,
|
||||
} from 'loot-core/src/client/actions/queries';
|
||||
import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
|
||||
import { runQuery } from 'loot-core/src/client/query-helpers';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
@@ -42,6 +38,7 @@ import { useFeatureFlag } from '../../hooks/useFeatureFlag';
|
||||
import { useSelected, SelectedProvider } from '../../hooks/useSelected';
|
||||
import { SvgDelete, SvgAdd, SvgSubtract } from '../../icons/v0';
|
||||
import { SvgAlignLeft, SvgCode, SvgInformationOutline } from '../../icons/v1';
|
||||
import { initiallyLoadPayees, setUndoEnabled } from '../../state/actions';
|
||||
import { styles, theme } from '../../style';
|
||||
import { Button } from '../common/Button2';
|
||||
import { Menu } from '../common/Menu';
|
||||
@@ -57,6 +54,8 @@ import { BetweenAmountInput } from '../util/AmountInput';
|
||||
import { DisplayId } from '../util/DisplayId';
|
||||
import { GenericInput } from '../util/GenericInput';
|
||||
|
||||
const MODAL_NAME = 'edit-rule';
|
||||
|
||||
function updateValue(array, value, update) {
|
||||
return array.map(v => (v === value ? update() : v));
|
||||
}
|
||||
@@ -758,12 +757,16 @@ const conditionFields = [
|
||||
['amount-outflow', mapField('amount', { outflow: true })],
|
||||
]);
|
||||
|
||||
export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
|
||||
export function EditRuleModal({
|
||||
name = MODAL_NAME,
|
||||
rule,
|
||||
onSave: originalOnSave,
|
||||
}) {
|
||||
const [conditions, setConditions] = useState(
|
||||
defaultRule.conditions.map(parse).map(c => ({ ...c, inputKey: uuid() })),
|
||||
rule.conditions.map(parse).map(c => ({ ...c, inputKey: uuid() })),
|
||||
);
|
||||
const [actionSplits, setActionSplits] = useState(() => {
|
||||
const parsedActions = defaultRule.actions.map(parse);
|
||||
const parsedActions = rule.actions.map(parse);
|
||||
return parsedActions.reduce(
|
||||
(acc, action) => {
|
||||
const splitIndex = action.options?.splitIndex ?? 0;
|
||||
@@ -775,8 +778,8 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
|
||||
[{ id: uuid(), actions: [] }],
|
||||
);
|
||||
});
|
||||
const [stage, setStage] = useState(defaultRule.stage);
|
||||
const [conditionsOp, setConditionsOp] = useState(defaultRule.conditionsOp);
|
||||
const [stage, setStage] = useState(rule.stage);
|
||||
const [conditionsOp, setConditionsOp] = useState(rule.conditionsOp);
|
||||
const [transactions, setTransactions] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
const scrollableEl = useRef();
|
||||
@@ -964,16 +967,16 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
|
||||
}
|
||||
|
||||
async function onSave(close) {
|
||||
const rule = {
|
||||
...defaultRule,
|
||||
const ruleToSave = {
|
||||
...rule,
|
||||
stage,
|
||||
conditionsOp,
|
||||
conditions: conditions.map(unparse),
|
||||
actions: getUnparsedActions(actionSplits),
|
||||
};
|
||||
|
||||
const method = rule.id ? 'rule-update' : 'rule-add';
|
||||
const { error, id: newId } = await send(method, rule);
|
||||
const method = ruleToSave.id ? 'rule-update' : 'rule-add';
|
||||
const { error, id: newId } = await send(method, ruleToSave);
|
||||
|
||||
if (error) {
|
||||
if (error.conditionErrors) {
|
||||
@@ -995,10 +998,10 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
|
||||
} else {
|
||||
// If adding a rule, we got back an id
|
||||
if (newId) {
|
||||
rule.id = newId;
|
||||
ruleToSave.id = newId;
|
||||
}
|
||||
|
||||
originalOnSave?.(rule);
|
||||
originalOnSave?.(ruleToSave);
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -1013,7 +1016,7 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
|
||||
const showSplitButton = actionSplits.length > 0;
|
||||
|
||||
return (
|
||||
<Modal name="edit-rule">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -1274,3 +1277,4 @@ export function EditRuleModal({ defaultRule, onSave: originalOnSave }) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
EditRuleModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, {
|
||||
} from 'react';
|
||||
|
||||
import { envelopeBudget } from 'loot-core/client/queries';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
|
||||
import { useCategory } from '../../hooks/useCategory';
|
||||
import { theme, styles } from '../../style';
|
||||
@@ -21,17 +22,46 @@ import {
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { CellValueText } from '../spreadsheet/CellValue';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'envelope-balance-menu' as const;
|
||||
|
||||
type EnvelopeBalanceMenuModalProps = ComponentPropsWithoutRef<
|
||||
typeof BalanceMenu
|
||||
>;
|
||||
> & {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
};
|
||||
|
||||
export function EnvelopeBalanceMenuModal({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
categoryId,
|
||||
onCarryover,
|
||||
onTransfer,
|
||||
onCover,
|
||||
}: EnvelopeBalanceMenuModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
|
||||
<EnvelopeBalanceMenuModalInner
|
||||
name={name}
|
||||
categoryId={categoryId}
|
||||
onCarryover={onCarryover}
|
||||
onTransfer={onTransfer}
|
||||
onCover={onCover}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
EnvelopeBalanceMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function EnvelopeBalanceMenuModalInner({
|
||||
name = MODAL_NAME,
|
||||
categoryId,
|
||||
onCarryover,
|
||||
onTransfer,
|
||||
onCover,
|
||||
}: Omit<EnvelopeBalanceMenuModalProps, 'month'>) {
|
||||
const defaultMenuItemStyle: CSSProperties = {
|
||||
...styles.mobileMenuItem,
|
||||
color: theme.menuItemText,
|
||||
@@ -46,7 +76,7 @@ export function EnvelopeBalanceMenuModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="envelope-balance-menu">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, {
|
||||
} from 'react';
|
||||
|
||||
import { envelopeBudget } from 'loot-core/client/queries';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
|
||||
|
||||
import { useCategory } from '../../hooks/useCategory';
|
||||
@@ -21,21 +22,51 @@ import {
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'envelope-budget-menu' as const;
|
||||
|
||||
type EnvelopeBudgetMenuModalProps = ComponentPropsWithoutRef<
|
||||
typeof BudgetMenu
|
||||
> & {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
categoryId: string;
|
||||
onUpdateBudget: (amount: number) => void;
|
||||
};
|
||||
|
||||
export function EnvelopeBudgetMenuModal({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
categoryId,
|
||||
onUpdateBudget,
|
||||
onCopyLastMonthAverage,
|
||||
onSetMonthsAverage,
|
||||
onApplyBudgetTemplate,
|
||||
}: EnvelopeBudgetMenuModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
|
||||
<EnvelopeBudgetModalInner
|
||||
name={name}
|
||||
categoryId={categoryId}
|
||||
onUpdateBudget={onUpdateBudget}
|
||||
onCopyLastMonthAverage={onCopyLastMonthAverage}
|
||||
onSetMonthsAverage={onSetMonthsAverage}
|
||||
onApplyBudgetTemplate={onApplyBudgetTemplate}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
EnvelopeBudgetMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function EnvelopeBudgetModalInner({
|
||||
name,
|
||||
categoryId,
|
||||
onUpdateBudget,
|
||||
onCopyLastMonthAverage,
|
||||
onSetMonthsAverage,
|
||||
onApplyBudgetTemplate,
|
||||
}: Omit<EnvelopeBudgetMenuModalProps, 'month'>) {
|
||||
const defaultMenuItemStyle: CSSProperties = {
|
||||
...styles.mobileMenuItem,
|
||||
color: theme.menuItemText,
|
||||
@@ -62,7 +93,7 @@ export function EnvelopeBudgetMenuModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="envelope-budget-menu">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useState, type CSSProperties } from 'react';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
|
||||
import { useNotes } from '../../hooks/useNotes';
|
||||
import { useUndo } from '../../hooks/useUndo';
|
||||
@@ -15,14 +15,41 @@ import { Button } from '../common/Button2';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { View } from '../common/View';
|
||||
import { Notes } from '../Notes';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'envelope-budget-month-menu' as const;
|
||||
|
||||
type EnvelopeBudgetMonthMenuModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
onBudgetAction: (month: string, action: string, arg?: unknown) => void;
|
||||
onEditNotes: (month: string) => void;
|
||||
};
|
||||
|
||||
export function EnvelopeBudgetMonthMenuModal({
|
||||
name,
|
||||
month,
|
||||
onBudgetAction,
|
||||
onEditNotes,
|
||||
}: EnvelopeBudgetMonthMenuModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider
|
||||
key={name}
|
||||
value={monthUtils.sheetForMonth(month)}
|
||||
>
|
||||
<EnvelopeBudgetMonthMenuModalInner
|
||||
name={name}
|
||||
month={month}
|
||||
onBudgetAction={onBudgetAction}
|
||||
onEditNotes={onEditNotes}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
EnvelopeBudgetMonthMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function EnvelopeBudgetMonthMenuModalInner({
|
||||
name,
|
||||
month,
|
||||
onBudgetAction,
|
||||
onEditNotes,
|
||||
@@ -59,7 +86,7 @@ export function EnvelopeBudgetMonthMenuModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="envelope-budget-month-menu"
|
||||
name={name}
|
||||
containerProps={{
|
||||
style: { height: '50vh' },
|
||||
}}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { collapseModals, pushModal } from 'loot-core/client/actions';
|
||||
import { envelopeBudget } from 'loot-core/client/queries';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
import { groupById, integerToCurrency } from 'loot-core/shared/util';
|
||||
import { format, sheetForMonth, prevMonth } from 'loot-core/src/shared/months';
|
||||
|
||||
import { useCategories } from '../../hooks/useCategories';
|
||||
import { useUndo } from '../../hooks/useUndo';
|
||||
import { collapseModals, pushModal } from '../../state/actions';
|
||||
import { styles } from '../../style';
|
||||
import { ToBudgetAmount } from '../budget/envelope/budgetsummary/ToBudgetAmount';
|
||||
import { TotalsList } from '../budget/envelope/budgetsummary/TotalsList';
|
||||
@@ -15,12 +16,32 @@ import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponen
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'envelope-budget-summary' as const;
|
||||
|
||||
type EnvelopeBudgetSummaryModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
onBudgetAction: (month: string, action: string, arg?: unknown) => void;
|
||||
month: string;
|
||||
};
|
||||
|
||||
export function EnvelopeBudgetSummaryModal({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
onBudgetAction,
|
||||
}: EnvelopeBudgetSummaryModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
|
||||
<EnvelopeBudgetSummaryModalInner
|
||||
name={name}
|
||||
month={month}
|
||||
onBudgetAction={onBudgetAction}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function EnvelopeBudgetSummaryModalInner({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
onBudgetAction,
|
||||
}: EnvelopeBudgetSummaryModalProps) {
|
||||
@@ -108,7 +129,7 @@ export function EnvelopeBudgetSummaryModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="envelope-budget-summary">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -140,3 +161,4 @@ export function EnvelopeBudgetSummaryModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
EnvelopeBudgetSummaryModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,20 +3,51 @@ import React, {
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
|
||||
import { theme, styles } from '../../style';
|
||||
import { ToBudgetMenu } from '../budget/envelope/budgetsummary/ToBudgetMenu';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'envelope-summary-to-budget-menu' as const;
|
||||
|
||||
type EnvelopeToBudgetMenuModalProps = ComponentPropsWithoutRef<
|
||||
typeof ToBudgetMenu
|
||||
>;
|
||||
> & {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
};
|
||||
|
||||
export function EnvelopeToBudgetMenuModal({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
onTransfer,
|
||||
onCover,
|
||||
onHoldBuffer,
|
||||
onResetHoldBuffer,
|
||||
}: EnvelopeToBudgetMenuModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
|
||||
<EnvelopeToBudgetMenuModalInner
|
||||
name={name}
|
||||
onTransfer={onTransfer}
|
||||
onCover={onCover}
|
||||
onHoldBuffer={onHoldBuffer}
|
||||
onResetHoldBuffer={onResetHoldBuffer}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
EnvelopeToBudgetMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function EnvelopeToBudgetMenuModalInner({
|
||||
name = MODAL_NAME,
|
||||
onTransfer,
|
||||
onCover,
|
||||
onHoldBuffer,
|
||||
onResetHoldBuffer,
|
||||
}: Omit<EnvelopeToBudgetMenuModalProps, 'month'>) {
|
||||
const defaultMenuItemStyle: CSSProperties = {
|
||||
...styles.mobileMenuItem,
|
||||
color: theme.menuItemText,
|
||||
@@ -25,7 +56,7 @@ export function EnvelopeToBudgetMenuModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="envelope-summary-to-budget-menu">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Form } from 'react-aria-components';
|
||||
|
||||
import { type FinanceModals } from 'loot-core/src/client/state-types/modals';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import { getTestKeyError } from 'loot-core/src/shared/errors';
|
||||
|
||||
@@ -22,15 +21,21 @@ import { Paragraph } from '../common/Paragraph';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'fix-encryption-key' as const;
|
||||
|
||||
type FixEncryptionKeyModalProps = {
|
||||
options: FinanceModals['fix-encryption-key'];
|
||||
name: typeof MODAL_NAME;
|
||||
hasExistingKey?: boolean;
|
||||
cloudFileId?: string;
|
||||
onSuccess?: () => void;
|
||||
};
|
||||
|
||||
export function FixEncryptionKeyModal({
|
||||
options = {},
|
||||
name,
|
||||
hasExistingKey,
|
||||
cloudFileId,
|
||||
onSuccess,
|
||||
}: FixEncryptionKeyModalProps) {
|
||||
const { hasExistingKey, cloudFileId, onSuccess } = options;
|
||||
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -58,7 +63,7 @@ export function FixEncryptionKeyModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="fix-encryption-key">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -177,3 +182,4 @@ export function FixEncryptionKeyModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
FixEncryptionKeyModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/src/client/actions/modals';
|
||||
import { sendCatch } from 'loot-core/src/platform/client/fetch';
|
||||
import {
|
||||
type GoCardlessInstitution,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
|
||||
import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus';
|
||||
import { AnimatedLoading } from '../../icons/AnimatedLoading';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Error, Warning } from '../alerts';
|
||||
import { Autocomplete } from '../autocomplete/Autocomplete';
|
||||
@@ -23,6 +23,8 @@ import { View } from '../common/View';
|
||||
import { FormField, FormLabel } from '../forms';
|
||||
import { COUNTRY_OPTIONS } from '../util/countries';
|
||||
|
||||
const MODAL_NAME = 'gocardless-external-msg' as const;
|
||||
|
||||
function useAvailableBanks(country: string) {
|
||||
const [banks, setBanks] = useState<GoCardlessInstitution[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -72,7 +74,8 @@ function renderError(error: 'unknown' | 'timeout', t: (key: string) => string) {
|
||||
);
|
||||
}
|
||||
|
||||
type GoCardlessExternalMsgProps = {
|
||||
type GoCardlessExternalMsgModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
onMoveExternal: (arg: {
|
||||
institutionId: string;
|
||||
}) => Promise<{ error?: 'unknown' | 'timeout'; data?: GoCardlessToken }>;
|
||||
@@ -81,10 +84,11 @@ type GoCardlessExternalMsgProps = {
|
||||
};
|
||||
|
||||
export function GoCardlessExternalMsgModal({
|
||||
name = MODAL_NAME,
|
||||
onMoveExternal,
|
||||
onSuccess,
|
||||
onClose,
|
||||
}: GoCardlessExternalMsgProps) {
|
||||
}: GoCardlessExternalMsgModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -237,7 +241,7 @@ export function GoCardlessExternalMsgModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="gocardless-external-msg"
|
||||
name={name}
|
||||
onClose={onClose}
|
||||
containerProps={{ style: { width: '30vw' } }}
|
||||
>
|
||||
@@ -320,3 +324,4 @@ export function GoCardlessExternalMsgModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
GoCardlessExternalMsgModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -18,11 +18,15 @@ import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { FormField, FormLabel } from '../forms';
|
||||
|
||||
const MODAL_NAME = 'gocardless-init' as const;
|
||||
|
||||
type GoCardlessInitialiseProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
onSuccess: () => void;
|
||||
};
|
||||
|
||||
export const GoCardlessInitialiseModal = ({
|
||||
name = MODAL_NAME,
|
||||
onSuccess,
|
||||
}: GoCardlessInitialiseProps) => {
|
||||
const [secretId, setSecretId] = useState('');
|
||||
@@ -55,7 +59,7 @@ export const GoCardlessInitialiseModal = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="gocardless-init" containerProps={{ style: { width: '30vw' } }}>
|
||||
<Modal name={name} containerProps={{ style: { width: '30vw' } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -128,3 +132,4 @@ export const GoCardlessInitialiseModal = ({
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
GoCardlessInitialiseModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -7,10 +7,18 @@ import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { TableHeader, Row, Field } from '../table';
|
||||
|
||||
export function GoalTemplateModal() {
|
||||
const MODAL_NAME = 'goal-templates' as const;
|
||||
|
||||
type GoalTemplateModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function GoalTemplateModal({
|
||||
name = MODAL_NAME,
|
||||
}: GoalTemplateModalProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal name="goal-templates" containerProps={{ style: { width: 850 } }}>
|
||||
<Modal name={name} containerProps={{ style: { width: 850 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -177,3 +185,4 @@ export function GoalTemplateModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
GoalTemplateModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { envelopeBudget } from 'loot-core/client/queries';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
|
||||
import { styles } from '../../style';
|
||||
import { useEnvelopeSheetValue } from '../budget/envelope/EnvelopeBudgetComponents';
|
||||
@@ -9,14 +10,34 @@ import { InitialFocus } from '../common/InitialFocus';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { View } from '../common/View';
|
||||
import { FieldLabel } from '../mobile/MobileForms';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
import { AmountInput } from '../util/AmountInput';
|
||||
|
||||
const MODAL_NAME = 'hold-buffer' as const;
|
||||
|
||||
type HoldBufferModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
onSubmit: (amount: number) => void;
|
||||
};
|
||||
|
||||
export function HoldBufferModal({ onSubmit }: HoldBufferModalProps) {
|
||||
export function HoldBufferModal({
|
||||
name,
|
||||
month,
|
||||
onSubmit,
|
||||
}: HoldBufferModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
|
||||
<HoldBufferModalInner name={name} onSubmit={onSubmit} />
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
HoldBufferModal.modalName = MODAL_NAME;
|
||||
|
||||
function HoldBufferModalInner({
|
||||
name,
|
||||
onSubmit,
|
||||
}: Omit<HoldBufferModalProps, 'month'>) {
|
||||
const available = useEnvelopeSheetValue(envelopeBudget.toBudget) ?? 0;
|
||||
const [amount, setAmount] = useState<number>(0);
|
||||
|
||||
@@ -27,7 +48,7 @@ export function HoldBufferModal({ onSubmit }: HoldBufferModalProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="hold-buffer">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
|
||||
@@ -32,6 +32,8 @@ import {
|
||||
stripCsvImportTransaction,
|
||||
} from './utils';
|
||||
|
||||
const MODAL_NAME = 'import-transactions';
|
||||
|
||||
function getFileType(filepath) {
|
||||
const m = filepath.match(/\.([^.]*)$/);
|
||||
if (!m) return 'ofx';
|
||||
@@ -133,7 +135,13 @@ function parseCategoryFields(trans, categories) {
|
||||
return match;
|
||||
}
|
||||
|
||||
export function ImportTransactionsModal({ options }) {
|
||||
export function ImportTransactionsModal({
|
||||
name = MODAL_NAME,
|
||||
accountId,
|
||||
filename: originalFilename,
|
||||
onImported,
|
||||
categories,
|
||||
}) {
|
||||
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
|
||||
const [prefs, savePrefs] = useSyncedPrefs();
|
||||
const {
|
||||
@@ -146,7 +154,7 @@ export function ImportTransactionsModal({ options }) {
|
||||
const [multiplierAmount, setMultiplierAmount] = useState('');
|
||||
const [loadingState, setLoadingState] = useState('parsing');
|
||||
const [error, setError] = useState(null);
|
||||
const [filename, setFilename] = useState(options.filename);
|
||||
const [filename, setFilename] = useState(originalFilename);
|
||||
const [transactions, setTransactions] = useState([]);
|
||||
const [filetype, setFileType] = useState(null);
|
||||
const [fieldMappings, setFieldMappings] = useState(null);
|
||||
@@ -154,7 +162,6 @@ export function ImportTransactionsModal({ options }) {
|
||||
const [flipAmount, setFlipAmount] = useState(false);
|
||||
const [multiplierEnabled, setMultiplierEnabled] = useState(false);
|
||||
const [reconcile, setReconcile] = useState(true);
|
||||
const { accountId, categories, onImported } = options;
|
||||
|
||||
// This cannot be set after parsing the file, because changing it
|
||||
// requires re-parsing the file. This is different from the other
|
||||
@@ -416,7 +423,7 @@ export function ImportTransactionsModal({ options }) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fileType = getFileType(options.filename);
|
||||
const fileType = getFileType(originalFilename);
|
||||
const parseOptions = getParseOptions(fileType, {
|
||||
delimiter,
|
||||
hasHeaderRow,
|
||||
@@ -424,10 +431,10 @@ export function ImportTransactionsModal({ options }) {
|
||||
fallbackMissingPayeeToMemo,
|
||||
});
|
||||
|
||||
parse(options.filename, parseOptions);
|
||||
parse(originalFilename, parseOptions);
|
||||
}, [
|
||||
parseTransactions,
|
||||
options.filename,
|
||||
originalFilename,
|
||||
delimiter,
|
||||
hasHeaderRow,
|
||||
skipLines,
|
||||
@@ -718,7 +725,7 @@ export function ImportTransactionsModal({ options }) {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="import-transactions"
|
||||
name={name}
|
||||
isLoading={loadingState === 'parsing'}
|
||||
containerProps={{ style: { width: 800 } }}
|
||||
>
|
||||
@@ -1069,6 +1076,7 @@ export function ImportTransactionsModal({ options }) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ImportTransactionsModal.modalName = MODAL_NAME;
|
||||
|
||||
function getParseOptions(fileType, options = {}) {
|
||||
if (fileType === 'csv') {
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'keyboard-shortcuts' as const;
|
||||
|
||||
type KeyIconProps = {
|
||||
shortcut: string;
|
||||
style?: CSSProperties;
|
||||
@@ -148,13 +150,19 @@ function Shortcut({
|
||||
);
|
||||
}
|
||||
|
||||
export function KeyboardShortcutModal() {
|
||||
type KeyboardShortcutModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function KeyboardShortcutModal({
|
||||
name = MODAL_NAME,
|
||||
}: KeyboardShortcutModalProps) {
|
||||
const location = useLocation();
|
||||
const onBudget = location.pathname.startsWith('/budget');
|
||||
const onAccounts = location.pathname.startsWith('/accounts');
|
||||
const ctrl = Platform.OS === 'mac' ? '⌘' : 'Ctrl';
|
||||
return (
|
||||
<Modal name="keyboard-shortcuts">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -340,3 +348,4 @@ export function KeyboardShortcutModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
KeyboardShortcutModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { loadBackup, makeBackup } from 'loot-core/client/actions';
|
||||
import { type Backup } from 'loot-core/server/backups';
|
||||
import { send, listen, unlisten } from 'loot-core/src/platform/client/fetch';
|
||||
|
||||
import { useMetadataPref } from '../../hooks/useMetadataPref';
|
||||
import { loadBackup, makeBackup } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Block } from '../common/Block';
|
||||
import { Button } from '../common/Button2';
|
||||
@@ -14,6 +14,8 @@ import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { Row, Cell } from '../table';
|
||||
|
||||
const MODAL_NAME = 'load-backup' as const;
|
||||
|
||||
type BackupTableProps = {
|
||||
backups: Backup[];
|
||||
onSelect: (backupId: string) => void;
|
||||
@@ -41,14 +43,16 @@ function BackupTable({ backups, onSelect }: BackupTableProps) {
|
||||
}
|
||||
|
||||
type LoadBackupModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
budgetId: string;
|
||||
watchUpdates: boolean;
|
||||
backupDisabled: boolean;
|
||||
watchUpdates?: boolean;
|
||||
backupDisabled?: boolean;
|
||||
};
|
||||
|
||||
export function LoadBackupModal({
|
||||
name = MODAL_NAME,
|
||||
budgetId,
|
||||
watchUpdates,
|
||||
watchUpdates = true,
|
||||
backupDisabled,
|
||||
}: LoadBackupModalProps) {
|
||||
const dispatch = useDispatch();
|
||||
@@ -75,7 +79,7 @@ export function LoadBackupModal({
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal name="load-backup" containerProps={{ style: { maxWidth: '30vw' } }}>
|
||||
<Modal name={name} containerProps={{ style: { maxWidth: '30vw' } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -146,3 +150,4 @@ export function LoadBackupModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
LoadBackupModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -7,11 +7,17 @@ import { isNonProductionEnvironment } from 'loot-core/src/shared/environment';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { ManageRules } from '../ManageRules';
|
||||
|
||||
const MODAL_NAME = 'manage-rules' as const;
|
||||
|
||||
type ManageRulesModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
payeeId?: string;
|
||||
};
|
||||
|
||||
export function ManageRulesModal({ payeeId }: ManageRulesModalProps) {
|
||||
export function ManageRulesModal({
|
||||
name = MODAL_NAME,
|
||||
payeeId,
|
||||
}: ManageRulesModalProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const location = useLocation();
|
||||
if (isNonProductionEnvironment()) {
|
||||
@@ -23,7 +29,7 @@ export function ManageRulesModal({ payeeId }: ManageRulesModalProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="manage-rules" isLoading={loading}>
|
||||
<Modal name={name} isLoading={loading}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -36,3 +42,4 @@ export function ManageRulesModal({ payeeId }: ManageRulesModalProps) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ManageRulesModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { replaceModal } from 'loot-core/src/client/actions/modals';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
|
||||
import { usePayees } from '../../hooks/usePayees';
|
||||
import { replaceModal } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Information } from '../alerts';
|
||||
import { Button } from '../common/Button2';
|
||||
@@ -13,9 +13,15 @@ import { Paragraph } from '../common/Paragraph';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
const MODAL_NAME = 'merge-unused-payees';
|
||||
|
||||
const highlightStyle = { color: theme.pageTextPositive };
|
||||
|
||||
export function MergeUnusedPayeesModal({ payeeIds, targetPayeeId }) {
|
||||
export function MergeUnusedPayeesModal({
|
||||
name = MODAL_NAME,
|
||||
payeeIds,
|
||||
targetPayeeId,
|
||||
}) {
|
||||
const allPayees = usePayees();
|
||||
const modalStack = useSelector(state => state.modals.modalStack);
|
||||
const isEditingRule = !!modalStack.find(m => m.name === 'edit-rule');
|
||||
@@ -76,7 +82,7 @@ export function MergeUnusedPayeesModal({ payeeIds, targetPayeeId }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="merge-unused-payees">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<View style={{ padding: 20, maxWidth: 500 }}>
|
||||
<View>
|
||||
@@ -181,3 +187,4 @@ export function MergeUnusedPayeesModal({ payeeIds, targetPayeeId }) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
MergeUnusedPayeesModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -8,13 +8,21 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { View } from '../common/View';
|
||||
import { Notes } from '../Notes';
|
||||
|
||||
const MODAL_NAME = 'notes' as const;
|
||||
|
||||
type NotesModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
onSave: (id: string, notes: string) => void;
|
||||
};
|
||||
|
||||
export function NotesModal({ id, name, onSave }: NotesModalProps) {
|
||||
export function NotesModal({
|
||||
name = MODAL_NAME,
|
||||
id,
|
||||
title,
|
||||
onSave,
|
||||
}: NotesModalProps) {
|
||||
const originalNotes = useNotes(id);
|
||||
|
||||
const [notes, setNotes] = useState(originalNotes);
|
||||
@@ -28,7 +36,7 @@ export function NotesModal({ id, name, onSave }: NotesModalProps) {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="notes"
|
||||
name={name}
|
||||
containerProps={{
|
||||
style: { height: '50vh' },
|
||||
}}
|
||||
@@ -36,7 +44,7 @@ export function NotesModal({ id, name, onSave }: NotesModalProps) {
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
title={`Notes: ${name}`}
|
||||
title={`Notes: ${title}`}
|
||||
rightContent={<ModalCloseButton onPress={close} />}
|
||||
/>
|
||||
<View
|
||||
@@ -87,3 +95,4 @@ export function NotesModal({ id, name, onSave }: NotesModalProps) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
NotesModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,8 +2,7 @@ import React from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { closeBudget } from 'loot-core/client/actions';
|
||||
|
||||
import { closeBudget } from '../../state/actions';
|
||||
import { Button } from '../common/Button2';
|
||||
import { Link } from '../common/Link';
|
||||
import { Modal, ModalHeader, ModalTitle } from '../common/Modal';
|
||||
@@ -11,7 +10,15 @@ import { Paragraph } from '../common/Paragraph';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
export function OutOfSyncMigrationsModal() {
|
||||
const MODAL_NAME = 'out-of-sync-migrations' as const;
|
||||
|
||||
type OutOfSyncMigrationsModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function OutOfSyncMigrationsModal({
|
||||
name,
|
||||
}: OutOfSyncMigrationsModalProps) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -22,7 +29,7 @@ export function OutOfSyncMigrationsModal() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="out-of-sync-migrations">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -89,3 +96,4 @@ export function OutOfSyncMigrationsModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
OutOfSyncMigrationsModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -13,12 +13,16 @@ import {
|
||||
ModalHeader,
|
||||
} from '../common/Modal';
|
||||
|
||||
const MODAL_NAME = 'payee-autocomplete' as const;
|
||||
|
||||
type PayeeAutocompleteModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
autocompleteProps: ComponentPropsWithoutRef<typeof PayeeAutocomplete>;
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export function PayeeAutocompleteModal({
|
||||
name = MODAL_NAME,
|
||||
autocompleteProps,
|
||||
onClose,
|
||||
}: PayeeAutocompleteModalProps) {
|
||||
@@ -35,7 +39,7 @@ export function PayeeAutocompleteModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="payee-autocomplete"
|
||||
name={name}
|
||||
noAnimation={!isNarrowWidth}
|
||||
onClose={onClose}
|
||||
containerProps={{
|
||||
@@ -81,3 +85,4 @@ export function PayeeAutocompleteModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
PayeeAutocompleteModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -19,9 +19,14 @@ import {
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps;
|
||||
const MODAL_NAME = 'scheduled-transaction-menu' as const;
|
||||
|
||||
type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps & {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function ScheduledTransactionMenuModal({
|
||||
name = MODAL_NAME,
|
||||
transactionId,
|
||||
onSkip,
|
||||
onPost,
|
||||
@@ -46,7 +51,7 @@ export function ScheduledTransactionMenuModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="scheduled-transaction-menu">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -78,6 +83,7 @@ export function ScheduledTransactionMenuModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ScheduledTransactionMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
type ScheduledTransactionMenuProps = Omit<
|
||||
ComponentPropsWithoutRef<typeof Menu>,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useAccounts } from '../../hooks/useAccounts';
|
||||
import {
|
||||
closeModal,
|
||||
linkAccount,
|
||||
linkAccountSimpleFin,
|
||||
unlinkAccount,
|
||||
} from 'loot-core/client/actions';
|
||||
|
||||
import { useAccounts } from '../../hooks/useAccounts';
|
||||
} from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Autocomplete } from '../autocomplete/Autocomplete';
|
||||
import { Button } from '../common/Button2';
|
||||
@@ -18,6 +17,8 @@ import { View } from '../common/View';
|
||||
import { PrivacyFilter } from '../PrivacyFilter';
|
||||
import { TableHeader, Table, Row, Field } from '../table';
|
||||
|
||||
const MODAL_NAME = 'select-linked-accounts';
|
||||
|
||||
const addOnBudgetAccountOption = { id: 'new-on', name: 'Create new account' };
|
||||
const addOffBudgetAccountOption = {
|
||||
id: 'new-off',
|
||||
@@ -25,7 +26,8 @@ const addOffBudgetAccountOption = {
|
||||
};
|
||||
|
||||
export function SelectLinkedAccountsModal({
|
||||
requisitionId,
|
||||
name = MODAL_NAME,
|
||||
requisitionId = '',
|
||||
externalAccounts,
|
||||
syncSource,
|
||||
}) {
|
||||
@@ -114,10 +116,7 @@ export function SelectLinkedAccountsModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="select-linked-accounts"
|
||||
containerProps={{ style: { width: 800 } }}
|
||||
>
|
||||
<Modal name={name} containerProps={{ style: { width: 800 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -260,3 +259,4 @@ function TableRow({
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
SelectLinkedAccountsModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -17,11 +17,15 @@ import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { FormField, FormLabel } from '../forms';
|
||||
|
||||
const MODAL_NAME = 'simplefin-init' as const;
|
||||
|
||||
type SimpleFinInitialiseProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
onSuccess: () => void;
|
||||
};
|
||||
|
||||
export const SimpleFinInitialiseModal = ({
|
||||
name = MODAL_NAME,
|
||||
onSuccess,
|
||||
}: SimpleFinInitialiseProps) => {
|
||||
const [token, setToken] = useState('');
|
||||
@@ -47,7 +51,7 @@ export const SimpleFinInitialiseModal = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="simplefin-init" containerProps={{ style: { width: 300 } }}>
|
||||
<Modal name={name} containerProps={{ style: { width: 300 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -102,3 +106,4 @@ export const SimpleFinInitialiseModal = ({
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
SimpleFinInitialiseModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -20,7 +20,7 @@ type SingleInputModalProps = {
|
||||
Header: ComponentType<ComponentPropsWithoutRef<typeof ModalHeader>>;
|
||||
buttonText: string;
|
||||
onSubmit: (value: string) => void;
|
||||
onValidate?: (value: string) => string[];
|
||||
onValidate?: (value: string) => string;
|
||||
inputPlaceholder?: string;
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ export function SingleInputModal({
|
||||
inputPlaceholder,
|
||||
}: SingleInputModalProps) {
|
||||
const [value, setValue] = useState('');
|
||||
const [errorMessage, setErrorMessage] = useState(null);
|
||||
const [errorMessage, setErrorMessage] = useState<string>(null);
|
||||
|
||||
const _onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, {
|
||||
} from 'react';
|
||||
|
||||
import { trackingBudget } from 'loot-core/client/queries';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
|
||||
import { useCategory } from '../../hooks/useCategory';
|
||||
import { theme, styles } from '../../style';
|
||||
@@ -21,15 +22,40 @@ import {
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { CellValueText } from '../spreadsheet/CellValue';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'tracking-balance-menu' as const;
|
||||
|
||||
type TrackingBalanceMenuModalProps = ComponentPropsWithoutRef<
|
||||
typeof BalanceMenu
|
||||
>;
|
||||
> & {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
};
|
||||
|
||||
export function TrackingBalanceMenuModal({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
categoryId,
|
||||
onCarryover,
|
||||
}: TrackingBalanceMenuModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
|
||||
<TrackingBalanceMenuModalInner
|
||||
name={name}
|
||||
categoryId={categoryId}
|
||||
onCarryover={onCarryover}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
TrackingBalanceMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function TrackingBalanceMenuModalInner({
|
||||
name = MODAL_NAME,
|
||||
categoryId,
|
||||
onCarryover,
|
||||
}: Omit<TrackingBalanceMenuModalProps, 'month'>) {
|
||||
const defaultMenuItemStyle: CSSProperties = {
|
||||
...styles.mobileMenuItem,
|
||||
color: theme.menuItemText,
|
||||
@@ -44,7 +70,7 @@ export function TrackingBalanceMenuModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="tracking-balance-menu">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, {
|
||||
} from 'react';
|
||||
|
||||
import { trackingBudget } from 'loot-core/client/queries';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
|
||||
|
||||
import { useCategory } from '../../hooks/useCategory';
|
||||
@@ -21,21 +22,51 @@ import {
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { FocusableAmountInput } from '../mobile/transactions/FocusableAmountInput';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'tracking-budget-menu' as const;
|
||||
|
||||
type TrackingBudgetMenuModalProps = ComponentPropsWithoutRef<
|
||||
typeof BudgetMenu
|
||||
> & {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
categoryId: string;
|
||||
onUpdateBudget: (amount: number) => void;
|
||||
};
|
||||
|
||||
export function TrackingBudgetMenuModal({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
categoryId,
|
||||
onUpdateBudget,
|
||||
onCopyLastMonthAverage,
|
||||
onSetMonthsAverage,
|
||||
onApplyBudgetTemplate,
|
||||
}: TrackingBudgetMenuModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month)}>
|
||||
<TrackingBudgetMenuModalInner
|
||||
name={name}
|
||||
categoryId={categoryId}
|
||||
onUpdateBudget={onUpdateBudget}
|
||||
onCopyLastMonthAverage={onCopyLastMonthAverage}
|
||||
onSetMonthsAverage={onSetMonthsAverage}
|
||||
onApplyBudgetTemplate={onApplyBudgetTemplate}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
TrackingBudgetMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function TrackingBudgetMenuModalInner({
|
||||
name = MODAL_NAME,
|
||||
categoryId,
|
||||
onUpdateBudget,
|
||||
onCopyLastMonthAverage,
|
||||
onSetMonthsAverage,
|
||||
onApplyBudgetTemplate,
|
||||
}: Omit<TrackingBudgetMenuModalProps, 'month'>) {
|
||||
const defaultMenuItemStyle: CSSProperties = {
|
||||
...styles.mobileMenuItem,
|
||||
color: theme.menuItemText,
|
||||
@@ -62,7 +93,7 @@ export function TrackingBudgetMenuModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="tracking-budget-menu">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
|
||||
@@ -15,14 +15,41 @@ import { Button } from '../common/Button2';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { View } from '../common/View';
|
||||
import { Notes } from '../Notes';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'tracking-budget-month-menu' as const;
|
||||
|
||||
type TrackingBudgetMonthMenuModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
onBudgetAction: (month: string, action: string, arg?: unknown) => void;
|
||||
onEditNotes: (month: string) => void;
|
||||
};
|
||||
|
||||
export function TrackingBudgetMonthMenuModal({
|
||||
name,
|
||||
month,
|
||||
onBudgetAction,
|
||||
onEditNotes,
|
||||
}: TrackingBudgetMonthMenuModalProps) {
|
||||
return (
|
||||
<NamespaceContext.Provider
|
||||
key={name}
|
||||
value={monthUtils.sheetForMonth(month)}
|
||||
>
|
||||
<TrackingBudgetMonthMenuModalInner
|
||||
name={name}
|
||||
month={month}
|
||||
onBudgetAction={onBudgetAction}
|
||||
onEditNotes={onEditNotes}
|
||||
/>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
TrackingBudgetMonthMenuModal.modalName = MODAL_NAME;
|
||||
|
||||
function TrackingBudgetMonthMenuModalInner({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
onBudgetAction,
|
||||
onEditNotes,
|
||||
@@ -59,7 +86,7 @@ export function TrackingBudgetMonthMenuModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="tracking-budget-month-menu"
|
||||
name={name}
|
||||
containerProps={{
|
||||
style: { height: '50vh' },
|
||||
}}
|
||||
|
||||
@@ -11,16 +11,20 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
import { Stack } from '../common/Stack';
|
||||
import { NamespaceContext } from '../spreadsheet/NamespaceContext';
|
||||
|
||||
const MODAL_NAME = 'tracking-budget-summary' as const;
|
||||
|
||||
type TrackingBudgetSummaryModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
month: string;
|
||||
};
|
||||
|
||||
export function TrackingBudgetSummaryModal({
|
||||
name = MODAL_NAME,
|
||||
month,
|
||||
}: TrackingBudgetSummaryModalProps) {
|
||||
const currentMonth = monthUtils.currentMonth();
|
||||
return (
|
||||
<Modal name="tracking-budget-summary">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -49,3 +53,4 @@ export function TrackingBudgetSummaryModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
TrackingBudgetSummaryModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,10 +2,10 @@ import React, { useMemo, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { type CategoryEntity } from 'loot-core/types/models';
|
||||
|
||||
import { useCategories } from '../../hooks/useCategories';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { styles } from '../../style';
|
||||
import {
|
||||
addToBeBudgetedGroup,
|
||||
@@ -18,7 +18,10 @@ import { View } from '../common/View';
|
||||
import { FieldLabel, TapField } from '../mobile/MobileForms';
|
||||
import { AmountInput } from '../util/AmountInput';
|
||||
|
||||
const MODAL_NAME = 'transfer' as const;
|
||||
|
||||
type TransferModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
title: string;
|
||||
categoryId?: CategoryEntity['id'];
|
||||
month: string;
|
||||
@@ -28,6 +31,7 @@ type TransferModalProps = {
|
||||
};
|
||||
|
||||
export function TransferModal({
|
||||
name = MODAL_NAME,
|
||||
title,
|
||||
categoryId,
|
||||
month,
|
||||
@@ -60,12 +64,15 @@ export function TransferModal({
|
||||
const openCategoryModal = () => {
|
||||
dispatch(
|
||||
pushModal('category-autocomplete', {
|
||||
categoryGroups,
|
||||
month,
|
||||
showHiddenCategories: true,
|
||||
onSelect: categoryId => {
|
||||
setToCategoryId(categoryId);
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
categoryGroups,
|
||||
showHiddenCategories: true,
|
||||
onSelect: categoryId => {
|
||||
setToCategoryId(categoryId);
|
||||
},
|
||||
},
|
||||
month,
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -79,7 +86,7 @@ export function TransferModal({
|
||||
const toCategory = categories.find(c => c.id === toCategoryId);
|
||||
|
||||
return (
|
||||
<Modal name="transfer">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -145,3 +152,4 @@ export function TransferModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
TransferModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,9 +2,8 @@ import React, { useCallback, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { addNotification } from 'loot-core/client/actions';
|
||||
|
||||
import { useGlobalPref } from '../../../hooks/useGlobalPref';
|
||||
import { addNotification } from '../../../state/actions';
|
||||
import { theme, styles } from '../../../style';
|
||||
import { Information } from '../../alerts';
|
||||
import { Button, ButtonWithLoading } from '../../common/Button2';
|
||||
@@ -13,6 +12,8 @@ import { Text } from '../../common/Text';
|
||||
import { View } from '../../common/View';
|
||||
import { Checkbox } from '../../forms';
|
||||
|
||||
const MODAL_NAME = 'confirm-change-document-dir' as const;
|
||||
|
||||
function DirectoryDisplay({ directory }: { directory: string }) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', gap: '0.5rem', width: '100%' }}>
|
||||
@@ -37,13 +38,17 @@ function DirectoryDisplay({ directory }: { directory: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function ConfirmChangeDocumentDirModal({
|
||||
currentBudgetDirectory,
|
||||
newDirectory,
|
||||
}: {
|
||||
type ConfirmChangeDocumentDirModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
currentBudgetDirectory: string;
|
||||
newDirectory: string;
|
||||
}) {
|
||||
};
|
||||
|
||||
export function ConfirmChangeDocumentDirModal({
|
||||
name = MODAL_NAME,
|
||||
currentBudgetDirectory,
|
||||
newDirectory,
|
||||
}: ConfirmChangeDocumentDirModalProps) {
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [moveFiles, setMoveFiles] = useState(false);
|
||||
@@ -92,7 +97,7 @@ export function ConfirmChangeDocumentDirModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="confirm-change-document-dir">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -206,3 +211,4 @@ export function ConfirmChangeDocumentDirModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ConfirmChangeDocumentDirModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,20 +2,23 @@ import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { deleteBudget } from 'loot-core/client/actions';
|
||||
import { type File } from 'loot-core/src/types/file';
|
||||
|
||||
import { deleteBudget } from '../../../state/actions';
|
||||
import { theme } from '../../../style';
|
||||
import { ButtonWithLoading } from '../../common/Button2';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
|
||||
import { Text } from '../../common/Text';
|
||||
import { View } from '../../common/View';
|
||||
|
||||
const MODAL_NAME = 'delete-budget' as const;
|
||||
|
||||
type DeleteFileProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
file: File;
|
||||
};
|
||||
|
||||
export function DeleteFileModal({ file }: DeleteFileProps) {
|
||||
export function DeleteFileModal({ name = MODAL_NAME, file }: DeleteFileProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// If the state is "broken" that means it was created by another
|
||||
@@ -29,7 +32,7 @@ export function DeleteFileModal({ file }: DeleteFileProps) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal name="delete-budget">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -152,3 +155,4 @@ export function DeleteFileModal({ file }: DeleteFileProps) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
DeleteFileModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,16 +2,17 @@ import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { loadAllFiles, pushModal } from 'loot-core/client/actions';
|
||||
|
||||
import { useGlobalPref } from '../../../hooks/useGlobalPref';
|
||||
import { SvgPencil1 } from '../../../icons/v2';
|
||||
import { loadAllFiles, pushModal } from '../../../state/actions';
|
||||
import { theme, styles } from '../../../style';
|
||||
import { Button } from '../../common/Button2';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
|
||||
import { Text } from '../../common/Text';
|
||||
import { View } from '../../common/View';
|
||||
|
||||
const MODAL_NAME = 'files-settings' as const;
|
||||
|
||||
function FileLocationSettings() {
|
||||
const [documentDir, _setDocumentDirPref] = useGlobalPref('documentDir');
|
||||
const [_documentDirChanged, setDirChanged] = useState(false);
|
||||
@@ -141,7 +142,11 @@ function SelfSignedCertLocationSettings() {
|
||||
);
|
||||
}
|
||||
|
||||
export function FilesSettingsModal() {
|
||||
type FilesSettingsModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function FilesSettingsModal({ name }: FilesSettingsModalProps) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
function closeModal(close: () => void) {
|
||||
@@ -150,7 +155,7 @@ export function FilesSettingsModal() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="files-settings">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -188,3 +193,4 @@ export function FilesSettingsModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
FilesSettingsModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,8 +3,7 @@ import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { importBudget } from 'loot-core/src/client/actions/budgets';
|
||||
|
||||
import { importBudget } from '../../../state/actions';
|
||||
import { styles, theme } from '../../../style';
|
||||
import { Block } from '../../common/Block';
|
||||
import { ButtonWithLoading } from '../../common/Button2';
|
||||
@@ -12,6 +11,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
|
||||
import { Paragraph } from '../../common/Paragraph';
|
||||
import { View } from '../../common/View';
|
||||
|
||||
const MODAL_NAME = 'import-actual' as const;
|
||||
|
||||
function getErrorMessage(error: string): string {
|
||||
switch (error) {
|
||||
case 'parse-error':
|
||||
@@ -29,7 +30,13 @@ function getErrorMessage(error: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function ImportActualModal() {
|
||||
type ImportActualModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function ImportActualModal({
|
||||
name = MODAL_NAME,
|
||||
}: ImportActualModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -55,7 +62,7 @@ export function ImportActualModal() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="import-actual" containerProps={{ style: { width: 400 } }}>
|
||||
<Modal name={name} containerProps={{ style: { width: 400 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -103,3 +110,4 @@ export function ImportActualModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ImportActualModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
|
||||
import { pushModal } from '../../../state/actions';
|
||||
import { styles, theme } from '../../../style';
|
||||
import { Block } from '../../common/Block';
|
||||
import { Button } from '../../common/Button2';
|
||||
@@ -11,6 +10,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
|
||||
import { Text } from '../../common/Text';
|
||||
import { View } from '../../common/View';
|
||||
|
||||
const MODAL_NAME = 'import' as const;
|
||||
|
||||
function getErrorMessage(error: 'not-ynab4' | boolean) {
|
||||
switch (error) {
|
||||
case 'not-ynab4':
|
||||
@@ -20,7 +21,11 @@ function getErrorMessage(error: 'not-ynab4' | boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
export function ImportModal() {
|
||||
type ImportModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function ImportModal({ name = MODAL_NAME }: ImportModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -50,7 +55,7 @@ export function ImportModal() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal name="import" containerProps={{ style: { width: 400 } }}>
|
||||
<Modal name={name} containerProps={{ style: { width: 400 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -97,3 +102,4 @@ export function ImportModal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ImportModal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,8 +3,7 @@ import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { importBudget } from 'loot-core/src/client/actions/budgets';
|
||||
|
||||
import { importBudget } from '../../../state/actions';
|
||||
import { styles, theme } from '../../../style';
|
||||
import { Block } from '../../common/Block';
|
||||
import { ButtonWithLoading } from '../../common/Button2';
|
||||
@@ -12,6 +11,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
|
||||
import { Paragraph } from '../../common/Paragraph';
|
||||
import { View } from '../../common/View';
|
||||
|
||||
const MODAL_NAME = 'import-ynab4' as const;
|
||||
|
||||
function getErrorMessage(error: string): string {
|
||||
switch (error) {
|
||||
case 'not-ynab4':
|
||||
@@ -21,7 +22,11 @@ function getErrorMessage(error: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function ImportYNAB4Modal() {
|
||||
type ImportYNAB4ModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function ImportYNAB4Modal({ name = MODAL_NAME }: ImportYNAB4ModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -47,7 +52,7 @@ export function ImportYNAB4Modal() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="import-ynab4" containerProps={{ style: { width: 400 } }}>
|
||||
<Modal name={name} containerProps={{ style: { width: 400 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -96,3 +101,4 @@ export function ImportYNAB4Modal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ImportYNAB4Modal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,8 +3,7 @@ import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { importBudget } from 'loot-core/src/client/actions/budgets';
|
||||
|
||||
import { importBudget } from '../../../state/actions';
|
||||
import { styles, theme } from '../../../style';
|
||||
import { Block } from '../../common/Block';
|
||||
import { ButtonWithLoading } from '../../common/Button2';
|
||||
@@ -13,6 +12,8 @@ import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal';
|
||||
import { Paragraph } from '../../common/Paragraph';
|
||||
import { View } from '../../common/View';
|
||||
|
||||
const MODAL_NAME = 'import-ynab5' as const;
|
||||
|
||||
function getErrorMessage(error: string): string {
|
||||
switch (error) {
|
||||
case 'parse-error':
|
||||
@@ -24,7 +25,11 @@ function getErrorMessage(error: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function ImportYNAB5Modal() {
|
||||
type ImportYNAB5ModalProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function ImportYNAB5Modal({ name = MODAL_NAME }: ImportYNAB5ModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -50,7 +55,7 @@ export function ImportYNAB5Modal() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name="import-ynab5" containerProps={{ style: { width: 400 } }}>
|
||||
<Modal name={name} containerProps={{ style: { width: 400 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -108,3 +113,4 @@ export function ImportYNAB5Modal() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ImportYNAB5Modal.modalName = MODAL_NAME;
|
||||
|
||||
@@ -5,10 +5,6 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
addNotification,
|
||||
removeNotification,
|
||||
} from 'loot-core/src/client/actions';
|
||||
import { useDashboard } from 'loot-core/src/client/data-hooks/dashboard';
|
||||
import { useReports } from 'loot-core/src/client/data-hooks/reports';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
@@ -23,6 +19,7 @@ import { useAccounts } from '../../hooks/useAccounts';
|
||||
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
|
||||
import { useNavigate } from '../../hooks/useNavigate';
|
||||
import { useResponsive } from '../../ResponsiveProvider';
|
||||
import { addNotification, removeNotification } from '../../state/actions';
|
||||
import { breakpoints } from '../../tokens';
|
||||
import { Button } from '../common/Button2';
|
||||
import { Menu } from '../common/Menu';
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useParams } from 'react-router-dom';
|
||||
|
||||
import * as d from 'date-fns';
|
||||
|
||||
import { addNotification } from 'loot-core/client/actions';
|
||||
import { useWidget } from 'loot-core/client/data-hooks/widget';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
import { useFilters } from '../../../hooks/useFilters';
|
||||
import { useNavigate } from '../../../hooks/useNavigate';
|
||||
import { useResponsive } from '../../../ResponsiveProvider';
|
||||
import { addNotification } from '../../../state/actions';
|
||||
import { theme } from '../../../style';
|
||||
import { AlignedText } from '../../common/AlignedText';
|
||||
import { Block } from '../../common/Block';
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { send, sendCatch } from 'loot-core/platform/client/fetch/index';
|
||||
import { addNotification } from 'loot-core/src/client/actions';
|
||||
import { calculateHasWarning } from 'loot-core/src/client/reports';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import { type CustomReportEntity } from 'loot-core/types/models/reports';
|
||||
@@ -13,6 +12,7 @@ import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
|
||||
import { usePayees } from '../../../hooks/usePayees';
|
||||
import { useSyncedPref } from '../../../hooks/useSyncedPref';
|
||||
import { SvgExclamationSolid } from '../../../icons/v1';
|
||||
import { addNotification } from '../../../state/actions';
|
||||
import { styles } from '../../../style/index';
|
||||
import { theme } from '../../../style/theme';
|
||||
import { Text } from '../../common/Text';
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useParams } from 'react-router-dom';
|
||||
|
||||
import * as d from 'date-fns';
|
||||
|
||||
import { addNotification } from 'loot-core/src/client/actions';
|
||||
import { useWidget } from 'loot-core/src/client/data-hooks/widget';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
@@ -16,6 +15,7 @@ import { useAccounts } from '../../../hooks/useAccounts';
|
||||
import { useFilters } from '../../../hooks/useFilters';
|
||||
import { useNavigate } from '../../../hooks/useNavigate';
|
||||
import { useResponsive } from '../../../ResponsiveProvider';
|
||||
import { addNotification } from '../../../state/actions';
|
||||
import { theme, styles } from '../../../style';
|
||||
import { Button } from '../../common/Button2';
|
||||
import { Paragraph } from '../../common/Paragraph';
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useParams } from 'react-router-dom';
|
||||
|
||||
import * as d from 'date-fns';
|
||||
|
||||
import { addNotification } from 'loot-core/client/actions';
|
||||
import { useWidget } from 'loot-core/client/data-hooks/widget';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
@@ -17,6 +16,7 @@ import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
|
||||
import { useFilters } from '../../../hooks/useFilters';
|
||||
import { useNavigate } from '../../../hooks/useNavigate';
|
||||
import { useResponsive } from '../../../ResponsiveProvider';
|
||||
import { addNotification } from '../../../state/actions';
|
||||
import { theme, styles } from '../../../style';
|
||||
import { AlignedText } from '../../common/AlignedText';
|
||||
import { Block } from '../../common/Block';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getPayeesById } from 'loot-core/src/client/reducers/queries';
|
||||
import { describeSchedule } from 'loot-core/src/shared/schedules';
|
||||
import { type ScheduleEntity } from 'loot-core/src/types/models';
|
||||
|
||||
import { usePayees } from '../../hooks/usePayees';
|
||||
import { useSchedules } from '../../hooks/useSchedules';
|
||||
import { getPayeesById } from '../../state/queries';
|
||||
|
||||
import { Value } from './Value';
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import { DisplayId } from '../util/DisplayId';
|
||||
|
||||
import { ScheduleAmountCell } from './SchedulesTable';
|
||||
|
||||
const MODAL_NAME = 'schedules-discover' as const;
|
||||
|
||||
const ROW_HEIGHT = 43;
|
||||
|
||||
function DiscoverSchedulesTable({
|
||||
@@ -139,7 +141,13 @@ function DiscoverSchedulesTable({
|
||||
);
|
||||
}
|
||||
|
||||
export function DiscoverSchedules() {
|
||||
type DiscoverSchedulesProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
};
|
||||
|
||||
export function DiscoverSchedules({
|
||||
name = MODAL_NAME,
|
||||
}: DiscoverSchedulesProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, isLoading } = useSendPlatformRequest('schedule/discover');
|
||||
@@ -187,10 +195,7 @@ export function DiscoverSchedules() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="schedules-discover"
|
||||
containerProps={{ style: { width: 850, height: 650 } }}
|
||||
>
|
||||
<Modal name={name} containerProps={{ style: { width: 850, height: 650 } }}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -242,3 +247,4 @@ export function DiscoverSchedules() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
DiscoverSchedules.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,10 +3,10 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { popModal } from 'loot-core/client/actions';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
|
||||
import { useFormatList } from '../../hooks/useFormatList';
|
||||
import { popModal } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Button } from '../common/Button2';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
@@ -15,7 +15,9 @@ import { Stack } from '../common/Stack';
|
||||
import { Text } from '../common/Text';
|
||||
import { DisplayId } from '../util/DisplayId';
|
||||
|
||||
export function PostsOfflineNotification() {
|
||||
const MODAL_NAME = 'schedule-posts-offline-notification';
|
||||
|
||||
export function PostsOfflineNotification({ name = MODAL_NAME }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const location = useLocation();
|
||||
@@ -36,7 +38,7 @@ export function PostsOfflineNotification() {
|
||||
const payeeNamesList = useFormatList(payeesList, t.language);
|
||||
|
||||
return (
|
||||
<Modal name="schedule-posts-offline-notification">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -102,3 +104,4 @@ export function PostsOfflineNotification() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
PostsOfflineNotification.modalName = MODAL_NAME;
|
||||
|
||||
@@ -4,8 +4,6 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import { t } from 'i18next';
|
||||
|
||||
import { getPayeesById } from 'loot-core/client/reducers/queries';
|
||||
import { pushModal } from 'loot-core/src/client/actions/modals';
|
||||
import { runQuery, liveQuery } from 'loot-core/src/client/query-helpers';
|
||||
import { send, sendCatch } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
@@ -15,6 +13,8 @@ import { extractScheduleConds } from 'loot-core/src/shared/schedules';
|
||||
import { useDateFormat } from '../../hooks/useDateFormat';
|
||||
import { usePayees } from '../../hooks/usePayees';
|
||||
import { useSelected, SelectedProvider } from '../../hooks/useSelected';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { getPayeesById } from '../../state/queries';
|
||||
import { theme } from '../../style';
|
||||
import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete';
|
||||
import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete';
|
||||
@@ -33,6 +33,8 @@ import { SimpleTransactionsTable } from '../transactions/SimpleTransactionsTable
|
||||
import { AmountInput, BetweenAmountInput } from '../util/AmountInput';
|
||||
import { GenericInput } from '../util/GenericInput';
|
||||
|
||||
const MODAL_NAME = 'schedule-edit';
|
||||
|
||||
function updateScheduleConditions(schedule, fields) {
|
||||
const conds = extractScheduleConds(schedule._conditions);
|
||||
|
||||
@@ -73,7 +75,7 @@ function updateScheduleConditions(schedule, fields) {
|
||||
};
|
||||
}
|
||||
|
||||
export function ScheduleDetails({ id, transaction }) {
|
||||
export function ScheduleDetails({ name = MODAL_NAME, id, transaction }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const adding = id == null;
|
||||
@@ -457,7 +459,7 @@ export function ScheduleDetails({ id, transaction }) {
|
||||
// This is derived from the date
|
||||
const repeats = state.fields.date ? !!state.fields.date.frequency : false;
|
||||
return (
|
||||
<Modal name="schedule-edit">
|
||||
<Modal name={name}>
|
||||
{({ state: { close } }) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
@@ -848,3 +850,4 @@ function NoTransactionsMessage(props) {
|
||||
</View>
|
||||
);
|
||||
}
|
||||
ScheduleDetails.modalName = MODAL_NAME;
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import { type Query } from 'loot-core/src/shared/query';
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
} from 'loot-core/src/types/models';
|
||||
|
||||
import { SvgAdd } from '../../icons/v0';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { Button } from '../common/Button2';
|
||||
import { InitialFocus } from '../common/InitialFocus';
|
||||
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
|
||||
@@ -22,17 +22,23 @@ import { View } from '../common/View';
|
||||
|
||||
import { ROW_HEIGHT, SchedulesTable } from './SchedulesTable';
|
||||
|
||||
export function ScheduleLink({
|
||||
transactionIds: ids,
|
||||
getTransaction,
|
||||
accountName,
|
||||
onScheduleLinked,
|
||||
}: {
|
||||
const MODAL_NAME = 'schedule-link' as const;
|
||||
|
||||
type ScheduleLinkProps = {
|
||||
name: typeof MODAL_NAME;
|
||||
transactionIds: string[];
|
||||
getTransaction: (transactionId: string) => TransactionEntity;
|
||||
accountName?: string;
|
||||
onScheduleLinked?: (schedule: ScheduleEntity) => void;
|
||||
}) {
|
||||
};
|
||||
|
||||
export function ScheduleLink({
|
||||
name = MODAL_NAME,
|
||||
transactionIds: ids,
|
||||
getTransaction,
|
||||
accountName,
|
||||
onScheduleLinked,
|
||||
}: ScheduleLinkProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -69,7 +75,7 @@ export function ScheduleLink({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
name="schedule-link"
|
||||
name={name}
|
||||
containerProps={{
|
||||
style: {
|
||||
width: 800,
|
||||
@@ -149,3 +155,4 @@ export function ScheduleLink({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
ScheduleLink.modalName = MODAL_NAME;
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
|
||||
import { useMetadataPref } from '../../hooks/useMetadataPref';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { Button } from '../common/Button2';
|
||||
import { Link } from '../common/Link';
|
||||
|
||||
@@ -2,9 +2,7 @@ import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { moveAccount } from 'loot-core/src/client/actions';
|
||||
import * as queries from 'loot-core/src/client/queries';
|
||||
import { type State } from 'loot-core/src/client/state-types';
|
||||
import { type AccountEntity } from 'loot-core/types/models';
|
||||
|
||||
import { useAccounts } from '../../hooks/useAccounts';
|
||||
@@ -14,6 +12,8 @@ import { useFailedAccounts } from '../../hooks/useFailedAccounts';
|
||||
import { useLocalPref } from '../../hooks/useLocalPref';
|
||||
import { useOffBudgetAccounts } from '../../hooks/useOffBudgetAccounts';
|
||||
import { useUpdatedAccounts } from '../../hooks/useUpdatedAccounts';
|
||||
import { type State } from '../../state';
|
||||
import { moveAccount } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
import { View } from '../common/View';
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useDispatch } from 'react-redux';
|
||||
import { css } from '@emotion/css';
|
||||
import { Resizable } from 're-resizable';
|
||||
|
||||
import { closeBudget, replaceModal } from 'loot-core/src/client/actions';
|
||||
import * as Platform from 'loot-core/src/client/platform';
|
||||
|
||||
import { useGlobalPref } from '../../hooks/useGlobalPref';
|
||||
@@ -16,6 +15,7 @@ import { useResizeObserver } from '../../hooks/useResizeObserver';
|
||||
import { SvgExpandArrow } from '../../icons/v0';
|
||||
import { SvgAdd } from '../../icons/v1';
|
||||
import { useResponsive } from '../../ResponsiveProvider';
|
||||
import { closeBudget, replaceModal } from '../../state/actions';
|
||||
import { styles, theme } from '../../style';
|
||||
import { Button } from '../common/Button2';
|
||||
import { InitialFocus } from '../common/InitialFocus';
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { isPreviewId } from 'loot-core/shared/transactions';
|
||||
import { validForTransfer } from 'loot-core/src/client/transfer';
|
||||
import { type TransactionEntity } from 'loot-core/types/models';
|
||||
|
||||
import { useSelectedItems } from '../../hooks/useSelected';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { Menu } from '../common/Menu';
|
||||
import { SelectedItemsButton } from '../table';
|
||||
|
||||
|
||||
@@ -6,10 +6,6 @@ import {
|
||||
parseISO,
|
||||
} from 'date-fns';
|
||||
|
||||
import {
|
||||
getAccountsById,
|
||||
getCategoriesById,
|
||||
} from 'loot-core/src/client/reducers/queries';
|
||||
import { integerToCurrency } from 'loot-core/src/shared/util';
|
||||
|
||||
import { useAccounts } from '../../hooks/useAccounts';
|
||||
@@ -18,6 +14,7 @@ import { useDateFormat } from '../../hooks/useDateFormat';
|
||||
import { usePayees } from '../../hooks/usePayees';
|
||||
import { useSelectedItems, useSelectedDispatch } from '../../hooks/useSelected';
|
||||
import { SvgArrowsSynchronize } from '../../icons/v2';
|
||||
import { getAccountsById, getCategoriesById } from '../../state/queries';
|
||||
import { styles, theme } from '../../style';
|
||||
import { Cell, Field, Row, SelectCell, Table } from '../table';
|
||||
import { DisplayId } from '../util/DisplayId';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useRef, useCallback, useLayoutEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import {
|
||||
splitTransaction,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { getChangedValues, applyChanges } from 'loot-core/src/shared/util';
|
||||
|
||||
import { useNavigate } from '../../hooks/useNavigate';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import { theme } from '../../style';
|
||||
|
||||
import { TransactionTable } from './TransactionsTable';
|
||||
|
||||
@@ -20,13 +20,7 @@ import {
|
||||
isValid as isDateValid,
|
||||
} from 'date-fns';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { useCachedSchedules } from 'loot-core/src/client/data-hooks/schedules';
|
||||
import {
|
||||
getAccountsById,
|
||||
getPayeesById,
|
||||
getCategoriesById,
|
||||
} from 'loot-core/src/client/reducers/queries';
|
||||
import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
|
||||
import { currentDay } from 'loot-core/src/shared/months';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
@@ -58,6 +52,12 @@ import {
|
||||
SvgCalendar,
|
||||
SvgHyperlink2,
|
||||
} from '../../icons/v2';
|
||||
import { pushModal } from '../../state/actions';
|
||||
import {
|
||||
getAccountsById,
|
||||
getPayeesById,
|
||||
getCategoriesById,
|
||||
} from '../../state/queries';
|
||||
import { styles, theme } from '../../style';
|
||||
import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete';
|
||||
import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete';
|
||||
@@ -586,8 +586,11 @@ function PayeeCell({
|
||||
onSelect={() =>
|
||||
dispatch(
|
||||
pushModal('payee-autocomplete', {
|
||||
onSelect: payeeId => {
|
||||
onUpdate('payee', payeeId);
|
||||
autocompleteProps: {
|
||||
value: null,
|
||||
onSelect: payeeId => {
|
||||
onUpdate('payee', payeeId);
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// @ts-strict-ignore
|
||||
import { type Store } from 'redux';
|
||||
|
||||
import * as sharedListeners from 'loot-core/src/client/shared-listeners';
|
||||
import type { State } from 'loot-core/src/client/state-types';
|
||||
import { listen } from 'loot-core/src/platform/client/fetch';
|
||||
import * as undo from 'loot-core/src/platform/client/undo';
|
||||
|
||||
import { type BoundActions } from './hooks/useActions';
|
||||
import * as sharedListeners from './sharedListeners';
|
||||
import { type State } from './state';
|
||||
|
||||
export function handleGlobalEvents(actions: BoundActions, store: Store<State>) {
|
||||
listen('server-error', () => {
|
||||
@@ -21,8 +21,8 @@ export function handleGlobalEvents(actions: BoundActions, store: Store<State>) {
|
||||
});
|
||||
});
|
||||
|
||||
listen('schedules-offline', ({ payees }) => {
|
||||
actions.pushModal('schedule-posts-offline-notification', { payees });
|
||||
listen('schedules-offline', () => {
|
||||
actions.pushModal('schedule-posts-offline-notification');
|
||||
});
|
||||
|
||||
// This is experimental: we sync data locally automatically when
|
||||
@@ -1,8 +1,9 @@
|
||||
import { type Dispatch } from 'loot-core/client/actions/types';
|
||||
import { pushModal } from 'loot-core/src/client/actions/modals';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import { type GoCardlessToken } from 'loot-core/src/types/models';
|
||||
|
||||
import { type Dispatch } from './state';
|
||||
import { pushModal } from './state/actions';
|
||||
|
||||
function _authorize(
|
||||
dispatch: Dispatch,
|
||||
upgradingAccountId: string | undefined,
|
||||
@@ -47,12 +48,15 @@ export async function authorizeBank(
|
||||
onSuccess: async data => {
|
||||
dispatch(
|
||||
pushModal('select-linked-accounts', {
|
||||
accounts: data.accounts,
|
||||
externalAccounts: data.accounts,
|
||||
requisitionId: data.id,
|
||||
upgradingAccountId,
|
||||
// upgradingAccountId,
|
||||
syncSource: 'goCardless',
|
||||
}),
|
||||
);
|
||||
},
|
||||
onClose: () => {
|
||||
send('gocardless-poll-web-token-stop');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { getAccounts } from 'loot-core/src/client/actions';
|
||||
import { type State } from 'loot-core/src/client/state-types';
|
||||
import { type State } from '../state';
|
||||
import { getAccounts } from '../state/actions';
|
||||
|
||||
export function useAccounts() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -4,11 +4,16 @@ import { useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { type ThunkAction } from 'redux-thunk';
|
||||
|
||||
import * as actions from 'loot-core/src/client/actions';
|
||||
import type { Action, State } from 'loot-core/src/client/state-types';
|
||||
import type { State } from '../state';
|
||||
import * as actions from '../state/actions';
|
||||
|
||||
type ActionReturnType<T extends (...args: unknown[]) => unknown> =
|
||||
ReturnType<T> extends ThunkAction<infer ReturnType, State, never, Action>
|
||||
ReturnType<T> extends ThunkAction<
|
||||
infer ReturnType,
|
||||
State,
|
||||
never,
|
||||
actions.Action
|
||||
>
|
||||
? ReturnType
|
||||
: ReturnType<T>;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { getCategories } from 'loot-core/src/client/actions';
|
||||
import { type State } from 'loot-core/src/client/state-types';
|
||||
import { type State } from '../state';
|
||||
import { getCategories } from '../state/actions';
|
||||
|
||||
export function useCategories() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user