From e3a8366dd7ab2ab11c27336c181d7c77e7a752f2 Mon Sep 17 00:00:00 2001 From: Neil <55785687+carkom@users.noreply.github.com> Date: Fri, 2 Feb 2024 23:52:35 -0800 Subject: [PATCH] Update and organize reports (#2274) * Add schema work * notes * merge fixes * Add Reports Save Menu * merge fixes * updates * notes * updates * updates * save updates fix * typecheck fixes * revert changes * notes * error fixes * update * fix * merge fixes * review changes * reportChange and savedStatus * Update packages/desktop-client/src/components/reports/SaveReport.tsx Co-authored-by: DJ Mountney <david.mountney@twkie.net> * merge fixes --------- Co-authored-by: DJ Mountney <david.mountney@twkie.net> --- .../src/components/reports/ReportOptions.ts | 14 +-- .../src/components/reports/ReportSidebar.jsx | 11 ++- .../src/components/reports/ReportTopbar.jsx | 13 ++- .../src/components/reports/SaveReport.tsx | 3 +- .../reports/reports/CustomReport.jsx | 86 ++++++++++++++++--- .../src/client/data-hooks/reports.ts | 1 - packages/loot-core/src/server/reports/app.ts | 7 +- .../loot-core/src/types/models/reports.d.ts | 14 +-- upcoming-release-notes/2274.md | 6 ++ 9 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 upcoming-release-notes/2274.md diff --git a/packages/desktop-client/src/components/reports/ReportOptions.ts b/packages/desktop-client/src/components/reports/ReportOptions.ts index 3a0c1fe69..8c2d361db 100644 --- a/packages/desktop-client/src/components/reports/ReportOptions.ts +++ b/packages/desktop-client/src/components/reports/ReportOptions.ts @@ -10,8 +10,13 @@ import { const startDate = monthUtils.subMonths(monthUtils.currentMonth(), 5); const endDate = monthUtils.currentMonth(); -export const defaultState: CustomReportEntity = { +export const defaultReport: CustomReportEntity = { id: undefined, + name: 'Default', + startDate, + endDate, + isDateStatic: false, + dateRange: 'Last 6 months', mode: 'total', groupBy: 'Category', balanceType: 'Payment', @@ -19,13 +24,10 @@ export const defaultState: CustomReportEntity = { showOffBudget: false, showHiddenCategories: false, showUncategorized: false, + selectedCategories: [], graphType: 'BarGraph', - startDate, - endDate, - selectedCategories: null, - isDateStatic: false, + conditions: [], conditionsOp: 'and', - name: 'Default', }; const balanceTypeOptions = [ diff --git a/packages/desktop-client/src/components/reports/ReportSidebar.jsx b/packages/desktop-client/src/components/reports/ReportSidebar.jsx index 4fba2b217..0ccb5ef2b 100644 --- a/packages/desktop-client/src/components/reports/ReportSidebar.jsx +++ b/packages/desktop-client/src/components/reports/ReportSidebar.jsx @@ -41,9 +41,11 @@ export function ReportSidebar({ setSelectedCategories, onChangeDates, onChangeViews, + onReportChange, }) { const [menuOpen, setMenuOpen] = useState(false); const onSelectRange = cond => { + onReportChange(null, 'modify'); setDateRange(cond); switch (cond) { case 'All time': @@ -77,6 +79,7 @@ export function ReportSidebar({ }; const onChangeMode = cond => { + onReportChange(null, 'modify'); setMode(cond); if (cond === 'time') { if (customReportItems.graphType === 'TableGraph') { @@ -107,6 +110,7 @@ export function ReportSidebar({ }; const onChangeSplit = cond => { + onReportChange(null, 'modify'); setGroupBy(cond); if (customReportItems.mode === 'total') { if (customReportItems.graphType !== 'TableGraph') { @@ -123,6 +127,11 @@ export function ReportSidebar({ } }; + const onChangeBalanceType = cond => { + onReportChange(null, 'modify'); + setBalanceType(cond); + }; + return ( <View style={{ @@ -206,7 +215,7 @@ export function ReportSidebar({ </Text> <Select value={customReportItems.balanceType} - onChange={setBalanceType} + onChange={e => onChangeBalanceType(e)} options={ReportOptions.balanceType.map(option => [ option.description, option.description, diff --git a/packages/desktop-client/src/components/reports/ReportTopbar.jsx b/packages/desktop-client/src/components/reports/ReportTopbar.jsx index 0704c7c8a..d8944ae8c 100644 --- a/packages/desktop-client/src/components/reports/ReportTopbar.jsx +++ b/packages/desktop-client/src/components/reports/ReportTopbar.jsx @@ -18,6 +18,7 @@ import { SaveReportMenuButton } from './SaveReport'; export function ReportTopbar({ customReportItems, + savedStatus, setGraphType, setTypeDisabled, setBalanceType, @@ -27,6 +28,7 @@ export function ReportTopbar({ viewLabels, onApplyFilter, onChangeViews, + onReportChange, }) { return ( <View @@ -158,10 +160,17 @@ export function ReportTopbar({ marginRight: 15, flexShrink: 0, }} + />{' '} + <FilterButton + compact + hover + onApply={e => { + onApplyFilter(e); + onReportChange(null, 'modify'); + }} /> - <FilterButton onApply={onApplyFilter} compact hover /> <View style={{ flex: 1 }} /> - <SaveReportMenuButton /> + <SaveReportMenuButton savedStatus={savedStatus} /> </View> ); } diff --git a/packages/desktop-client/src/components/reports/SaveReport.tsx b/packages/desktop-client/src/components/reports/SaveReport.tsx index 3d1328e57..00b59ada9 100644 --- a/packages/desktop-client/src/components/reports/SaveReport.tsx +++ b/packages/desktop-client/src/components/reports/SaveReport.tsx @@ -38,7 +38,7 @@ function SaveReportMenu({ setMenuOpen }) { ); } -export function SaveReportMenuButton() { +export function SaveReportMenuButton({ savedStatus }: { savedStatus: string }) { const [menuOpen, setMenuOpen] = useState(false); return ( @@ -65,6 +65,7 @@ export function SaveReportMenuButton() { > Unsaved Report </Text> + {savedStatus === 'modified' && <Text>(modified) </Text>} <SvgExpandArrow width={8} height={8} style={{ marginRight: 5 }} /> </Button> {menuOpen && <SaveReportMenu setMenuOpen={setMenuOpen} />} diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx index a9bf769cb..7e679f233 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx @@ -24,7 +24,7 @@ import { ChooseGraph } from '../ChooseGraph'; import { Header } from '../Header'; import { LoadingIndicator } from '../LoadingIndicator'; import { ReportLegend } from '../ReportLegend'; -import { ReportOptions, defaultState } from '../ReportOptions'; +import { ReportOptions, defaultReport } from '../ReportOptions'; import { ReportSidebar } from '../ReportSidebar'; import { ReportSummary } from '../ReportSummary'; import { ReportTopbar } from '../ReportTopbar'; @@ -54,7 +54,9 @@ export function CustomReport() { } = useFilters(); const location = useLocation(); - const loadReport = location.state && (location.state.report ?? defaultState); + const loadReport = location.state + ? location.state.report ?? defaultReport + : defaultReport; const [allMonths, setAllMonths] = useState(null); const [typeDisabled, setTypeDisabled] = useState(['Net']); @@ -78,14 +80,18 @@ export function CustomReport() { ); const [graphType, setGraphType] = useState(loadReport.graphType); - const [dateRange, setDateRange] = useState('Last 6 months'); + const [dateRange, setDateRange] = useState(loadReport.dateRange); const [dataCheck, setDataCheck] = useState(false); const dateRangeLine = ReportOptions.dateRange.length - 3; + const [report, setReport] = useState(loadReport); + const [savedStatus, setSavedStatus] = useState( + location.state ? (location.state.report ? 'saved' : 'new') : 'new', + ); const months = monthUtils.rangeInclusive(startDate, endDate); useEffect(() => { - if (selectedCategories === null && categories.list.length !== 0) { + if (selectedCategories.length === 0 && categories.list.length !== 0) { setSelectedCategories(categories.list); } }, [categories, selectedCategories]); @@ -196,9 +202,13 @@ export function CustomReport() { const groupedData = useReport('grouped', getGroupData); const data = { ...graphData, groupedData }; - const customReportItems = { - id: null, + id: undefined, + name: undefined, + startDate, + endDate, + isDateStatic, + dateRange, mode, groupBy, balanceType, @@ -206,16 +216,13 @@ export function CustomReport() { showOffBudget, showHiddenCategories, showUncategorized, - graphType, - startDate, - endDate, selectedCategories, - isDateStatic, - dateRange, - filters, + graphType, + conditions: filters, conditionsOp, data, }; + const [scrollWidth, setScrollWidth] = useState(0); if (!allMonths || !data) { @@ -225,6 +232,7 @@ export function CustomReport() { const onChangeDates = (startDate, endDate) => { setStartDate(startDate); setEndDate(endDate); + setSavedStatus('modified'); }; const onChangeViews = (viewType, status) => { @@ -239,6 +247,48 @@ export function CustomReport() { } }; + const onChangeAppliedFilter = (filter, changedElement) => { + onReportChange(null, 'modify'); + return changedElement(filter); + }; + + const onReportChange = (savedReport, type) => { + switch (type) { + case 'add-update': + setSavedStatus('saved'); + setReport(savedReport); + break; + case 'rename': + setReport({ ...report, name: savedReport.name }); + break; + case 'modify': + if (report.name) { + setSavedStatus('modified'); + } + break; + case 'reload': + setSavedStatus('saved'); + + setStartDate(report.startDate); + setEndDate(report.endDate); + setIsDateStatic(report.isDateStatic); + setDateRange(report.dateRange); + setMode(report.mode); + setGroupBy(report.groupBy); + setBalanceType(report.balanceType); + setShowEmpty(report.showEmpty); + setShowOffBudget(report.showOffBudget); + setShowUncategorized(report.showUncategorized); + setSelectedCategories(report.selectedCategories); + setGraphType(report.graphType); + onApplyFilter(null); + report.conditions.forEach(condition => onApplyFilter(condition)); + onCondOpChange(report.conditionsOp); + break; + default: + } + }; + return ( <View style={{ ...styles.page, minWidth: 650, overflow: 'hidden' }}> <Header title="Custom Reports" /> @@ -271,6 +321,7 @@ export function CustomReport() { setSelectedCategories={setSelectedCategories} onChangeDates={onChangeDates} onChangeViews={onChangeViews} + onReportChange={onReportChange} /> <View style={{ @@ -279,6 +330,7 @@ export function CustomReport() { > <ReportTopbar customReportItems={customReportItems} + savedStatus={savedStatus} setGraphType={setGraphType} setTypeDisabled={setTypeDisabled} setBalanceType={setBalanceType} @@ -288,6 +340,7 @@ export function CustomReport() { viewLabels={viewLabels} onApplyFilter={onApplyFilter} onChangeViews={onChangeViews} + onReportChange={onReportChange} /> {filters && filters.length > 0 && ( <View @@ -300,9 +353,14 @@ export function CustomReport() { <AppliedFilters filters={filters} onUpdate={onUpdateFilter} - onDelete={onDeleteFilter} + onDelete={filter => + onChangeAppliedFilter(filter, onDeleteFilter) + } conditionsOp={conditionsOp} - onCondOpChange={onCondOpChange} + onCondOpChange={filter => + onChangeAppliedFilter(filter, onCondOpChange) + } + onUpdateChange={onReportChange} /> </View> )} diff --git a/packages/loot-core/src/client/data-hooks/reports.ts b/packages/loot-core/src/client/data-hooks/reports.ts index 1810a91f7..e347c48fc 100644 --- a/packages/loot-core/src/client/data-hooks/reports.ts +++ b/packages/loot-core/src/client/data-hooks/reports.ts @@ -12,7 +12,6 @@ function toJS(rows: CustomReportData[]) { const test: CustomReportEntity = { ...row, conditionsOp: row.conditions_op ?? 'and', - filters: row.conditions, }; return test; }); diff --git a/packages/loot-core/src/server/reports/app.ts b/packages/loot-core/src/server/reports/app.ts index 8ee59b912..e311bed65 100644 --- a/packages/loot-core/src/server/reports/app.ts +++ b/packages/loot-core/src/server/reports/app.ts @@ -4,7 +4,6 @@ import { type CustomReportData, type CustomReportEntity, } from '../../types/models'; -import { parseConditionsOrActions } from '../accounts/transaction-rules'; import { createApp } from '../app'; import * as db from '../db'; import { requiredFields } from '../models'; @@ -30,22 +29,20 @@ const reportModel = { return { ...row, conditionsOp: row.conditions_op, - filters: parseConditionsOrActions(row.conditions), }; }, fromJS(report: CustomReportEntity) { - const { filters, conditionsOp, ...row }: CustomReportData = report; + const { conditionsOp, ...row }: CustomReportData = report; if (conditionsOp) { row.conditions_op = conditionsOp; - row.conditions = filters; } return row; }, }; async function reportNameExists( - name: string, + name: string | undefined, reportId: string | undefined, newItem: boolean, ) { diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts index 6ca225631..a9b8269a4 100644 --- a/packages/loot-core/src/types/models/reports.d.ts +++ b/packages/loot-core/src/types/models/reports.d.ts @@ -1,7 +1,13 @@ +import { CategoryEntity } from './category'; import { type RuleConditionEntity } from './rule'; export interface CustomReportEntity { id: string | undefined; + name: string; + startDate: string; + endDate: string; + isDateStatic: boolean; + dateRange: string; mode: string; groupBy: string; balanceType: string; @@ -9,14 +15,10 @@ export interface CustomReportEntity { showOffBudget: boolean; showHiddenCategories: boolean; showUncategorized: boolean; + selectedCategories: CategoryEntity[]; graphType: string; - selectedCategories; - filters?: RuleConditionEntity[]; + conditions?: RuleConditionEntity[]; conditionsOp: string; - name: string; - startDate: string; - endDate: string; - isDateStatic: boolean; data?: GroupedEntity; tombstone?: boolean; } diff --git a/upcoming-release-notes/2274.md b/upcoming-release-notes/2274.md new file mode 100644 index 000000000..07259c2f7 --- /dev/null +++ b/upcoming-release-notes/2274.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [carkom] +--- + +Updating and organizing code in preperation for saved custom reports menu. -- GitLab