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 564f198a32b..dee2d2855a4 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 f1346447949..386edf7e87b 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/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png index ec8a8bef52c..a6b665cbc7e 100644 Binary files a/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png and b/packages/desktop-client/e2e/reports.test.js-snapshots/Reports-loads-net-worth-graph-and-checks-visuals-2-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/reports/Header.jsx b/packages/desktop-client/src/components/reports/Header.jsx index cb4d8d54a66..bd4ebe076e9 100644 --- a/packages/desktop-client/src/components/reports/Header.jsx +++ b/packages/desktop-client/src/components/reports/Header.jsx @@ -50,12 +50,22 @@ function boundedRange(earliest, start, end) { return [start, end]; } -export function getLatestRange(offset) { +function getLatestRange(offset) { const end = monthUtils.currentMonth(); const start = monthUtils.subMonths(end, offset); return [start, end]; } +export function getSpecificRange(offset, addNumber) { + const currMonth = monthUtils.currentMonth(); + const start = monthUtils.subMonths(currMonth, offset); + const end = monthUtils.addMonths( + start, + addNumber === null ? offset : addNumber, + ); + return [start, end]; +} + export function getFullRange(allMonths) { const start = allMonths[allMonths.length - 1].name; const end = monthUtils.currentMonth(); diff --git a/packages/desktop-client/src/components/reports/Overview.jsx b/packages/desktop-client/src/components/reports/Overview.jsx index ce0c49b4499..975735eec85 100644 --- a/packages/desktop-client/src/components/reports/Overview.jsx +++ b/packages/desktop-client/src/components/reports/Overview.jsx @@ -4,11 +4,13 @@ import { useReports } from 'loot-core/src/client/data-hooks/reports'; import { useAccounts } from '../../hooks/useAccounts'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; -import { theme, styles } from '../../style'; +import { styles } from '../../style'; +import { AnchorLink } from '../common/AnchorLink'; +import { Button } from '../common/Button'; +import { Text } from '../common/Text'; import { View } from '../common/View'; import { CashFlowCard } from './reports/CashFlowCard'; -import { CustomReportCard } from './reports/CustomReportCard'; import { CustomReportListCards } from './reports/CustomReportListCards'; import { NetWorthCard } from './reports/NetWorthCard'; import { SankeyCard } from './reports/SankeyCard'; @@ -19,8 +21,6 @@ export function Overview() { const customReportsFeatureFlag = useFeatureFlag('customReports'); - const featureCount = - 3 - (sankeyFeatureFlag ? 1 : 0) - (customReportsFeatureFlag ? 1 : 0); const accounts = useAccounts(); return ( + {customReportsFeatureFlag && ( + + + + + + )} {sankeyFeatureFlag && } - {customReportsFeatureFlag && } - {featureCount !== 3 && - [...Array(featureCount)].map((e, i) => ( - - ))} {customReportsFeatureFlag && ( - <> - - - + )} ); diff --git a/packages/desktop-client/src/components/reports/ReportOptions.ts b/packages/desktop-client/src/components/reports/ReportOptions.ts index 523e024ac3e..d8eda05e91e 100644 --- a/packages/desktop-client/src/components/reports/ReportOptions.ts +++ b/packages/desktop-client/src/components/reports/ReportOptions.ts @@ -150,7 +150,21 @@ export const categoryLists = (categories: { grouped: CategoryGroupEntity[]; }) => { const categoryList = [ - ...categories.list, + ...categories.list.sort((a, b) => { + //The point of this sorting is to make the graphs match the "budget" page + const catGroupA = categories.grouped.find(f => f.id === a.cat_group); + const catGroupB = categories.grouped.find(f => f.id === b.cat_group); + //initial check that both a and b have a sort_order and category group + return a.sort_order && b.sort_order && catGroupA && catGroupB + ? /*sorting by "is_income" because sort_order for this group is + separate from other groups*/ + Number(catGroupA.is_income) - Number(catGroupB.is_income) || + //Next, sorting by group sort_order + (catGroupA.sort_order ?? 0) - (catGroupB.sort_order ?? 0) || + //Finally, sorting by category within each group + a.sort_order - b.sort_order + : 0; + }), uncategorizedCategory, offBudgetCategory, transferCategory, diff --git a/packages/desktop-client/src/components/reports/ReportSidebar.jsx b/packages/desktop-client/src/components/reports/ReportSidebar.jsx index 6bf50b8463f..1c0125d39bd 100644 --- a/packages/desktop-client/src/components/reports/ReportSidebar.jsx +++ b/packages/desktop-client/src/components/reports/ReportSidebar.jsx @@ -14,9 +14,9 @@ import { CategorySelector } from './CategorySelector'; import { validateStart, validateEnd, - getLatestRange, getFullRange, validateRange, + getSpecificRange, } from './Header'; import { ModeButton } from './ModeButton'; import { ReportOptions } from './ReportOptions'; @@ -75,7 +75,12 @@ export function ReportSidebar({ ); break; default: - onChangeDates(...getLatestRange(ReportOptions.dateRangeMap.get(cond))); + onChangeDates( + ...getSpecificRange( + ReportOptions.dateRangeMap.get(cond), + cond === 'Last month' ? 0 : null, + ), + ); } }; diff --git a/packages/desktop-client/src/components/reports/SaveReport.tsx b/packages/desktop-client/src/components/reports/SaveReport.tsx index a71416e3249..0e10df084de 100644 --- a/packages/desktop-client/src/components/reports/SaveReport.tsx +++ b/packages/desktop-client/src/components/reports/SaveReport.tsx @@ -48,7 +48,10 @@ export function SaveReport({ setName(chooseSavedReport === undefined ? '' : chooseSavedReport.name); } - const onAddUpdate = async (menuChoice: string) => { + const onAddUpdate = async ({ menuChoice }: { menuChoice?: string }) => { + if (!menuChoice) { + return null; + } if (menuChoice === 'save-report') { const newSavedReport = { ...report, @@ -111,7 +114,7 @@ export function SaveReport({ case 'update-report': setErr(''); setMenuOpen(false); - onAddUpdate(item); + onAddUpdate({ menuChoice: item }); break; case 'save-report': setErr(''); diff --git a/packages/desktop-client/src/components/reports/SaveReportName.tsx b/packages/desktop-client/src/components/reports/SaveReportName.tsx index 828a20a60ab..fb598092168 100644 --- a/packages/desktop-client/src/components/reports/SaveReportName.tsx +++ b/packages/desktop-client/src/components/reports/SaveReportName.tsx @@ -1,5 +1,7 @@ import React, { type RefObject, useEffect } from 'react'; +import { type CustomReportEntity } from 'loot-core/types/models/reports'; + import { theme } from '../../style'; import { Button } from '../common/Button'; import { Input } from '../common/Input'; @@ -15,8 +17,15 @@ type SaveReportNameProps = { name: string; setName: (name: string) => void; inputRef: RefObject; - onAddUpdate: (menuItem: string) => void; + onAddUpdate: ({ + menuChoice, + reportData, + }: { + menuChoice?: string; + reportData?: CustomReportEntity; + }) => void; err: string; + report?: CustomReportEntity; }; export function SaveReportName({ @@ -27,6 +36,7 @@ export function SaveReportName({ inputRef, onAddUpdate, err, + report, }: SaveReportNameProps) { useEffect(() => { if (inputRef.current) { @@ -63,7 +73,10 @@ export function SaveReportName({ style={{ marginTop: 30 }} onClick={e => { e.preventDefault(); - onAddUpdate(menuItem); + onAddUpdate({ + menuChoice: menuItem ?? undefined, + reportData: report ?? undefined, + }); }} > {menuItem === 'save-report' ? 'Add' : 'Update'} diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx index ed0bd28d1f3..e94d025d7ea 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx @@ -312,7 +312,23 @@ export function CustomReport() { return ( -
+ +
+ + {report.name || 'Unsaved report'} + + { - return createCustomSpreadsheet({ - startDate, - endDate, - groupBy, - balanceTypeOp: 'totalDebts', - categories, - showEmpty: false, - showOffBudget: false, - showHiddenCategories: false, - showUncategorized: false, - }); - }, [startDate, endDate, categories]); - const data = useReport('default', getGraphData); - - return ( - - - - - - Custom Report - - - - - - - {data ? ( - - ) : ( - - )} - - ); -} diff --git a/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx b/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx index cb7b80e6649..6111d9b40d4 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx @@ -1,37 +1,39 @@ -import React, { useMemo, useState } from 'react'; +import React, { createRef, useMemo, useState } from 'react'; -import { send } from 'loot-core/src/platform/client/fetch'; +import { send, sendCatch } from 'loot-core/platform/client/fetch/index'; import { type CustomReportEntity } from 'loot-core/types/models/reports'; import { styles } from '../../../style'; +import { theme } from '../../../style/theme'; import { Block } from '../../common/Block'; import { Menu } from '../../common/Menu'; import { MenuButton } from '../../common/MenuButton'; import { MenuTooltip } from '../../common/MenuTooltip'; +import { Text } from '../../common/Text'; import { View } from '../../common/View'; import { ChooseGraph } from '../ChooseGraph'; import { DateRange } from '../DateRange'; import { LoadingIndicator } from '../LoadingIndicator'; import { ReportCard } from '../ReportCard'; +import { SaveReportName } from '../SaveReportName'; type CardMenuProps = { onClose: () => void; - onMenuSelect: (item: string, reportId: string) => void; - reportId: string; + onMenuSelect: (item: string, report: CustomReportEntity) => void; + report: CustomReportEntity; }; -function CardMenu({ onClose, onMenuSelect, reportId }: CardMenuProps) { +function CardMenu({ onClose, onMenuSelect, report }: CardMenuProps) { return ( { - onMenuSelect(item, reportId); + onMenuSelect(item, report); }} items={[ { name: 'rename', text: 'Rename report', - disabled: true, }, { name: 'delete', @@ -61,13 +63,48 @@ export function CustomReportListCards({ }) { const result: { [key: string]: boolean }[] = index(reports); const [reportMenu, setReportMenu] = useState(result); + const [nameMenuOpen, setNameMenuOpen] = useState(result); + const [err, setErr] = useState(''); + const [name, setName] = useState(''); + const inputRef = createRef(); const [isCardHovered, setIsCardHovered] = useState(''); - const onMenuSelect = async (item: string, reportId: string) => { + const onAddUpdate = async ({ + reportData, + }: { + reportData?: CustomReportEntity; + }) => { + if (!reportData) { + return null; + } + + const updatedReport = { + ...reportData, + name, + }; + + const response = await sendCatch('report/update', updatedReport); + + if (response.error) { + setErr(response.error.message); + onNameMenuOpen(reportData.id === undefined ? '' : reportData.id, true); + return; + } + + onNameMenuOpen(reportData.id === undefined ? '' : reportData.id, false); + }; + + const onMenuSelect = async (item: string, report: CustomReportEntity) => { if (item === 'delete') { - onMenuOpen(reportId, false); - await send('report/delete', reportId); + onMenuOpen(report.id, false); + await send('report/delete', report.id); + } + if (item === 'rename') { + onMenuOpen(report.id, false); + onNameMenuOpen(report.id, true); + setName(report.name); + setErr(''); } }; @@ -75,6 +112,10 @@ export function CustomReportListCards({ setReportMenu({ ...reportMenu, [item]: state }); }; + const onNameMenuOpen = (item: string, state: boolean) => { + setNameMenuOpen({ ...nameMenuOpen, [item]: state }); + }; + const chunkSize = 3; const groups = useMemo(() => { @@ -138,10 +179,16 @@ export function CustomReportListCards({ > {report.name} - + {report.isDateStatic ? ( + + ) : ( + + {report.dateRange} + + )} @@ -192,7 +239,26 @@ export function CustomReportListCards({ false, ) } - reportId={report.id} + report={report} + /> + )} + {report.id === undefined + ? null + : nameMenuOpen[report.id as keyof typeof nameMenuOpen] && ( + + onNameMenuOpen( + report.id === undefined ? '' : report.id, + false, + ) + } + menuItem="rename" + name={name} + setName={setName} + inputRef={inputRef} + onAddUpdate={onAddUpdate} + err={err} + report={report} /> )} diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts index 4d6464573e6..b5cbcad9b64 100644 --- a/packages/loot-core/src/types/models/reports.d.ts +++ b/packages/loot-core/src/types/models/reports.d.ts @@ -81,7 +81,6 @@ export interface CustomReportData { date_range: string; mode: string; group_by: string; - interval: string; balance_type: string; show_empty: number; show_offbudget: number; @@ -92,6 +91,6 @@ export interface CustomReportData { conditions?: RuleConditionEntity[]; conditions_op: string; metadata?: GroupedEntity; - interval?: string; + interval: string; color_scheme?: string; } diff --git a/upcoming-release-notes/2386.md b/upcoming-release-notes/2386.md new file mode 100644 index 00000000000..590ff93930a --- /dev/null +++ b/upcoming-release-notes/2386.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [carkom] +--- + +Enable dashboard card "rename" menu. Change default custom report card to just show a button. Adjust time filters. Fix category order.