Skip to content

Commit

Permalink
Summary report (#3792)
Browse files Browse the repository at this point in the history
* Summary card report

* Apply suggestions from code rabbit

* Apply suggestions from code review

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* MORE CODE RABBIT SUGGESTIONS

* typecheck fix

* change view form the details page

* added privacy filter

* Apply suggestions from code review

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* debounce

* removed binary search and changed the summary page to not use the card component

* Update packages/desktop-client/src/components/reports/spreadsheets/summary-spreadsheet.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix on recommended code rabbit commit

* added some padding to number so it fits the window better for big numbers

* accept infinite

* feedback fixes

* Update packages/desktop-client/src/components/reports/reports/SummaryCard.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* translations

* fix on the save, linter and changed "include summary date range" to "all time divisor"

* changed MD from enhancements to feature

* typo

* change card

* typecheck

* Update packages/desktop-client/src/components/reports/SummaryNumber.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* typecheck

* changes to fit the number better

* small fix

* fix on filters

* code review

* revert code to check for height

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
lelemm and coderabbitai[bot] authored Nov 21, 2024
1 parent f523d25 commit c626fc2
Show file tree
Hide file tree
Showing 14 changed files with 1,298 additions and 2 deletions.
14 changes: 13 additions & 1 deletion packages/desktop-client/src/components/reports/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import { CustomReportListCards } from './reports/CustomReportListCards';
import { MarkdownCard } from './reports/MarkdownCard';
import { NetWorthCard } from './reports/NetWorthCard';
import { SpendingCard } from './reports/SpendingCard';

import './overview.scss';
import { SummaryCard } from './reports/SummaryCard';

const ResponsiveGridLayout = WidthProvider(Responsive);

Expand Down Expand Up @@ -381,6 +381,10 @@ export function Overview() {
name: 'markdown-card' as const,
text: t('Text widget'),
},
{
name: 'summary-card' as const,
text: t('Summary card'),
},
{
name: 'custom-report' as const,
text: t('New custom report'),
Expand Down Expand Up @@ -522,6 +526,14 @@ export function Overview() {
report={customReportMap.get(item.meta.id)}
onRemove={() => onRemoveWidget(item.i)}
/>
) : item.type === 'summary-card' ? (
<SummaryCard
widgetId={item.i}
isEditing={isEditing}
meta={item.meta}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
/>
) : null}
</div>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CashFlow } from './reports/CashFlow';
import { CustomReport } from './reports/CustomReport';
import { NetWorth } from './reports/NetWorth';
import { Spending } from './reports/Spending';
import { Summary } from './reports/Summary';

export function ReportRouter() {
return (
Expand All @@ -19,6 +20,8 @@ export function ReportRouter() {
<Route path="/custom/:id" element={<CustomReport />} />
<Route path="/spending" element={<Spending />} />
<Route path="/spending/:id" element={<Spending />} />
<Route path="/summary" element={<Summary />} />
<Route path="/summary/:id" element={<Summary />} />
</Routes>
);
}
148 changes: 148 additions & 0 deletions packages/desktop-client/src/components/reports/SummaryNumber.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { type Ref, useRef, useState } from 'react';

import { debounce } from 'debounce';

import { amountToCurrency } from 'loot-core/shared/util';

import { useMergedRefs } from '../../hooks/useMergedRefs';
import { useResizeObserver } from '../../hooks/useResizeObserver';
import { View } from '../common/View';
import { PrivacyFilter } from '../PrivacyFilter';

import { chartTheme } from './chart-theme';
import { LoadingIndicator } from './LoadingIndicator';

const FONT_SIZE_SCALE_FACTOR = 0.9;
const MAX_RECURSION_DEPTH = 10;

type SummaryNumberProps = {
value: number;
animate?: boolean;
suffix?: string;
loading?: boolean;
initialFontSize?: number;
fontSizeChanged?: (fontSize: number) => void;
};

export function SummaryNumber({
value,
animate = false,
suffix = '',
loading = true,
initialFontSize = 14,
fontSizeChanged,
}: SummaryNumberProps) {
const [fontSize, setFontSize] = useState<number>(0);
const refDiv = useRef<HTMLDivElement>(null);
const offScreenRef = useRef<HTMLDivElement>(null);

const adjustFontSizeBinary = (minFontSize: number, maxFontSize: number) => {
if (!offScreenRef.current || !refDiv.current) return;

const offScreenDiv = offScreenRef.current;
const refDivCurrent = refDiv.current;

const binarySearchFontSize = (
min: number,
max: number,
depth: number = 0,
) => {
if (depth >= MAX_RECURSION_DEPTH) {
setFontSize(min);
return;
}

const testFontSize = (min + max) / 2;
offScreenDiv.style.fontSize = `${testFontSize}px`;

requestAnimationFrame(() => {
const isOverflowing =
offScreenDiv.scrollWidth > refDivCurrent.clientWidth ||
offScreenDiv.scrollHeight > refDivCurrent.clientHeight;

if (isOverflowing) {
binarySearchFontSize(min, testFontSize, depth + 1);
} else {
const isUnderflowing =
offScreenDiv.scrollWidth <=
refDivCurrent.clientWidth * FONT_SIZE_SCALE_FACTOR ||
offScreenDiv.scrollHeight <=
refDivCurrent.clientHeight * FONT_SIZE_SCALE_FACTOR;

if (isUnderflowing && testFontSize < max) {
binarySearchFontSize(testFontSize, max, depth + 1);
} else {
setFontSize(testFontSize);
if (initialFontSize !== testFontSize && fontSizeChanged) {
fontSizeChanged(testFontSize);
}
}
}
});
};

binarySearchFontSize(minFontSize, maxFontSize);
};

const handleResize = debounce(() => {
adjustFontSizeBinary(14, 200);
}, 250);

const ref = useResizeObserver(handleResize);
const mergedRef = useMergedRefs(ref, refDiv);

return (
<>
{loading && <LoadingIndicator />}
{!loading && (
<>
<div
ref={offScreenRef}
style={{
position: 'fixed',
left: '-999px',
top: '-999px',
fontSize: `${initialFontSize}px`,
lineHeight: 1,
visibility: 'hidden',
whiteSpace: 'nowrap',
padding: 8,
}}
>
<PrivacyFilter>
{amountToCurrency(Math.abs(value))}
{suffix}
</PrivacyFilter>
</div>

<View
ref={mergedRef as Ref<HTMLDivElement>}
role="text"
aria-label={`${value < 0 ? 'Negative' : 'Positive'} amount: ${amountToCurrency(Math.abs(value))}${suffix}`}
style={{
alignItems: 'center',
flexGrow: 1,
flexShrink: 1,
width: '100%',
height: '100%',
maxWidth: '100%',
fontSize: `${fontSize}px`,
lineHeight: 1,
padding: 8,
justifyContent: 'center',
transition: animate ? 'font-size 0.3s ease' : '',
color: value < 0 ? chartTheme.colors.red : chartTheme.colors.blue,
}}
>
<span aria-hidden="true">
<PrivacyFilter>
{amountToCurrency(Math.abs(value))}
{suffix}
</PrivacyFilter>
</span>
</View>
</>
)}
</>
);
}
Loading

0 comments on commit c626fc2

Please sign in to comment.