diff --git a/.eslintignore b/.eslintignore index f5f8a9e7ccd..2487b54410d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,6 @@ packages/api/app/bundle.api.js packages/api/dist +packages/api/@types packages/api/migrations packages/crdt/dist diff --git a/.eslintrc.js b/.eslintrc.js index d757f5187a8..53796782da5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,7 +57,6 @@ module.exports = { '@typescript-eslint/no-unused-vars': [ 'warn', { - args: 'none', varsIgnorePattern: '^_', ignoreRestSiblings: true, }, @@ -165,6 +164,11 @@ module.exports = { { patterns: [...restrictedImportPatterns, ...restrictedImportColors] }, ], + '@typescript-eslint/ban-ts-comment': [ + 'error', + { 'ts-ignore': 'allow-with-description' }, + ], + // Rules disable during TS migration '@typescript-eslint/no-var-requires': 'off', 'prefer-const': 'warn', diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 254c18462bc..85235450ebf 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -18,6 +18,18 @@ body: required: true validations: required: true + - type: checkboxes + id: bank-sync-issue + attributes: + label: 'Is this related to GoCardless, Simplefin or another bank-sync provider?' + description: 'Most issues with bank-sync providers are due to a lack of a custom bank-mapper (i.e. payee or other fields not coming through). In such cases you can create a custom bank mapper in [actual-server](https://github.com/actualbudget/actual-server/blob/master/src/app-gocardless/README.md) repository. Other likely issue is misconfigured server - in which case please reach out via the [community Discord](https://discord.gg/pRYNYr4W5A) to get support.' + options: + - label: 'I have checked my server logs and could not see any errors there' + - label: 'I will be attaching my server logs to this issue' + - label: 'I will be attaching my client-side (browser) logs to this issue' + - label: 'I understand that this issue will be automatically closed if insufficient information is provided' + validations: + required: false - type: textarea id: what-happened attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0cec6..150ae8082b4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,5 @@ blank_issues_enabled: false +contact_links: + - name: Support + url: https://discord.gg/pRYNYr4W5A + about: Need help with something? Perhaps having issues setting up bank-sync with GoCardless or SimpleFin? Reach out to the community on Discord. diff --git a/.gitignore b/.gitignore index 691472708e3..9777ed2d001 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ !data/.gitkeep /data2 packages/api/dist +packages/api/@types packages/crdt/dist packages/desktop-electron/client-build packages/desktop-electron/.electron-symbols diff --git a/packages/api/index.js b/packages/api/index.js deleted file mode 100644 index 4473e540c86..00000000000 --- a/packages/api/index.js +++ /dev/null @@ -1,38 +0,0 @@ -// eslint-disable-next-line import/extensions -import * as bundle from './app/bundle.api.js'; -import * as injected from './injected'; -import { validateNodeVersion } from './validateNodeVersion'; - -let actualApp; -export const internal = bundle.lib; - -// DEPRECATED: remove the next line in @actual-app/api v7 -export * as methods from './methods'; - -export * from './methods'; -export * as utils from './utils'; - -export async function init(config = {}) { - if (actualApp) { - return; - } - - validateNodeVersion(); - - global.fetch = (...args) => - import('node-fetch').then(({ default: fetch }) => fetch(...args)); - - await bundle.init(config); - actualApp = bundle.lib; - - injected.override(bundle.lib.send); - return bundle.lib; -} - -export async function shutdown() { - if (actualApp) { - await actualApp.send('sync'); - await actualApp.send('close-budget'); - actualApp = null; - } -} diff --git a/packages/api/index.ts b/packages/api/index.ts new file mode 100644 index 00000000000..be3f92d8bff --- /dev/null +++ b/packages/api/index.ts @@ -0,0 +1,53 @@ +import type { + RequestInfo as FetchInfo, + RequestInit as FetchInit, + // @ts-ignore: false-positive commonjs module error on build until typescript 5.3 +} from 'node-fetch'; // with { 'resolution-mode': 'import' }; + +// loot-core types +import type { InitConfig } from 'loot-core/server/main'; + +// @ts-ignore: bundle not available until we build it +// eslint-disable-next-line import/extensions +import * as bundle from './app/bundle.api.js'; +import * as injected from './injected'; +import { validateNodeVersion } from './validateNodeVersion'; + +let actualApp: null | typeof bundle.lib; +export const internal = bundle.lib; + +// DEPRECATED: remove the next line in @actual-app/api v7 +export * as methods from './methods'; + +export * from './methods'; +export * as utils from './utils'; + +export async function init(config: InitConfig = {}) { + if (actualApp) { + return; + } + + validateNodeVersion(); + + if (!globalThis.fetch) { + globalThis.fetch = (url: URL | RequestInfo, init?: RequestInit) => { + return import('node-fetch').then(({ default: fetch }) => + fetch(url as unknown as FetchInfo, init as unknown as FetchInit), + ) as unknown as Promise; + }; + } + + await bundle.init(config); + actualApp = bundle.lib; + + injected.override(bundle.lib.send); + return bundle.lib; +} + +export async function shutdown() { + if (actualApp) { + await actualApp.send('sync'); + await actualApp.send('close-budget'); + actualApp = null; + } +} diff --git a/packages/api/methods.js b/packages/api/methods.ts similarity index 86% rename from packages/api/methods.js rename to packages/api/methods.ts index 9af541f2670..03acaf0f4f6 100644 --- a/packages/api/methods.js +++ b/packages/api/methods.ts @@ -1,8 +1,14 @@ +// @ts-strict-ignore +import type { Handlers } from 'loot-core/src/types/handlers'; + import * as injected from './injected'; export { q } from './app/query'; -function send(name, args) { +function send( + name: K, + args?: Parameters[0], +): Promise>> { return injected.send(name, args); } @@ -21,7 +27,7 @@ export async function loadBudget(budgetId) { return send('api/load-budget', { id: budgetId }); } -export async function downloadBudget(syncId, { password } = {}) { +export async function downloadBudget(syncId, { password }: { password? } = {}) { return send('api/download-budget', { syncId, password }); } @@ -79,10 +85,6 @@ export function getTransactions(accountId, startDate, endDate) { return send('api/transactions-get', { accountId, startDate, endDate }); } -export function filterTransactions(accountId, text) { - return send('api/transactions-filter', { accountId, text }); -} - export function updateTransaction(id, fields) { return send('api/transaction-update', { id, fields }); } @@ -95,7 +97,7 @@ export function getAccounts() { return send('api/accounts-get'); } -export function createAccount(account, initialBalance) { +export function createAccount(account, initialBalance?) { return send('api/account-create', { account, initialBalance }); } @@ -103,7 +105,7 @@ export function updateAccount(id, fields) { return send('api/account-update', { id, fields }); } -export function closeAccount(id, transferAccountId, transferCategoryId) { +export function closeAccount(id, transferAccountId?, transferCategoryId?) { return send('api/account-close', { id, transferAccountId, @@ -127,7 +129,7 @@ export function updateCategoryGroup(id, fields) { return send('api/category-group-update', { id, fields }); } -export function deleteCategoryGroup(id, transferCategoryId) { +export function deleteCategoryGroup(id, transferCategoryId?) { return send('api/category-group-delete', { id, transferCategoryId }); } @@ -143,7 +145,7 @@ export function updateCategory(id, fields) { return send('api/category-update', { id, fields }); } -export function deleteCategory(id, transferCategoryId) { +export function deleteCategory(id, transferCategoryId?) { return send('api/category-delete', { id, transferCategoryId }); } diff --git a/packages/api/package.json b/packages/api/package.json index 7c2d9a01e5b..b0433a638e8 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -7,17 +7,18 @@ "node": ">=18.12.0" }, "main": "dist/index.js", - "types": "dist/index.d.ts", + "types": "@types/index.d.ts", "files": [ "dist" ], "scripts": { "build:app": "yarn workspace loot-core build:api", - "build:node": "tsc --p tsconfig.dist.json", + "build:node": "tsc --p tsconfig.dist.json && tsc-alias -p tsconfig.dist.json", "build:migrations": "cp migrations/*.sql dist/migrations", "build:default-db": "cp default-db.sqlite dist/", - "build": "rm -rf dist && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db", - "test": "yarn run build:app && jest -c jest.config.js" + "build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db", + "test": "yarn run build:app && jest -c jest.config.js", + "clean": "rm -rf dist @types" }, "dependencies": { "better-sqlite3": "^9.2.2", @@ -31,6 +32,7 @@ "@types/jest": "^27.5.0", "@types/uuid": "^9.0.2", "jest": "^27.0.0", + "tsc-alias": "^1.8.8", "typescript": "^5.0.2" } } diff --git a/packages/api/tsconfig.dist.json b/packages/api/tsconfig.dist.json index 6704cb1fb7c..12caac8f770 100644 --- a/packages/api/tsconfig.dist.json +++ b/packages/api/tsconfig.dist.json @@ -8,8 +8,12 @@ "moduleResolution": "Node16", "noEmit": false, "declaration": true, - "outDir": "dist" + "outDir": "dist", + "declarationDir": "@types", + "paths": { + "loot-core/*": ["./@types/loot-core/*"], + } }, "include": ["."], - "exclude": ["dist"] + "exclude": ["**/node_modules/*", "dist", "@types"] } diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png index d52d2ef3024..c2ed7f61e9b 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png index 139319f84e7..c4ee6bae769 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-cash-flow-graph-and-checks-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png index 6ad0736f6f5..633364db146 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png index 4052e48c433..21fd182d2dd 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-and-cash-flow-reports-2-chromium-linux.png differ diff --git a/packages/desktop-client/src/browser-preload.browser.js b/packages/desktop-client/src/browser-preload.browser.js index 37bd1f53719..ca5bc118d90 100644 --- a/packages/desktop-client/src/browser-preload.browser.js +++ b/packages/desktop-client/src/browser-preload.browser.js @@ -51,7 +51,7 @@ global.Actual = { window.location.reload(); }, - openFileDialog: async ({ filters = [], properties }) => { + openFileDialog: async ({ filters = [] }) => { return new Promise(resolve => { let createdElement = false; // Attempt to reuse an already-created file input. @@ -91,7 +91,7 @@ global.Actual = { .uploadFile(filename, ev.target.result) .then(() => resolve([filepath])); }; - reader.onerror = function (ev) { + reader.onerror = function () { alert('Error reading file'); }; } @@ -107,7 +107,7 @@ global.Actual = { }); }, - saveFile: (contents, defaultFilename, dialogTitle) => { + saveFile: (contents, defaultFilename) => { const temp = document.createElement('a'); temp.style = 'display: none'; temp.download = defaultFilename; @@ -121,9 +121,9 @@ global.Actual = { openURLInBrowser: url => { window.open(url, '_blank'); }, - onEventFromMain: (type, handler) => {}, + onEventFromMain: () => {}, applyAppUpdate: () => {}, - updateAppMenu: isBudgetOpen => {}, + updateAppMenu: () => {}, ipcConnect: () => {}, getServerSocket: async () => { diff --git a/packages/desktop-client/src/components/FatalError.tsx b/packages/desktop-client/src/components/FatalError.tsx index f15b6852610..ebed6c72ee6 100644 --- a/packages/desktop-client/src/components/FatalError.tsx +++ b/packages/desktop-client/src/components/FatalError.tsx @@ -131,7 +131,7 @@ function SharedArrayBufferOverride() { > setUnderstand(!understand)} + onChange={() => setUnderstand(!understand)} />{' '} I understand the risks, run Actual in the unsupported fallback mode diff --git a/packages/desktop-client/src/components/FixedSizeList.tsx b/packages/desktop-client/src/components/FixedSizeList.tsx index 2e74b00d5f8..b2257a0a099 100644 --- a/packages/desktop-client/src/components/FixedSizeList.tsx +++ b/packages/desktop-client/src/components/FixedSizeList.tsx @@ -2,7 +2,6 @@ import { createRef, PureComponent, - type ReactElement, type ReactNode, type Ref, type MutableRefObject, @@ -11,7 +10,6 @@ import { import memoizeOne from 'memoize-one'; -import { useResizeObserver } from '../hooks/useResizeObserver'; import { type CSSProperties } from '../style'; import { View } from './common/View'; @@ -20,16 +18,6 @@ const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; const defaultItemKey: FixedSizeListProps['itemKey'] = (index: number) => index; -type ResizeObserverProps = { - onResize: Parameters[0]; - children: (ref: Ref) => ReactElement; -}; - -function ResizeObserver({ onResize, children }: ResizeObserverProps) { - const ref = useResizeObserver(onResize); - return children(ref); -} - type FixedSizeListProps = { className?: string; direction?: 'rtl' | 'ltr'; @@ -262,33 +250,29 @@ export class FixedSizeList extends PureComponent< const estimatedTotalSize = this.getEstimatedTotalSize(); return ( - - {headerRef => ( -
- {header} -
- {items} -
-
- )} -
+
+ {header} +
+ {items} +
+
); } @@ -305,10 +289,6 @@ export class FixedSizeList extends PureComponent< } }; - onHeaderResize = (rect: { height: number }) => { - // this.setState({ headerHeight: rect.height }); - }; - anchor() { const itemKey = this.props.itemKey || defaultItemKey; @@ -499,6 +479,7 @@ export class FixedSizeList extends PureComponent< return style; }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars _getItemStyleCache = memoizeOne((_, __, ___) => ({})); _getRangeToRender() { diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx index 6e82621f92f..0bcbfe254b4 100644 --- a/packages/desktop-client/src/components/ManageRules.tsx +++ b/packages/desktop-client/src/components/ManageRules.tsx @@ -230,7 +230,7 @@ function ManageRulesContent({ dispatch( pushModal('edit-rule', { rule, - onSave: async newRule => { + onSave: async () => { await loadRules(); setLoading(false); }, diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index b78573b3c64..ebec57e5e4b 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -32,6 +32,7 @@ import { PlaidExternalMsg } from './modals/PlaidExternalMsg'; import { ReportBudgetSummary } from './modals/ReportBudgetSummary'; import { RolloverBudgetSummary } from './modals/RolloverBudgetSummary'; import { SelectLinkedAccounts } from './modals/SelectLinkedAccounts'; +import { SimpleFinInitialise } from './modals/SimpleFinInitialise'; import { SingleInput } from './modals/SingleInput'; import { SwitchBudgetType } from './modals/SwitchBudgetType'; import { DiscoverSchedules } from './schedules/DiscoverSchedules'; @@ -80,6 +81,7 @@ export function Modals() { ); @@ -109,6 +111,7 @@ export function Modals() { requisitionId={options.requisitionId} localAccounts={accounts.filter(acct => acct.closed === 0)} actions={actions} + syncSource={options.syncSource} /> ); @@ -196,6 +199,14 @@ export function Modals() { /> ); + case 'simplefin-init': + return ( + + ); + case 'gocardless-external-msg': return ( { + const onUndo = async ({ tables, messages }) => { await maybeRefetch(tables); // If all the messages are dealing with transactions, find the @@ -517,7 +516,7 @@ class AccountInternal extends PureComponent { }); } }, - mappedData => { + () => { return data; }, ); @@ -589,7 +588,9 @@ class AccountInternal extends PureComponent { switch (item) { case 'link': - authorizeBank(this.props.pushModal, { upgradingAccountId: accountId }); + this.props.pushModal('add-account', { + upgradingAccountId: accountId, + }); break; case 'unlink': this.props.unlinkAccount(accountId); diff --git a/packages/desktop-client/src/components/accounts/MobileAccount.jsx b/packages/desktop-client/src/components/accounts/MobileAccount.jsx index aaaed0db57e..b8d4bb6ce77 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccount.jsx +++ b/packages/desktop-client/src/components/accounts/MobileAccount.jsx @@ -43,7 +43,7 @@ const getSchedulesTransform = memoizeOne((id, hasSearch) => { }; }); -function PreviewTransactions({ accountId, children }) { +function PreviewTransactions({ children }) { const scheduleData = useCachedSchedules(); if (scheduleData == null) { diff --git a/packages/desktop-client/src/components/budget/MobileBudget.tsx b/packages/desktop-client/src/components/budget/MobileBudget.tsx index 2a426909a4c..b1b59f76c8c 100644 --- a/packages/desktop-client/src/components/budget/MobileBudget.tsx +++ b/packages/desktop-client/src/components/budget/MobileBudget.tsx @@ -408,19 +408,15 @@ function BudgetInner(props: BudgetInnerProps) { await sync(); }} > - {({ refreshing, onRefresh }) => ( + {({ onRefresh }) => ( - // } editMode={editMode} onEditMode={flag => setEditMode(flag)} onShowBudgetSummary={onShowBudgetSummary} @@ -438,9 +434,6 @@ function BudgetInner(props: BudgetInnerProps) { onBudgetAction={applyBudgetAction} onRefresh={onRefresh} onSwitchBudgetType={onSwitchBudgetType} - onSaveNotes={onSaveNotes} - onEditGroupNotes={onEditGroupNotes} - onEditCategoryNotes={onEditCategoryNotes} savePrefs={savePrefs} pushModal={pushModal} onEditGroup={onEditGroup} diff --git a/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx b/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx index 3c2251d77b3..cb75b90eef9 100644 --- a/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx +++ b/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx @@ -145,7 +145,7 @@ function BudgetCell({ }); } - function onAmountClick(e) { + function onAmountClick() { onEdit?.(categoryId); } @@ -243,7 +243,6 @@ const ExpenseCategory = memo(function ExpenseCategory({ blank, style, month, - editMode, onEdit, isEditingBudget, onEditBudget, @@ -569,7 +568,6 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({ budgeted, balance, style, - editMode, onEdit, }) { const listItemRef = useRef(); @@ -657,7 +655,6 @@ const IncomeCategory = memo(function IncomeCategory({ balance, month, style, - editMode, onEdit, onBudgetAction, isEditingBudget, @@ -1122,7 +1119,6 @@ function BudgetGroups({ export function BudgetTable({ type, categoryGroups, - categories, month, monthBounds, editMode, @@ -1143,9 +1139,6 @@ export function BudgetTable({ onBudgetAction, onRefresh, onSwitchBudgetType, - onSaveNotes, - onEditGroupNotes, - onEditCategoryNotes, savePrefs, pushModal, onEditGroup, @@ -1154,7 +1147,6 @@ export function BudgetTable({ onEditCategoryBudget, openBalanceActionMenuId, onOpenBalanceActionMenu, - ...props }) { const { width } = useResponsive(); const show3Cols = width >= 360; diff --git a/packages/desktop-client/src/components/budget/SidebarCategory.tsx b/packages/desktop-client/src/components/budget/SidebarCategory.tsx index 04bd471d24b..8ca35fde6b9 100644 --- a/packages/desktop-client/src/components/budget/SidebarCategory.tsx +++ b/packages/desktop-client/src/components/budget/SidebarCategory.tsx @@ -154,7 +154,7 @@ export function SidebarCategory({ > displayed} + formatter={() => displayed} width="flex" exposed={editing || temporary} onUpdate={value => { diff --git a/packages/desktop-client/src/components/budget/SidebarGroup.tsx b/packages/desktop-client/src/components/budget/SidebarGroup.tsx index c298e3d28e3..bbdd36b3603 100644 --- a/packages/desktop-client/src/components/budget/SidebarGroup.tsx +++ b/packages/desktop-client/src/components/budget/SidebarGroup.tsx @@ -27,7 +27,6 @@ type SidebarGroupProps = { collapsed: boolean; dragPreview?: boolean; innerRef?: ConnectDragSource; - borderColor?: string; style?: CSSProperties; onEdit?: (id: string) => void; onSave?: (group: object) => Promise; @@ -44,7 +43,6 @@ export function SidebarGroup({ dragPreview, innerRef, style, - borderColor = theme.tableBorder, onEdit, onSave, onDelete, @@ -63,7 +61,7 @@ export function SidebarGroup({ userSelect: 'none', WebkitUserSelect: 'none', }} - onClick={e => { + onClick={() => { onToggleCollapse(group.id); }} > @@ -181,7 +179,7 @@ export function SidebarGroup({ > displayed} + formatter={() => displayed} width="flex" exposed={editing} onUpdate={value => { diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx index 884567a27b5..3363140e6f7 100644 --- a/packages/desktop-client/src/components/budget/index.tsx +++ b/packages/desktop-client/src/components/budget/index.tsx @@ -533,7 +533,7 @@ const RolloverBudgetSummary = memo<{ month: string }>(props => { ); }); -export function Budget(props) { +export function Budget() { const startMonth = useSelector( state => state.prefs.local['budget.startMonth'], ); diff --git a/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx b/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx index 76eddb31928..bf806d12b90 100644 --- a/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx +++ b/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx @@ -38,7 +38,7 @@ export function BalanceTooltip({ {...tooltipProps} > { + onMenuSelect={() => { onBudgetAction(monthIndex, 'carryover', { category: categoryId, flag: !carryover, diff --git a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx index bd822c9cb96..2f33e371f7d 100644 --- a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx @@ -48,7 +48,7 @@ export function CoverTooltip({ categoryGroups={categoryGroups} value={null} openOnFocus={true} - onUpdate={id => {}} + onUpdate={() => {}} onSelect={id => setCategory(id)} inputProps={{ inputRef: node, diff --git a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx index 8671a1261c7..0d27946f138 100644 --- a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx @@ -98,7 +98,7 @@ export function TransferTooltip({ categoryGroups={categoryGroups} value={null} openOnFocus={true} - onUpdate={id => {}} + onUpdate={() => {}} onSelect={id => setCategory(id)} inputProps={{ onEnter: submit, placeholder: '(none)' }} /> diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx index dc66bed7a9f..209ab5e31be 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx @@ -35,7 +35,7 @@ export function BudgetSummary({ } = useRollover(); const [menuOpen, setMenuOpen] = useState(false); - function onMenuOpen(e) { + function onMenuOpen() { setMenuOpen(true); } diff --git a/packages/desktop-client/src/components/common/AnchorLink.tsx b/packages/desktop-client/src/components/common/AnchorLink.tsx index 5defecba686..ed596b00d28 100644 --- a/packages/desktop-client/src/components/common/AnchorLink.tsx +++ b/packages/desktop-client/src/components/common/AnchorLink.tsx @@ -3,6 +3,8 @@ import { NavLink, useMatch } from 'react-router-dom'; import { css } from 'glamor'; +import { type CustomReportEntity } from 'loot-core/src/types/models'; + import { type CSSProperties, styles } from '../../style'; type AnchorLinkProps = { @@ -10,6 +12,7 @@ type AnchorLinkProps = { style?: CSSProperties; activeStyle?: CSSProperties; children?: ReactNode; + report?: CustomReportEntity; }; export function AnchorLink({ @@ -17,12 +20,14 @@ export function AnchorLink({ style, activeStyle, children, + report, }: AnchorLinkProps) { const match = useMatch({ path: to }); return ( ({ }} onMouseEnter={() => setHoveredIndex(idx)} onMouseLeave={() => setHoveredIndex(null)} - onClick={e => + onClick={() => !item.disabled && onMenuSelect && onMenuSelect(item.name) } > diff --git a/packages/desktop-client/src/components/modals/CategoryGroupMenu.tsx b/packages/desktop-client/src/components/modals/CategoryGroupMenu.tsx index bd8d3e5d112..6a1368b5a4b 100644 --- a/packages/desktop-client/src/components/modals/CategoryGroupMenu.tsx +++ b/packages/desktop-client/src/components/modals/CategoryGroupMenu.tsx @@ -137,7 +137,7 @@ export function CategoryGroupMenu({ notes={notes?.length > 0 ? notes : 'No notes'} editable={false} focused={false} - getStyle={editable => ({ + getStyle={() => ({ ...styles.mediumText, borderRadius: 6, ...((!notes || notes.length === 0) && { diff --git a/packages/desktop-client/src/components/modals/CategoryMenu.tsx b/packages/desktop-client/src/components/modals/CategoryMenu.tsx index f42bb96904b..2e2b79181f3 100644 --- a/packages/desktop-client/src/components/modals/CategoryMenu.tsx +++ b/packages/desktop-client/src/components/modals/CategoryMenu.tsx @@ -128,7 +128,7 @@ export function CategoryMenu({ notes={originalNotes?.length > 0 ? originalNotes : 'No notes'} editable={false} focused={false} - getStyle={editable => ({ + getStyle={() => ({ borderRadius: 6, ...((!originalNotes || originalNotes.length === 0) && { justifySelf: 'center', diff --git a/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx b/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx index 15cd79a2313..daacc77931a 100644 --- a/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx +++ b/packages/desktop-client/src/components/modals/ConfirmTransactionEdit.tsx @@ -42,6 +42,12 @@ export function ConfirmTransactionEdit({ Saving your changes to this reconciled transaction may bring your reconciliation out of balance. + ) : confirmReason === 'unlockReconciled' ? ( + + Unlocking this transaction means you won‘t be warned about changes + that can impact your reconciled balance. (Changes to amount, + account, payee, etc). + ) : confirmReason === 'deleteReconciled' ? ( Deleting this reconciled transaction may bring your reconciliation diff --git a/packages/desktop-client/src/components/modals/CreateAccount.tsx b/packages/desktop-client/src/components/modals/CreateAccount.tsx index fab820fb99a..c577ab7f8d2 100644 --- a/packages/desktop-client/src/components/modals/CreateAccount.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccount.tsx @@ -1,9 +1,13 @@ // @ts-strict-ignore import React, { useEffect, useState } from 'react'; +import { send } from 'loot-core/src/platform/client/fetch'; + import { authorizeBank } from '../../gocardless'; import { useActions } from '../../hooks/useActions'; +import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus'; +import { useSimpleFinStatus } from '../../hooks/useSimpleFinStatus'; import { type SyncServerStatus } from '../../hooks/useSyncServerStatus'; import { theme } from '../../style'; import { type CommonModalProps } from '../../types/modals'; @@ -17,23 +21,75 @@ import { View } from '../common/View'; type CreateAccountProps = { modalProps: CommonModalProps; syncServerStatus: SyncServerStatus; + upgradingAccountId?: string; }; export function CreateAccount({ modalProps, syncServerStatus, + upgradingAccountId, }: CreateAccountProps) { const actions = useActions(); const [isGoCardlessSetupComplete, setIsGoCardlessSetupComplete] = useState(null); + const [isSimpleFinSetupComplete, setIsSimpleFinSetupComplete] = + useState(null); - const onConnect = () => { + const onConnectGoCardless = () => { if (!isGoCardlessSetupComplete) { onGoCardlessInit(); return; } - authorizeBank(actions.pushModal); + if (upgradingAccountId == null) { + authorizeBank(actions.pushModal); + } else { + authorizeBank(actions.pushModal, { + upgradingAccountId, + }); + } + }; + + const onConnectSimpleFin = async () => { + if (!isSimpleFinSetupComplete) { + onSimpleFinInit(); + return; + } + + if (loadingSimpleFinAccounts) { + return; + } + + setLoadingSimpleFinAccounts(true); + + const results = await send('simplefin-accounts'); + + const newAccounts = []; + + type NormalizedAccount = { + account_id: string; + name: string; + institution: string; + orgDomain: string; + }; + + for (const oldAccount of results.accounts) { + const newAccount: NormalizedAccount = { + account_id: oldAccount.id, + name: oldAccount.name, + institution: oldAccount.org.name, + orgDomain: oldAccount.org.domain, + }; + + newAccounts.push(newAccount); + } + + actions.pushModal('select-linked-accounts', { + accounts: newAccounts, + syncSource: 'simpleFin', + }); + + setLoadingSimpleFinAccounts(false); }; const onGoCardlessInit = () => { @@ -42,45 +98,68 @@ export function CreateAccount({ }); }; + const onSimpleFinInit = () => { + actions.pushModal('simplefin-init', { + onSuccess: () => setIsSimpleFinSetupComplete(true), + }); + }; + const onCreateLocalAccount = () => { actions.pushModal('add-local-account'); }; - const { configured } = useGoCardlessStatus(); + const { configuredGoCardless } = useGoCardlessStatus(); useEffect(() => { - setIsGoCardlessSetupComplete(configured); - }, [configured]); + setIsGoCardlessSetupComplete(configuredGoCardless); + }, [configuredGoCardless]); + + const { configuredSimpleFin } = useSimpleFinStatus(); + useEffect(() => { + setIsSimpleFinSetupComplete(configuredSimpleFin); + }, [configuredSimpleFin]); + + let title = 'Add Account'; + const [loadingSimpleFinAccounts, setLoadingSimpleFinAccounts] = + useState(false); + + if (upgradingAccountId != null) { + title = 'Link Account'; + } + + const simpleFinSyncFeatureFlag = useFeatureFlag('simpleFinSync'); return ( - + {() => ( - - - - - Create a local account if you want to add - transactions manually. You can also{' '} - - import QIF/OFX/QFX files into a local account - - . - + {upgradingAccountId == null && ( + + + + + Create a local account if you want to add + transactions manually. You can also{' '} + + import QIF/OFX/QFX files into a local account + + . + + - + )} {syncServerStatus === 'online' ? ( <> @@ -92,17 +171,46 @@ export function CreateAccount({ fontWeight: 600, flex: 1, }} - onClick={onConnect} + onClick={onConnectGoCardless} > {isGoCardlessSetupComplete ? 'Link bank account with GoCardless' : 'Set up GoCardless for bank sync'} - Link a bank account to automatically download - transactions. GoCardless provides reliable, up-to-date - information from hundreds of banks. + + Link a European bank account + {' '} + to automatically download transactions. GoCardless provides + reliable, up-to-date information from hundreds of banks. + {simpleFinSyncFeatureFlag === true && ( + <> + + {isSimpleFinSetupComplete + ? 'Link bank account with SimpleFIN' + : 'Set up SimpleFIN for bank sync'} + + + + Link a North American bank account + {' '} + to automatically download transactions. SimpleFIN provides + reliable, up-to-date information from hundreds of banks. + + + )} ) : ( <> @@ -114,7 +222,7 @@ export function CreateAccount({ fontWeight: 600, }} > - Set up GoCardless for bank sync + Set up bank sync Connect to an Actual server to set up{' '} @@ -122,7 +230,7 @@ export function CreateAccount({ to="https://actualbudget.org/docs/advanced/bank-sync" linkColor="muted" > - automatic syncing with GoCardless + automatic syncing. . diff --git a/packages/desktop-client/src/components/modals/EditRule.jsx b/packages/desktop-client/src/components/modals/EditRule.jsx index 661cc799e8c..4d42814e53d 100644 --- a/packages/desktop-client/src/components/modals/EditRule.jsx +++ b/packages/desktop-client/src/components/modals/EditRule.jsx @@ -122,7 +122,7 @@ export function OpSelect({ ); } -function EditorButtons({ onAdd, onDelete, style }) { +function EditorButtons({ onAdd, onDelete }) { return ( <> {onDelete && ( @@ -310,7 +310,7 @@ const actionFields = [ 'date', 'amount', ].map(field => [field, mapField(field)]); -function ActionEditor({ ops, action, editorStyle, onChange, onDelete, onAdd }) { +function ActionEditor({ action, editorStyle, onChange, onDelete, onAdd }) { const { field, op, value, type, error, inputKey = 'initial' } = action; return ( diff --git a/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx b/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx index dbb74a9562b..9e3b4cfc9b1 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessExternalMsg.tsx @@ -110,8 +110,10 @@ export function GoCardlessExternalMsg({ isLoading: isBankOptionsLoading, isError: isBankOptionError, } = useAvailableBanks(country); - const { configured: isConfigured, isLoading: isConfigurationLoading } = - useGoCardlessStatus(); + const { + configuredGoCardless: isConfigured, + isLoading: isConfigurationLoading, + } = useGoCardlessStatus(); async function onJump() { setError(null); diff --git a/packages/desktop-client/src/components/modals/ImportTransactions.jsx b/packages/desktop-client/src/components/modals/ImportTransactions.jsx index db53af08937..53b289d3fff 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactions.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactions.jsx @@ -132,7 +132,7 @@ function getFileType(filepath) { return rawType; } -function ParsedDate({ parseDateFormat, showParsed, dateFormat, date }) { +function ParsedDate({ parseDateFormat, dateFormat, date }) { const parsed = date && formatDate( @@ -184,29 +184,29 @@ function getInitialMappings(transactions) { } const dateField = key( - fields.find(([name, value]) => name.toLowerCase().includes('date')) || - fields.find(([name, value]) => value.match(/^\d+[-/]\d+[-/]\d+$/)), + fields.find(([name]) => name.toLowerCase().includes('date')) || + fields.find(([, value]) => value.match(/^\d+[-/]\d+[-/]\d+$/)), ); const amountField = key( - fields.find(([name, value]) => name.toLowerCase().includes('amount')) || - fields.find(([name, value]) => value.match(/^-?[.,\d]+$/)), + fields.find(([name]) => name.toLowerCase().includes('amount')) || + fields.find(([, value]) => value.match(/^-?[.,\d]+$/)), ); const categoryField = key( - fields.find(([name, value]) => name.toLowerCase().includes('category')), + fields.find(([name]) => name.toLowerCase().includes('category')), ); const payeeField = key( fields.find( - ([name, value]) => + ([name]) => name !== dateField && name !== amountField && name !== categoryField, ), ); const notesField = key( fields.find( - ([name, value]) => + ([name]) => name !== dateField && name !== amountField && name !== categoryField && @@ -216,7 +216,7 @@ function getInitialMappings(transactions) { const inOutField = key( fields.find( - ([name, value]) => + ([name]) => name !== dateField && name !== amountField && name !== payeeField && @@ -1019,7 +1019,7 @@ export function ImportTransactions({ modalProps, options }) { ); }} - renderItem={({ key, style, item, editing, focusedField }) => ( + renderItem={({ key, style, item }) => ( ({ + getStyle={() => ({ borderRadius: 6, flex: 1, minWidth: 0, diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx index a5a2aadcd1e..8d8d82475bc 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx @@ -16,6 +16,7 @@ export function SelectLinkedAccounts({ externalAccounts, localAccounts, actions, + syncSource, }) { const [chosenAccounts, setChosenAccounts] = useState(() => { return Object.fromEntries( @@ -49,13 +50,22 @@ export function SelectLinkedAccounts({ } // Finally link the matched account - actions.linkAccount( - requisitionId, - externalAccount, - chosenLocalAccountId !== addAccountOption.id - ? chosenLocalAccountId - : undefined, - ); + if (syncSource === 'simpleFin') { + actions.linkAccountSimpleFin( + externalAccount, + chosenLocalAccountId !== addAccountOption.id + ? chosenLocalAccountId + : undefined, + ); + } else { + actions.linkAccount( + requisitionId, + externalAccount, + chosenLocalAccountId !== addAccountOption.id + ? chosenLocalAccountId + : undefined, + ); + } }, ); diff --git a/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx b/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx new file mode 100644 index 00000000000..4db5bff8822 --- /dev/null +++ b/packages/desktop-client/src/components/modals/SimpleFinInitialise.tsx @@ -0,0 +1,88 @@ +// @ts-strict-ignore +import React, { useState } from 'react'; + +import { send } from 'loot-core/src/platform/client/fetch'; + +import { Error } from '../alerts'; +import { ButtonWithLoading } from '../common/Button'; +import { ExternalLink } from '../common/ExternalLink'; +import { Input } from '../common/Input'; +import { Modal, ModalButtons } from '../common/Modal'; +import type { ModalProps } from '../common/Modal'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; +import { FormField, FormLabel } from '../forms'; + +type SimpleFinInitialiseProps = { + modalProps?: Partial; + onSuccess: () => void; +}; + +export const SimpleFinInitialise = ({ + modalProps, + onSuccess, +}: SimpleFinInitialiseProps) => { + const [token, setToken] = useState(''); + const [isValid, setIsValid] = useState(true); + const [isLoading, setIsLoading] = useState(false); + + const onSubmit = async () => { + if (!token) { + setIsValid(false); + return; + } + + setIsLoading(true); + + await send('secret-set', { + name: 'simplefin_token', + value: token, + }); + + onSuccess(); + modalProps.onClose(); + setIsLoading(false); + }; + + return ( + + + + In order to enable bank-sync via SimpleFIN (only for North American + banks) you will need to create a token. This can be done by creating + an account with{' '} + + SimpleFIN + + . + + + + + setIsValid(true)} + /> + + + {!isValid && It is required to provide a token.} + + + + + Save and continue + + + + ); +}; diff --git a/packages/desktop-client/src/components/modals/SingleInput.tsx b/packages/desktop-client/src/components/modals/SingleInput.tsx index 8c4daef9451..89f4b334799 100644 --- a/packages/desktop-client/src/components/modals/SingleInput.tsx +++ b/packages/desktop-client/src/components/modals/SingleInput.tsx @@ -81,7 +81,7 @@ export function SingleInput({ ...styles.mediumText, flexBasis: '50%', }} - onPointerUp={e => _onSubmit(value)} + onPointerUp={() => _onSubmit(value)} > {buttonText} diff --git a/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx b/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx index 338adbb08cd..a828c5e73f2 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx +++ b/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx @@ -63,7 +63,7 @@ export function ManagePayeesWithData({ initialSelectedIds }) { }; }, []); - async function onUndo({ tables, messages, meta, url }, scroll = false) { + async function onUndo({ tables, messages, meta }) { if (!tables.includes('payees') && !tables.includes('payee_mapping')) { return; } @@ -83,7 +83,7 @@ export function ManagePayeesWithData({ initialSelectedIds }) { useEffect(() => { if (lastUndoState.current) { - onUndo(lastUndoState.current, true); + onUndo(lastUndoState.current); } return listen('undo-event', onUndo); diff --git a/packages/desktop-client/src/components/reports/CategorySelector.tsx b/packages/desktop-client/src/components/reports/CategorySelector.tsx index 21688b24845..ef096bd8e7c 100644 --- a/packages/desktop-client/src/components/reports/CategorySelector.tsx +++ b/packages/desktop-client/src/components/reports/CategorySelector.tsx @@ -151,7 +151,7 @@ export function CategorySelector({ { + onChange={() => { const selectedCategoriesExcludingGroupCategories = selectedCategories.filter( selectedCategory => @@ -189,7 +189,7 @@ export function CategorySelector({ paddingLeft: 10, }} > - {categoryGroup.categories.map((category, index) => { + {categoryGroup.categories.map(category => { const isChecked = selectedCategories.some( selectedCategory => selectedCategory.id === category.id, ); @@ -206,7 +206,7 @@ export function CategorySelector({ { + onChange={() => { if (isChecked) { setSelectedCategories( selectedCategories.filter( diff --git a/packages/desktop-client/src/components/reports/ChooseGraph.tsx b/packages/desktop-client/src/components/reports/ChooseGraph.tsx index a4e0b5b21bf..a3149c39119 100644 --- a/packages/desktop-client/src/components/reports/ChooseGraph.tsx +++ b/packages/desktop-client/src/components/reports/ChooseGraph.tsx @@ -26,7 +26,6 @@ type ChooseGraphProps = { graphType: string; balanceType: string; groupBy: string; - scrollWidth?: number; setScrollWidth?: (value: number) => void; months?: Month[]; viewLabels?: boolean; @@ -40,7 +39,6 @@ export function ChooseGraph({ graphType, balanceType, groupBy, - scrollWidth, setScrollWidth, months, viewLabels, @@ -139,7 +137,6 @@ export function ChooseGraph({ headerScrollRef={headerScrollRef} handleScroll={handleScroll} interval={mode === 'time' && data.monthData} - scrollWidth={scrollWidth} groupBy={groupBy} balanceType={balanceType} compact={compact} @@ -158,7 +155,6 @@ export function ChooseGraph({ } {sankeyFeatureFlag && } {customReportsFeatureFlag ? ( - + ) : (
)} diff --git a/packages/desktop-client/src/components/reports/ReportCard.tsx b/packages/desktop-client/src/components/reports/ReportCard.tsx index db8b6b2429e..ad9fe83d189 100644 --- a/packages/desktop-client/src/components/reports/ReportCard.tsx +++ b/packages/desktop-client/src/components/reports/ReportCard.tsx @@ -1,11 +1,26 @@ -// @ts-strict-ignore -import React from 'react'; +import React, { type ReactNode } from 'react'; -import { theme } from '../../style'; +import { type CustomReportEntity } from 'loot-core/src/types/models'; + +import { type CSSProperties, theme } from '../../style'; import { AnchorLink } from '../common/AnchorLink'; import { View } from '../common/View'; -export function ReportCard({ flex, to, style, children }) { +type ReportCardProps = { + to: string; + report: CustomReportEntity; + children: ReactNode; + flex?: string; + style?: CSSProperties; +}; + +export function ReportCard({ + to, + report, + children, + flex, + style, +}: ReportCardProps) { const containerProps = { flex, margin: 15 }; const content = ( @@ -34,7 +49,8 @@ export function ReportCard({ flex, to, style, children }) { return ( {content} diff --git a/packages/desktop-client/src/components/reports/ReportOptions.ts b/packages/desktop-client/src/components/reports/ReportOptions.ts index f32dbe5e1fe..7cc13b86e27 100644 --- a/packages/desktop-client/src/components/reports/ReportOptions.ts +++ b/packages/desktop-client/src/components/reports/ReportOptions.ts @@ -1,11 +1,32 @@ -// @ts-strict-ignore +import * as monthUtils from 'loot-core/src/shared/months'; import { + type CustomReportEntity, type AccountEntity, type CategoryEntity, type CategoryGroupEntity, type PayeeEntity, } from 'loot-core/src/types/models'; +const startDate = monthUtils.subMonths(monthUtils.currentMonth(), 5); +const endDate = monthUtils.currentMonth(); + +export const defaultState: CustomReportEntity = { + id: undefined, + mode: 'total', + groupBy: 'Category', + balanceType: 'Payment', + showEmpty: false, + showOffBudgetHidden: false, + showUncategorized: false, + graphType: 'BarGraph', + startDate, + endDate, + selectedCategories: null, + isDateStatic: false, + conditionsOp: 'and', + name: 'Default', +}; + const balanceTypeOptions = [ { description: 'Payment', format: 'totalDebts' as const }, { description: 'Deposit', format: 'totalAssets' as const }, @@ -83,7 +104,7 @@ export type UncategorizedEntity = Pick< const uncategorizedCategory: UncategorizedEntity = { name: 'Uncategorized', - id: null, + id: undefined, uncategorized_id: '1', hidden: false, is_off_budget: false, @@ -92,7 +113,7 @@ const uncategorizedCategory: UncategorizedEntity = { }; const transferCategory: UncategorizedEntity = { name: 'Transfers', - id: null, + id: undefined, uncategorized_id: '2', hidden: false, is_off_budget: false, @@ -101,7 +122,7 @@ const transferCategory: UncategorizedEntity = { }; const offBudgetCategory: UncategorizedEntity = { name: 'Off Budget', - id: null, + id: undefined, uncategorized_id: '3', hidden: false, is_off_budget: true, @@ -118,7 +139,7 @@ type UncategorizedGroupEntity = Pick< const uncategorizedGroup: UncategorizedGroupEntity = { name: 'Uncategorized & Off Budget', - id: null, + id: undefined, hidden: false, categories: [uncategorizedCategory, transferCategory, offBudgetCategory], }; diff --git a/packages/desktop-client/src/components/reports/ReportSidebar.jsx b/packages/desktop-client/src/components/reports/ReportSidebar.jsx index 49857c63d88..dc2fdd24768 100644 --- a/packages/desktop-client/src/components/reports/ReportSidebar.jsx +++ b/packages/desktop-client/src/components/reports/ReportSidebar.jsx @@ -20,34 +20,23 @@ import { ModeButton } from './ModeButton'; import { ReportOptions } from './ReportOptions'; export function ReportSidebar({ - startDate, - endDate, - onChangeDates, - dateRange, - setDateRange, + customReportItems, + categories, dateRangeLine, allMonths, - graphType, - setGraphType, + setDateRange, typeDisabled, setTypeDisabled, - groupBy, + setGraphType, setGroupBy, - balanceType, setBalanceType, - mode, setMode, - isDateStatic, setIsDateStatic, - showEmpty, setShowEmpty, - showOffBudgetHidden, setShowOffBudgetHidden, - showUncategorized, setShowUncategorized, - categories, - selectedCategories, setSelectedCategories, + onChangeDates, onChangeViews, }) { const onSelectRange = cond => { @@ -86,26 +75,26 @@ export function ReportSidebar({ const onChangeMode = cond => { setMode(cond); if (cond === 'time') { - if (graphType === 'TableGraph') { + if (customReportItems.graphType === 'TableGraph') { setTypeDisabled([]); } else { setTypeDisabled(['Net']); - if (['Net'].includes(balanceType)) { + if (['Net'].includes(customReportItems.balanceType)) { setBalanceType('Payment'); } } - if (graphType === 'BarGraph') { + if (customReportItems.graphType === 'BarGraph') { setGraphType('StackedBarGraph'); } - if (['AreaGraph', 'DonutGraph'].includes(graphType)) { + if (['AreaGraph', 'DonutGraph'].includes(customReportItems.graphType)) { setGraphType('TableGraph'); onChangeViews('viewLegend', false); } - if (['Month', 'Year'].includes(groupBy)) { + if (['Month', 'Year'].includes(customReportItems.groupBy)) { setGroupBy('Category'); } } else { - if (graphType === 'StackedBarGraph') { + if (customReportItems.graphType === 'StackedBarGraph') { setGraphType('BarGraph'); } else { setTypeDisabled([]); @@ -115,12 +104,17 @@ export function ReportSidebar({ const onChangeSplit = cond => { setGroupBy(cond); - if (mode === 'total') { - if (graphType !== 'TableGraph') { - setTypeDisabled(!['Month', 'Year'].includes(groupBy) ? [] : ['Net']); + if (customReportItems.mode === 'total') { + if (customReportItems.graphType !== 'TableGraph') { + setTypeDisabled( + !['Month', 'Year'].includes(customReportItems.groupBy) ? [] : ['Net'], + ); } } - if (['Net'].includes(balanceType) && graphType !== 'TableGraph') { + if ( + ['Net'].includes(customReportItems.balanceType) && + customReportItems.graphType !== 'TableGraph' + ) { setBalanceType('Payment'); } }; @@ -158,13 +152,13 @@ export function ReportSidebar({ Mode: onChangeMode('total')} > Total onChangeMode('time')} > Time @@ -181,16 +175,16 @@ export function ReportSidebar({ Split: [ option.description, @@ -251,9 +245,9 @@ export function ReportSidebar({ setShowEmpty(!showEmpty)} + checked={customReportItems.showEmpty} + value={customReportItems.showEmpty} + onChange={() => setShowEmpty(!customReportItems.showEmpty)} />