diff --git a/packages/desktop-client/src/components/reports/ReportOptions.ts b/packages/desktop-client/src/components/reports/ReportOptions.ts index 8c2d361dbe6..a79c2059abf 100644 --- a/packages/desktop-client/src/components/reports/ReportOptions.ts +++ b/packages/desktop-client/src/components/reports/ReportOptions.ts @@ -11,8 +11,6 @@ const startDate = monthUtils.subMonths(monthUtils.currentMonth(), 5); const endDate = monthUtils.currentMonth(); export const defaultReport: CustomReportEntity = { - id: undefined, - name: 'Default', startDate, endDate, isDateStatic: false, @@ -24,7 +22,6 @@ export const defaultReport: CustomReportEntity = { showOffBudget: false, showHiddenCategories: false, showUncategorized: false, - selectedCategories: [], graphType: 'BarGraph', conditions: [], conditionsOp: 'and', diff --git a/packages/desktop-client/src/components/reports/ReportSidebar.jsx b/packages/desktop-client/src/components/reports/ReportSidebar.jsx index 0ccb5ef2bc9..46981a2c085 100644 --- a/packages/desktop-client/src/components/reports/ReportSidebar.jsx +++ b/packages/desktop-client/src/components/reports/ReportSidebar.jsx @@ -45,7 +45,7 @@ export function ReportSidebar({ }) { const [menuOpen, setMenuOpen] = useState(false); const onSelectRange = cond => { - onReportChange(null, 'modify'); + onReportChange({ type: 'modify' }); setDateRange(cond); switch (cond) { case 'All time': @@ -79,7 +79,7 @@ export function ReportSidebar({ }; const onChangeMode = cond => { - onReportChange(null, 'modify'); + onReportChange({ type: 'modify' }); setMode(cond); if (cond === 'time') { if (customReportItems.graphType === 'TableGraph') { @@ -110,7 +110,7 @@ export function ReportSidebar({ }; const onChangeSplit = cond => { - onReportChange(null, 'modify'); + onReportChange({ type: 'modify' }); setGroupBy(cond); if (customReportItems.mode === 'total') { if (customReportItems.graphType !== 'TableGraph') { @@ -128,7 +128,7 @@ export function ReportSidebar({ }; const onChangeBalanceType = cond => { - onReportChange(null, 'modify'); + onReportChange({ type: 'modify' }); setBalanceType(cond); }; @@ -275,6 +275,8 @@ export function ReportSidebar({ > { + onReportChange({ type: 'modify' }); + if (type === 'show-hidden-categories') { setShowHiddenCategories( !customReportItems.showHiddenCategories, @@ -464,7 +466,10 @@ export function ReportSidebar({ : false; })} selectedCategories={customReportItems.selectedCategories} - setSelectedCategories={setSelectedCategories} + setSelectedCategories={e => { + setSelectedCategories(e); + onReportChange({ type: 'modify' }); + }} showHiddenCategories={customReportItems.showHiddenCategories} /> diff --git a/packages/desktop-client/src/components/reports/ReportTopbar.jsx b/packages/desktop-client/src/components/reports/ReportTopbar.jsx index d8944ae8c14..d63744e9b31 100644 --- a/packages/desktop-client/src/components/reports/ReportTopbar.jsx +++ b/packages/desktop-client/src/components/reports/ReportTopbar.jsx @@ -14,10 +14,11 @@ import { View } from '../common/View'; import { FilterButton } from '../filters/FiltersMenu'; import { GraphButton } from './GraphButton'; -import { SaveReportMenuButton } from './SaveReport'; +import { SaveReport } from './SaveReport'; export function ReportTopbar({ customReportItems, + report, savedStatus, setGraphType, setTypeDisabled, @@ -29,6 +30,7 @@ export function ReportTopbar({ onApplyFilter, onChangeViews, onReportChange, + onResetReports, }) { return ( { + onReportChange({ type: 'modify' }); setGraphType('TableGraph'); onChangeViews('viewLegend', false); setTypeDisabled([]); @@ -60,6 +63,7 @@ export function ReportTopbar({ customReportItems.graphType === 'StackedBarGraph' } onSelect={() => { + onReportChange({ type: 'modify' }); if (customReportItems.mode === 'total') { setGraphType('BarGraph'); if (['Net'].includes(customReportItems.balanceType)) { @@ -84,6 +88,7 @@ export function ReportTopbar({ title="Area Graph" selected={customReportItems.graphType === 'AreaGraph'} onSelect={() => { + onReportChange({ type: 'modify' }); setGraphType('AreaGraph'); setGroupBy('Month'); onChangeViews('viewLegend', false); @@ -98,6 +103,7 @@ export function ReportTopbar({ title="Donut Graph" selected={customReportItems.graphType === 'DonutGraph'} onSelect={() => { + onReportChange({ type: 'modify' }); setGraphType('DonutGraph'); setTypeDisabled(['Net']); setBalanceType('Payment'); @@ -166,11 +172,17 @@ export function ReportTopbar({ hover onApply={e => { onApplyFilter(e); - onReportChange(null, 'modify'); + onReportChange({ type: 'modify' }); }} /> - + ); } diff --git a/packages/desktop-client/src/components/reports/SaveReport.tsx b/packages/desktop-client/src/components/reports/SaveReport.tsx index 00b59ada937..e7e26a3a503 100644 --- a/packages/desktop-client/src/components/reports/SaveReport.tsx +++ b/packages/desktop-client/src/components/reports/SaveReport.tsx @@ -1,45 +1,132 @@ -// @ts-strict-ignore -import React, { useState } from 'react'; +import React, { createRef, useState } from 'react'; + +import { v4 as uuidv4 } from 'uuid'; + +//import { send, sendCatch } from 'loot-core/src/platform/client/fetch'; +import { type CustomReportEntity } from 'loot-core/src/types/models'; import { SvgExpandArrow } from '../../icons/v0'; import { Button } from '../common/Button'; -import { Menu } from '../common/Menu'; -import { MenuTooltip } from '../common/MenuTooltip'; import { Text } from '../common/Text'; import { View } from '../common/View'; -function SaveReportMenu({ setMenuOpen }) { - return ( - setMenuOpen(false)}> - { - switch (item) { - case 'save': - case 'clear': - setMenuOpen(false); - break; - default: - } - }} - items={[ - { - name: 'save', - text: 'Save new report', - disabled: true, - }, - { - name: 'clear', - text: 'Clear all', - disabled: true, - }, - ]} - /> - - ); -} +import { SaveReportMenu } from './SaveReportMenu'; +import { SaveReportName } from './SaveReportName'; -export function SaveReportMenuButton({ savedStatus }: { savedStatus: string }) { +type SaveReportProps = { + customReportItems: T; + report: CustomReportEntity; + savedStatus: string; + onReportChange: ({ + savedReport, + type, + }: { + savedReport?: T; + type: string; + }) => void; + onResetReports: () => void; +}; + +export function SaveReport({ + customReportItems, + report, + savedStatus, + onReportChange, + onResetReports, +}: SaveReportProps) { + const [nameMenuOpen, setNameMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false); + const [menuItem, setMenuItem] = useState(''); + const [err, setErr] = useState(''); + const [res, setRes] = useState(''); + const [name, setName] = useState(report.name); + const inputRef = createRef(); + + const onAddUpdate = async (menuChoice: string) => { + let savedReport: CustomReportEntity; + //save existing states + savedReport = { + ...report, + ...customReportItems, + }; + + if (menuChoice === 'save-report') { + setRes(''); + //create new flow + /* + res = await sendCatch('report/create', { + ...savedReport, + }); + */ + savedReport = { + ...savedReport, + id: uuidv4(), + name, + }; + } + + if (menuChoice === 'rename-report') { + //rename + savedReport = { + ...savedReport, + name, + }; + } + + if (menuChoice === 'update-report') { + //send update and rename to DB + /* + res = await sendCatch('report/update', { + ...savedReport, + }); + */ + } + if (res !== '') { + setErr(res); + setNameMenuOpen(true); + } else { + setNameMenuOpen(false); + onReportChange({ + savedReport, + type: menuChoice === 'rename-report' ? 'rename' : 'add-update', + }); + } + }; + + const onMenuSelect = async (item: string) => { + setMenuItem(item); + switch (item) { + case 'rename-report': + setErr(''); + setMenuOpen(false); + setNameMenuOpen(true); + break; + case 'delete-report': + setMenuOpen(false); + //await send('report/delete', id); + onResetReports(); + break; + case 'update-report': + setErr(''); + setMenuOpen(false); + onAddUpdate(item); + break; + case 'save-report': + setErr(''); + setMenuOpen(false); + setNameMenuOpen(true); + break; + case 'reload-report': + setMenuOpen(false); + onReportChange({ type: 'reload' }); + break; + case 'reset-report': + setMenuOpen(false); + onResetReports(); + break; + default: + } + }; return ( - Unsaved Report  + {!report.id ? 'Unsaved report' : report.name}  {savedStatus === 'modified' && (modified) } - {menuOpen && } + {menuOpen && ( + setMenuOpen(false)} + report={report} + onMenuSelect={onMenuSelect} + savedStatus={savedStatus} + /> + )} + {nameMenuOpen && ( + setNameMenuOpen(false)} + menuItem={menuItem} + setName={setName} + inputRef={inputRef} + onAddUpdate={onAddUpdate} + err={err} + /> + )} ); } diff --git a/packages/desktop-client/src/components/reports/SaveReportMenu.tsx b/packages/desktop-client/src/components/reports/SaveReportMenu.tsx new file mode 100644 index 00000000000..01e311dd260 --- /dev/null +++ b/packages/desktop-client/src/components/reports/SaveReportMenu.tsx @@ -0,0 +1,76 @@ +import React from 'react'; + +import { type CustomReportEntity } from 'loot-core/src/types/models'; + +import { Menu } from '../common/Menu'; +import { MenuTooltip } from '../common/MenuTooltip'; + +export function SaveReportMenu({ + report, + onClose, + onMenuSelect, + savedStatus, +}: { + report: CustomReportEntity; + onClose: () => void; + onMenuSelect: (item: string) => void; + savedStatus: string; +}) { + return ( + + { + onMenuSelect(item); + }} + items={ + report.id === undefined + ? [ + { + name: 'save-report', + text: 'Save new report', + }, + { + name: 'reset-report', + text: 'Reset to default', + }, + ] + : savedStatus === 'saved' + ? [ + { name: 'rename-report', text: 'Rename' }, + { name: 'delete-report', text: 'Delete' }, + Menu.line, + { + name: 'save-report', + text: 'Save new report', + }, + { + name: 'reset-report', + text: 'Reset to default', + }, + ] + : [ + { name: 'rename-report', text: 'Rename' }, + { + name: 'update-report', + text: 'Update report', + }, + { + name: 'reload-report', + text: 'Revert changes', + }, + { name: 'delete-report', text: 'Delete' }, + Menu.line, + { + name: 'save-report', + text: 'Save new report', + }, + { + name: 'reset-report', + text: 'Reset to default', + }, + ] + } + /> + + ); +} diff --git a/packages/desktop-client/src/components/reports/SaveReportName.tsx b/packages/desktop-client/src/components/reports/SaveReportName.tsx new file mode 100644 index 00000000000..34c1d817625 --- /dev/null +++ b/packages/desktop-client/src/components/reports/SaveReportName.tsx @@ -0,0 +1,74 @@ +import React, { type RefObject, useEffect } from 'react'; + +import { theme } from '../../style'; +import { Button } from '../common/Button'; +import { Input } from '../common/Input'; +import { MenuTooltip } from '../common/MenuTooltip'; +import { Stack } from '../common/Stack'; +import { Text } from '../common/Text'; +import { FormField, FormLabel } from '../forms'; + +type SaveReportNameProps = { + onClose: () => void; + menuItem: string; + setName: (name: string) => void; + inputRef: RefObject; + onAddUpdate: (menuItem: string) => void; + err: string; +}; + +export function SaveReportName({ + onClose, + menuItem, + setName, + inputRef, + onAddUpdate, + err, +}: SaveReportNameProps) { + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + return ( + + {menuItem !== 'update-report' && ( +
+ + + + + + + +
+ )} + {err !== '' ? ( + + {err} + + ) : ( + + )} +
+ ); +} diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx index 7e679f233f0..97fd06aaa78 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx @@ -91,7 +91,7 @@ export function CustomReport() { const months = monthUtils.rangeInclusive(startDate, endDate); useEffect(() => { - if (selectedCategories.length === 0 && categories.list.length !== 0) { + if (selectedCategories === undefined && categories.list.length !== 0) { setSelectedCategories(categories.list); } }, [categories, selectedCategories]); @@ -203,8 +203,6 @@ export function CustomReport() { const data = { ...graphData, groupedData }; const customReportItems = { - id: undefined, - name: undefined, startDate, endDate, isDateStatic, @@ -232,7 +230,7 @@ export function CustomReport() { const onChangeDates = (startDate, endDate) => { setStartDate(startDate); setEndDate(endDate); - setSavedStatus('modified'); + onReportChange({ type: 'modify' }); }; const onChangeViews = (viewType, status) => { @@ -247,12 +245,37 @@ export function CustomReport() { } }; + const onResetReports = () => { + const selectAll = []; + categories.grouped.map(categoryGroup => + categoryGroup.categories.map(category => selectAll.push(category)), + ); + + setStartDate(defaultReport.startDate); + setEndDate(defaultReport.endDate); + setIsDateStatic(defaultReport.isDateStatic); + setDateRange(defaultReport.dateRange); + setMode(defaultReport.mode); + setGroupBy(defaultReport.groupBy); + setBalanceType(defaultReport.balanceType); + setShowEmpty(defaultReport.showEmpty); + setShowOffBudget(defaultReport.showOffBudget); + setShowHiddenCategories(defaultReport.showHiddenCategories); + setShowUncategorized(defaultReport.showUncategorized); + setSelectedCategories(selectAll); + setGraphType(defaultReport.graphType); + onApplyFilter(null); + onCondOpChange(defaultReport.conditionsOp); + setReport(defaultReport); + setSavedStatus('new'); + }; + const onChangeAppliedFilter = (filter, changedElement) => { - onReportChange(null, 'modify'); + onReportChange({ type: 'modify' }); return changedElement(filter); }; - const onReportChange = (savedReport, type) => { + const onReportChange = ({ savedReport, type }) => { switch (type) { case 'add-update': setSavedStatus('saved'); @@ -278,6 +301,7 @@ export function CustomReport() { setBalanceType(report.balanceType); setShowEmpty(report.showEmpty); setShowOffBudget(report.showOffBudget); + setShowHiddenCategories(report.showHiddenCategories); setShowUncategorized(report.showUncategorized); setSelectedCategories(report.selectedCategories); setGraphType(report.graphType); @@ -330,6 +354,7 @@ export function CustomReport() { > {filters && filters.length > 0 && ( - a.name - .trim() - .localeCompare(b.name.trim(), undefined, { ignorePunctuation: true }), + a.name && b.name + ? a.name.trim().localeCompare(b.name.trim(), undefined, { + ignorePunctuation: true, + }) + : 0, ); } diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts index a9b8269a472..ef888a2a87c 100644 --- a/packages/loot-core/src/types/models/reports.d.ts +++ b/packages/loot-core/src/types/models/reports.d.ts @@ -2,8 +2,8 @@ import { CategoryEntity } from './category'; import { type RuleConditionEntity } from './rule'; export interface CustomReportEntity { - id: string | undefined; - name: string; + id?: string; + name?: string; startDate: string; endDate: string; isDateStatic: boolean; @@ -15,7 +15,7 @@ export interface CustomReportEntity { showOffBudget: boolean; showHiddenCategories: boolean; showUncategorized: boolean; - selectedCategories: CategoryEntity[]; + selectedCategories?: CategoryEntity[]; graphType: string; conditions?: RuleConditionEntity[]; conditionsOp: string; diff --git a/upcoming-release-notes/2257.md b/upcoming-release-notes/2257.md new file mode 100644 index 00000000000..9413515c7ea --- /dev/null +++ b/upcoming-release-notes/2257.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [carkom] +--- + +Expanding the menu for saving reports and adding hooks and logic.