From 47058b1a44c050f75279e6eae10d8fd81a17c9da Mon Sep 17 00:00:00 2001 From: carkom Date: Fri, 27 Oct 2023 17:20:00 +0100 Subject: [PATCH] data spreadsheet updates/groups added to matrix --- .../src/components/reports/Custom.js | 26 +- .../src/components/reports/ReportTable.tsx | 121 +++++---- .../src/components/reports/SavedGraphs.js | 35 ++- .../reports/graphs/StackedBarGraph.tsx | 8 +- .../spreadsheets/default-spreadsheet.tsx | 231 +++++++++++------- 5 files changed, 245 insertions(+), 176 deletions(-) diff --git a/packages/desktop-client/src/components/reports/Custom.js b/packages/desktop-client/src/components/reports/Custom.js index 9e7a75e3f11..03ded220026 100644 --- a/packages/desktop-client/src/components/reports/Custom.js +++ b/packages/desktop-client/src/components/reports/Custom.js @@ -111,6 +111,12 @@ export default function Custom() { onCondOpChange, } = useFilters(); + const typeOptions = [ + { value: 1, description: 'Expense', format: 'totalDebts' }, + { value: 2, description: 'Income', format: 'totalAssets' }, + { value: 3, description: 'All', format: 'totalTotals' }, + ]; + const [allMonths, setAllMonths] = useState(null); const [start, setStart] = useState( monthUtils.subMonths(monthUtils.currentMonth(), 5), @@ -135,6 +141,7 @@ export default function Custom() { start, end, split, + typeOptions.find(opt => opt.value === type).format, categories, payees, accounts, @@ -146,6 +153,7 @@ export default function Custom() { start, end, split, + type, categories, payees, accounts, @@ -277,7 +285,7 @@ export default function Custom() { data={data} empty={!empty} months={months} - type={type} + typeItem={typeOptions.find(opt => opt.value === type).format} mode={mode} split={splitOptions.find(opt => opt.value === split).description} /> @@ -286,7 +294,7 @@ export default function Custom() { scrollWidth={scrollWidth} data={data} mode={mode} - totalItem={typeOptions.find(opt => opt.value === type).format} + typeItem={typeOptions.find(opt => opt.value === type).format} /> ); @@ -387,12 +395,6 @@ export default function Custom() { { value: 6, description: 'Year' }, ]; - const typeOptions = [ - { value: 1, description: 'Expense', format: 'totalDebts' }, - { value: 2, description: 'Income', format: 'totalAssets' }, - { value: 3, description: 'All', format: 'totalTotals' }, - ]; - /* const intervalOptions = [ { value: 1, description: 'Daily', name: 1 }, @@ -485,8 +487,8 @@ export default function Custom() { mode === 'time' ? [5, 6] : graphType === 'AreaGraph' - ? [1, 2, 3, 4] - : [0] + ? [1, 2, 3, 4, 6] + : [6] } /> @@ -507,9 +509,7 @@ export default function Custom() { option.value, option.description, ])} - disabledKeys={ - mode === 'total' && graphType === 'DonutGraph' ? [3] : [0] - } + disabledKeys={[3]} /> {/* diff --git a/packages/desktop-client/src/components/reports/ReportTable.tsx b/packages/desktop-client/src/components/reports/ReportTable.tsx index 8e20b4d7698..7f744b33e4d 100644 --- a/packages/desktop-client/src/components/reports/ReportTable.tsx +++ b/packages/desktop-client/src/components/reports/ReportTable.tsx @@ -13,16 +13,16 @@ type TableRowProps = { item: { date: string; name: string; + monthData: []; }; - totalItem: string; typeItem?: string | null; splitItem: string; mode: string; - data?: [] | null; + style?: object | null; }; const TableRow = memo( - ({ item, totalItem, typeItem, splitItem, mode, data }: TableRowProps) => { + ({ item, typeItem, splitItem, mode, style }: TableRowProps) => { return ( - {data && + {item.monthData && mode === 'time' && - data.map(item => { + item.monthData.map(item => { return ( + + + {item.categories + .filter(i => (empty ? i[typeItem] !== 0 : true)) + .map(item => { + return ( + + ); + })} + + + + ); +} + export function TableHeader({ scrollWidth, split, interval }) { return ( { return ( ); })} @@ -120,45 +156,42 @@ export function TableTotals({ data, scrollWidth, totalItem, mode }) { ); } -export function TotalTableList({ data, empty, months, type, mode, split }) { +export function TotalTableList({ data, empty, months, typeItem, mode, split }) { const splitItem = ['Month', 'Year'].includes(split) ? 'date' : 'name'; - const splitData = ['Month', 'Year'].includes(split) ? 'monthData' : 'data'; - - let typeItem; - let totalItem; - - switch (type) { - case 1: - typeItem = 'debts'; - totalItem = 'totalDebts'; - break; - case 2: - typeItem = 'assets'; - totalItem = 'totalAssets'; - break; - case 3: - typeItem = 'y'; - totalItem = 'totalTotals'; - break; - default: - } + const splitData = + split === 'Category' + ? 'gData' + : ['Month', 'Year'].includes(split) + ? 'monthData' + : 'data'; return ( {data[splitData] - .filter(i => (empty ? i[totalItem] !== 0 : true)) + .filter(i => (empty ? i[typeItem] !== 0 : true)) .map(item => { - return ( - - ); + if (split === 'Category') { + return ( + + ); + } else { + return ( + + ); + } })} ); diff --git a/packages/desktop-client/src/components/reports/SavedGraphs.js b/packages/desktop-client/src/components/reports/SavedGraphs.js index 65258a5aff2..f59f90c9a52 100644 --- a/packages/desktop-client/src/components/reports/SavedGraphs.js +++ b/packages/desktop-client/src/components/reports/SavedGraphs.js @@ -8,45 +8,38 @@ import Text from '../common/Text'; import View from '../common/View'; export function SavedGraphMenuButton({ selectGraph }) { - let [dataMenuOpen, setDataMenuOpen] = useState(false); + let [menuOpen, setMenuOpen] = useState(false); - const onDataMenuSelect = async item => { + const onGraphMenuSelect = async item => { switch (item) { - case 'NetWorth': - setDataMenuOpen(false); + case 'save': + setMenuOpen(false); break; - case 'CashFlow': - setDataMenuOpen(false); - break; - case 'Income': - setDataMenuOpen(false); - break; - case 'Expense': - setDataMenuOpen(false); - break; - case 'All': - setDataMenuOpen(false); + case 'clear': + setMenuOpen(false); break; default: } }; - function DataMenu({ onClose }) { + function SavedGraphMenu({ onClose }) { return ( { - onDataMenuSelect(item); + onGraphMenuSelect(item); }} items={[ ...[ { - name: 'NetWorth', + name: 'save', text: 'Save new report', + disabled: true, }, { - name: 'CashFlow', + name: 'clear', text: 'Clear all', + disabled: true, }, ], ]} @@ -65,7 +58,7 @@ export function SavedGraphMenuButton({ selectGraph }) { - {dataMenuOpen && setDataMenuOpen(false)} />} + {menuOpen && setMenuOpen(false)} />} ); } diff --git a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx index d53acadfc06..90e87ce39cc 100644 --- a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx @@ -142,9 +142,9 @@ function StackedBarGraph({ const getVal = (obj, key) => { if (typeOp === 'totalDebts') { - return -1 * obj[key][typeOp]; + return -1 * obj[key].amount; } else { - return obj[key][typeOp]; + return obj[key].amount; } }; @@ -156,14 +156,14 @@ function StackedBarGraph({ }} > {(width, height, portalHost) => - data.monthData && ( + data.stackedData && (
{!compact &&
} } /> diff --git a/packages/desktop-client/src/components/reports/spreadsheets/default-spreadsheet.tsx b/packages/desktop-client/src/components/reports/spreadsheets/default-spreadsheet.tsx index 5e083435561..929dd729d91 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/default-spreadsheet.tsx +++ b/packages/desktop-client/src/components/reports/spreadsheets/default-spreadsheet.tsx @@ -3,11 +3,7 @@ import * as d from 'date-fns'; import q, { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; -import { - integerToCurrency, - integerToAmount, - amountToInteger, -} from 'loot-core/src/shared/util'; +import { integerToAmount, amountToInteger } from 'loot-core/src/shared/util'; import { index } from '../util'; @@ -15,6 +11,7 @@ export default function createSpreadsheet( start, end, split, + typeItem, categories, payees, accounts, @@ -137,9 +134,9 @@ export default function createSpreadsheet( return { id: splt.id, name: splt.name, + starting, hidden: splt.hidden, balances: index(balances, 'date'), - starting, }; }), ); @@ -164,17 +161,16 @@ export default function createSpreadsheet( }); return { date: month, - // eslint-disable-next-line rulesdir/typography - dateFormatted: d.format(d.parseISO(`${month}-01`), "MMM ''yy"), amount: groupedAmount, }; }), ); return { - starting: groupedStarting, - name: group.name, id: group.id, + name: group.name, + starting: groupedStarting, + hidden: group.hidden, balances: index(mon, 'date'), }; }), @@ -182,6 +178,43 @@ export default function createSpreadsheet( const splitData = split === 2 ? groupData : graphData; + const data = await Promise.all( + splitData.map(async graph => { + const calc = recalculate(graph, start, end); + return { ...calc }; + }), + ); + + const gData = + [1].includes(split) && + (await Promise.all( + categories.grouped.map(async group => { + const catData = await Promise.all( + group.categories.map(async graph => { + let catMatch = null; + graphData.map(async cat => { + if (cat.id === graph.id) { + catMatch = cat; + } + }); + const calcCat = recalculate(catMatch, start, end); + return { ...calcCat }; + }), + ); + let groupMatch = null; + groupData.map(async split => { + if (split.id === group.id) { + groupMatch = split; + } + }); + const calcGroup = recalculate(groupMatch, start, end); + return { + ...calcGroup, + categories: catData, + }; + }), + )); + let totalAssets = 0; let totalDebts = 0; let totalTotals = 0; @@ -191,129 +224,148 @@ export default function createSpreadsheet( let perMonthAssets = 0; let perMonthDebts = 0; let perMonthTotals = 0; + graphData.map(async graph => { + if (graph.balances[month]) { + if (graph.balances[month].amount < 0) { + perMonthDebts += graph.balances[month].amount; + } else { + perMonthAssets += graph.balances[month].amount; + } + perMonthTotals = perMonthAssets + perMonthDebts; + } + }); + totalAssets += perMonthAssets; + totalDebts += perMonthDebts; + totalTotals += perMonthTotals; + + return { + // eslint-disable-next-line rulesdir/typography + date: d.format(d.parseISO(`${month}-01`), "MMM ''yy"), + totalDebts: integerToAmount(perMonthDebts), + totalAssets: integerToAmount(perMonthAssets), + totalTotals: integerToAmount(perMonthTotals), + }; + }), + ); + + const sData = await Promise.all( + months.map(async month => { + let perMonthAmounts = 0; const stacked = await Promise.all( - splitData.map(async graph => { - let stackDebts = 0; - let stackAssets = 0; - let stackTotals = 0; - if (graph.balances[month]) { - if (graph.balances[month].amount < 0) { - perMonthDebts += -graph.balances[month].amount; - stackDebts += -graph.balances[month].amount; - } else { - perMonthAssets += graph.balances[month].amount; - stackAssets += graph.balances[month].amount; - } - perMonthTotals = perMonthAssets - perMonthDebts; - stackTotals += stackAssets - stackDebts; + data.map(async graph => { + let stackAmounts = 0; + if (graph.indexedMonthData[month]) { + perMonthAmounts += graph.indexedMonthData[month][typeItem]; + stackAmounts += graph.indexedMonthData[month][typeItem]; } + /*const nested = await Promise.all( + graph.categories.map(async cat => { + let catAmounts = 0; + if (cat.monthData[month]) { + catAmounts += cat.monthData[month][type]; + } + return { + name: cat.name, + id: cat.id, + amount: catAmounts, + }; + }), + );*/ return { name: graph.name, - totalAssets: integerToAmount(stackAssets), - totalDebts: -1 * integerToAmount(stackDebts), - totalTotals: integerToAmount(stackTotals), + id: graph.id, + amount: stackAmounts, }; }), ); - totalAssets += perMonthAssets; - totalDebts += perMonthDebts; - totalTotals += perMonthTotals; const indexedSplit = index(stacked, 'name'); return { // eslint-disable-next-line rulesdir/typography date: d.format(d.parseISO(`${month}-01`), "MMM ''yy"), ...indexedSplit, - totalDebts: -1 * integerToAmount(perMonthDebts), - totalAssets: integerToAmount(perMonthAssets), - totalTotals: - perMonthAssets >= perMonthDebts - ? integerToAmount(perMonthTotals) - : integerToAmount(perMonthTotals), + totalTotals: perMonthAmounts, }; }), ); - const data = await Promise.all( - splitData.map(async graph => { - const calc = recalculate(graph, start, end); - return { ...calc }; - }), - ); - setData({ + stackedData: sData, split: splitList, data, + gData: gData, monthData, - totalDebts: -1 * integerToAmount(totalDebts), + start, + end, + totalDebts: integerToAmount(totalDebts), totalAssets: integerToAmount(totalAssets), - totalTotals: - totalAssets >= totalDebts - ? integerToAmount(totalTotals) - : -1 * integerToAmount(totalTotals), + totalTotals: integerToAmount(totalTotals), }); }; } -function recalculate(data, start, end) { +function recalculate(item, start, end) { const months = monthUtils.rangeInclusive(start, end); let totalDebts = 0; let totalAssets = 0; let totalTotals = 0; - //let startingDebts = 0; - //let startingAssets = 0; + let exists = false; + /* + let startingDebts = 0; + let startingAssets = 0; let hasNegative = false; let startNetWorth = 0; let endNetWorth = 0; let lowestNetWorth = null; let highestNetWorth = null; + */ - const graphData = months.reduce((arr, month) => { + const monthData = months.reduce((arr, month) => { let debts = 0; let assets = 0; let total = 0; const last = arr.length === 0 ? null : arr[arr.length - 1]; - if (data.balances[month]) { - if (data.balances[month].amount < 0) { - debts += -data.balances[month].amount; - //startingDebts += -data.balances[month].amount; - totalDebts += -data.balances[month].amount; + if (item.balances[month]) { + exists = true; + if (item.balances[month].amount < 0) { + debts += item.balances[month].amount; + //startingDebts += data.balances[month].amount; + totalDebts += item.balances[month].amount; } else { - assets += data.balances[month].amount; + assets += item.balances[month].amount; //startingAssets += data.balances[month].amount; - totalAssets += data.balances[month].amount; + totalAssets += item.balances[month].amount; } - total = assets - debts; - totalTotals = totalAssets - totalDebts; + total = assets + debts; + totalTotals = totalAssets + totalDebts; } - if (total < 0) { + /*if (total < 0) { hasNegative = true; - } + }*/ - const x = d.parseISO(`${month}-01`); - const y = - assets >= debts ? integerToAmount(total) : -1 * integerToAmount(total); - const change = last ? total - amountToInteger(last.y) : 0; + const dateParse = d.parseISO(`${month}-01`); + const change = last ? total - amountToInteger(last.totalTotals) : 0; - if (arr.length === 0) { + /*if (arr.length === 0) { startNetWorth = total; } - endNetWorth = total; + endNetWorth = total;*/ arr.push({ - x, - y, - assets: integerToAmount(assets), - debts: integerToAmount(debts), - change: integerToCurrency(change), - networth: integerToCurrency(total), + dateParse, + totalTotals: integerToAmount(total), + totalAssets: integerToAmount(assets), + totalDebts: integerToAmount(debts), + totalChange: integerToAmount(change), // eslint-disable-next-line rulesdir/typography - date: d.format(x, "MMM ''yy"), + date: d.format(dateParse, "MMM ''yy"), + dateLookup: month, }); + /* arr.forEach(item => { if (item.y < lowestNetWorth || lowestNetWorth === null) { lowestNetWorth = item.y; @@ -322,29 +374,20 @@ function recalculate(data, start, end) { highestNetWorth = item.y; } }); + */ + return arr; }, []); - const yTotal = - totalAssets > totalDebts - ? integerToAmount(totalTotals) - : -1 * integerToAmount(totalTotals); + const indexedSplit = exists ? index(monthData, 'dateLookup') : monthData; return { - graphData: { - data: graphData, - hasNegative, - start, - end, - }, + indexedMonthData: indexedSplit, + monthData: monthData, totalAssets: integerToAmount(totalAssets), - totalDebts: -1 * integerToAmount(totalDebts), - totalTotals: yTotal, - netWorth: endNetWorth, - totalChange: endNetWorth - startNetWorth, - lowestNetWorth, - highestNetWorth, - id: data.id, - name: data.name, + totalDebts: integerToAmount(totalDebts), + totalTotals: integerToAmount(totalTotals), + id: item.id, + name: item.name, }; }