Add Notes to Monthly Budget Cell (#6620)
* Add Notes to Monthly Budget Cell Changed Modal menus layout to follow month menu on mobile * Fixed rebase errors * Update VRT screenshots Auto-generated by VRT workflow PR: #6620 * Addressed youngcw's comments (notes id format, notesButton defaultColor and modal layout) * Update VRT screenshots Auto-generated by VRT workflow PR: #6620 * Updated mobile budget menu modal page model --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: youngcw <calebyoung94@gmail.com>
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
@@ -5,6 +5,7 @@ export class BudgetMenuModal {
|
||||
readonly locator: Locator;
|
||||
readonly heading: Locator;
|
||||
readonly budgetAmountInput: Locator;
|
||||
readonly actionsButton: Locator;
|
||||
readonly copyLastMonthBudgetButton: Locator;
|
||||
readonly setTo3MonthAverageButton: Locator;
|
||||
readonly setTo6MonthAverageButton: Locator;
|
||||
@@ -17,6 +18,9 @@ export class BudgetMenuModal {
|
||||
|
||||
this.heading = locator.getByRole('heading');
|
||||
this.budgetAmountInput = locator.getByTestId('amount-input');
|
||||
this.actionsButton = locator.getByRole('button', {
|
||||
name: 'Actions',
|
||||
});
|
||||
this.copyLastMonthBudgetButton = locator.getByRole('button', {
|
||||
name: "Copy last month's budget",
|
||||
});
|
||||
@@ -38,6 +42,10 @@ export class BudgetMenuModal {
|
||||
await this.heading.getByRole('button', { name: 'Close' }).click();
|
||||
}
|
||||
|
||||
async showActions() {
|
||||
await this.actionsButton.click();
|
||||
}
|
||||
|
||||
async setBudgetAmount(newAmount: string) {
|
||||
await this.budgetAmountInput.fill(newAmount);
|
||||
await this.budgetAmountInput.blur();
|
||||
@@ -45,22 +53,27 @@ export class BudgetMenuModal {
|
||||
}
|
||||
|
||||
async copyLastMonthBudget() {
|
||||
await this.showActions();
|
||||
await this.copyLastMonthBudgetButton.click();
|
||||
}
|
||||
|
||||
async setTo3MonthAverage() {
|
||||
await this.showActions();
|
||||
await this.setTo3MonthAverageButton.click();
|
||||
}
|
||||
|
||||
async setTo6MonthAverage() {
|
||||
await this.showActions();
|
||||
await this.setTo6MonthAverageButton.click();
|
||||
}
|
||||
|
||||
async setToYearlyAverage() {
|
||||
await this.showActions();
|
||||
await this.setToYearlyAverageButton.click();
|
||||
}
|
||||
|
||||
async applyBudgetTemplate() {
|
||||
await this.showActions();
|
||||
await this.applyBudgetTemplateButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { IncomeMenu } from './IncomeMenu';
|
||||
|
||||
import { BalanceWithCarryover } from '@desktop-client/components/budget/BalanceWithCarryover';
|
||||
import { makeAmountGrey } from '@desktop-client/components/budget/util';
|
||||
import { NotesButton } from '@desktop-client/components/NotesButton';
|
||||
import {
|
||||
CellValue,
|
||||
CellValueText,
|
||||
@@ -281,85 +282,100 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({
|
||||
}}
|
||||
>
|
||||
{!editing && (
|
||||
<View
|
||||
className={`hover-expand ${budgetMenuOpen ? 'force-visible' : ''}`}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 1,
|
||||
paddingLeft: 3,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: theme.tableBorder,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="bare"
|
||||
onPress={() => {
|
||||
resetBudgetPosition(2, -4);
|
||||
setBudgetMenuOpen(true);
|
||||
}}
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
color: theme.budgetNumberNeutral, //make sure button is visible when hovered
|
||||
padding: 3,
|
||||
paddingLeft: 3,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: theme.tableBorder,
|
||||
}}
|
||||
>
|
||||
<SvgCheveronDown
|
||||
width={14}
|
||||
height={14}
|
||||
className="hover-visible"
|
||||
<NotesButton
|
||||
id={`${category.id}-${month}`}
|
||||
defaultColor={theme.pageTextLight}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
triggerRef={budgetMenuTriggerRef}
|
||||
placement="bottom left"
|
||||
isOpen={budgetMenuOpen}
|
||||
onOpenChange={() => setBudgetMenuOpen(false)}
|
||||
style={{ width: 200 }}
|
||||
isNonModal
|
||||
{...budgetPosition}
|
||||
</View>
|
||||
<View
|
||||
className={`hover-expand ${budgetMenuOpen ? 'force-visible' : ''}`}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 1,
|
||||
paddingLeft: 3,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: theme.tableBorder,
|
||||
}}
|
||||
>
|
||||
<BudgetMenu
|
||||
onCopyLastMonthAverage={() => {
|
||||
onMenuAction(month, 'copy-single-last', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(`Budget set to last month's budget.`),
|
||||
});
|
||||
<Button
|
||||
variant="bare"
|
||||
onPress={() => {
|
||||
resetBudgetPosition(2, -4);
|
||||
setBudgetMenuOpen(true);
|
||||
}}
|
||||
onSetMonthsAverage={numberOfMonths => {
|
||||
if (
|
||||
numberOfMonths !== 3 &&
|
||||
numberOfMonths !== 6 &&
|
||||
numberOfMonths !== 12
|
||||
) {
|
||||
return;
|
||||
}
|
||||
style={{
|
||||
padding: 3,
|
||||
}}
|
||||
>
|
||||
<SvgCheveronDown
|
||||
width={14}
|
||||
height={14}
|
||||
className="hover-visible"
|
||||
/>
|
||||
</Button>
|
||||
<Popover
|
||||
triggerRef={budgetMenuTriggerRef}
|
||||
placement="bottom left"
|
||||
isOpen={budgetMenuOpen}
|
||||
onOpenChange={() => setBudgetMenuOpen(false)}
|
||||
style={{ width: 200 }}
|
||||
isNonModal
|
||||
{...budgetPosition}
|
||||
>
|
||||
<BudgetMenu
|
||||
onCopyLastMonthAverage={() => {
|
||||
onMenuAction(month, 'copy-single-last', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(`Budget set to last month's budget.`),
|
||||
});
|
||||
}}
|
||||
onSetMonthsAverage={numberOfMonths => {
|
||||
if (
|
||||
numberOfMonths !== 3 &&
|
||||
numberOfMonths !== 6 &&
|
||||
numberOfMonths !== 12
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
onMenuAction(month, `set-single-${numberOfMonths}-avg`, {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(
|
||||
'Budget set to {{numberOfMonths}}-month average.',
|
||||
{ numberOfMonths },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onApplyBudgetTemplate={() => {
|
||||
onMenuAction(month, 'apply-single-category-template', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(`Budget template applied.`),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
</View>
|
||||
onMenuAction(month, `set-single-${numberOfMonths}-avg`, {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(
|
||||
'Budget set to {{numberOfMonths}}-month average.',
|
||||
{ numberOfMonths },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onApplyBudgetTemplate={() => {
|
||||
onMenuAction(month, 'apply-single-category-template', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(`Budget template applied.`),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<EnvelopeSheetCell
|
||||
name="budget"
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Text } from '@actual-app/components/text';
|
||||
import { theme } from '@actual-app/components/theme';
|
||||
import { View } from '@actual-app/components/view';
|
||||
import { css } from '@emotion/css';
|
||||
import { t } from 'i18next';
|
||||
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
|
||||
@@ -25,6 +26,7 @@ import { BudgetMenu } from './BudgetMenu';
|
||||
|
||||
import { BalanceWithCarryover } from '@desktop-client/components/budget/BalanceWithCarryover';
|
||||
import { makeAmountGrey } from '@desktop-client/components/budget/util';
|
||||
import { NotesButton } from '@desktop-client/components/NotesButton';
|
||||
import {
|
||||
CellValue,
|
||||
CellValueText,
|
||||
@@ -261,77 +263,96 @@ export const CategoryMonth = memo(function CategoryMonth({
|
||||
}}
|
||||
>
|
||||
{!editing && (
|
||||
<View
|
||||
className={`hover-expand ${menuOpen ? 'force-visible' : ''}`}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
paddingLeft: 3,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: theme.tableBorder,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
ref={triggerRef}
|
||||
variant="bare"
|
||||
onPress={() => setMenuOpen(true)}
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
color: theme.budgetNumberNeutral, //make sure button is visible when hovered
|
||||
padding: 3,
|
||||
paddingLeft: 3,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: theme.tableBorder,
|
||||
}}
|
||||
>
|
||||
<SvgCheveronDown
|
||||
width={14}
|
||||
height={14}
|
||||
className="hover-visible"
|
||||
<NotesButton
|
||||
id={`${category.id}-${month}`}
|
||||
defaultColor={theme.pageTextLight}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
triggerRef={triggerRef}
|
||||
isOpen={menuOpen}
|
||||
onOpenChange={() => setMenuOpen(false)}
|
||||
placement="bottom start"
|
||||
</View>
|
||||
<View
|
||||
className={`hover-expand ${menuOpen ? 'force-visible' : ''}`}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
paddingLeft: 3,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: theme.tableBorder,
|
||||
}}
|
||||
>
|
||||
<BudgetMenu
|
||||
onCopyLastMonthAverage={() => {
|
||||
onMenuAction(month, 'copy-single-last', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: `Budget set to last month's budget.`,
|
||||
});
|
||||
<Button
|
||||
ref={triggerRef}
|
||||
variant="bare"
|
||||
onPress={() => setMenuOpen(true)}
|
||||
style={{
|
||||
padding: 3,
|
||||
}}
|
||||
onSetMonthsAverage={numberOfMonths => {
|
||||
if (
|
||||
numberOfMonths !== 3 &&
|
||||
numberOfMonths !== 6 &&
|
||||
numberOfMonths !== 12
|
||||
) {
|
||||
return;
|
||||
}
|
||||
>
|
||||
<SvgCheveronDown
|
||||
width={14}
|
||||
height={14}
|
||||
className="hover-visible"
|
||||
/>
|
||||
</Button>
|
||||
|
||||
onMenuAction(month, `set-single-${numberOfMonths}-avg`, {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: `Budget set to ${numberOfMonths}-month average.`,
|
||||
});
|
||||
}}
|
||||
onApplyBudgetTemplate={() => {
|
||||
onMenuAction(month, 'apply-single-category-template', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: `Budget template applied.`,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
</View>
|
||||
<Popover
|
||||
triggerRef={triggerRef}
|
||||
isOpen={menuOpen}
|
||||
onOpenChange={() => setMenuOpen(false)}
|
||||
placement="bottom start"
|
||||
>
|
||||
<BudgetMenu
|
||||
onCopyLastMonthAverage={() => {
|
||||
onMenuAction(month, 'copy-single-last', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(`Budget set to last month's budget.`),
|
||||
});
|
||||
}}
|
||||
onSetMonthsAverage={numberOfMonths => {
|
||||
if (
|
||||
numberOfMonths !== 3 &&
|
||||
numberOfMonths !== 6 &&
|
||||
numberOfMonths !== 12
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
onMenuAction(month, `set-single-${numberOfMonths}-avg`, {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(
|
||||
'Budget set to {{numberOfMonths}}-month average.',
|
||||
{ numberOfMonths },
|
||||
),
|
||||
});
|
||||
}}
|
||||
onApplyBudgetTemplate={() => {
|
||||
onMenuAction(month, 'apply-single-category-template', {
|
||||
category: category.id,
|
||||
});
|
||||
showUndoNotification({
|
||||
message: t(`Budget template applied.`),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<TrackingSheetCell
|
||||
name="budget"
|
||||
|
||||
@@ -7,6 +7,8 @@ import { styles } from '@actual-app/components/styles';
|
||||
import { Text } from '@actual-app/components/text';
|
||||
import { AutoTextSize } from 'auto-text-size';
|
||||
|
||||
import { send } from 'loot-core/platform/client/connection';
|
||||
import * as monthUtils from 'loot-core/shared/months';
|
||||
import type { CategoryEntity } from 'loot-core/types/models';
|
||||
|
||||
import { getColumnWidth, PILL_STYLE } from './BudgetTable';
|
||||
@@ -15,6 +17,7 @@ import { makeAmountGrey } from '@desktop-client/components/budget/util';
|
||||
import { PrivacyFilter } from '@desktop-client/components/PrivacyFilter';
|
||||
import { CellValue } from '@desktop-client/components/spreadsheet/CellValue';
|
||||
import { useFormat } from '@desktop-client/hooks/useFormat';
|
||||
import { useLocale } from '@desktop-client/hooks/useLocale';
|
||||
import { useNotes } from '@desktop-client/hooks/useNotes';
|
||||
import { useSyncedPref } from '@desktop-client/hooks/useSyncedPref';
|
||||
import { useUndo } from '@desktop-client/hooks/useUndo';
|
||||
@@ -43,6 +46,7 @@ export function BudgetCell<
|
||||
...props
|
||||
}: BudgetCellProps<SheetFieldName>) {
|
||||
const { t } = useTranslation();
|
||||
const locale = useLocale();
|
||||
const columnWidth = getColumnWidth();
|
||||
const dispatch = useDispatch();
|
||||
const format = useFormat();
|
||||
@@ -50,6 +54,31 @@ export function BudgetCell<
|
||||
const [budgetType = 'envelope'] = useSyncedPref('budgetType');
|
||||
const categoryNotes = useNotes(category.id);
|
||||
|
||||
const onSaveNotes = useCallback(async (id: string, notes: string) => {
|
||||
await send('notes-save', { id, note: notes });
|
||||
}, []);
|
||||
|
||||
const onEditNotes = useCallback(
|
||||
(id: string, month: string) => {
|
||||
dispatch(
|
||||
pushModal({
|
||||
modal: {
|
||||
name: 'notes',
|
||||
options: {
|
||||
id,
|
||||
name:
|
||||
category.name +
|
||||
' - ' +
|
||||
monthUtils.format(month, "MMMM ''yy", locale),
|
||||
onSave: onSaveNotes,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
[category.name, locale, dispatch, onSaveNotes],
|
||||
);
|
||||
|
||||
const onOpenCategoryBudgetMenu = useCallback(() => {
|
||||
const modalBudgetType = budgetType === 'envelope' ? 'envelope' : 'tracking';
|
||||
const categoryBudgetMenuModal = `${modalBudgetType}-budget-menu` as const;
|
||||
@@ -60,6 +89,7 @@ export function BudgetCell<
|
||||
options: {
|
||||
categoryId: category.id,
|
||||
month,
|
||||
onEditNotes,
|
||||
onUpdateBudget: amount => {
|
||||
onBudgetAction(month, 'budget-amount', {
|
||||
category: category.id,
|
||||
@@ -114,6 +144,7 @@ export function BudgetCell<
|
||||
month,
|
||||
onBudgetAction,
|
||||
showUndoNotification,
|
||||
onEditNotes,
|
||||
format,
|
||||
]);
|
||||
|
||||
|
||||
@@ -2,10 +2,17 @@ import React, { useEffect, useState } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
import { Button } from '@actual-app/components/button';
|
||||
import {
|
||||
SvgCheveronDown,
|
||||
SvgCheveronUp,
|
||||
} from '@actual-app/components/icons/v1';
|
||||
import { SvgNotesPaper } from '@actual-app/components/icons/v2';
|
||||
import { styles } from '@actual-app/components/styles';
|
||||
import { Text } from '@actual-app/components/text';
|
||||
import { theme } from '@actual-app/components/theme';
|
||||
import { View } from '@actual-app/components/view';
|
||||
import { t } from 'i18next';
|
||||
|
||||
import * as Platform from 'loot-core/shared/platform';
|
||||
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
|
||||
@@ -19,14 +26,16 @@ import {
|
||||
ModalTitle,
|
||||
} from '@desktop-client/components/common/Modal';
|
||||
import { FocusableAmountInput } from '@desktop-client/components/mobile/transactions/FocusableAmountInput';
|
||||
import { Notes } from '@desktop-client/components/Notes';
|
||||
import { useCategory } from '@desktop-client/hooks/useCategory';
|
||||
import { useNotes } from '@desktop-client/hooks/useNotes';
|
||||
import type { Modal as ModalType } from '@desktop-client/modals/modalsSlice';
|
||||
import { envelopeBudget } from '@desktop-client/spreadsheet/bindings';
|
||||
|
||||
type EnvelopeBudgetMenuModalProps = Omit<
|
||||
Extract<ModalType, { name: 'envelope-budget-menu' }>['options'],
|
||||
'month'
|
||||
>;
|
||||
type EnvelopeBudgetMenuModalProps = Extract<
|
||||
ModalType,
|
||||
{ name: 'envelope-budget-menu' }
|
||||
>['options'];
|
||||
|
||||
export function EnvelopeBudgetMenuModal({
|
||||
categoryId,
|
||||
@@ -34,7 +43,17 @@ export function EnvelopeBudgetMenuModal({
|
||||
onCopyLastMonthAverage,
|
||||
onSetMonthsAverage,
|
||||
onApplyBudgetTemplate,
|
||||
onEditNotes,
|
||||
month,
|
||||
}: EnvelopeBudgetMenuModalProps) {
|
||||
const buttonStyle: CSSProperties = {
|
||||
...styles.mediumText,
|
||||
height: styles.mobileMinHeight,
|
||||
color: theme.formLabelText,
|
||||
// Adjust based on desired number of buttons per row.
|
||||
flexBasis: '100%',
|
||||
};
|
||||
|
||||
const defaultMenuItemStyle: CSSProperties = {
|
||||
...styles.mobileMenuItem,
|
||||
color: theme.menuItemText,
|
||||
@@ -48,10 +67,24 @@ export function EnvelopeBudgetMenuModal({
|
||||
const { data: category } = useCategory(categoryId);
|
||||
const [amountFocused, setAmountFocused] = useState(false);
|
||||
|
||||
const notesId = category ? `${category.id}-${month}` : '';
|
||||
const originalNotes = useNotes(notesId) ?? '';
|
||||
const _onUpdateBudget = (amount: number) => {
|
||||
onUpdateBudget?.(amountToInteger(amount));
|
||||
};
|
||||
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
const onShowMore = () => {
|
||||
setShowMore(!showMore);
|
||||
};
|
||||
|
||||
const _onEditNotes = () => {
|
||||
if (category && month) {
|
||||
onEditNotes?.(`${category.id}-${month}`, month);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// iOS does not support automatically opening up the keyboard for the
|
||||
// total amount field. Hence we should not focus on it on page render.
|
||||
@@ -76,7 +109,6 @@ export function EnvelopeBudgetMenuModal({
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
@@ -106,12 +138,71 @@ export function EnvelopeBudgetMenuModal({
|
||||
data-testid="budget-amount"
|
||||
/>
|
||||
</View>
|
||||
<BudgetMenu
|
||||
getItemStyle={() => defaultMenuItemStyle}
|
||||
onCopyLastMonthAverage={onCopyLastMonthAverage}
|
||||
onSetMonthsAverage={onSetMonthsAverage}
|
||||
onApplyBudgetTemplate={onApplyBudgetTemplate}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
display: showMore ? 'none' : undefined,
|
||||
overflowY: 'auto',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Notes
|
||||
notes={originalNotes.length > 0 ? originalNotes : t('No notes')}
|
||||
editable={false}
|
||||
focused={false}
|
||||
getStyle={() => ({
|
||||
borderRadius: 6,
|
||||
...(originalNotes.length === 0 && {
|
||||
justifySelf: 'center',
|
||||
alignSelf: 'center',
|
||||
color: theme.pageTextSubdued,
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
display: showMore ? 'none' : undefined,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Button style={buttonStyle} onPress={_onEditNotes}>
|
||||
<SvgNotesPaper
|
||||
width={20}
|
||||
height={20}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
<Trans>Edit notes</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
<Button variant="bare" style={buttonStyle} onPress={onShowMore}>
|
||||
{!showMore ? (
|
||||
<SvgCheveronUp
|
||||
width={30}
|
||||
height={30}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<SvgCheveronDown
|
||||
width={30}
|
||||
height={30}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
)}
|
||||
<Trans>Actions</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
{showMore && (
|
||||
<BudgetMenu
|
||||
getItemStyle={() => defaultMenuItemStyle}
|
||||
onCopyLastMonthAverage={onCopyLastMonthAverage}
|
||||
onSetMonthsAverage={onSetMonthsAverage}
|
||||
onApplyBudgetTemplate={onApplyBudgetTemplate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
@@ -2,10 +2,17 @@ import React, { useEffect, useState } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
import { Button } from '@actual-app/components/button';
|
||||
import {
|
||||
SvgCheveronDown,
|
||||
SvgCheveronUp,
|
||||
} from '@actual-app/components/icons/v1';
|
||||
import { SvgNotesPaper } from '@actual-app/components/icons/v2';
|
||||
import { styles } from '@actual-app/components/styles';
|
||||
import { Text } from '@actual-app/components/text';
|
||||
import { theme } from '@actual-app/components/theme';
|
||||
import { View } from '@actual-app/components/view';
|
||||
import { t } from 'i18next';
|
||||
|
||||
import * as Platform from 'loot-core/shared/platform';
|
||||
import { amountToInteger, integerToAmount } from 'loot-core/shared/util';
|
||||
@@ -19,14 +26,16 @@ import {
|
||||
ModalTitle,
|
||||
} from '@desktop-client/components/common/Modal';
|
||||
import { FocusableAmountInput } from '@desktop-client/components/mobile/transactions/FocusableAmountInput';
|
||||
import { Notes } from '@desktop-client/components/Notes';
|
||||
import { useCategory } from '@desktop-client/hooks/useCategory';
|
||||
import { useNotes } from '@desktop-client/hooks/useNotes';
|
||||
import type { Modal as ModalType } from '@desktop-client/modals/modalsSlice';
|
||||
import { trackingBudget } from '@desktop-client/spreadsheet/bindings';
|
||||
|
||||
type TrackingBudgetMenuModalProps = Omit<
|
||||
Extract<ModalType, { name: 'tracking-budget-menu' }>['options'],
|
||||
'month'
|
||||
>;
|
||||
type TrackingBudgetMenuModalProps = Extract<
|
||||
ModalType,
|
||||
{ name: 'tracking-budget-menu' }
|
||||
>['options'];
|
||||
|
||||
export function TrackingBudgetMenuModal({
|
||||
categoryId,
|
||||
@@ -34,6 +43,8 @@ export function TrackingBudgetMenuModal({
|
||||
onCopyLastMonthAverage,
|
||||
onSetMonthsAverage,
|
||||
onApplyBudgetTemplate,
|
||||
onEditNotes,
|
||||
month,
|
||||
}: TrackingBudgetMenuModalProps) {
|
||||
const defaultMenuItemStyle: CSSProperties = {
|
||||
...styles.mobileMenuItem,
|
||||
@@ -42,16 +53,38 @@ export function TrackingBudgetMenuModal({
|
||||
borderTop: `1px solid ${theme.pillBorder}`,
|
||||
};
|
||||
|
||||
const buttonStyle: CSSProperties = {
|
||||
...styles.mediumText,
|
||||
height: styles.mobileMinHeight,
|
||||
color: theme.formLabelText,
|
||||
// Adjust based on desired number of buttons per row.
|
||||
flexBasis: '100%',
|
||||
};
|
||||
const budgeted = useTrackingSheetValue(
|
||||
trackingBudget.catBudgeted(categoryId),
|
||||
);
|
||||
const { data: category } = useCategory(categoryId);
|
||||
const notesId = category ? `${category.id}-${month}` : '';
|
||||
const originalNotes = useNotes(notesId) ?? '';
|
||||
|
||||
const [amountFocused, setAmountFocused] = useState(false);
|
||||
|
||||
const _onUpdateBudget = (amount: number) => {
|
||||
onUpdateBudget?.(amountToInteger(amount));
|
||||
};
|
||||
|
||||
const _onEditNotes = () => {
|
||||
if (category && month) {
|
||||
onEditNotes?.(`${category.id}-${month}`, month);
|
||||
}
|
||||
};
|
||||
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
const onShowMore = () => {
|
||||
setShowMore(!showMore);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// iOS does not support automatically opening up the keyboard for the
|
||||
// total amount field. Hence we should not focus on it on page render.
|
||||
@@ -76,7 +109,6 @@ export function TrackingBudgetMenuModal({
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
@@ -106,12 +138,71 @@ export function TrackingBudgetMenuModal({
|
||||
data-testid="budget-amount"
|
||||
/>
|
||||
</View>
|
||||
<BudgetMenu
|
||||
getItemStyle={() => defaultMenuItemStyle}
|
||||
onCopyLastMonthAverage={onCopyLastMonthAverage}
|
||||
onSetMonthsAverage={onSetMonthsAverage}
|
||||
onApplyBudgetTemplate={onApplyBudgetTemplate}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
display: showMore ? 'none' : undefined,
|
||||
overflowY: 'auto',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Notes
|
||||
notes={originalNotes.length > 0 ? originalNotes : t('No notes')}
|
||||
editable={false}
|
||||
focused={false}
|
||||
getStyle={() => ({
|
||||
borderRadius: 6,
|
||||
...(originalNotes.length === 0 && {
|
||||
justifySelf: 'center',
|
||||
alignSelf: 'center',
|
||||
color: theme.pageTextSubdued,
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
display: showMore ? 'none' : undefined,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Button style={buttonStyle} onPress={_onEditNotes}>
|
||||
<SvgNotesPaper
|
||||
width={20}
|
||||
height={20}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
<Trans>Edit notes</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
<Button variant="bare" style={buttonStyle} onPress={onShowMore}>
|
||||
{!showMore ? (
|
||||
<SvgCheveronUp
|
||||
width={30}
|
||||
height={30}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<SvgCheveronDown
|
||||
width={30}
|
||||
height={30}
|
||||
style={{ paddingRight: 5 }}
|
||||
/>
|
||||
)}
|
||||
<Trans>Actions</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
{showMore && (
|
||||
<BudgetMenu
|
||||
getItemStyle={() => defaultMenuItemStyle}
|
||||
onCopyLastMonthAverage={onCopyLastMonthAverage}
|
||||
onSetMonthsAverage={onSetMonthsAverage}
|
||||
onApplyBudgetTemplate={onApplyBudgetTemplate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
@@ -334,6 +334,7 @@ export type Modal =
|
||||
onCopyLastMonthAverage: () => void;
|
||||
onSetMonthsAverage: (numberOfMonths: number) => void;
|
||||
onApplyBudgetTemplate: () => void;
|
||||
onEditNotes: (id: NoteEntity['id'], month: string) => void;
|
||||
};
|
||||
}
|
||||
| {
|
||||
@@ -345,6 +346,7 @@ export type Modal =
|
||||
onCopyLastMonthAverage: () => void;
|
||||
onSetMonthsAverage: (numberOfMonths: number) => void;
|
||||
onApplyBudgetTemplate: () => void;
|
||||
onEditNotes: (id: NoteEntity['id'], month: string) => void;
|
||||
};
|
||||
}
|
||||
| {
|
||||
|
||||