mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-29 19:14:22 -05:00
179 lines
4.4 KiB
TypeScript
179 lines
4.4 KiB
TypeScript
// @ts-strict-ignore
|
|
import React, { useEffect, type ComponentProps } from 'react';
|
|
import { useHotkeys } from 'react-hotkeys-hook';
|
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
|
|
|
import { View } from '@actual-app/components/view';
|
|
|
|
import * as monthUtils from 'loot-core/shared/months';
|
|
|
|
import { useBudgetMonthCount } from './BudgetMonthCountContext';
|
|
import { BudgetPageHeader } from './BudgetPageHeader';
|
|
import { BudgetTable } from './BudgetTable';
|
|
|
|
import { useFeatureFlag } from '@desktop-client/hooks/useFeatureFlag';
|
|
import { useGlobalPref } from '@desktop-client/hooks/useGlobalPref';
|
|
|
|
function getNumPossibleMonths(width: number, categoryWidth: number) {
|
|
const estimatedTableWidth = width - categoryWidth;
|
|
|
|
if (estimatedTableWidth < 500) {
|
|
return 1;
|
|
} else if (estimatedTableWidth < 750) {
|
|
return 2;
|
|
} else if (estimatedTableWidth < 1000) {
|
|
return 3;
|
|
} else if (estimatedTableWidth < 1250) {
|
|
return 4;
|
|
} else if (estimatedTableWidth < 1500) {
|
|
return 5;
|
|
}
|
|
|
|
return 6;
|
|
}
|
|
|
|
type DynamicBudgetTableProps = {
|
|
width: number;
|
|
height: number;
|
|
} & AutoSizingBudgetTableProps;
|
|
|
|
const DynamicBudgetTable = ({
|
|
type,
|
|
width,
|
|
height,
|
|
prewarmStartMonth,
|
|
startMonth,
|
|
maxMonths = 3,
|
|
monthBounds,
|
|
onMonthSelect,
|
|
...props
|
|
}: DynamicBudgetTableProps) => {
|
|
const { setDisplayMax } = useBudgetMonthCount();
|
|
const [categoryExpandedStatePref] = useGlobalPref('categoryExpandedState');
|
|
const categoryExpandedState = categoryExpandedStatePref ?? 0;
|
|
|
|
const numPossible = getNumPossibleMonths(
|
|
width,
|
|
200 + 100 * categoryExpandedState,
|
|
);
|
|
const numMonths = Math.min(numPossible, maxMonths);
|
|
const maxWidth = 200 + 100 * categoryExpandedState + 500 * numMonths;
|
|
|
|
useEffect(() => {
|
|
setDisplayMax(numPossible);
|
|
}, [numPossible]);
|
|
|
|
function getValidMonth(month) {
|
|
const start = monthBounds.start;
|
|
const end = monthUtils.subMonths(monthBounds.end, numMonths - 1);
|
|
|
|
if (month < start) {
|
|
return start;
|
|
} else if (month > end) {
|
|
return end;
|
|
}
|
|
return month;
|
|
}
|
|
|
|
function _onMonthSelect(month) {
|
|
onMonthSelect(getValidMonth(month), numMonths);
|
|
}
|
|
|
|
// Table V2 uses alt+left/right for month navigation
|
|
// so that users can use left/right to navigate cells
|
|
const budgetTableV2Enabled = useFeatureFlag('budgetTableV2');
|
|
|
|
useHotkeys(
|
|
budgetTableV2Enabled ? 'alt+left' : 'left',
|
|
() => {
|
|
_onMonthSelect(monthUtils.prevMonth(startMonth));
|
|
},
|
|
{
|
|
preventDefault: true,
|
|
scopes: ['app'],
|
|
},
|
|
[_onMonthSelect, startMonth],
|
|
);
|
|
useHotkeys(
|
|
budgetTableV2Enabled ? 'alt+right' : 'right',
|
|
() => {
|
|
_onMonthSelect(monthUtils.nextMonth(startMonth));
|
|
},
|
|
{
|
|
preventDefault: true,
|
|
scopes: ['app'],
|
|
},
|
|
[_onMonthSelect, startMonth],
|
|
);
|
|
useHotkeys(
|
|
'0',
|
|
() => {
|
|
_onMonthSelect(
|
|
monthUtils.subMonths(
|
|
monthUtils.currentMonth(),
|
|
type === 'envelope'
|
|
? Math.floor((numMonths - 1) / 2)
|
|
: numMonths === 2
|
|
? 1
|
|
: Math.max(numMonths - 2, 0),
|
|
),
|
|
);
|
|
},
|
|
{
|
|
preventDefault: true,
|
|
scopes: ['app'],
|
|
},
|
|
[_onMonthSelect, startMonth, numMonths],
|
|
);
|
|
|
|
return (
|
|
<View
|
|
style={{
|
|
width,
|
|
height,
|
|
alignItems: 'center',
|
|
opacity: width <= 0 || height <= 0 ? 0 : 1,
|
|
}}
|
|
>
|
|
<View style={{ width: '100%', maxWidth }}>
|
|
<BudgetPageHeader
|
|
startMonth={prewarmStartMonth}
|
|
numMonths={numMonths}
|
|
monthBounds={monthBounds}
|
|
onMonthSelect={_onMonthSelect}
|
|
/>
|
|
<BudgetTable
|
|
type={type}
|
|
prewarmStartMonth={prewarmStartMonth}
|
|
startMonth={startMonth}
|
|
numMonths={numMonths}
|
|
monthBounds={monthBounds}
|
|
{...props}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
DynamicBudgetTable.displayName = 'DynamicBudgetTable';
|
|
|
|
type AutoSizingBudgetTableProps = Omit<
|
|
ComponentProps<typeof BudgetTable>,
|
|
'numMonths'
|
|
> & {
|
|
maxMonths: number;
|
|
onMonthSelect: (month: string, numMonths: number) => void;
|
|
};
|
|
|
|
export const AutoSizingBudgetTable = (props: AutoSizingBudgetTableProps) => {
|
|
return (
|
|
<AutoSizer>
|
|
{({ width, height }) => (
|
|
<DynamicBudgetTable width={width} height={height} {...props} />
|
|
)}
|
|
</AutoSizer>
|
|
);
|
|
};
|
|
|
|
AutoSizingBudgetTable.displayName = 'AutoSizingBudgetTable';
|