From 485902af6bc08ad670ea212cd6a0a72abc10aabd Mon Sep 17 00:00:00 2001 From: Neil <55785687+carkom@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:54:47 -0800 Subject: [PATCH] Update Custom Report styles (#2345) * updated saved work * merge fixes * Disable CREATE TABLE * notes * turn on db table * Fix TableGraph recall crash * table format changes * type fixes * fixing some card displays * merge fixes * revert table change * Revert Changes * notes * merge fixes * notes * notes * revert notes * Area changes --- .../src/components/reports/ChooseGraph.tsx | 23 +++- .../src/components/reports/SaveReport.tsx | 3 +- .../src/components/reports/SaveReportName.tsx | 9 +- .../components/reports/graphs/AreaGraph.tsx | 32 +++++- .../components/reports/graphs/DonutGraph.tsx | 86 +++++++++++--- .../reports/graphs/StackedBarGraph.tsx | 10 +- .../reports/graphs/renderCustomLabel.tsx | 3 +- .../reports/graphs/tableGraph/ReportTable.tsx | 23 +++- .../graphs/tableGraph/ReportTableHeader.tsx | 32 +++--- .../graphs/tableGraph/ReportTableList.tsx | 56 +++++---- .../graphs/tableGraph/ReportTableRow.tsx | 32 +++--- .../graphs/tableGraph/ReportTableTotals.tsx | 32 +++--- .../reports/reports/CustomReport.jsx | 3 +- .../src/client/data-hooks/reports.ts | 22 +++- packages/loot-core/src/server/reports/app.ts | 107 +++++++++++++----- .../loot-core/src/types/models/reports.d.ts | 22 +++- upcoming-release-notes/2345.md | 6 + 17 files changed, 367 insertions(+), 134 deletions(-) create mode 100644 upcoming-release-notes/2345.md diff --git a/packages/desktop-client/src/components/reports/ChooseGraph.tsx b/packages/desktop-client/src/components/reports/ChooseGraph.tsx index 6fcddebb3..ad8f0c3a8 100644 --- a/packages/desktop-client/src/components/reports/ChooseGraph.tsx +++ b/packages/desktop-client/src/components/reports/ChooseGraph.tsx @@ -1,12 +1,11 @@ // @ts-strict-ignore import React, { useRef } from 'react'; -import { - type GroupedEntity, - type Month, -} from 'loot-core/src/types/models/reports'; +import * as monthUtils from 'loot-core/src/shared/months'; +import { type GroupedEntity } from 'loot-core/src/types/models/reports'; import { type CSSProperties } from '../../style'; +import { styles } from '../../style/styles'; import { View } from '../common/View'; import { AreaGraph } from './graphs/AreaGraph'; @@ -21,30 +20,33 @@ import { ReportTableTotals } from './graphs/tableGraph/ReportTableTotals'; import { ReportOptions } from './ReportOptions'; type ChooseGraphProps = { + startDate: string; + endDate: string; data: GroupedEntity; mode: string; graphType: string; balanceType: string; groupBy: string; setScrollWidth?: (value: number) => void; - months?: Month[]; viewLabels?: boolean; compact?: boolean; style?: CSSProperties; }; export function ChooseGraph({ + startDate, + endDate, data, mode, graphType, balanceType, groupBy, setScrollWidth, - months, viewLabels, compact, style, }: ChooseGraphProps) { + const months: string[] = monthUtils.rangeInclusive(startDate, endDate); const graphStyle = compact ? { ...style } : { flexGrow: 1 }; const balanceTypeOp = ReportOptions.balanceTypeMap.get(balanceType); const groupByData = @@ -58,6 +60,9 @@ export function ChooseGraph({ setScrollWidth(!value ? 0 : value); }; + const rowStyle = compact && { flex: '0 0 20px', height: 20 }; + const compactStyle = compact && { ...styles.tinyText }; + const headerScrollRef = useRef<HTMLDivElement>(null); const listScrollRef = useRef<HTMLDivElement>(null); const totalScrollRef = useRef<HTMLDivElement>(null); @@ -141,6 +146,8 @@ export function ChooseGraph({ groupBy={groupBy} balanceType={balanceType} compact={compact} + style={rowStyle} + compactStyle={compactStyle} /> <ReportTable saveScrollWidth={saveScrollWidth} @@ -152,6 +159,8 @@ export function ChooseGraph({ mode={mode} monthsCount={months.length} compact={compact} + style={rowStyle} + compactStyle={compactStyle} /> <ReportTableTotals totalScrollRef={totalScrollRef} @@ -161,6 +170,8 @@ export function ChooseGraph({ balanceTypeOp={balanceTypeOp} monthsCount={months.length} compact={compact} + style={rowStyle} + compactStyle={compactStyle} /> </View> ); diff --git a/packages/desktop-client/src/components/reports/SaveReport.tsx b/packages/desktop-client/src/components/reports/SaveReport.tsx index e7e26a3a5..37ca8e4e9 100644 --- a/packages/desktop-client/src/components/reports/SaveReport.tsx +++ b/packages/desktop-client/src/components/reports/SaveReport.tsx @@ -39,7 +39,7 @@ export function SaveReport({ const [menuItem, setMenuItem] = useState(''); const [err, setErr] = useState(''); const [res, setRes] = useState(''); - const [name, setName] = useState(report.name); + const [name, setName] = useState(report.name ?? ''); const inputRef = createRef<HTMLInputElement>(); const onAddUpdate = async (menuChoice: string) => { @@ -167,6 +167,7 @@ export function SaveReport({ <SaveReportName onClose={() => setNameMenuOpen(false)} menuItem={menuItem} + name={name} setName={setName} inputRef={inputRef} onAddUpdate={onAddUpdate} diff --git a/packages/desktop-client/src/components/reports/SaveReportName.tsx b/packages/desktop-client/src/components/reports/SaveReportName.tsx index 34c1d8176..c6ef39ed6 100644 --- a/packages/desktop-client/src/components/reports/SaveReportName.tsx +++ b/packages/desktop-client/src/components/reports/SaveReportName.tsx @@ -11,6 +11,7 @@ import { FormField, FormLabel } from '../forms'; type SaveReportNameProps = { onClose: () => void; menuItem: string; + name: string; setName: (name: string) => void; inputRef: RefObject<HTMLInputElement>; onAddUpdate: (menuItem: string) => void; @@ -20,6 +21,7 @@ type SaveReportNameProps = { export function SaveReportName({ onClose, menuItem, + name, setName, inputRef, onAddUpdate, @@ -47,7 +49,12 @@ export function SaveReportName({ htmlFor="name-field" style={{ userSelect: 'none' }} /> - <Input inputRef={inputRef} onUpdate={setName} /> + <Input + value={name} + id="name-field" + inputRef={inputRef} + onUpdate={setName} + /> </FormField> <Button type="primary" diff --git a/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx b/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx index 58caaebb8..a261be8e3 100644 --- a/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx @@ -220,7 +220,13 @@ export function AreaGraph({ isAnimationActive={false} /> <defs> - <linearGradient id="splitColor" x1="0" y1="0" x2="0" y2="1"> + <linearGradient + id={`fill${balanceTypeOp}`} + x1="0" + y1="0" + x2="0" + y2="1" + > <stop offset={off} stopColor={theme.reportsBlue} @@ -232,6 +238,24 @@ export function AreaGraph({ stopOpacity={0.2} /> </linearGradient> + <linearGradient + id={`stroke${balanceTypeOp}`} + x1="0" + y1="0" + x2="0" + y2="1" + > + <stop + offset={off} + stopColor={theme.reportsBlue} + stopOpacity={1} + /> + <stop + offset={off} + stopColor={theme.reportsRed} + stopOpacity={1} + /> + </linearGradient> </defs> <Area @@ -240,11 +264,11 @@ export function AreaGraph({ activeDot={false} animationDuration={0} dataKey={balanceTypeOp} - stroke={theme.reportsBlue} - fill="url(#splitColor)" + stroke={`url(#stroke${balanceTypeOp})`} + fill={`url(#fill${balanceTypeOp})`} fillOpacity={1} > - {viewLabels && ( + {viewLabels && !compact && ( <LabelList dataKey={balanceTypeOp} content={e => customLabel(e, width, lastLabel)} diff --git a/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx b/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx index 6fb3ebc03..e60bfa1e0 100644 --- a/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx @@ -7,12 +7,62 @@ import { amountToCurrency } from 'loot-core/src/shared/util'; import { type GroupedEntity } from 'loot-core/src/types/models/reports'; import { theme, type CSSProperties } from '../../../style'; +import { PrivacyFilter } from '../../PrivacyFilter'; import { Container } from '../Container'; import { adjustTextSize } from './adjustTextSize'; import { renderCustomLabel } from './renderCustomLabel'; const RADIAN = Math.PI / 180; + +const ActiveShapeMobile = props => { + const { + cx, + cy, + innerRadius, + outerRadius, + startAngle, + endAngle, + fill, + payload, + percent, + value, + } = props; + const yAxis = payload.name ?? payload.date; + + return ( + <g> + <text x={cx} y={cy + 65} dy={-8} textAnchor="middle" fill={fill}> + {`${yAxis}`} + </text> + <text x={cx - 30} y={cy + 40} dy={0} textAnchor="end" fill={fill}> + {`${amountToCurrency(value)}`} + </text> + <text x={cx + 30} y={cy + 40} dy={0} textAnchor="start" fill="#999"> + {`${(percent * 100).toFixed(2)}%`} + </text> + <Sector + cx={cx} + cy={cy} + innerRadius={innerRadius} + outerRadius={outerRadius} + startAngle={startAngle} + endAngle={endAngle} + fill={fill} + /> + <Sector + cx={cx} + cy={cy} + startAngle={startAngle} + endAngle={endAngle} + innerRadius={innerRadius - 8} + outerRadius={innerRadius - 6} + fill={fill} + /> + </g> + ); +}; + const ActiveShape = props => { const { cx, @@ -70,22 +120,24 @@ const ActiveShape = props => { textAnchor={textAnchor} fill={fill} >{`${yAxis}`}</text> - <text - x={ex + (cos <= 0 ? 1 : -1) * 16} - y={ey} - dy={18} - textAnchor={textAnchor} - fill={fill} - >{`${amountToCurrency(value)}`}</text> - <text - x={ex + (cos <= 0 ? 1 : -1) * 16} - y={ey} - dy={36} - textAnchor={textAnchor} - fill="#999" - > - {`(${(percent * 100).toFixed(2)}%)`} - </text> + <PrivacyFilter> + <text + x={ex + (cos <= 0 ? 1 : -1) * 16} + y={ey} + dy={18} + textAnchor={textAnchor} + fill={fill} + >{`${amountToCurrency(value)}`}</text> + <text + x={ex + (cos <= 0 ? 1 : -1) * 16} + y={ey} + dy={36} + textAnchor={textAnchor} + fill="#999" + > + {`(${(percent * 100).toFixed(2)}%)`} + </text> + </PrivacyFilter> </g> ); }; @@ -165,7 +217,7 @@ export function DonutGraph({ <PieChart width={width} height={height}> <Pie activeIndex={activeIndex} - activeShape={ActiveShape} + activeShape={compact ? ActiveShapeMobile : ActiveShape} dataKey={val => getVal(val)} nameKey={yAxis} isAnimationActive={false} diff --git a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx index 67d0ec1d1..ab2fcf993 100644 --- a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx @@ -166,14 +166,14 @@ export function StackedBarGraph({ isAnimationActive={false} cursor={{ fill: 'transparent' }} /> + <XAxis + dataKey="date" + tick={{ fill: theme.pageText }} + tickLine={{ stroke: theme.pageText }} + /> {!compact && ( <> <CartesianGrid strokeDasharray="3 3" /> - <XAxis - dataKey="date" - tick={{ fill: theme.pageText }} - tickLine={{ stroke: theme.pageText }} - /> <YAxis tickFormatter={value => getCustomTick( diff --git a/packages/desktop-client/src/components/reports/graphs/renderCustomLabel.tsx b/packages/desktop-client/src/components/reports/graphs/renderCustomLabel.tsx index 303773cce..187b50394 100644 --- a/packages/desktop-client/src/components/reports/graphs/renderCustomLabel.tsx +++ b/packages/desktop-client/src/components/reports/graphs/renderCustomLabel.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { theme } from '../../../style'; +import { PrivacyFilter } from '../../PrivacyFilter'; export const renderCustomLabel = ( calcX: number, @@ -22,7 +23,7 @@ export const renderCustomLabel = ( dominantBaseline="middle" fontSize={textSize} > - {display} + <PrivacyFilter>{display}</PrivacyFilter> </text> ) : ( <text /> diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx index 9aa4e7a92..284200c6c 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx @@ -20,26 +20,28 @@ type ReportTableProps = { saveScrollWidth: (value: number) => void; listScrollRef: RefProp<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>; - style?: CSSProperties; groupBy: string; balanceTypeOp: 'totalDebts' | 'totalTotals' | 'totalAssets'; data: DataEntity[]; mode: string; monthsCount: number; compact: boolean; + style?: CSSProperties; + compactStyle?: CSSProperties; }; export function ReportTable({ saveScrollWidth, listScrollRef, handleScroll, - style, groupBy, balanceTypeOp, data, mode, monthsCount, compact, + style, + compactStyle, }: ReportTableProps) { const contentRef = useRef<HTMLDivElement>(null); @@ -50,16 +52,25 @@ export function ReportTable({ }); const renderItem = useCallback( - ({ item, groupByItem, mode, style, monthsCount, compact }) => { + ({ + item, + groupByItem, + mode, + monthsCount, + compact, + style, + compactStyle, + }) => { return ( <ReportTableRow item={item} balanceTypeOp={balanceTypeOp} groupByItem={groupByItem} mode={mode} - style={style} monthsCount={monthsCount} compact={compact} + style={style} + compactStyle={compactStyle} /> ); }, @@ -73,7 +84,6 @@ export function ReportTable({ flexDirection: 'row', outline: 'none', '& .animated .animated-row': { transition: '.25s transform' }, - ...style, }} tabIndex={1} > @@ -88,7 +98,6 @@ export function ReportTable({ flex: 1, outline: 'none', '& .animated .animated-row': { transition: '.25s transform' }, - ...style, }} > <ReportTableList @@ -98,6 +107,8 @@ export function ReportTable({ groupBy={groupBy} renderItem={renderItem} compact={compact} + style={style} + compactStyle={compactStyle} /> </Block> </View> diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx index 4bb20248c..14cafd14b 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx @@ -4,7 +4,8 @@ import { type RefProp } from 'react-spring'; import { type DataEntity } from 'loot-core/src/types/models/reports'; -import { styles, theme } from '../../../../style'; +import { theme } from '../../../../style'; +import { type CSSProperties } from '../../../../style/types'; import { View } from '../../../common/View'; import { Row, Cell } from '../../../table'; @@ -15,6 +16,8 @@ type ReportTableHeaderProps = { headerScrollRef: RefProp<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>; compact: boolean; + style?: CSSProperties; + compactStyle?: CSSProperties; }; export function ReportTableHeader({ @@ -24,6 +27,8 @@ export function ReportTableHeader({ headerScrollRef, handleScroll, compact, + style, + compactStyle, }: ReportTableHeaderProps) { return ( <Row @@ -35,6 +40,7 @@ export function ReportTableHeader({ color: theme.tableHeaderText, backgroundColor: theme.tableHeaderBackground, fontWeight: 600, + ...style, }} > <View @@ -51,10 +57,10 @@ export function ReportTableHeader({ > <Cell style={{ - width: 120, + width: compact ? 80 : 125, flexShrink: 0, - ...styles.tnum, }} + valueStyle={compactStyle} value={groupBy} /> {interval @@ -62,9 +68,9 @@ export function ReportTableHeader({ return ( <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} key={index} value={header.date} width="flex" @@ -75,17 +81,17 @@ export function ReportTableHeader({ <> <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value="Deposits" width="flex" /> <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value="Payments" width="flex" /> @@ -93,17 +99,17 @@ export function ReportTableHeader({ )} <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value="Totals" width="flex" /> <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value="Average" width="flex" /> diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx index d961144b6..33f9431a6 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx @@ -5,7 +5,7 @@ import { type DataEntity } from 'loot-core/src/types/models/reports'; import { type CSSProperties, theme } from '../../../../style'; import { View } from '../../../common/View'; -import { Cell, Row } from '../../../table'; +import { Row } from '../../../table'; type ReportTableListProps = { data: DataEntity[]; @@ -14,6 +14,8 @@ type ReportTableListProps = { groupBy: string; renderItem; compact: boolean; + style?: CSSProperties; + compactStyle?: CSSProperties; }; export function ReportTableList({ @@ -23,16 +25,25 @@ export function ReportTableList({ groupBy, renderItem, compact, + style, + compactStyle, }: ReportTableListProps) { const groupByItem = ['Month', 'Year'].includes(groupBy) ? 'date' : 'name'; type RenderRowProps = { index: number; parent_index?: number; - style?: CSSProperties; compact: boolean; + style?: CSSProperties; + compactStyle?: CSSProperties; }; - function RenderRow({ index, parent_index, style, compact }: RenderRowProps) { + function RenderRow({ + index, + parent_index, + compact, + style, + compactStyle, + }: RenderRowProps) { const item = parent_index === undefined ? data[index] @@ -42,29 +53,32 @@ export function ReportTableList({ item, groupByItem, mode, - style, monthsCount, compact, + style, + compactStyle, }); } return ( <View> - {data.map((item, index) => { - return ( - <View key={item.id}> - {data ? ( - <> + {data ? ( + <View> + {data.map((item, index) => { + return ( + <View key={item.id}> <RenderRow index={index} compact={compact} - style={ - item.categories && { + style={{ + ...(item.categories && { color: theme.tableRowHeaderText, backgroundColor: theme.tableRowHeaderBackground, fontWeight: 600, - } - } + }), + ...style, + }} + compactStyle={compactStyle} /> {item.categories && ( <> @@ -76,6 +90,8 @@ export function ReportTableList({ index={i} compact={compact} parent_index={index} + style={style} + compactStyle={compactStyle} /> ); })} @@ -83,13 +99,13 @@ export function ReportTableList({ <Row height={20} /> </> )} - </> - ) : ( - <Cell width="flex" /> - )} - </View> - ); - })} + </View> + ); + })} + </View> + ) : ( + <View width="flex" /> + )} </View> ); } diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx index 4779f22df..831f0b091 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx @@ -7,7 +7,7 @@ import { } from 'loot-core/src/shared/util'; import { type DataEntity } from 'loot-core/src/types/models/reports'; -import { type CSSProperties, styles, theme } from '../../../../style'; +import { type CSSProperties, theme } from '../../../../style'; import { Row, Cell } from '../../../table'; type ReportTableRowProps = { @@ -15,9 +15,10 @@ type ReportTableRowProps = { balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; groupByItem: 'id' | 'name'; mode: string; - style?: CSSProperties; monthsCount: number; compact: boolean; + style?: CSSProperties; + compactStyle?: CSSProperties; }; export const ReportTableRow = memo( @@ -26,9 +27,10 @@ export const ReportTableRow = memo( balanceTypeOp, groupByItem, mode, - style, monthsCount, compact, + style, + compactStyle, }: ReportTableRowProps) => { const average = amountToInteger(item[balanceTypeOp]) / monthsCount; return ( @@ -45,10 +47,10 @@ export const ReportTableRow = memo( value={item[groupByItem]} title={item[groupByItem].length > 12 ? item[groupByItem] : undefined} style={{ - width: 120, + width: compact ? 80 : 125, flexShrink: 0, - ...styles.tnum, }} + valueStyle={compactStyle} /> {item.monthData && mode === 'time' ? item.monthData.map(month => { @@ -56,9 +58,9 @@ export const ReportTableRow = memo( <Cell key={amountToCurrency(month[balanceTypeOp])} style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value={amountToCurrency(month[balanceTypeOp])} title={ Math.abs(month[balanceTypeOp]) > 100000 @@ -82,9 +84,9 @@ export const ReportTableRow = memo( width="flex" privacyFilter style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} /> <Cell value={amountToCurrency(item.totalDebts)} @@ -96,9 +98,9 @@ export const ReportTableRow = memo( width="flex" privacyFilter style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} /> </> )} @@ -111,9 +113,9 @@ export const ReportTableRow = memo( } style={{ fontWeight: 600, - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} width="flex" privacyFilter /> @@ -126,9 +128,9 @@ export const ReportTableRow = memo( } style={{ fontWeight: 600, - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} width="flex" privacyFilter /> diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx index 5153ae1b0..40228d0d2 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx @@ -9,7 +9,8 @@ import { } from 'loot-core/src/shared/util'; import { type GroupedEntity } from 'loot-core/src/types/models/reports'; -import { styles, theme } from '../../../../style'; +import { theme } from '../../../../style'; +import { type CSSProperties } from '../../../../style/types'; import { View } from '../../../common/View'; import { Row, Cell } from '../../../table'; @@ -21,6 +22,8 @@ type ReportTableTotalsProps = { totalScrollRef: RefProp<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>; compact: boolean; + style?: CSSProperties; + compactStyle?: CSSProperties; }; export function ReportTableTotals({ @@ -31,6 +34,8 @@ export function ReportTableTotals({ totalScrollRef, handleScroll, compact, + style, + compactStyle, }: ReportTableTotalsProps) { const [scrollWidthTotals, setScrollWidthTotals] = useState(0); @@ -58,6 +63,7 @@ export function ReportTableTotals({ color: theme.tableHeaderText, backgroundColor: theme.tableHeaderBackground, fontWeight: 600, + ...style, }} > <View @@ -72,10 +78,10 @@ export function ReportTableTotals({ > <Cell style={{ - width: 120, + width: compact ? 80 : 125, flexShrink: 0, - ...styles.tnum, }} + valueStyle={compactStyle} value="Totals" /> {mode === 'time' @@ -83,9 +89,9 @@ export function ReportTableTotals({ return ( <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} key={amountToCurrency(item[balanceTypeOp])} value={amountToCurrency(item[balanceTypeOp])} title={ @@ -102,9 +108,9 @@ export function ReportTableTotals({ <> <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value={amountToCurrency(data.totalAssets)} title={ Math.abs(data.totalAssets) > 100000 @@ -116,9 +122,9 @@ export function ReportTableTotals({ /> <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value={amountToCurrency(data.totalDebts)} title={ Math.abs(data.totalDebts) > 100000 @@ -132,9 +138,9 @@ export function ReportTableTotals({ )} <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value={amountToCurrency(data[balanceTypeOp])} title={ Math.abs(data[balanceTypeOp]) > 100000 @@ -146,9 +152,9 @@ export function ReportTableTotals({ /> <Cell style={{ - minWidth: compact ? 80 : 125, - ...styles.tnum, + minWidth: compact ? 50 : 85, }} + valueStyle={compactStyle} value={integerToCurrency(Math.round(average))} title={ Math.abs(Math.round(average / 100)) > 100000 diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx index 97fd06aaa..f786572ef 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx @@ -440,6 +440,8 @@ export function CustomReport() { {dataCheck ? ( <ChooseGraph + startDate={startDate} + endDate={endDate} data={data} mode={mode} graphType={graphType} @@ -448,7 +450,6 @@ export function CustomReport() { showEmpty={showEmpty} scrollWidth={scrollWidth} setScrollWidth={setScrollWidth} - months={months} viewLabels={viewLabels} compact={false} /> diff --git a/packages/loot-core/src/client/data-hooks/reports.ts b/packages/loot-core/src/client/data-hooks/reports.ts index 07142d2cb..95b023ff3 100644 --- a/packages/loot-core/src/client/data-hooks/reports.ts +++ b/packages/loot-core/src/client/data-hooks/reports.ts @@ -9,11 +9,27 @@ import { useLiveQuery } from '../query-hooks'; function toJS(rows: CustomReportData[]) { const reports: CustomReportEntity[] = rows.map(row => { - const test: CustomReportEntity = { - ...row, + const report: CustomReportEntity = { + id: row.id, + name: row.name, + startDate: row.start_date, + endDate: row.end_date, + isDateStatic: row.date_static === 1, + dateRange: row.date_range, + mode: row.mode, + groupBy: row.group_by, + balanceType: row.balance_type, + showEmpty: row.show_empty === 1, + showOffBudget: row.show_offbudget === 1, + showHiddenCategories: row.show_hidden === 1, + showUncategorized: row.show_uncategorized === 1, + selectedCategories: row.selected_categories, + graphType: row.graph_type, + conditions: row.conditions, conditionsOp: row.conditions_op ?? 'and', + data: row.metadata, }; - return test; + return report; }); return reports; } diff --git a/packages/loot-core/src/server/reports/app.ts b/packages/loot-core/src/server/reports/app.ts index e311bed65..d78d09cb4 100644 --- a/packages/loot-core/src/server/reports/app.ts +++ b/packages/loot-core/src/server/reports/app.ts @@ -14,7 +14,7 @@ import { ReportsHandlers } from './types/handlers'; const reportModel = { validate(report: CustomReportEntity, { update }: { update?: boolean } = {}) { - requiredFields('reports', report, ['conditionsOp'], update); + requiredFields('Report', report, ['conditionsOp'], update); if (!update || 'conditionsOp' in report) { if (!['and', 'or'].includes(report.conditionsOp)) { @@ -27,66 +27,121 @@ const reportModel = { toJS(row: CustomReportData) { return { - ...row, + id: row.id, + name: row.name, + startDate: row.start_date, + endDate: row.end_date, + isDateStatic: row.date_static === 1, + dateRange: row.date_range, + mode: row.mode, + groupBy: row.group_by, + balanceType: row.balance_type, + showEmpty: row.show_empty === 1, + showOffBudget: row.show_offbudget === 1, + showHiddenCategories: row.show_hidden === 1, + showUncategorized: row.show_uncategorized === 1, + selectedCategories: row.selected_categories, + graphType: row.graph_type, + conditions: row.conditions, conditionsOp: row.conditions_op, + data: row.metadata, }; }, fromJS(report: CustomReportEntity) { - const { conditionsOp, ...row }: CustomReportData = report; - if (conditionsOp) { - row.conditions_op = conditionsOp; - } - return row; + return { + id: report.id, + name: report.name, + start_date: report.startDate, + end_date: report.endDate, + date_static: report.isDateStatic ? 1 : 0, + date_range: report.dateRange, + mode: report.mode, + group_by: report.groupBy, + balance_type: report.balanceType, + show_empty: report.showEmpty ? 1 : 0, + show_offbudget: report.showOffBudget ? 1 : 0, + show_hidden: report.showHiddenCategories ? 1 : 0, + show_uncategorized: report.showUncategorized ? 1 : 0, + selected_categories: report.selectedCategories, + graph_type: report.graphType, + conditions: report.conditions, + conditions_op: report.conditionsOp, + metadata: report.data, + }; }, }; async function reportNameExists( - name: string | undefined, - reportId: string | undefined, + name: string, + reportId: string, newItem: boolean, ) { - if (!name) { - throw new Error('Report name is required'); - } - - if (!reportId) { - throw new Error('Report recall error'); - } - const idForName: { id: string } = await db.first( - 'SELECT id from reports WHERE tombstone = 0 AND name = ?', + 'SELECT id from custom_reports WHERE tombstone = 0 AND name = ?', [name], ); - if (!newItem && idForName.id !== reportId) { - throw new Error('There is already a report named ' + name); + //no existing name found + if (idForName === null) { + return false; + } + + //for update/rename + if (!newItem) { + /* + -if the found item is the same as the existing item + then no name change was made. + -if they are not the same then there is another + item with that name already. + */ + return idForName.id !== reportId; } + + //default return: item was found but does not match current name + return true; } async function createReport(report: CustomReportEntity) { const reportId = uuidv4(); - const item: CustomReportData = { + const item: CustomReportEntity = { ...report, id: reportId, }; + if (!item.name) { + throw new Error('Report name is required'); + } - reportNameExists(item.name, item.id, true); + const nameExists = await reportNameExists(item.name, item.id ?? '', true); + if (nameExists) { + throw new Error('There is already a filter named ' + item.name); + } // Create the report here based on the info - await db.insertWithSchema('reports', reportModel.fromJS(item)); + await db.insertWithSchema('custom_reports', reportModel.fromJS(item)); return reportId; } async function updateReport(item: CustomReportEntity) { - reportNameExists(item.name, item.id, false); + if (!item.name) { + throw new Error('Report name is required'); + } + + if (!item.id) { + throw new Error('Report recall error'); + } + + const nameExists = await reportNameExists(item.name, item.id, false); + if (nameExists) { + throw new Error('There is already a filter named ' + item.name); + } - await db.insertWithSchema('reports', reportModel.fromJS(item)); + await db.insertWithSchema('custom_reports', reportModel.fromJS(item)); } async function deleteReport(id: string) { - await db.delete_('reports', id); + await db.delete_('custom_reports', id); } // Expose functions to the client diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts index ef888a2a8..06a6e56e0 100644 --- a/packages/loot-core/src/types/models/reports.d.ts +++ b/packages/loot-core/src/types/models/reports.d.ts @@ -71,7 +71,25 @@ export type Month = { month: string; }; -export interface CustomReportData extends CustomReportEntity { - conditions_op?: string; +export interface CustomReportData { + id?: string; + name?: string; + start_date: string; + end_date: string; + date_static: number; + date_range: string; + mode: string; + group_by: string; + balance_type: string; + show_empty: number; + show_offbudget: number; + show_hidden: number; + show_uncategorized: number; + selected_categories?: CategoryEntity[]; + graph_type: string; conditions?: RuleConditionEntity[]; + conditions_op: string; + metadata?: GroupedEntity; + interval?: string; + color_scheme?: string; } diff --git a/upcoming-release-notes/2345.md b/upcoming-release-notes/2345.md new file mode 100644 index 000000000..8e7f48a8a --- /dev/null +++ b/upcoming-release-notes/2345.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [carkom] +--- + +Changing graph styles so that they can be used for cards on Reports Dashboard. Also updating Entities and create/update calls for saved reports. -- GitLab