Skip to content
Snippets Groups Projects
Unverified Commit 9ec9aef6 authored by Matiss Janis Aboltins's avatar Matiss Janis Aboltins Committed by GitHub
Browse files

:sparkles: (budget-type) moving the selector to settings page (#3017)

* :sparkles: (budget-type) moving the selector to settings page

* Feedback: move the block down
parent 3be7dd75
No related branches found
No related tags found
No related merge requests found
Showing
with 107 additions and 321 deletions
...@@ -42,7 +42,7 @@ import { ScrollProvider } from './ScrollProvider'; ...@@ -42,7 +42,7 @@ import { ScrollProvider } from './ScrollProvider';
import { Settings } from './settings'; import { Settings } from './settings';
import { FloatableSidebar } from './sidebar'; import { FloatableSidebar } from './sidebar';
import { SidebarProvider } from './sidebar/SidebarProvider'; import { SidebarProvider } from './sidebar/SidebarProvider';
import { Titlebar, TitlebarProvider } from './Titlebar'; import { Titlebar } from './Titlebar';
function NarrowNotSupported({ function NarrowNotSupported({
redirectTo = '/budget', redirectTo = '/budget',
...@@ -246,15 +246,13 @@ export function FinancesApp() { ...@@ -246,15 +246,13 @@ export function FinancesApp() {
return ( return (
<SpreadsheetProvider> <SpreadsheetProvider>
<TitlebarProvider> <SidebarProvider>
<SidebarProvider> <BudgetMonthCountProvider>
<BudgetMonthCountProvider> <DndProvider backend={Backend}>
<DndProvider backend={Backend}> <ScrollProvider>{app}</ScrollProvider>
<ScrollProvider>{app}</ScrollProvider> </DndProvider>
</DndProvider> </BudgetMonthCountProvider>
</BudgetMonthCountProvider> </SidebarProvider>
</SidebarProvider>
</TitlebarProvider>
</SpreadsheetProvider> </SpreadsheetProvider>
); );
} }
...@@ -53,7 +53,6 @@ import { ScheduledTransactionMenuModal } from './modals/ScheduledTransactionMenu ...@@ -53,7 +53,6 @@ import { ScheduledTransactionMenuModal } from './modals/ScheduledTransactionMenu
import { SelectLinkedAccounts } from './modals/SelectLinkedAccounts'; import { SelectLinkedAccounts } from './modals/SelectLinkedAccounts';
import { SimpleFinInitialise } from './modals/SimpleFinInitialise'; import { SimpleFinInitialise } from './modals/SimpleFinInitialise';
import { SingleInputModal } from './modals/SingleInputModal'; import { SingleInputModal } from './modals/SingleInputModal';
import { SwitchBudgetTypeModal } from './modals/SwitchBudgetTypeModal';
import { TransferModal } from './modals/TransferModal'; import { TransferModal } from './modals/TransferModal';
import { DiscoverSchedules } from './schedules/DiscoverSchedules'; import { DiscoverSchedules } from './schedules/DiscoverSchedules';
import { PostsOfflineNotification } from './schedules/PostsOfflineNotification'; import { PostsOfflineNotification } from './schedules/PostsOfflineNotification';
...@@ -423,15 +422,6 @@ export function Modals() { ...@@ -423,15 +422,6 @@ export function Modals() {
/> />
); );
case 'switch-budget-type':
return (
<SwitchBudgetTypeModal
key={name}
modalProps={modalProps}
onSwitch={options.onSwitch}
/>
);
case 'account-menu': case 'account-menu':
return ( return (
<AccountMenuModal <AccountMenuModal
...@@ -623,7 +613,6 @@ export function Modals() { ...@@ -623,7 +613,6 @@ export function Modals() {
onAddCategoryGroup={options.onAddCategoryGroup} onAddCategoryGroup={options.onAddCategoryGroup}
onToggleHiddenCategories={options.onToggleHiddenCategories} onToggleHiddenCategories={options.onToggleHiddenCategories}
onSwitchBudgetFile={options.onSwitchBudgetFile} onSwitchBudgetFile={options.onSwitchBudgetFile}
onSwitchBudgetType={options.onSwitchBudgetType}
/> />
); );
......
import React, { import React, { useState, useEffect } from 'react';
createContext,
useState,
useEffect,
useRef,
useContext,
type ReactNode,
} from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { Routes, Route, useLocation } from 'react-router-dom'; import { Routes, Route, useLocation } from 'react-router-dom';
...@@ -13,10 +6,8 @@ import * as Platform from 'loot-core/src/client/platform'; ...@@ -13,10 +6,8 @@ import * as Platform from 'loot-core/src/client/platform';
import * as queries from 'loot-core/src/client/queries'; import * as queries from 'loot-core/src/client/queries';
import { listen } from 'loot-core/src/platform/client/fetch'; import { listen } from 'loot-core/src/platform/client/fetch';
import { isDevelopmentEnvironment } from 'loot-core/src/shared/environment'; import { isDevelopmentEnvironment } from 'loot-core/src/shared/environment';
import { type LocalPrefs } from 'loot-core/src/types/prefs';
import { useActions } from '../hooks/useActions'; import { useActions } from '../hooks/useActions';
import { useFeatureFlag } from '../hooks/useFeatureFlag';
import { useGlobalPref } from '../hooks/useGlobalPref'; import { useGlobalPref } from '../hooks/useGlobalPref';
import { useLocalPref } from '../hooks/useLocalPref'; import { useLocalPref } from '../hooks/useLocalPref';
import { useNavigate } from '../hooks/useNavigate'; import { useNavigate } from '../hooks/useNavigate';
...@@ -33,10 +24,8 @@ import { theme, type CSSProperties, styles } from '../style'; ...@@ -33,10 +24,8 @@ import { theme, type CSSProperties, styles } from '../style';
import { AccountSyncCheck } from './accounts/AccountSyncCheck'; import { AccountSyncCheck } from './accounts/AccountSyncCheck';
import { AnimatedRefresh } from './AnimatedRefresh'; import { AnimatedRefresh } from './AnimatedRefresh';
import { MonthCountSelector } from './budget/MonthCountSelector'; import { MonthCountSelector } from './budget/MonthCountSelector';
import { Button, ButtonWithLoading } from './common/Button'; import { Button } from './common/Button';
import { Link } from './common/Link'; import { Link } from './common/Link';
import { Paragraph } from './common/Paragraph';
import { Popover } from './common/Popover';
import { Text } from './common/Text'; import { Text } from './common/Text';
import { View } from './common/View'; import { View } from './common/View';
import { LoggedInUser } from './LoggedInUser'; import { LoggedInUser } from './LoggedInUser';
...@@ -45,55 +34,6 @@ import { useSidebar } from './sidebar/SidebarProvider'; ...@@ -45,55 +34,6 @@ import { useSidebar } from './sidebar/SidebarProvider';
import { useSheetValue } from './spreadsheet/useSheetValue'; import { useSheetValue } from './spreadsheet/useSheetValue';
import { ThemeSelector } from './ThemeSelector'; import { ThemeSelector } from './ThemeSelector';
export const SWITCH_BUDGET_MESSAGE_TYPE = 'budget/switch-type';
type SwitchBudgetTypeMessage = {
type: typeof SWITCH_BUDGET_MESSAGE_TYPE;
payload: {
newBudgetType: LocalPrefs['budgetType'];
};
};
export type TitlebarMessage = SwitchBudgetTypeMessage;
type Listener = (msg: TitlebarMessage) => void;
export type TitlebarContextValue = {
sendEvent: (msg: TitlebarMessage) => void;
subscribe: (listener: Listener) => () => void;
};
export const TitlebarContext = createContext<TitlebarContextValue>({
sendEvent() {
throw new Error('TitlebarContext not initialized');
},
subscribe() {
throw new Error('TitlebarContext not initialized');
},
});
type TitlebarProviderProps = {
children?: ReactNode;
};
export function TitlebarProvider({ children }: TitlebarProviderProps) {
const listeners = useRef<Listener[]>([]);
function sendEvent(msg: TitlebarMessage) {
listeners.current.forEach(func => func(msg));
}
function subscribe(listener: Listener) {
listeners.current.push(listener);
return () =>
(listeners.current = listeners.current.filter(func => func !== listener));
}
return (
<TitlebarContext.Provider value={{ sendEvent, subscribe }}>
{children}
</TitlebarContext.Provider>
);
}
function UncategorizedButton() { function UncategorizedButton() {
const count: number | null = useSheetValue(queries.uncategorizedCount()); const count: number | null = useSheetValue(queries.uncategorizedCount());
if (count === null || count <= 0) { if (count === null || count <= 0) {
...@@ -287,31 +227,6 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) { ...@@ -287,31 +227,6 @@ function SyncButton({ style, isMobile = false }: SyncButtonProps) {
function BudgetTitlebar() { function BudgetTitlebar() {
const [maxMonths, setMaxMonthsPref] = useGlobalPref('maxMonths'); const [maxMonths, setMaxMonthsPref] = useGlobalPref('maxMonths');
const [budgetType] = useLocalPref('budgetType');
const { sendEvent } = useContext(TitlebarContext);
const [loading, setLoading] = useState(false);
const [showPopover, setShowPopover] = useState(false);
const triggerRef = useRef(null);
const reportBudgetEnabled = useFeatureFlag('reportBudget');
function onSwitchType() {
setLoading(true);
if (!loading) {
const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
sendEvent({
type: SWITCH_BUDGET_MESSAGE_TYPE,
payload: {
newBudgetType,
},
});
}
}
useEffect(() => {
setLoading(false);
}, [budgetType]);
return ( return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}>
...@@ -319,61 +234,6 @@ function BudgetTitlebar() { ...@@ -319,61 +234,6 @@ function BudgetTitlebar() {
maxMonths={maxMonths || 1} maxMonths={maxMonths || 1}
onChange={value => setMaxMonthsPref(value)} onChange={value => setMaxMonthsPref(value)}
/> />
{reportBudgetEnabled && (
<View style={{ marginLeft: -5 }}>
<ButtonWithLoading
ref={triggerRef}
type="bare"
loading={loading}
style={{
alignSelf: 'flex-start',
padding: '4px 7px',
}}
title="Learn more about budgeting"
onClick={() => setShowPopover(true)}
>
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}
</ButtonWithLoading>
<Popover
triggerRef={triggerRef}
placement="bottom start"
isOpen={showPopover}
onOpenChange={() => setShowPopover(false)}
style={{
padding: 10,
maxWidth: 400,
}}
>
<Paragraph>
You are currently using a{' '}
<Text style={{ fontWeight: 600 }}>
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
</Text>{' '}
Switching will not lose any data and you can always switch back.
</Paragraph>
<Paragraph>
<ButtonWithLoading
type="primary"
loading={loading}
onClick={onSwitchType}
>
Switch to a{' '}
{budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
</ButtonWithLoading>
</Paragraph>
<Paragraph isLast={true}>
<Link
variant="external"
to="https://actualbudget.org/docs/experimental/report-budget"
linkColor="muted"
>
How do these types of budgeting work?
</Link>
</Paragraph>
</Popover>
</View>
)}
</View> </View>
); );
} }
......
// @ts-strict-ignore // @ts-strict-ignore
import React, { memo, useContext, useMemo, useState, useEffect } from 'react'; import React, { memo, useMemo, useState, useEffect } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { import {
...@@ -10,7 +10,6 @@ import { ...@@ -10,7 +10,6 @@ import {
deleteCategory, deleteCategory,
deleteGroup, deleteGroup,
getCategories, getCategories,
loadPrefs,
moveCategory, moveCategory,
moveCategoryGroup, moveCategoryGroup,
pushModal, pushModal,
...@@ -28,19 +27,13 @@ import { useNavigate } from '../../hooks/useNavigate'; ...@@ -28,19 +27,13 @@ import { useNavigate } from '../../hooks/useNavigate';
import { styles } from '../../style'; import { styles } from '../../style';
import { View } from '../common/View'; import { View } from '../common/View';
import { NamespaceContext } from '../spreadsheet/NamespaceContext'; import { NamespaceContext } from '../spreadsheet/NamespaceContext';
import {
SWITCH_BUDGET_MESSAGE_TYPE,
TitlebarContext,
type TitlebarContextValue,
type TitlebarMessage,
} from '../Titlebar';
import { DynamicBudgetTable } from './DynamicBudgetTable'; import { DynamicBudgetTable } from './DynamicBudgetTable';
import * as report from './report/ReportComponents'; import * as report from './report/ReportComponents';
import { ReportProvider } from './report/ReportContext'; import { ReportProvider } from './report/ReportContext';
import * as rollover from './rollover/RolloverComponents'; import * as rollover from './rollover/RolloverComponents';
import { RolloverProvider } from './rollover/RolloverContext'; import { RolloverProvider } from './rollover/RolloverContext';
import { prewarmAllMonths, prewarmMonth, switchBudgetType } from './util'; import { prewarmAllMonths, prewarmMonth } from './util';
type ReportComponents = { type ReportComponents = {
SummaryComponent: typeof report.BudgetSummary; SummaryComponent: typeof report.BudgetSummary;
...@@ -66,7 +59,6 @@ type BudgetInnerProps = { ...@@ -66,7 +59,6 @@ type BudgetInnerProps = {
accountId?: string; accountId?: string;
reportComponents: ReportComponents; reportComponents: ReportComponents;
rolloverComponents: RolloverComponents; rolloverComponents: RolloverComponents;
titlebar: TitlebarContextValue;
}; };
function BudgetInner(props: BudgetInnerProps) { function BudgetInner(props: BudgetInnerProps) {
...@@ -95,8 +87,6 @@ function BudgetInner(props: BudgetInnerProps) { ...@@ -95,8 +87,6 @@ function BudgetInner(props: BudgetInnerProps) {
} }
useEffect(() => { useEffect(() => {
const { titlebar } = props;
async function run() { async function run() {
loadCategories(); loadCategories();
...@@ -132,8 +122,6 @@ function BudgetInner(props: BudgetInnerProps) { ...@@ -132,8 +122,6 @@ function BudgetInner(props: BudgetInnerProps) {
loadCategories(); loadCategories();
} }
}), }),
titlebar.subscribe(onTitlebarEvent),
]; ];
return () => { return () => {
...@@ -323,24 +311,6 @@ function BudgetInner(props: BudgetInnerProps) { ...@@ -323,24 +311,6 @@ function BudgetInner(props: BudgetInnerProps) {
setSummaryCollapsedPref(!summaryCollapsed); setSummaryCollapsedPref(!summaryCollapsed);
}; };
const onTitlebarEvent = async ({ type, payload }: TitlebarMessage) => {
switch (type) {
case SWITCH_BUDGET_MESSAGE_TYPE: {
await switchBudgetType(
payload.newBudgetType,
spreadsheet,
bounds,
startMonth,
async () => {
dispatch(loadPrefs());
},
);
break;
}
default:
}
};
const { reportComponents, rolloverComponents } = props; const { reportComponents, rolloverComponents } = props;
if (!initialized || !categoryGroups) { if (!initialized || !categoryGroups) {
...@@ -416,8 +386,6 @@ const RolloverBudgetSummary = memo<{ month: string }>(props => { ...@@ -416,8 +386,6 @@ const RolloverBudgetSummary = memo<{ month: string }>(props => {
RolloverBudgetSummary.displayName = 'RolloverBudgetSummary'; RolloverBudgetSummary.displayName = 'RolloverBudgetSummary';
export function Budget() { export function Budget() {
const titlebar = useContext(TitlebarContext);
const reportComponents = useMemo<ReportComponents>( const reportComponents = useMemo<ReportComponents>(
() => ({ () => ({
SummaryComponent: report.BudgetSummary, SummaryComponent: report.BudgetSummary,
...@@ -460,7 +428,6 @@ export function Budget() { ...@@ -460,7 +428,6 @@ export function Budget() {
<BudgetInner <BudgetInner
reportComponents={reportComponents} reportComponents={reportComponents}
rolloverComponents={rolloverComponents} rolloverComponents={rolloverComponents}
titlebar={titlebar}
/> />
</View> </View>
); );
......
...@@ -16,7 +16,6 @@ import { ...@@ -16,7 +16,6 @@ import {
updateCategory, updateCategory,
updateGroup, updateGroup,
sync, sync,
loadPrefs,
} from 'loot-core/client/actions'; } from 'loot-core/client/actions';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider'; import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import { send, listen } from 'loot-core/src/platform/client/fetch'; import { send, listen } from 'loot-core/src/platform/client/fetch';
...@@ -31,7 +30,7 @@ import { useLocalPref } from '../../../hooks/useLocalPref'; ...@@ -31,7 +30,7 @@ import { useLocalPref } from '../../../hooks/useLocalPref';
import { useSetThemeColor } from '../../../hooks/useSetThemeColor'; import { useSetThemeColor } from '../../../hooks/useSetThemeColor';
import { AnimatedLoading } from '../../../icons/AnimatedLoading'; import { AnimatedLoading } from '../../../icons/AnimatedLoading';
import { theme } from '../../../style'; import { theme } from '../../../style';
import { prewarmMonth, switchBudgetType } from '../../budget/util'; import { prewarmMonth } from '../../budget/util';
import { View } from '../../common/View'; import { View } from '../../common/View';
import { NamespaceContext } from '../../spreadsheet/NamespaceContext'; import { NamespaceContext } from '../../spreadsheet/NamespaceContext';
import { SyncRefresh } from '../../SyncRefresh'; import { SyncRefresh } from '../../SyncRefresh';
...@@ -289,23 +288,6 @@ function BudgetInner(props: BudgetInnerProps) { ...@@ -289,23 +288,6 @@ function BudgetInner(props: BudgetInnerProps) {
// ); // );
// }; // };
const onSwitchBudgetType = async () => {
setInitialized(false);
const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
await switchBudgetType(
newBudgetType,
spreadsheet,
bounds,
startMonth,
async () => {
dispatch(loadPrefs());
},
);
setInitialized(true);
};
const onSaveNotes = async (id, notes) => { const onSaveNotes = async (id, notes) => {
await send('notes-save', { id, note: notes }); await send('notes-save', { id, note: notes });
}; };
...@@ -358,17 +340,6 @@ function BudgetInner(props: BudgetInnerProps) { ...@@ -358,17 +340,6 @@ function BudgetInner(props: BudgetInnerProps) {
); );
}; };
const onOpenSwitchBudgetTypeModal = () => {
dispatch(
pushModal('switch-budget-type', {
onSwitch: () => {
onSwitchBudgetType();
dispatch(collapseModals('budget-page-menu'));
},
}),
);
};
const [showHiddenCategories, setShowHiddenCategoriesPref] = useLocalPref( const [showHiddenCategories, setShowHiddenCategoriesPref] = useLocalPref(
'budget.showHiddenCategories', 'budget.showHiddenCategories',
); );
...@@ -408,7 +379,6 @@ function BudgetInner(props: BudgetInnerProps) { ...@@ -408,7 +379,6 @@ function BudgetInner(props: BudgetInnerProps) {
onAddCategoryGroup: onOpenNewCategoryGroupModal, onAddCategoryGroup: onOpenNewCategoryGroupModal,
onToggleHiddenCategories, onToggleHiddenCategories,
onSwitchBudgetFile, onSwitchBudgetFile,
onSwitchBudgetType: onOpenSwitchBudgetTypeModal,
}), }),
); );
}; };
......
import React, { type ComponentPropsWithoutRef } from 'react'; import React, { type ComponentPropsWithoutRef } from 'react';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { useLocalPref } from '../../hooks/useLocalPref'; import { useLocalPref } from '../../hooks/useLocalPref';
import { type CSSProperties, theme, styles } from '../../style'; import { type CSSProperties, theme, styles } from '../../style';
import { Menu } from '../common/Menu'; import { Menu } from '../common/Menu';
...@@ -18,7 +17,6 @@ export function BudgetPageMenuModal({ ...@@ -18,7 +17,6 @@ export function BudgetPageMenuModal({
onAddCategoryGroup, onAddCategoryGroup,
onToggleHiddenCategories, onToggleHiddenCategories,
onSwitchBudgetFile, onSwitchBudgetFile,
onSwitchBudgetType,
}: BudgetPageMenuModalProps) { }: BudgetPageMenuModalProps) {
const defaultMenuItemStyle: CSSProperties = { const defaultMenuItemStyle: CSSProperties = {
...styles.mobileMenuItem, ...styles.mobileMenuItem,
...@@ -34,7 +32,6 @@ export function BudgetPageMenuModal({ ...@@ -34,7 +32,6 @@ export function BudgetPageMenuModal({
onAddCategoryGroup={onAddCategoryGroup} onAddCategoryGroup={onAddCategoryGroup}
onToggleHiddenCategories={onToggleHiddenCategories} onToggleHiddenCategories={onToggleHiddenCategories}
onSwitchBudgetFile={onSwitchBudgetFile} onSwitchBudgetFile={onSwitchBudgetFile}
onSwitchBudgetType={onSwitchBudgetType}
/> />
</Modal> </Modal>
); );
...@@ -47,17 +44,14 @@ type BudgetPageMenuProps = Omit< ...@@ -47,17 +44,14 @@ type BudgetPageMenuProps = Omit<
onAddCategoryGroup: () => void; onAddCategoryGroup: () => void;
onToggleHiddenCategories: () => void; onToggleHiddenCategories: () => void;
onSwitchBudgetFile: () => void; onSwitchBudgetFile: () => void;
onSwitchBudgetType: () => void;
}; };
function BudgetPageMenu({ function BudgetPageMenu({
onAddCategoryGroup, onAddCategoryGroup,
onToggleHiddenCategories, onToggleHiddenCategories,
onSwitchBudgetFile, onSwitchBudgetFile,
onSwitchBudgetType,
...props ...props
}: BudgetPageMenuProps) { }: BudgetPageMenuProps) {
const isReportBudgetEnabled = useFeatureFlag('reportBudget');
const [showHiddenCategories] = useLocalPref('budget.showHiddenCategories'); const [showHiddenCategories] = useLocalPref('budget.showHiddenCategories');
const onMenuSelect = (name: string) => { const onMenuSelect = (name: string) => {
...@@ -74,9 +68,6 @@ function BudgetPageMenu({ ...@@ -74,9 +68,6 @@ function BudgetPageMenu({
case 'switch-budget-file': case 'switch-budget-file':
onSwitchBudgetFile?.(); onSwitchBudgetFile?.();
break; break;
case 'switch-budget-type':
onSwitchBudgetType?.();
break;
default: default:
throw new Error(`Unrecognized menu item: ${name}`); throw new Error(`Unrecognized menu item: ${name}`);
} }
...@@ -99,14 +90,6 @@ function BudgetPageMenu({ ...@@ -99,14 +90,6 @@ function BudgetPageMenu({
name: 'switch-budget-file', name: 'switch-budget-file',
text: 'Switch budget file', text: 'Switch budget file',
}, },
...(isReportBudgetEnabled
? [
{
name: 'switch-budget-type',
text: 'Switch budget type',
},
]
: []),
]} ]}
/> />
); );
......
// @ts-strict-ignore
import React from 'react';
import { useLocalPref } from '../../hooks/useLocalPref';
import { useResponsive } from '../../ResponsiveProvider';
import { styles } from '../../style';
import { Button } from '../common/Button';
import { Link } from '../common/Link';
import { Modal, ModalTitle } from '../common/Modal';
import { Paragraph } from '../common/Paragraph';
import { Text } from '../common/Text';
import { type CommonModalProps } from '../Modals';
type SwitchBudgetTypeModalProps = {
modalProps: CommonModalProps;
onSwitch: () => void;
};
export function SwitchBudgetTypeModal({
modalProps,
onSwitch,
}: SwitchBudgetTypeModalProps) {
const [budgetType] = useLocalPref('budgetType');
const { isNarrowWidth } = useResponsive();
const narrowStyle = isNarrowWidth
? {
height: styles.mobileMinHeight,
}
: {};
return (
<Modal
title={<ModalTitle title="Switch budget type?" shrinkOnOverflow />}
{...modalProps}
>
<>
<Paragraph>
You are currently using a{' '}
<Text style={{ fontWeight: 600 }}>
{budgetType === 'report' ? 'Report budget' : 'Rollover budget'}.
</Text>{' '}
Switching will not lose any data and you can always switch back.
</Paragraph>
<Button
type="primary"
style={{
...narrowStyle,
}}
onClick={() => {
onSwitch?.();
modalProps.onClose?.();
}}
>
Switch to a{' '}
{budgetType === 'report' ? 'Rollover budget' : 'Report budget'}
</Button>
<Paragraph
isLast={true}
style={{
marginTop: 10,
}}
>
<Link
variant="external"
to="https://actualbudget.org/docs/experimental/report-budget"
linkColor="muted"
>
How do these types of budgeting work?
</Link>
</Paragraph>
</>
</Modal>
);
}
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { loadPrefs } from 'loot-core/src/client/actions';
import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
import * as monthUtils from 'loot-core/src/shared/months';
import { useLocalPref } from '../../hooks/useLocalPref';
import { switchBudgetType } from '../budget/util';
import { ButtonWithLoading } from '../common/Button2';
import { Link } from '../common/Link';
import { Text } from '../common/Text';
import { Setting } from './UI';
export function BudgetTypeSettings() {
const dispatch = useDispatch();
const [budgetType] = useLocalPref('budgetType');
const [loading, setLoading] = useState(false);
const currentMonth = monthUtils.currentMonth();
const [startMonthPref] = useLocalPref('budget.startMonth');
const startMonth = startMonthPref || currentMonth;
const spreadsheet = useSpreadsheet();
function onSwitchType() {
setLoading(true);
if (!loading) {
const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
switchBudgetType(
newBudgetType,
spreadsheet,
{
start: startMonth,
end: startMonth,
},
startMonth,
async () => {
dispatch(loadPrefs());
setLoading(false);
},
);
}
}
return (
<Setting
primaryAction={
<ButtonWithLoading isLoading={loading} onPress={onSwitchType}>
Switch to {budgetType === 'report' ? 'envelope' : 'tracking'}{' '}
budgeting
</ButtonWithLoading>
}
>
<Text>
<strong>Envelope budgeting</strong> (recommended) digitally mimics
physical envelope budgeting system by allocating funds into virtual
envelopes for different expenses. It helps track spending and ensure you
don‘t overspend in any category.{' '}
<Link
variant="external"
to="https://actualbudget.org/docs/getting-started/envelope-budgeting"
linkColor="purple"
>
Learn more…
</Link>
</Text>
<Text>
With <strong>tracking budgeting</strong>, category balances reset each
month, and funds are managed using a “Saved” metric instead of “To Be
Budgeted.” Income is forecasted to plan future spending, rather than
relying on current available funds.{' '}
<Link
variant="external"
to="https://actualbudget.org/docs/experimental/report-budget"
linkColor="purple"
>
Learn more…
</Link>
</Text>
</Setting>
);
}
...@@ -6,6 +6,7 @@ import * as Platform from 'loot-core/src/client/platform'; ...@@ -6,6 +6,7 @@ import * as Platform from 'loot-core/src/client/platform';
import { listen } from 'loot-core/src/platform/client/fetch'; import { listen } from 'loot-core/src/platform/client/fetch';
import { useActions } from '../../hooks/useActions'; import { useActions } from '../../hooks/useActions';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useGlobalPref } from '../../hooks/useGlobalPref';
import { useLatestVersion, useIsOutdated } from '../../hooks/useLatestVersion'; import { useLatestVersion, useIsOutdated } from '../../hooks/useLatestVersion';
import { useLocalPref } from '../../hooks/useLocalPref'; import { useLocalPref } from '../../hooks/useLocalPref';
...@@ -23,6 +24,7 @@ import { MOBILE_NAV_HEIGHT } from '../mobile/MobileNavTabs'; ...@@ -23,6 +24,7 @@ import { MOBILE_NAV_HEIGHT } from '../mobile/MobileNavTabs';
import { Page } from '../Page'; import { Page } from '../Page';
import { useServerVersion } from '../ServerContext'; import { useServerVersion } from '../ServerContext';
import { BudgetTypeSettings } from './BudgetTypeSettings';
import { EncryptionSettings } from './Encryption'; import { EncryptionSettings } from './Encryption';
import { ExperimentalFeatures } from './Experimental'; import { ExperimentalFeatures } from './Experimental';
import { ExportBudget } from './Export'; import { ExportBudget } from './Export';
...@@ -178,6 +180,7 @@ export function Settings() { ...@@ -178,6 +180,7 @@ export function Settings() {
<ThemeSettings /> <ThemeSettings />
<FormatSettings /> <FormatSettings />
<EncryptionSettings /> <EncryptionSettings />
{useFeatureFlag('reportBudget') && <BudgetTypeSettings />}
<ExportBudget /> <ExportBudget />
<AdvancedToggle> <AdvancedToggle>
......
...@@ -129,7 +129,6 @@ type FinanceModals = { ...@@ -129,7 +129,6 @@ type FinanceModals = {
'schedules-discover': null; 'schedules-discover': null;
'schedule-posts-offline-notification': null; 'schedule-posts-offline-notification': null;
'switch-budget-type': { onSwitch: () => void };
'account-menu': { 'account-menu': {
accountId: string; accountId: string;
onSave: (account: AccountEntity) => void; onSave: (account: AccountEntity) => void;
...@@ -237,7 +236,6 @@ type FinanceModals = { ...@@ -237,7 +236,6 @@ type FinanceModals = {
onAddCategoryGroup: () => void; onAddCategoryGroup: () => void;
onToggleHiddenCategories: () => void; onToggleHiddenCategories: () => void;
onSwitchBudgetFile: () => void; onSwitchBudgetFile: () => void;
onSwitchBudgetType: () => void;
}; };
'rollover-budget-month-menu': { 'rollover-budget-month-menu': {
month: string; month: string;
......
---
category: Enhancements
authors: [MatissJanis]
---
Moved budget type toggle to the settings page
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment