From 740d769e006b467fc532b144f30e6a295e9b9ea2 Mon Sep 17 00:00:00 2001 From: youngcw Date: Tue, 19 Sep 2023 18:09:10 -0700 Subject: [PATCH] fix the error by adding colors to the report side (#42) * [refactor] Migrate Schedules Table to typescript (#1691) * :wrench: removing unnecessary manual module resolution (#1707) * :bug: (mobile) scrolling in lists with pull-to-refresh (#1706) * :lipstick: (mobile) updating apple home-screen icon (#1705) * Enhance Y-Axis Scaling on Net Worth Graph (#1709) * fix report budget, by adding in the goal coloring --------- Co-authored-by: Mohamed Muhsin <62111075+muhsinkamil@users.noreply.github.com> Co-authored-by: Matiss Janis Aboltins Co-authored-by: Crazypkr1099 --- package.json | 3 - .../public/apple-touch-icon.png | Bin 7867 -> 5935 bytes .../accounts/MobileAccountDetails.js | 2 +- .../src/components/accounts/MobileAccounts.js | 2 +- .../components/budget/report/components.tsx | 2 + .../src/components/common/Menu.tsx | 2 +- .../src/components/reports/NetWorth.js | 4 +- .../reports/graphs/NetWorthGraph.tsx | 11 +- .../reports/graphs/net-worth-spreadsheet.tsx | 13 ++ .../components/responsive/PullToRefresh.tsx | 28 +++ .../components/schedules/SchedulesTable.tsx | 187 ++++++++++++------ .../src/components/schedules/StatusBadge.tsx | 112 +++++------ .../src/components/schedules/index.tsx | 12 +- .../desktop-client/src/components/table.tsx | 17 +- .../src/client/data-hooks/accounts.tsx | 19 +- .../src/client/data-hooks/payees.tsx | 19 +- .../src/client/data-hooks/schedules.tsx | 21 +- packages/loot-core/src/shared/util.ts | 2 +- .../loot-core/src/types/models/schedule.d.ts | 20 +- upcoming-release-notes/1691.md | 6 + upcoming-release-notes/1705.md | 6 + upcoming-release-notes/1706.md | 6 + upcoming-release-notes/1707.md | 6 + upcoming-release-notes/1709.md | 6 + yarn.lock | 8 +- 25 files changed, 342 insertions(+), 172 deletions(-) create mode 100644 packages/desktop-client/src/components/responsive/PullToRefresh.tsx create mode 100644 upcoming-release-notes/1691.md create mode 100644 upcoming-release-notes/1705.md create mode 100644 upcoming-release-notes/1706.md create mode 100644 upcoming-release-notes/1707.md create mode 100644 upcoming-release-notes/1709.md diff --git a/package.json b/package.json index e0f7ebe2f93..27fd3f37c33 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,6 @@ "source-map-support": "^0.5.21", "typescript": "^5.0.2" }, - "resolutions": { - "react-error-overlay": "6.0.9" - }, "engines": { "node": ">=18.0.0" }, diff --git a/packages/desktop-client/public/apple-touch-icon.png b/packages/desktop-client/public/apple-touch-icon.png index f71a013c3bf3af7f9a67ef569cf8ee52a5c8325a..5031d7697b38487b322182c48f49a86293546331 100644 GIT binary patch literal 5935 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 WVvjR9;Z&dskgB4lLY16(=zjrO1^UeZ diff --git a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js index e3dd2098505..b5cbcc4e3a4 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js +++ b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js @@ -1,6 +1,5 @@ import React, { useState, useMemo } from 'react'; import { Link } from 'react-router-dom'; -import PullToRefresh from 'react-simple-pull-to-refresh'; import { useActions } from '../../hooks/useActions'; import Add from '../../icons/v1/Add'; @@ -12,6 +11,7 @@ import InputWithContent from '../common/InputWithContent'; import Label from '../common/Label'; import Text from '../common/Text'; import View from '../common/View'; +import PullToRefresh from '../responsive/PullToRefresh'; import CellValue from '../spreadsheet/CellValue'; import { TransactionList } from '../transactions/MobileTransaction'; diff --git a/packages/desktop-client/src/components/accounts/MobileAccounts.js b/packages/desktop-client/src/components/accounts/MobileAccounts.js index 457c645a463..e7a122d6b42 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccounts.js +++ b/packages/desktop-client/src/components/accounts/MobileAccounts.js @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; -import PullToRefresh from 'react-simple-pull-to-refresh'; import * as queries from 'loot-core/src/client/queries'; @@ -14,6 +13,7 @@ import Text from '../common/Text'; import TextOneLine from '../common/TextOneLine'; import View from '../common/View'; import { Page } from '../Page'; +import PullToRefresh from '../responsive/PullToRefresh'; import CellValue from '../spreadsheet/CellValue'; function AccountHeader({ name, amount, style = {} }) { diff --git a/packages/desktop-client/src/components/budget/report/components.tsx b/packages/desktop-client/src/components/budget/report/components.tsx index 8255a06d43e..fa491dcd10e 100644 --- a/packages/desktop-client/src/components/budget/report/components.tsx +++ b/packages/desktop-client/src/components/budget/report/components.tsx @@ -368,6 +368,8 @@ export const CategoryMonth = memo(function CategoryMonth({ disabled={category.is_income} carryover={reportBudget.catCarryover(category.id)} balance={reportBudget.catBalance(category.id)} + goal={reportBudget.catGoal(category.id)} + budgeted={reportBudget.catBudgeted(category.id)} /> {balanceTooltip.isOpen && ( diff --git a/packages/desktop-client/src/components/common/Menu.tsx b/packages/desktop-client/src/components/common/Menu.tsx index 84ced272fbf..43aabf4dce9 100644 --- a/packages/desktop-client/src/components/common/Menu.tsx +++ b/packages/desktop-client/src/components/common/Menu.tsx @@ -37,7 +37,7 @@ type MenuProps = { header?: ReactNode; footer?: ReactNode; items: Array; - onMenuSelect; + onMenuSelect: (itemName: MenuItem['name']) => void; }; export default function Menu({ diff --git a/packages/desktop-client/src/components/reports/NetWorth.js b/packages/desktop-client/src/components/reports/NetWorth.js index 5c9a8acbe6c..e03f82d5a65 100644 --- a/packages/desktop-client/src/components/reports/NetWorth.js +++ b/packages/desktop-client/src/components/reports/NetWorth.js @@ -43,7 +43,6 @@ export default function NetWorth() { [start, end, accounts, filters, conditionsOp], ); const data = useReport('net_worth', params); - useEffect(() => { async function run() { const trans = await send('get-earliest-transaction'); @@ -133,6 +132,9 @@ export default function NetWorth() { start={start} end={end} graphData={data.graphData} + domain={{ + y: [data.lowestNetWorth * 0.99, data.highestNetWorth * 1.01], + }} /> diff --git a/packages/desktop-client/src/components/reports/graphs/NetWorthGraph.tsx b/packages/desktop-client/src/components/reports/graphs/NetWorthGraph.tsx index 5c58527bd1a..ae18bd1f94a 100644 --- a/packages/desktop-client/src/components/reports/graphs/NetWorthGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/NetWorthGraph.tsx @@ -21,8 +21,16 @@ type NetWorthGraphProps = { style?: CSSProperties; graphData; compact: boolean; + domain?: { + y?: [number, number]; + }; }; -function NetWorthGraph({ style, graphData, compact }: NetWorthGraphProps) { +function NetWorthGraph({ + style, + graphData, + compact, + domain, +}: NetWorthGraphProps) { const Chart = compact ? VictoryGroup : VictoryChart; return ( @@ -38,6 +46,7 @@ function NetWorthGraph({ style, graphData, compact }: NetWorthGraphProps) { scale={{ x: 'time', y: 'linear' }} theme={chartTheme} domainPadding={{ x: 0, y: 10 }} + domain={domain} width={width} height={height} containerComponent={ diff --git a/packages/desktop-client/src/components/reports/graphs/net-worth-spreadsheet.tsx b/packages/desktop-client/src/components/reports/graphs/net-worth-spreadsheet.tsx index 997dffc4fb6..4750d289f64 100644 --- a/packages/desktop-client/src/components/reports/graphs/net-worth-spreadsheet.tsx +++ b/packages/desktop-client/src/components/reports/graphs/net-worth-spreadsheet.tsx @@ -93,6 +93,8 @@ function recalculate(data, start, end) { let hasNegative = false; let startNetWorth = 0; let endNetWorth = 0; + let lowestNetWorth = null; + let highestNetWorth = null; const graphData = months.reduce((arr, month, idx) => { let debt = 0; @@ -140,6 +142,15 @@ function recalculate(data, start, end) { endNetWorth = total; arr.push({ x, y: integerToAmount(total), premadeLabel: label }); + + arr.forEach(item => { + if (item.y < lowestNetWorth || lowestNetWorth === null) { + lowestNetWorth = item.y; + } + if (item.y > highestNetWorth || highestNetWorth === null) { + highestNetWorth = item.y; + } + }); return arr; }, []); @@ -152,5 +163,7 @@ function recalculate(data, start, end) { }, netWorth: endNetWorth, totalChange: endNetWorth - startNetWorth, + lowestNetWorth, + highestNetWorth, }; } diff --git a/packages/desktop-client/src/components/responsive/PullToRefresh.tsx b/packages/desktop-client/src/components/responsive/PullToRefresh.tsx new file mode 100644 index 00000000000..5dba845680d --- /dev/null +++ b/packages/desktop-client/src/components/responsive/PullToRefresh.tsx @@ -0,0 +1,28 @@ +import React, { type ComponentProps } from 'react'; +import BasePullToRefresh from 'react-simple-pull-to-refresh'; + +import { css } from 'glamor'; + +type PullToRefreshProps = ComponentProps; + +export default function PullToRefresh(props: PullToRefreshProps) { + return ( +
+ +
+ ); +} diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx index e95348718ef..8bfb8062e2f 100644 --- a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx +++ b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx @@ -1,11 +1,16 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, type CSSProperties } from 'react'; import { useSelector } from 'react-redux'; import { useCachedAccounts } from 'loot-core/src/client/data-hooks/accounts'; import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees'; -import * as monthUtils from 'loot-core/src/shared/months'; +import { + type ScheduleStatusType, + type ScheduleStatuses, +} from 'loot-core/src/client/data-hooks/schedules'; +import { format as monthUtilFormat } from 'loot-core/src/shared/months'; import { getScheduledAmount } from 'loot-core/src/shared/schedules'; import { integerToCurrency } from 'loot-core/src/shared/util'; +import { type ScheduleEntity } from 'loot-core/src/types/models'; import DotsHorizontalTriple from '../../icons/v1/DotsHorizontalTriple'; import Check from '../../icons/v2/Check'; @@ -21,10 +26,73 @@ import DisplayId from '../util/DisplayId'; import { StatusBadge } from './StatusBadge'; -export let ROW_HEIGHT = 43; +type SchedulesTableProps = { + schedules: ScheduleEntity[]; + statuses: ScheduleStatuses; + filter: string; + allowCompleted: boolean; + onSelect: (id: ScheduleEntity['id']) => void; + onAction: (actionName: ScheduleItemAction, id: ScheduleEntity['id']) => void; + style: CSSProperties; + minimal?: boolean; + tableStyle?: CSSProperties; +}; -function OverflowMenu({ schedule, status, onAction }) { - let [open, setOpen] = useState(false); +type CompletedScheduleItem = { id: 'show-completed' }; +type SchedulesTableItem = ScheduleEntity | CompletedScheduleItem; + +export type ScheduleItemAction = + | 'post-transaction' + | 'skip' + | 'complete' + | 'restart' + | 'delete'; + +export const ROW_HEIGHT = 43; + +function OverflowMenu({ + schedule, + status, + onAction, +}: { + schedule: ScheduleEntity; + status: ScheduleStatusType; + onAction: SchedulesTableProps['onAction']; +}) { + const [open, setOpen] = useState(false); + + const getMenuItems = () => { + const menuItems: { name: ScheduleItemAction; text: string }[] = []; + + if (status === 'due') { + menuItems.push({ + name: 'post-transaction', + text: 'Post transaction', + }); + } + + if (status === 'completed') { + menuItems.push({ + name: 'restart', + text: 'Restart', + }); + } else { + menuItems.push( + { + name: 'skip', + text: 'Skip next date', + }, + { + name: 'complete', + text: 'Complete', + }, + ); + } + + menuItems.push({ name: 'delete', text: 'Delete' }); + + return menuItems; + }; return ( @@ -49,23 +117,11 @@ function OverflowMenu({ schedule, status, onAction }) { onClose={() => setOpen(false)} > { + onMenuSelect={(name: ScheduleItemAction) => { onAction(name, schedule.id); setOpen(false); }} - items={[ - status === 'due' && { - name: 'post-transaction', - text: 'Post transaction', - }, - ...(schedule.completed - ? [{ name: 'restart', text: 'Restart' }] - : [ - { name: 'skip', text: 'Skip next date' }, - { name: 'complete', text: 'Complete' }, - ]), - { name: 'delete', text: 'Delete' }, - ]} + items={getMenuItems()} /> )} @@ -73,10 +129,16 @@ function OverflowMenu({ schedule, status, onAction }) { ); } -export function ScheduleAmountCell({ amount, op }) { - let num = getScheduledAmount(amount); - let str = integerToCurrency(Math.abs(num || 0)); - let isApprox = op === 'isapprox' || op === 'isbetween'; +export function ScheduleAmountCell({ + amount, + op, +}: { + amount: ScheduleEntity['_amount']; + op: ScheduleEntity['_amountOp']; +}) { + const num = getScheduledAmount(amount); + const str = integerToCurrency(Math.abs(num || 0)); + const isApprox = op === 'isapprox' || op === 'isbetween'; return ( { +}: SchedulesTableProps) { + const dateFormat = useSelector(state => { return state.prefs.local.dateFormat || 'MM/dd/yyyy'; }); - let [showCompleted, setShowCompleted] = useState(false); + const [showCompleted, setShowCompleted] = useState(false); - let payees = useCachedPayees(); - let accounts = useCachedAccounts(); + const payees = useCachedPayees(); + const accounts = useCachedAccounts(); - let filteredSchedules = useMemo(() => { + const filteredSchedules = useMemo(() => { if (!filter) { return schedules; } - const filterIncludes = str => + const filterIncludes = (str: string) => str ? str.toLowerCase().includes(filter.toLowerCase()) || filter.toLowerCase().includes(str.toLowerCase()) : false; return schedules.filter(schedule => { - let payee = payees.find(p => schedule._payee === p.id); - let account = accounts.find(a => schedule._account === a.id); - let amount = getScheduledAmount(schedule._amount); - let amountStr = + const payee = payees.find(p => schedule._payee === p.id); + const account = accounts.find(a => schedule._account === a.id); + const amount = getScheduledAmount(schedule._amount); + const amountStr = (schedule._amountOp === 'isapprox' || schedule._amountOp === 'isbetween' ? '~' : '') + (amount > 0 ? '+' : '') + integerToCurrency(Math.abs(amount || 0)); - let dateStr = schedule.next_date - ? monthUtils.format(schedule.next_date, dateFormat) + const dateStr = schedule.next_date + ? monthUtilFormat(schedule.next_date, dateFormat) : null; return ( @@ -174,26 +236,29 @@ export function SchedulesTable({ }); }, [schedules, filter, statuses]); - let items = useMemo(() => { + const items: SchedulesTableItem[] = useMemo(() => { + const unCompletedSchedules = filteredSchedules.filter(s => !s.completed); + if (!allowCompleted) { - return filteredSchedules.filter(s => !s.completed); + return unCompletedSchedules; } if (showCompleted) { return filteredSchedules; } - let arr = filteredSchedules.filter(s => !s.completed); - if (filteredSchedules.find(s => s.completed)) { - arr.push({ type: 'show-completed' }); - } - return arr; + + const hasCompletedSchedule = filteredSchedules.find(s => s.completed); + + if (!hasCompletedSchedule) return unCompletedSchedules; + + return [...unCompletedSchedules, { id: 'show-completed' }]; }, [filteredSchedules, showCompleted, allowCompleted]); - function renderSchedule({ item }) { + function renderSchedule({ schedule }: { schedule: ScheduleEntity }) { return ( onSelect(item.id)} + onClick={() => onSelect(schedule.id)} style={{ cursor: 'pointer', backgroundColor: theme.tableBackground, @@ -204,33 +269,33 @@ export function SchedulesTable({ - {item.name ? item.name : 'None'} + {schedule.name ? schedule.name : 'None'} - + - + - {item.next_date - ? monthUtils.format(item.next_date, dateFormat) + {schedule.next_date + ? monthUtilFormat(schedule.next_date, dateFormat) : null} - + - + {!minimal && ( - {item._date && item._date.frequency && ( + {schedule._date && schedule._date.frequency && ( )} @@ -238,8 +303,8 @@ export function SchedulesTable({ {!minimal && ( @@ -248,8 +313,8 @@ export function SchedulesTable({ ); } - function renderItem({ item }) { - if (item.type === 'show-completed') { + function renderItem({ item }: { item: SchedulesTableItem }) { + if (item.id === 'show-completed') { return ( ); } - return renderSchedule({ item }); + return renderSchedule({ schedule: item as ScheduleEntity }); } return ( @@ -300,7 +365,7 @@ export function SchedulesTable({ backgroundColor="transparent" version="v2" style={{ flex: 1, backgroundColor: 'transparent', ...style }} - items={items} + items={items as ScheduleEntity[]} renderItem={renderItem} renderEmpty={filter ? 'No matching schedules' : 'No schedules'} allowPopupsEscape={items.length < 6} diff --git a/packages/desktop-client/src/components/schedules/StatusBadge.tsx b/packages/desktop-client/src/components/schedules/StatusBadge.tsx index ce52ff4a112..d0b7ae84cba 100644 --- a/packages/desktop-client/src/components/schedules/StatusBadge.tsx +++ b/packages/desktop-client/src/components/schedules/StatusBadge.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import { type ScheduleStatusType } from 'loot-core/src/client/data-hooks/schedules'; import { titleFirst } from 'loot-core/src/shared/util'; import AlertTriangle from '../../icons/v2/AlertTriangle'; @@ -9,81 +10,73 @@ import CheckCircleHollow from '../../icons/v2/CheckCircleHollow'; import EditSkull1 from '../../icons/v2/EditSkull1'; import FavoriteStar from '../../icons/v2/FavoriteStar'; import ValidationCheck from '../../icons/v2/ValidationCheck'; -import { theme, type CSSProperties } from '../../style'; +import { theme } from '../../style'; import Text from '../common/Text'; import View from '../common/View'; -export function getStatusProps(status: Status) { - let color, backgroundColor, Icon; - +// Consists of Schedule Statuses + Transaction statuses +type StatusTypes = ScheduleStatusType | 'cleared' | 'pending'; +export function getStatusProps(status: StatusTypes) { switch (status) { case 'missed': - color = theme.altErrorText; - backgroundColor = theme.altErrorBackground; - Icon = EditSkull1; - break; + return { + color: theme.altErrorText, + backgroundColor: theme.altErrorBackground, + Icon: EditSkull1, + }; case 'due': - color = theme.altWarningText; - backgroundColor = theme.altWarningBackground; - Icon = AlertTriangle; - break; + return { + color: theme.altWarningText, + backgroundColor: theme.altWarningBackground, + Icon: AlertTriangle, + }; case 'upcoming': - color = theme.upcomingText; - backgroundColor = theme.upcomingBackground; - Icon = CalendarIcon; - break; + return { + color: theme.upcomingText, + backgroundColor: theme.upcomingBackground, + Icon: CalendarIcon, + }; case 'paid': - color = theme.alt2NoticeText; - backgroundColor = theme.altNoticeBackground; - Icon = ValidationCheck; - break; + return { + color: theme.alt2NoticeText, + backgroundColor: theme.altNoticeBackground, + Icon: ValidationCheck, + }; case 'completed': - color = theme.alt2TableText; - backgroundColor = theme.altTableBackground; - Icon = FavoriteStar; - break; + return { + color: theme.alt2TableText, + backgroundColor: theme.altTableBackground, + Icon: FavoriteStar, + }; case 'pending': - color = theme.alt3NoticeText; - backgroundColor = theme.alt2NoticeBackground; - Icon = CalendarIcon; - break; + return { + color: theme.alt3NoticeText, + backgroundColor: theme.alt2NoticeBackground, + Icon: CalendarIcon, + }; case 'scheduled': - color = theme.menuItemText; - backgroundColor = theme.altTableBackground; - Icon = CalendarIcon; - break; + return { + color: theme.menuItemText, + backgroundColor: theme.altTableBackground, + Icon: CalendarIcon, + }; case 'cleared': - color = theme.noticeText; - backgroundColor = theme.altTableBackground; - Icon = CheckCircle1; - break; + return { + color: theme.noticeText, + backgroundColor: theme.altTableBackground, + Icon: CheckCircle1, + }; default: - color = theme.buttonNormalDisabledText; - backgroundColor = theme.altTableBackground; - Icon = CheckCircleHollow; - break; + return { + color: theme.buttonNormalDisabledText, + backgroundColor: theme.altTableBackground, + Icon: CheckCircleHollow, + }; } - - return { color, backgroundColor, Icon }; } -type Status = - | 'missed' - | 'due' - | 'upcoming' - | 'paid' - | 'completed' - | 'pending' - | 'scheduled' - | 'cleared'; - -type StatusBadgeProps = { - status: Status; - style?: CSSProperties; -}; - -export function StatusBadge({ status, style }: StatusBadgeProps) { - let { color, backgroundColor, Icon } = getStatusProps(status); +export function StatusBadge({ status }: { status: ScheduleStatusType }) { + const { color, backgroundColor, Icon } = getStatusProps(status); return ( diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx index 19aead8b566..7d0d2aca3cb 100644 --- a/packages/desktop-client/src/components/table.tsx +++ b/packages/desktop-client/src/components/table.tsx @@ -11,6 +11,8 @@ import React, { type ReactNode, type KeyboardEvent, type UIEvent, + type ReactElement, + type Ref, } from 'react'; import { useStore } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -833,7 +835,7 @@ let rowStyle: CSSProperties = { type TableHandleRef = { scrollTo: (id: number, alignment?: string) => void; scrollToTop: () => void; - getScrolledItem: () => number; + getScrolledItem: () => TableItem['id']; setRowAnimation: (flag) => void; edit(id: number, field, shouldScroll): void; anchor(): void; @@ -852,10 +854,10 @@ export const TableWithNavigator = forwardRef< return ; }); -type TableItem = { id: number }; +type TableItem = { id: number | string }; -type TableProps = { - items: TableItem[]; +type TableProps = { + items: T[]; count?: number; headers?: ReactNode | TableHeaderProps['headers']; contentHeader?: ReactNode; @@ -863,7 +865,7 @@ type TableProps = { rowHeight?: number; backgroundColor?: string; renderItem: (arg: { - item: TableItem; + item: T; editing: boolean; focusedField: unknown; onEdit: (id, field) => void; @@ -875,6 +877,7 @@ type TableProps = { loadMore?: () => void; style?: CSSProperties; navigator?: ReturnType; + listRef?: unknown; onScroll?: () => void; version?: string; allowPopupsEscape?: boolean; @@ -882,7 +885,9 @@ type TableProps = { saveScrollWidth?: (parent, child) => void; }; -export const Table = forwardRef( +export const Table: ( + props: TableProps & { ref?: Ref }, +) => ReactElement = forwardRef( ( { items, diff --git a/packages/loot-core/src/client/data-hooks/accounts.tsx b/packages/loot-core/src/client/data-hooks/accounts.tsx index 147e545c6f8..084060f3b87 100644 --- a/packages/loot-core/src/client/data-hooks/accounts.tsx +++ b/packages/loot-core/src/client/data-hooks/accounts.tsx @@ -1,26 +1,33 @@ import React, { createContext, useContext } from 'react'; +import { type AccountEntity } from '../../types/models'; import q from '../query-helpers'; import { useLiveQuery } from '../query-hooks'; import { getAccountsById } from '../reducers/queries'; -function useAccounts() { +function useAccounts(): AccountEntity[] { return useLiveQuery(() => q('accounts').select('*'), []); } -let AccountsContext = createContext(null); +const AccountsContext = createContext(null); export function AccountsProvider({ children }) { - let data = useAccounts(); + const data = useAccounts(); return ; } export function CachedAccounts({ children, idKey }) { - let data = useCachedAccounts({ idKey }); + const data = useCachedAccounts({ idKey }); return children(data); } -export function useCachedAccounts({ idKey }: { idKey? } = {}) { - let data = useContext(AccountsContext); +export function useCachedAccounts(): AccountEntity[]; +export function useCachedAccounts({ + idKey, +}: { + idKey: boolean; +}): Record; +export function useCachedAccounts({ idKey }: { idKey?: boolean } = {}) { + const data = useContext(AccountsContext); return idKey && data ? getAccountsById(data) : data; } diff --git a/packages/loot-core/src/client/data-hooks/payees.tsx b/packages/loot-core/src/client/data-hooks/payees.tsx index 2adba3bb71f..f5b1956aca4 100644 --- a/packages/loot-core/src/client/data-hooks/payees.tsx +++ b/packages/loot-core/src/client/data-hooks/payees.tsx @@ -1,26 +1,33 @@ import React, { createContext, useContext } from 'react'; +import { type PayeeEntity } from '../../types/models'; import q from '../query-helpers'; import { useLiveQuery } from '../query-hooks'; import { getPayeesById } from '../reducers/queries'; -function usePayees() { +function usePayees(): PayeeEntity[] { return useLiveQuery(() => q('payees').select('*'), []); } -let PayeesContext = createContext(null); +const PayeesContext = createContext(null); export function PayeesProvider({ children }) { - let data = usePayees(); + const data = usePayees(); return ; } export function CachedPayees({ children, idKey }) { - let data = useCachedPayees({ idKey }); + const data = useCachedPayees({ idKey }); return children(data); } -export function useCachedPayees({ idKey }: { idKey? } = {}) { - let data = useContext(PayeesContext); +export function useCachedPayees(): PayeeEntity[]; +export function useCachedPayees({ + idKey, +}: { + idKey: boolean; +}): Record; +export function useCachedPayees({ idKey }: { idKey?: boolean } = {}) { + const data = useContext(PayeesContext); return idKey && data ? getPayeesById(data) : data; } diff --git a/packages/loot-core/src/client/data-hooks/schedules.tsx b/packages/loot-core/src/client/data-hooks/schedules.tsx index 9131d3c96b1..a11bb1c7f0e 100644 --- a/packages/loot-core/src/client/data-hooks/schedules.tsx +++ b/packages/loot-core/src/client/data-hooks/schedules.tsx @@ -5,6 +5,9 @@ import { getStatus, getHasTransactionsQuery } from '../../shared/schedules'; import { type ScheduleEntity } from '../../types/models'; import q, { liveQuery } from '../query-helpers'; +export type ScheduleStatusType = ReturnType; +export type ScheduleStatuses = Map; + function loadStatuses(schedules: ScheduleEntity[], onData) { return liveQuery(getHasTransactionsQuery(schedules), onData, { mapper: data => { @@ -23,13 +26,15 @@ function loadStatuses(schedules: ScheduleEntity[], onData) { type UseSchedulesArgs = { transform?: (q: Query) => Query }; type UseSchedulesReturnType = { schedules: ScheduleEntity[]; - statuses: Record>; + statuses: ScheduleStatuses; } | null; -export function useSchedules({ transform }: UseSchedulesArgs = {}) { - let [data, setData] = useState(null); +export function useSchedules({ + transform, +}: UseSchedulesArgs = {}): UseSchedulesReturnType { + const [data, setData] = useState(null); useEffect(() => { - let query = q('schedules').select('*'); + const query = q('schedules').select('*'); let scheduleQuery, statusQuery; scheduleQuery = liveQuery( @@ -40,10 +45,8 @@ export function useSchedules({ transform }: UseSchedulesArgs = {}) { statusQuery.unsubscribe(); } - statusQuery = loadStatuses( - schedules, - (statuses: Record>) => - setData({ schedules, statuses }), + statusQuery = loadStatuses(schedules, (statuses: ScheduleStatuses) => + setData({ schedules, statuses }), ); } }, @@ -65,7 +68,7 @@ export function useSchedules({ transform }: UseSchedulesArgs = {}) { let SchedulesContext = createContext(null); export function SchedulesProvider({ transform, children }) { - let data = useSchedules({ transform }); + const data = useSchedules({ transform }); return ; } diff --git a/packages/loot-core/src/shared/util.ts b/packages/loot-core/src/shared/util.ts index 8c748d475e4..b4d64754853 100644 --- a/packages/loot-core/src/shared/util.ts +++ b/packages/loot-core/src/shared/util.ts @@ -179,7 +179,7 @@ export function fastSetMerge(set1, set2) { return finalSet; } -export function titleFirst(str) { +export function titleFirst(str: string) { return str[0].toUpperCase() + str.slice(1); } diff --git a/packages/loot-core/src/types/models/schedule.d.ts b/packages/loot-core/src/types/models/schedule.d.ts index f170b736a8f..5384eab2161 100644 --- a/packages/loot-core/src/types/models/schedule.d.ts +++ b/packages/loot-core/src/types/models/schedule.d.ts @@ -3,9 +3,9 @@ import type { PayeeEntity } from './payee'; import type { RuleEntity } from './rule'; export interface ScheduleEntity { - id?: string; + id: string; name?: string; - rule: RuleEntity; + rule: RuleEntity['id']; next_date: string; completed: boolean; posts_transaction: boolean; @@ -13,11 +13,21 @@ export interface ScheduleEntity { // These are special fields that are actually pulled from the // underlying rule - _payee: PayeeEntity; - _account: AccountEntity; + _payee: PayeeEntity['id']; + _account: AccountEntity['id']; _amount: unknown; _amountOp: string; - _date: unknown; + _date: { + interval: number; + patterns: { + value: number; + type: 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'day'; + }[]; + skipWeekend: boolean; + start: string; + weekendSolveMode: 'before' | 'after'; + frequency: 'daily' | 'weekly' | 'monthly' | 'yearly'; + }; _conditions: unknown; _actions: unknown; } diff --git a/upcoming-release-notes/1691.md b/upcoming-release-notes/1691.md new file mode 100644 index 00000000000..3af55f81084 --- /dev/null +++ b/upcoming-release-notes/1691.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [muhsinkamil] +--- + +Refactor SchedulesTable and its components to tsx. diff --git a/upcoming-release-notes/1705.md b/upcoming-release-notes/1705.md new file mode 100644 index 00000000000..cc489908c33 --- /dev/null +++ b/upcoming-release-notes/1705.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Updating mobile icon to have no white border diff --git a/upcoming-release-notes/1706.md b/upcoming-release-notes/1706.md new file mode 100644 index 00000000000..9c6857f774c --- /dev/null +++ b/upcoming-release-notes/1706.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [MatissJanis] +--- + +Mobile: fix transaction list scrolling requiring a previous interaction before scroll happens diff --git a/upcoming-release-notes/1707.md b/upcoming-release-notes/1707.md new file mode 100644 index 00000000000..774e3ed848b --- /dev/null +++ b/upcoming-release-notes/1707.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Remove unnecessary react-error-overlay manual resolution diff --git a/upcoming-release-notes/1709.md b/upcoming-release-notes/1709.md new file mode 100644 index 00000000000..4955bfc0290 --- /dev/null +++ b/upcoming-release-notes/1709.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [Crazypkr1099] +--- + +Enhance Y-Axis Scaling on Net Worth Graph diff --git a/yarn.lock b/yarn.lock index 8c8cf2d60f3..c68af424224 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16776,10 +16776,10 @@ __metadata: languageName: node linkType: hard -"react-error-overlay@npm:6.0.9": - version: 6.0.9 - resolution: "react-error-overlay@npm:6.0.9" - checksum: 695853bc885e798008a00c10d8d94e5ac91626e8130802fea37345f9c037f41b80104345db2ee87f225feb4a4ef71b0df572b17c378a6d397b6815f6d4a84293 +"react-error-overlay@npm:^6.0.11": + version: 6.0.11 + resolution: "react-error-overlay@npm:6.0.11" + checksum: ce7b44c38fadba9cedd7c095cf39192e632daeccf1d0747292ed524f17dcb056d16bc197ddee5723f9dd888f0b9b19c3b486c430319e30504289b9296f2d2c42 languageName: node linkType: hard