[Maintenance] Use Page component for mobile pages (#1993)
* Use Page component for mobile pages * Release notes * Use Button instead of Link in MobileBackButton * Update mobile budget table to use Page component * Settings page cleanup * Fix lint error * Updates + small font size increase in page headings * Fix rebase error * Button height * Revert payees navtab
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
48
packages/desktop-client/src/components/MobileBackButton.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
|
||||
import useNavigate from '../hooks/useNavigate';
|
||||
import CheveronLeft from '../icons/v1/CheveronLeft';
|
||||
import { type CSSProperties, styles, theme } from '../style';
|
||||
|
||||
import Button from './common/Button';
|
||||
import Text from './common/Text';
|
||||
|
||||
type MobileBackButtonProps = {
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
export default function MobileBackButton({ style }: MobileBackButtonProps) {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
color: theme.mobileHeaderText,
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
paddingLeft: 5,
|
||||
paddingRight: 3,
|
||||
...style,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
onPointerUp={() => navigate(-1)}
|
||||
>
|
||||
<CheveronLeft
|
||||
style={{ width: 30, height: 30, margin: -10, marginLeft: -5 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
...styles.text,
|
||||
fontWeight: 500,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Text>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { type ReactNode } from 'react';
|
||||
import React, { type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
||||
|
||||
import { useResponsive } from '../ResponsiveProvider';
|
||||
import { theme, styles, type CSSProperties } from '../style';
|
||||
@@ -7,18 +7,24 @@ import Text from './common/Text';
|
||||
import View from './common/View';
|
||||
|
||||
type PageHeaderProps = {
|
||||
name: ReactNode;
|
||||
title: ReactNode;
|
||||
titleContainerProps?: ComponentPropsWithoutRef<typeof View>;
|
||||
style?: CSSProperties;
|
||||
leftContentContainerProps?: ComponentPropsWithoutRef<typeof View>;
|
||||
leftContent?: ReactNode;
|
||||
rightContentContainerProps?: ComponentPropsWithoutRef<typeof View>;
|
||||
rightContent?: ReactNode;
|
||||
};
|
||||
|
||||
const HEADER_HEIGHT = 50;
|
||||
|
||||
function PageHeader({
|
||||
name,
|
||||
title,
|
||||
titleContainerProps,
|
||||
style,
|
||||
leftContentContainerProps,
|
||||
leftContent,
|
||||
rightContentContainerProps,
|
||||
rightContent,
|
||||
}: PageHeaderProps) {
|
||||
const { isNarrowWidth } = useResponsive();
|
||||
@@ -28,8 +34,8 @@ function PageHeader({
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.mobilePageBackground,
|
||||
color: theme.mobileModalText,
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
color: theme.mobileHeaderText,
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
height: HEADER_HEIGHT,
|
||||
@@ -37,32 +43,40 @@ function PageHeader({
|
||||
}}
|
||||
>
|
||||
<View
|
||||
{...leftContentContainerProps}
|
||||
style={{
|
||||
flexBasis: '25%',
|
||||
justifyContent: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
...leftContentContainerProps?.style,
|
||||
}}
|
||||
>
|
||||
{leftContent}
|
||||
</View>
|
||||
<View
|
||||
role="heading"
|
||||
{...titleContainerProps}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
flexBasis: '50%',
|
||||
fontSize: 18,
|
||||
fontSize: 17,
|
||||
fontWeight: 500,
|
||||
justifyContent: 'center',
|
||||
overflowY: 'auto',
|
||||
...titleContainerProps?.style,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
{title}
|
||||
</View>
|
||||
<View
|
||||
{...rightContentContainerProps}
|
||||
style={{
|
||||
flexBasis: '25%',
|
||||
justifyContent: 'flex-end',
|
||||
flexDirection: 'row',
|
||||
...rightContentContainerProps?.style,
|
||||
}}
|
||||
>
|
||||
{rightContent}
|
||||
@@ -80,53 +94,88 @@ function PageHeader({
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
{title}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
type PageProps = {
|
||||
title: ReactNode;
|
||||
titleStyle?: CSSProperties;
|
||||
headerLeftContent?: ReactNode;
|
||||
headerRightContent?: ReactNode;
|
||||
titleContainerProps?: PageHeaderProps['titleContainerProps'];
|
||||
title: PageHeaderProps['title'];
|
||||
headerStyle?: CSSProperties;
|
||||
headerLeftContentContainerProps?: PageHeaderProps['leftContentContainerProps'];
|
||||
headerLeftContent?: PageHeaderProps['leftContent'];
|
||||
headerRightContentContainerProps?: PageHeaderProps['rightContentContainerProps'];
|
||||
headerRightContent?: PageHeaderProps['rightContent'];
|
||||
style?: CSSProperties;
|
||||
padding?: number;
|
||||
childrenContainerProps?: ComponentPropsWithoutRef<typeof View>;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
};
|
||||
|
||||
export function Page({
|
||||
titleContainerProps,
|
||||
title,
|
||||
titleStyle,
|
||||
headerStyle,
|
||||
headerLeftContentContainerProps,
|
||||
headerLeftContent,
|
||||
headerRightContentContainerProps,
|
||||
headerRightContent,
|
||||
style,
|
||||
padding,
|
||||
childrenContainerProps,
|
||||
children,
|
||||
footer,
|
||||
}: PageProps) {
|
||||
let { isNarrowWidth } = useResponsive();
|
||||
let HORIZONTAL_PADDING = isNarrowWidth ? 10 : 20;
|
||||
const { isNarrowWidth } = useResponsive();
|
||||
const _padding = padding != null ? padding : isNarrowWidth ? 10 : 20;
|
||||
|
||||
return (
|
||||
<View style={isNarrowWidth ? undefined : styles.page}>
|
||||
<View
|
||||
style={{
|
||||
...(!isNarrowWidth && styles.page),
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<PageHeader
|
||||
name={title}
|
||||
title={title}
|
||||
titleContainerProps={titleContainerProps}
|
||||
leftContentContainerProps={headerLeftContentContainerProps}
|
||||
leftContent={headerLeftContent}
|
||||
rightContentContainerProps={headerRightContentContainerProps}
|
||||
rightContent={headerRightContent}
|
||||
style={{
|
||||
...titleStyle,
|
||||
...(!isNarrowWidth && { paddingInline: HORIZONTAL_PADDING }),
|
||||
...(!isNarrowWidth && { paddingInline: _padding }),
|
||||
...headerStyle,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
isNarrowWidth
|
||||
? { overflowY: 'auto', padding: HORIZONTAL_PADDING }
|
||||
: {
|
||||
paddingLeft: HORIZONTAL_PADDING,
|
||||
paddingRight: HORIZONTAL_PADDING,
|
||||
flex: 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
{isNarrowWidth ? (
|
||||
<View
|
||||
{...childrenContainerProps}
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
padding: _padding,
|
||||
...childrenContainerProps?.style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
{...childrenContainerProps}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingLeft: _padding,
|
||||
paddingRight: _padding,
|
||||
...childrenContainerProps?.style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
)}
|
||||
{footer}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ type SyncButtonProps = {
|
||||
style?: CSSProperties;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
export function SyncButton({ style, isMobile = false }: SyncButtonProps) {
|
||||
function SyncButton({ style, isMobile = false }: SyncButtonProps) {
|
||||
let cloudFileId = useSelector(state => state.prefs.local.cloudFileId);
|
||||
let { sync } = useActions();
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useActions } from '../../hooks/useActions';
|
||||
import Add from '../../icons/v1/Add';
|
||||
import CheveronLeft from '../../icons/v1/CheveronLeft';
|
||||
import SearchAlternate from '../../icons/v2/SearchAlternate';
|
||||
import { theme, styles } from '../../style';
|
||||
import Button from '../common/Button';
|
||||
import { theme } from '../../style';
|
||||
import ButtonLink from '../common/ButtonLink';
|
||||
import InputWithContent from '../common/InputWithContent';
|
||||
import Label from '../common/Label';
|
||||
import Text from '../common/Text';
|
||||
import View from '../common/View';
|
||||
import MobileBackButton from '../MobileBackButton';
|
||||
import { Page } from '../Page';
|
||||
import PullToRefresh from '../responsive/PullToRefresh';
|
||||
import CellValue from '../spreadsheet/CellValue';
|
||||
import { TransactionList } from '../transactions/MobileTransaction';
|
||||
@@ -63,9 +61,6 @@ function TransactionSearchInput({ accountName, onSearch }) {
|
||||
);
|
||||
}
|
||||
|
||||
const LEFT_RIGHT_FLEX_WIDTH = 70;
|
||||
const BUDGET_HEADER_HEIGHT = 50;
|
||||
|
||||
export default function AccountDetails({
|
||||
account,
|
||||
prependTransactions,
|
||||
@@ -90,24 +85,42 @@ export default function AccountDetails({
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
<Page
|
||||
title={account.name}
|
||||
headerLeftContent={<MobileBackButton />}
|
||||
headerRightContent={
|
||||
<ButtonLink
|
||||
to="transactions/new"
|
||||
type="bare"
|
||||
aria-label="Add Transaction"
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
color: theme.mobileHeaderText,
|
||||
margin: 10,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
activeStyle={{ background: 'transparent' }}
|
||||
>
|
||||
<Add width={20} height={20} />
|
||||
</ButtonLink>
|
||||
}
|
||||
padding={0}
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.mobilePageBackground,
|
||||
overflowY: 'hidden',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
overflowY: 'hidden',
|
||||
top: 0,
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<AccountDetailsHeader account={account} />
|
||||
<Label title="BALANCE" style={{ marginTop: 10 }} />
|
||||
<Label title="BALANCE" />
|
||||
<CellValue
|
||||
binding={balance}
|
||||
type="financial"
|
||||
@@ -125,7 +138,6 @@ export default function AccountDetails({
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<PullToRefresh onRefresh={onRefresh}>
|
||||
<TransactionList
|
||||
transactions={allTransactions}
|
||||
@@ -139,115 +151,6 @@ export default function AccountDetails({
|
||||
pushModal={pushModal}
|
||||
/>
|
||||
</PullToRefresh>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountDetailsHeader({ account }) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
height: BUDGET_HEADER_HEIGHT,
|
||||
width: '100%',
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
color: theme.mobileHeaderText,
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
paddingLeft: 5,
|
||||
paddingRight: 3,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={-1}
|
||||
style={{
|
||||
...styles.noTapHighlight,
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<CheveronLeft
|
||||
style={{ width: 30, height: 30, margin: -10, marginLeft: -5 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
...styles.text,
|
||||
fontWeight: 500,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Text>
|
||||
</Link>
|
||||
</Button>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
role="heading"
|
||||
>
|
||||
{account.name}
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<ButtonLink
|
||||
to="transactions/new"
|
||||
type="bare"
|
||||
aria-label="Add Transaction"
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
activeStyle={{ background: 'transparent' }}
|
||||
>
|
||||
<Add width={20} height={20} style={{ margin: -5 }} />
|
||||
</ButtonLink>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,32 +158,28 @@ function AccountList({
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.mobilePageBackground }}>
|
||||
<Page
|
||||
title="Accounts"
|
||||
titleStyle={{
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
color: theme.mobileHeaderText,
|
||||
fontSize: 16,
|
||||
}}
|
||||
headerRightContent={
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
...noBackgroundColorStyle,
|
||||
}}
|
||||
activeStyle={noBackgroundColorStyle}
|
||||
hoveredStyle={noBackgroundColorStyle}
|
||||
onClick={onAddAccount}
|
||||
>
|
||||
<Add width={20} height={20} />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{accounts.length === 0 && <EmptyMessage />}
|
||||
<PullToRefresh onRefresh={onSync}>
|
||||
<Page
|
||||
title="Accounts"
|
||||
headerRightContent={
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
...noBackgroundColorStyle,
|
||||
margin: 10,
|
||||
}}
|
||||
activeStyle={noBackgroundColorStyle}
|
||||
hoveredStyle={noBackgroundColorStyle}
|
||||
onClick={onAddAccount}
|
||||
>
|
||||
<Add width={20} height={20} />
|
||||
</Button>
|
||||
}
|
||||
padding={0}
|
||||
style={{ flex: 1, backgroundColor: theme.mobilePageBackground }}
|
||||
>
|
||||
{accounts.length === 0 && <EmptyMessage />}
|
||||
<PullToRefresh onRefresh={onSync}>
|
||||
<View style={{ margin: 10 }}>
|
||||
{budgetedAccounts.length > 0 && (
|
||||
<AccountHeader name="For Budget" amount={getOnBudgetBalance()} />
|
||||
)}
|
||||
@@ -213,9 +209,9 @@ function AccountList({
|
||||
onSelect={onSelectAccount}
|
||||
/>
|
||||
))}
|
||||
</PullToRefresh>
|
||||
</Page>
|
||||
</View>
|
||||
</View>
|
||||
</PullToRefresh>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,13 +19,12 @@ import Label from '../common/Label';
|
||||
import Menu from '../common/Menu';
|
||||
import Text from '../common/Text';
|
||||
import View from '../common/View';
|
||||
import { Page } from '../Page';
|
||||
import PullToRefresh from '../responsive/PullToRefresh';
|
||||
import { useServerURL } from '../ServerContext';
|
||||
import CellValue from '../spreadsheet/CellValue';
|
||||
import NamespaceContext from '../spreadsheet/NamespaceContext';
|
||||
import useFormat from '../spreadsheet/useFormat';
|
||||
import useSheetValue from '../spreadsheet/useSheetValue';
|
||||
import { SyncButton } from '../Titlebar';
|
||||
import { Tooltip, useTooltip } from '../tooltips';
|
||||
import { AmountInput } from '../util/AmountInput';
|
||||
// import {
|
||||
@@ -1695,7 +1694,6 @@ export function BudgetTable(props) {
|
||||
const show3Cols = width >= 360;
|
||||
|
||||
// let editMode = false; // neuter editMode -- sorry, not rewriting drag-n-drop right now
|
||||
let currentMonth = monthUtils.currentMonth();
|
||||
let format = useFormat();
|
||||
|
||||
const mobileShowBudgetedColPref = useSelector(state => {
|
||||
@@ -1724,27 +1722,62 @@ export function BudgetTable(props) {
|
||||
borderRadius: 'unset',
|
||||
};
|
||||
|
||||
const _onSwitchBudgetType = () => {
|
||||
pushModal('switch-budget-type', {
|
||||
onSwitch: onSwitchBudgetType,
|
||||
});
|
||||
};
|
||||
|
||||
const onToggleHiddenCategories = () => {
|
||||
savePrefs({
|
||||
'budget.showHiddenCategories': !showHiddenCategories,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month, type)}>
|
||||
<View style={{ flex: 1, overflowY: 'hidden' }} data-testid="budget-table">
|
||||
<BudgetHeader
|
||||
currentMonth={month}
|
||||
toggleDisplay={toggleDisplay}
|
||||
monthBounds={monthBounds}
|
||||
editMode={editMode}
|
||||
onEditMode={onEditMode}
|
||||
// onOpenActionSheet={onOpenActionSheet}
|
||||
onPrevMonth={onPrevMonth}
|
||||
onNextMonth={onNextMonth}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
savePrefs={savePrefs}
|
||||
pushModal={pushModal}
|
||||
onSwitchBudgetType={onSwitchBudgetType}
|
||||
/>
|
||||
<Page
|
||||
padding={0}
|
||||
title={
|
||||
<MonthSelector
|
||||
month={month}
|
||||
monthBounds={monthBounds}
|
||||
onPrevMonth={onPrevMonth}
|
||||
onNextMonth={onNextMonth}
|
||||
/>
|
||||
}
|
||||
headerRightContent={
|
||||
!editMode ? (
|
||||
<BudgetMenu
|
||||
onEditMode={onEditMode}
|
||||
onToggleHiddenCategories={onToggleHiddenCategories}
|
||||
onSwitchBudgetType={_onSwitchBudgetType}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
type="bare"
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
style={{
|
||||
...styles.noTapHighlight,
|
||||
...styles.text,
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
onClick={() => onEditMode?.(false)}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flex: '0 0 auto',
|
||||
flexShrink: 0,
|
||||
padding: 10,
|
||||
paddingRight: 14,
|
||||
backgroundColor: theme.tableRowHeaderBackground,
|
||||
@@ -1754,7 +1787,7 @@ export function BudgetTable(props) {
|
||||
>
|
||||
{type === 'report' ? (
|
||||
<Saved
|
||||
projected={month >= currentMonth}
|
||||
projected={month >= monthUtils.currentMonth()}
|
||||
onClick={onShowBudgetSummary}
|
||||
/>
|
||||
) : (
|
||||
@@ -1873,123 +1906,95 @@ export function BudgetTable(props) {
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ overflowY: 'auto' }}>
|
||||
<PullToRefresh onRefresh={onRefresh}>
|
||||
{!editMode ? (
|
||||
// <ScrollView
|
||||
// ref={el => (this.list = el)}
|
||||
// keyboardShouldPersistTaps="always"
|
||||
// refreshControl={refreshControl}
|
||||
// style={{ backgroundColor: colors.n10 }}
|
||||
// automaticallyAdjustContentInsets={false}
|
||||
// >
|
||||
<View>
|
||||
<BudgetGroups
|
||||
type={type}
|
||||
categoryGroups={categoryGroups}
|
||||
showBudgetedCol={showBudgetedCol}
|
||||
show3Cols={show3Cols}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
// gestures={gestures}
|
||||
month={month}
|
||||
editMode={editMode}
|
||||
editingGroupId={editingGroupId}
|
||||
onEditGroup={onEditGroup}
|
||||
editingCategoryId={editingCategoryId}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
openBudgetActionMenuId={openBudgetActionMenuId}
|
||||
onOpenBudgetActionMenu={onOpenBudgetActionMenu}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
onAddGroup={onAddGroup}
|
||||
onSaveGroup={onSaveGroup}
|
||||
onDeleteGroup={onDeleteGroup}
|
||||
onReorderCategory={onReorderCategory}
|
||||
onReorderGroup={onReorderGroup}
|
||||
onBudgetAction={onBudgetAction}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
// </ScrollView>
|
||||
// <DragDrop>
|
||||
// {({
|
||||
// dragging,
|
||||
// onGestureEvent,
|
||||
// onHandlerStateChange,
|
||||
// scrollRef,
|
||||
// onScroll
|
||||
// }) => (
|
||||
<View>
|
||||
<BudgetGroups
|
||||
type={type}
|
||||
categoryGroups={categoryGroups}
|
||||
showBudgetedCol={showBudgetedCol}
|
||||
show3Cols={show3Cols}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
// gestures={gestures}
|
||||
editMode={editMode}
|
||||
editingGroupId={editingGroupId}
|
||||
onEditGroup={onEditGroup}
|
||||
editingCategoryId={editingCategoryId}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
onAddGroup={onAddGroup}
|
||||
onSaveGroup={onSaveGroup}
|
||||
onDeleteGroup={onDeleteGroup}
|
||||
onReorderCategory={onReorderCategory}
|
||||
onReorderGroup={onReorderGroup}
|
||||
onBudgetAction={onBudgetAction}
|
||||
/>
|
||||
</View>
|
||||
<PullToRefresh onRefresh={onRefresh}>
|
||||
{!editMode ? (
|
||||
// <ScrollView
|
||||
// ref={el => (this.list = el)}
|
||||
// keyboardShouldPersistTaps="always"
|
||||
// refreshControl={refreshControl}
|
||||
// style={{ backgroundColor: colors.n10 }}
|
||||
// automaticallyAdjustContentInsets={false}
|
||||
// >
|
||||
<View data-testid="budget-table">
|
||||
<BudgetGroups
|
||||
type={type}
|
||||
categoryGroups={categoryGroups}
|
||||
showBudgetedCol={showBudgetedCol}
|
||||
show3Cols={show3Cols}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
// gestures={gestures}
|
||||
month={month}
|
||||
editMode={editMode}
|
||||
editingGroupId={editingGroupId}
|
||||
onEditGroup={onEditGroup}
|
||||
editingCategoryId={editingCategoryId}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
openBudgetActionMenuId={openBudgetActionMenuId}
|
||||
onOpenBudgetActionMenu={onOpenBudgetActionMenu}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
onAddGroup={onAddGroup}
|
||||
onSaveGroup={onSaveGroup}
|
||||
onDeleteGroup={onDeleteGroup}
|
||||
onReorderCategory={onReorderCategory}
|
||||
onReorderGroup={onReorderGroup}
|
||||
onBudgetAction={onBudgetAction}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
// </ScrollView>
|
||||
// <DragDrop>
|
||||
// {({
|
||||
// dragging,
|
||||
// onGestureEvent,
|
||||
// onHandlerStateChange,
|
||||
// scrollRef,
|
||||
// onScroll
|
||||
// }) => (
|
||||
<View>
|
||||
<BudgetGroups
|
||||
type={type}
|
||||
categoryGroups={categoryGroups}
|
||||
showBudgetedCol={showBudgetedCol}
|
||||
show3Cols={show3Cols}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
// gestures={gestures}
|
||||
editMode={editMode}
|
||||
editingGroupId={editingGroupId}
|
||||
onEditGroup={onEditGroup}
|
||||
editingCategoryId={editingCategoryId}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
onAddGroup={onAddGroup}
|
||||
onSaveGroup={onSaveGroup}
|
||||
onDeleteGroup={onDeleteGroup}
|
||||
onReorderCategory={onReorderCategory}
|
||||
onReorderGroup={onReorderGroup}
|
||||
onBudgetAction={onBudgetAction}
|
||||
/>
|
||||
</View>
|
||||
|
||||
// <DragDropHighlight />
|
||||
// </DragDrop>
|
||||
)}
|
||||
</PullToRefresh>
|
||||
</View>
|
||||
</View>
|
||||
// <DragDropHighlight />
|
||||
// </DragDrop>
|
||||
)}
|
||||
</PullToRefresh>
|
||||
</Page>
|
||||
</NamespaceContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const LEFT_RIGHT_FLEX_WIDTH = 80;
|
||||
const BUDGET_HEADER_HEIGHT = 50;
|
||||
|
||||
function BudgetHeader({
|
||||
currentMonth,
|
||||
monthBounds,
|
||||
onPrevMonth,
|
||||
onNextMonth,
|
||||
editMode,
|
||||
function BudgetMenu({
|
||||
onEditMode,
|
||||
showHiddenCategories,
|
||||
savePrefs,
|
||||
pushModal,
|
||||
onToggleHiddenCategories,
|
||||
onSwitchBudgetType,
|
||||
}) {
|
||||
let serverURL = useServerURL();
|
||||
|
||||
let prevEnabled = currentMonth > monthBounds.start;
|
||||
let nextEnabled = currentMonth < monthUtils.subMonths(monthBounds.end, 1);
|
||||
|
||||
let buttonStyle = {
|
||||
padding: 10,
|
||||
margin: 2,
|
||||
};
|
||||
|
||||
let toggleHiddenCategories = () => {
|
||||
savePrefs({
|
||||
'budget.showHiddenCategories': !showHiddenCategories,
|
||||
});
|
||||
};
|
||||
|
||||
let tooltip = useTooltip();
|
||||
let isReportBudgetEnabled = useFeatureFlag('reportBudget');
|
||||
|
||||
@@ -2000,181 +2005,123 @@ function BudgetHeader({
|
||||
onEditMode?.(true);
|
||||
break;
|
||||
case 'toggle-hidden-categories':
|
||||
toggleHiddenCategories();
|
||||
onToggleHiddenCategories?.();
|
||||
break;
|
||||
case 'switch-budget-type':
|
||||
pushModal('switch-budget-type', {
|
||||
onSwitch: onSwitchBudgetType,
|
||||
});
|
||||
onSwitchBudgetType?.();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unrecognized menu option: ${name}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
...styles.noTapHighlight,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
{...tooltip.getOpenEvents()}
|
||||
>
|
||||
<DotsHorizontalTriple
|
||||
width="20"
|
||||
height="20"
|
||||
style={{ color: theme.mobileHeaderText }}
|
||||
/>
|
||||
</Button>
|
||||
{tooltip.isOpen && (
|
||||
<Tooltip
|
||||
position="bottom-right"
|
||||
width={250}
|
||||
style={{ padding: 0 }}
|
||||
onClose={tooltip.close}
|
||||
>
|
||||
<Menu
|
||||
onMenuSelect={onMenuSelect}
|
||||
items={[
|
||||
{ name: 'edit-mode', text: 'Edit mode' },
|
||||
{
|
||||
name: 'toggle-hidden-categories',
|
||||
text: 'Toggle hidden categories',
|
||||
},
|
||||
isReportBudgetEnabled && {
|
||||
name: 'switch-budget-type',
|
||||
text: 'Switch budget type',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MonthSelector({ month, monthBounds, onPrevMonth, onNextMonth }) {
|
||||
let prevEnabled = month > monthBounds.start;
|
||||
let nextEnabled = month < monthUtils.subMonths(monthBounds.end, 1);
|
||||
|
||||
let arrowButtonStyle = {
|
||||
padding: 10,
|
||||
margin: 2,
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
height: BUDGET_HEADER_HEIGHT,
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
<Button
|
||||
type="bare"
|
||||
onClick={prevEnabled && onPrevMonth}
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
...styles.noTapHighlight,
|
||||
...arrowButtonStyle,
|
||||
opacity: prevEnabled ? 1 : 0.6,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
{serverURL && (
|
||||
<SyncButton
|
||||
isMobile
|
||||
style={{
|
||||
color: theme.mobileHeaderText,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
<ArrowThinLeft width="15" height="15" style={{ margin: -5 }} />
|
||||
</Button>
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
color: theme.mobileHeaderText,
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="bare"
|
||||
// hitSlop={{ top: 5, bottom: 5, left: 0, right: 30 }}
|
||||
onClick={prevEnabled && onPrevMonth}
|
||||
style={{
|
||||
...buttonStyle,
|
||||
opacity: prevEnabled ? 1 : 0.6,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
<ArrowThinLeft width="15" height="15" style={{ margin: -5 }} />
|
||||
</Button>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.mobileHeaderText,
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
// zIndex: -1
|
||||
}}
|
||||
>
|
||||
{/* eslint-disable-next-line rulesdir/typography */}
|
||||
{monthUtils.format(currentMonth, "MMMM ''yy")}
|
||||
</Text>
|
||||
<Button
|
||||
type="bare"
|
||||
onClick={nextEnabled && onNextMonth}
|
||||
// hitSlop={{ top: 5, bottom: 5, left: 30, right: 5 }}
|
||||
style={{
|
||||
...buttonStyle,
|
||||
opacity: nextEnabled ? 1 : 0.6,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
<ArrowThinRight width="15" height="15" style={{ margin: -5 }} />
|
||||
</Button>
|
||||
</View>
|
||||
<View
|
||||
{/* eslint-disable-next-line rulesdir/typography */}
|
||||
{monthUtils.format(month, "MMMM ''yy")}
|
||||
</Text>
|
||||
<Button
|
||||
type="bare"
|
||||
onClick={nextEnabled && onNextMonth}
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
...styles.noTapHighlight,
|
||||
...arrowButtonStyle,
|
||||
opacity: nextEnabled ? 1 : 0.6,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
{!editMode ? (
|
||||
<>
|
||||
<Button
|
||||
type="bare"
|
||||
aria-label="Menu"
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
style={{
|
||||
paddingTop: 15,
|
||||
paddingBottom: 15,
|
||||
margin: 10,
|
||||
}}
|
||||
{...tooltip.getOpenEvents()}
|
||||
>
|
||||
<DotsHorizontalTriple
|
||||
width="20"
|
||||
height="20"
|
||||
style={{ color: theme.mobileHeaderText }}
|
||||
/>
|
||||
</Button>
|
||||
{tooltip.isOpen && (
|
||||
<Tooltip
|
||||
position="bottom-right"
|
||||
width={200}
|
||||
style={{ padding: 0 }}
|
||||
onClose={tooltip.close}
|
||||
>
|
||||
<Menu
|
||||
onMenuSelect={onMenuSelect}
|
||||
items={[
|
||||
{ name: 'edit-mode', text: 'Edit mode' },
|
||||
{
|
||||
name: 'toggle-hidden-categories',
|
||||
text: 'Toggle hidden categories',
|
||||
},
|
||||
isReportBudgetEnabled && {
|
||||
name: 'switch-budget-type',
|
||||
text: 'Switch budget type',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
type="bare"
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
padding: 10,
|
||||
paddingTop: 15,
|
||||
paddingBottom: 15,
|
||||
margin: 10,
|
||||
...styles.text,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
onClick={() => onEditMode?.(false)}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
<ArrowThinRight width="15" height="15" style={{ margin: -5 }} />
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ export const Setting = ({ primaryAction, style, children }: SettingProps) => {
|
||||
<View
|
||||
style={{
|
||||
marginBottom: primaryAction ? 10 : 0,
|
||||
maxWidth: 500,
|
||||
lineHeight: 1.5,
|
||||
gap: 10,
|
||||
}}
|
||||
|
||||
@@ -137,61 +137,48 @@ export default function Settings() {
|
||||
|
||||
useSetThemeColor(theme.mobileViewTheme);
|
||||
return (
|
||||
<View
|
||||
<Page
|
||||
title="Settings"
|
||||
style={{
|
||||
backgroundColor: isNarrowWidth && theme.mobilePageBackground,
|
||||
marginInline: floatingSidebar && !isNarrowWidth ? 'auto' : 0,
|
||||
}}
|
||||
>
|
||||
<Page
|
||||
title="Settings"
|
||||
titleStyle={
|
||||
isNarrowWidth
|
||||
? {
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
color: theme.mobileHeaderText,
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<View style={{ flexShrink: 0, gap: 30 }}>
|
||||
{isNarrowWidth && (
|
||||
<View
|
||||
style={{ gap: 10, flexDirection: 'row', alignItems: 'flex-end' }}
|
||||
>
|
||||
{/* The only spot to close a budget on mobile */}
|
||||
<FormField>
|
||||
<FormLabel title="Budget Name" />
|
||||
<Input
|
||||
value={budgetName}
|
||||
disabled
|
||||
style={{ color: theme.buttonNormalDisabledText }}
|
||||
/>
|
||||
</FormField>
|
||||
<Button onClick={closeBudget}>Close Budget</Button>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ flexShrink: 0, maxWidth: 530, gap: 30 }}>
|
||||
{isNarrowWidth && (
|
||||
<View
|
||||
style={{ gap: 10, flexDirection: 'row', alignItems: 'flex-end' }}
|
||||
>
|
||||
{/* The only spot to close a budget on mobile */}
|
||||
<FormField>
|
||||
<FormLabel title="Budget Name" />
|
||||
<Input
|
||||
value={budgetName}
|
||||
disabled
|
||||
style={{ color: theme.buttonNormalDisabledText }}
|
||||
/>
|
||||
</FormField>
|
||||
<Button onClick={closeBudget}>Close Budget</Button>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<About />
|
||||
<About />
|
||||
|
||||
{!Platform.isBrowser && <GlobalSettings />}
|
||||
{!Platform.isBrowser && <GlobalSettings />}
|
||||
|
||||
<ThemeSettings />
|
||||
<FormatSettings />
|
||||
<EncryptionSettings />
|
||||
<ExportBudget />
|
||||
<ThemeSettings />
|
||||
<FormatSettings />
|
||||
<EncryptionSettings />
|
||||
<ExportBudget />
|
||||
|
||||
<AdvancedToggle>
|
||||
<AdvancedAbout />
|
||||
<ResetCache />
|
||||
<ResetSync />
|
||||
<FixSplitsTool />
|
||||
<ExperimentalFeatures />
|
||||
</AdvancedToggle>
|
||||
</View>
|
||||
</Page>
|
||||
</View>
|
||||
<AdvancedToggle>
|
||||
<AdvancedAbout />
|
||||
<ResetCache />
|
||||
<ResetSync />
|
||||
<FixSplitsTool />
|
||||
<ExperimentalFeatures />
|
||||
</AdvancedToggle>
|
||||
</View>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import React, {
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useFocusRing } from '@react-aria/focus';
|
||||
import { useListBox, useListBoxSection, useOption } from '@react-aria/listbox';
|
||||
@@ -48,7 +48,6 @@ import useCategories from '../../hooks/useCategories';
|
||||
import useNavigate from '../../hooks/useNavigate';
|
||||
import { useSetThemeColor } from '../../hooks/useSetThemeColor';
|
||||
import SvgAdd from '../../icons/v1/Add';
|
||||
import CheveronLeft from '../../icons/v1/CheveronLeft';
|
||||
import SvgTrash from '../../icons/v1/Trash';
|
||||
import ArrowsSynchronize from '../../icons/v2/ArrowsSynchronize';
|
||||
import CheckCircle1 from '../../icons/v2/CheckCircle1';
|
||||
@@ -66,6 +65,8 @@ import {
|
||||
InputField,
|
||||
BooleanField,
|
||||
} from '../mobile/MobileForms';
|
||||
import MobileBackButton from '../MobileBackButton';
|
||||
import { Page } from '../Page';
|
||||
|
||||
const zIndices = { SECTION_HEADING: 10 };
|
||||
|
||||
@@ -165,9 +166,6 @@ function Status({ status }) {
|
||||
);
|
||||
}
|
||||
|
||||
const LEFT_RIGHT_FLEX_WIDTH = 70;
|
||||
const BUDGET_HEADER_HEIGHT = 50;
|
||||
|
||||
class TransactionEditInner extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -325,9 +323,7 @@ class TransactionEditInner extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { adding, categories, accounts, payees, renderChildEdit, navigate } =
|
||||
this.props;
|
||||
const { editingChild } = this.state;
|
||||
const { adding, categories, accounts, payees } = this.props;
|
||||
const transactions = this.serializeTransactions(
|
||||
this.state.transactions || [],
|
||||
);
|
||||
@@ -336,7 +332,7 @@ class TransactionEditInner extends PureComponent {
|
||||
|
||||
// Child transactions should always default to the signage
|
||||
// of the parent transaction
|
||||
const forcedSign = transaction.amount < 0 ? 'negative' : 'positive';
|
||||
// const forcedSign = transaction.amount < 0 ? 'negative' : 'positive';
|
||||
|
||||
const account = getAccountsById(accounts)[accountId];
|
||||
const isOffBudget = account && !!account.offbudget;
|
||||
@@ -360,350 +356,24 @@ class TransactionEditInner extends PureComponent {
|
||||
const dateDefaultValue = monthUtils.dayFromDate(transactionDate);
|
||||
|
||||
return (
|
||||
// <KeyboardAvoidingView>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.mobilePageBackground,
|
||||
flexGrow: 1,
|
||||
|
||||
// This shadow make the card "pop" off of the screen below
|
||||
// it
|
||||
shadowColor: theme.cardShadow,
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowRadius: 4,
|
||||
shadowOpacity: 1,
|
||||
<Page
|
||||
title={
|
||||
payeeId == null
|
||||
? adding
|
||||
? 'New Transaction'
|
||||
: 'Transaction'
|
||||
: descriptionPretty
|
||||
}
|
||||
titleStyle={{
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
height: BUDGET_HEADER_HEIGHT,
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
...styles.noTapHighlight,
|
||||
color: theme.mobileHeaderText,
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
paddingLeft: 5,
|
||||
paddingRight: 3,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={-1}
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<CheveronLeft
|
||||
style={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
margin: -10,
|
||||
marginLeft: -5,
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
...styles.text,
|
||||
fontWeight: 500,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Text>
|
||||
</Link>
|
||||
</Button>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
>
|
||||
<TextOneLine
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
userSelect: 'none',
|
||||
}}
|
||||
role="heading"
|
||||
>
|
||||
{payeeId == null
|
||||
? adding
|
||||
? 'New Transaction'
|
||||
: 'Transaction'
|
||||
: descriptionPretty}
|
||||
</TextOneLine>
|
||||
</View>
|
||||
{/* For centering the transaction title */}
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* <ScrollView
|
||||
ref={el => (this.scrollView = el)}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
keyboardShouldPersistTaps="always"
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
> */}
|
||||
<View
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
marginTop: 20,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
title="Amount"
|
||||
flush
|
||||
style={{ marginBottom: 0, paddingLeft: 0 }}
|
||||
/>
|
||||
<FocusableAmountInput
|
||||
ref={el => (this.amount = el)}
|
||||
value={transaction.amount}
|
||||
zeroIsNegative={true}
|
||||
onBlur={value =>
|
||||
this.onEdit(transaction, 'amount', value.toString())
|
||||
}
|
||||
onChange={value =>
|
||||
this.onQueueChange(transaction, 'amount', value)
|
||||
}
|
||||
style={{ transform: [] }}
|
||||
focusedStyle={{
|
||||
width: 'auto',
|
||||
padding: '5px',
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '20px',
|
||||
minWidth: 120,
|
||||
transform: [{ translateY: -0.5 }],
|
||||
}}
|
||||
textStyle={{ fontSize: 30, textAlign: 'center' }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel title="Payee" />
|
||||
<TapField
|
||||
value={descriptionPretty}
|
||||
onClick={() => this.onClick(transaction.id, 'payee')}
|
||||
data-testid="payee-field"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel
|
||||
title={
|
||||
transaction.is_parent ? 'Categories (split)' : 'Category'
|
||||
}
|
||||
/>
|
||||
{!transaction.is_parent ? (
|
||||
<TapField
|
||||
style={{
|
||||
...((isBudgetTransfer || isOffBudget) && {
|
||||
fontStyle: 'italic',
|
||||
color: theme.pageTextSubdued,
|
||||
fontWeight: 300,
|
||||
}),
|
||||
}}
|
||||
value={
|
||||
isOffBudget
|
||||
? 'Off Budget'
|
||||
: isBudgetTransfer
|
||||
? 'Transfer'
|
||||
: lookupName(categories, category)
|
||||
}
|
||||
disabled={isBudgetTransfer || isOffBudget}
|
||||
// TODO: the button to turn this transaction into a split
|
||||
// transaction was on top of the category button in the native
|
||||
// app, on the right-hand side
|
||||
//
|
||||
// On the web this doesn't work well and react gets upset if
|
||||
// nest a button in a button.
|
||||
//
|
||||
// rightContent={
|
||||
// <Button
|
||||
// contentStyle={{
|
||||
// paddingVertical: 4,
|
||||
// paddingHorizontal: 15,
|
||||
// margin: 0,
|
||||
// }}
|
||||
// onPress={this.onSplit}
|
||||
// >
|
||||
// Split
|
||||
// </Button>
|
||||
// }
|
||||
onClick={() => this.onClick(transaction.id, 'category')}
|
||||
data-testid="category-field"
|
||||
/>
|
||||
) : (
|
||||
<Text style={{ paddingLeft: styles.mobileEditingPadding }}>
|
||||
Split transaction editing is not supported on mobile at this
|
||||
time.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel title="Account" />
|
||||
<TapField
|
||||
disabled={!adding}
|
||||
value={account ? account.name : null}
|
||||
onClick={() => this.onClick(transaction.id, 'account')}
|
||||
data-testid="account-field"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<FieldLabel title="Date" />
|
||||
<InputField
|
||||
type="date"
|
||||
required
|
||||
style={{ color: theme.tableText, minWidth: '150px' }}
|
||||
defaultValue={dateDefaultValue}
|
||||
onUpdate={value =>
|
||||
this.onEdit(
|
||||
transaction,
|
||||
'date',
|
||||
formatDate(parseISO(value), this.props.dateFormat),
|
||||
)
|
||||
}
|
||||
onChange={e =>
|
||||
this.onQueueChange(
|
||||
transaction,
|
||||
'date',
|
||||
formatDate(
|
||||
parseISO(e.target.value),
|
||||
this.props.dateFormat,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
{transaction.reconciled ? (
|
||||
<View style={{ marginLeft: 0, marginRight: 8 }}>
|
||||
<FieldLabel title="Reconciled" />
|
||||
<BooleanField
|
||||
checked
|
||||
style={{
|
||||
margin: 'auto',
|
||||
width: 22,
|
||||
height: 22,
|
||||
}}
|
||||
disabled
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ marginLeft: 0, marginRight: 8 }}>
|
||||
<FieldLabel title="Cleared" />
|
||||
<BooleanField
|
||||
checked={transaction.cleared}
|
||||
onUpdate={checked =>
|
||||
this.onEdit(transaction, 'cleared', checked)
|
||||
}
|
||||
style={{
|
||||
margin: 'auto',
|
||||
width: 22,
|
||||
height: 22,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel title="Notes" />
|
||||
<InputField
|
||||
defaultValue={transaction.notes}
|
||||
onUpdate={value => this.onEdit(transaction, 'notes', value)}
|
||||
onChange={e =>
|
||||
this.onQueueChange(transaction, 'notes', e.target.value)
|
||||
}
|
||||
style={{ marginBottom: 10 }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!adding && (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<Button
|
||||
onClick={() => this.onDelete()}
|
||||
style={{
|
||||
height: 40,
|
||||
borderWidth: 0,
|
||||
paddingVertical: 5,
|
||||
marginLeft: styles.mobileEditingPadding,
|
||||
marginRight: styles.mobileEditingPadding,
|
||||
marginTop: 10,
|
||||
marginBottom: 15,
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
type="bare"
|
||||
>
|
||||
<SvgTrash
|
||||
width={17}
|
||||
height={17}
|
||||
style={{ color: theme.errorText }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.errorText,
|
||||
marginLeft: 5,
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
Delete transaction
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.mobilePageBackground,
|
||||
}}
|
||||
headerLeftContent={<MobileBackButton />}
|
||||
footer={
|
||||
<View
|
||||
style={{
|
||||
paddingLeft: styles.mobileEditingPadding,
|
||||
@@ -713,8 +383,6 @@ class TransactionEditInner extends PureComponent {
|
||||
backgroundColor: theme.tableHeaderBackground,
|
||||
borderTopWidth: 1,
|
||||
borderColor: theme.tableBorder,
|
||||
marginTop: 'auto',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{adding ? (
|
||||
@@ -737,7 +405,11 @@ class TransactionEditInner extends PureComponent {
|
||||
) : (
|
||||
<Button style={{ height: 40 }} onClick={() => this.onSave()}>
|
||||
<SvgPencilWriteAlternate
|
||||
style={{ width: 16, height: 16, color: theme.formInputText }}
|
||||
style={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: theme.formInputText,
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
@@ -751,27 +423,212 @@ class TransactionEditInner extends PureComponent {
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* <ExitTransition
|
||||
alive={editingChild}
|
||||
withProps={{
|
||||
transaction:
|
||||
editingChild && transactions.find(t => t.id === editingChild),
|
||||
}
|
||||
padding={0}
|
||||
>
|
||||
<View style={{ flexShrink: 0, marginTop: 20, marginBottom: 20 }}>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
> */}
|
||||
{renderChildEdit({
|
||||
transaction:
|
||||
editingChild && transactions.find(t => t.id === editingChild),
|
||||
amountSign: forcedSign,
|
||||
getCategoryName: id => lookupName(categories, id),
|
||||
navigate,
|
||||
onEdit: this.onEdit,
|
||||
onStartClose: this.onSaveChild,
|
||||
})}
|
||||
{/* </ExitTransition> */}
|
||||
>
|
||||
<FieldLabel
|
||||
title="Amount"
|
||||
flush
|
||||
style={{ marginBottom: 0, paddingLeft: 0 }}
|
||||
/>
|
||||
<FocusableAmountInput
|
||||
ref={el => (this.amount = el)}
|
||||
value={transaction.amount}
|
||||
zeroIsNegative={true}
|
||||
onBlur={value =>
|
||||
this.onEdit(transaction, 'amount', value.toString())
|
||||
}
|
||||
onChange={value =>
|
||||
this.onQueueChange(transaction, 'amount', value)
|
||||
}
|
||||
style={{ transform: [] }}
|
||||
focusedStyle={{
|
||||
width: 'auto',
|
||||
padding: '5px',
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '20px',
|
||||
minWidth: 120,
|
||||
transform: [{ translateY: -0.5 }],
|
||||
}}
|
||||
textStyle={{ fontSize: 30, textAlign: 'center' }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel title="Payee" />
|
||||
<TapField
|
||||
value={descriptionPretty}
|
||||
onClick={() => this.onClick(transaction.id, 'payee')}
|
||||
data-testid="payee-field"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel
|
||||
title={transaction.is_parent ? 'Categories (split)' : 'Category'}
|
||||
/>
|
||||
{!transaction.is_parent ? (
|
||||
<TapField
|
||||
style={{
|
||||
...((isBudgetTransfer || isOffBudget) && {
|
||||
fontStyle: 'italic',
|
||||
color: theme.pageTextSubdued,
|
||||
fontWeight: 300,
|
||||
}),
|
||||
}}
|
||||
value={
|
||||
isOffBudget
|
||||
? 'Off Budget'
|
||||
: isBudgetTransfer
|
||||
? 'Transfer'
|
||||
: lookupName(categories, category)
|
||||
}
|
||||
disabled={isBudgetTransfer || isOffBudget}
|
||||
// TODO: the button to turn this transaction into a split
|
||||
// transaction was on top of the category button in the native
|
||||
// app, on the right-hand side
|
||||
//
|
||||
// On the web this doesn't work well and react gets upset if
|
||||
// nest a button in a button.
|
||||
//
|
||||
// rightContent={
|
||||
// <Button
|
||||
// contentStyle={{
|
||||
// paddingVertical: 4,
|
||||
// paddingHorizontal: 15,
|
||||
// margin: 0,
|
||||
// }}
|
||||
// onPress={this.onSplit}
|
||||
// >
|
||||
// Split
|
||||
// </Button>
|
||||
// }
|
||||
onClick={() => this.onClick(transaction.id, 'category')}
|
||||
data-testid="category-field"
|
||||
/>
|
||||
) : (
|
||||
<Text style={{ paddingLeft: styles.mobileEditingPadding }}>
|
||||
Split transaction editing is not supported on mobile at this
|
||||
time.
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel title="Account" />
|
||||
<TapField
|
||||
disabled={!adding}
|
||||
value={account ? account.name : null}
|
||||
onClick={() => this.onClick(transaction.id, 'account')}
|
||||
data-testid="account-field"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<FieldLabel title="Date" />
|
||||
<InputField
|
||||
type="date"
|
||||
required
|
||||
style={{ color: theme.tableText, minWidth: '150px' }}
|
||||
defaultValue={dateDefaultValue}
|
||||
onUpdate={value =>
|
||||
this.onEdit(
|
||||
transaction,
|
||||
'date',
|
||||
formatDate(parseISO(value), this.props.dateFormat),
|
||||
)
|
||||
}
|
||||
onChange={e =>
|
||||
this.onQueueChange(
|
||||
transaction,
|
||||
'date',
|
||||
formatDate(parseISO(e.target.value), this.props.dateFormat),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
{transaction.reconciled ? (
|
||||
<View style={{ marginLeft: 0, marginRight: 8 }}>
|
||||
<FieldLabel title="Reconciled" />
|
||||
<BooleanField
|
||||
checked
|
||||
style={{
|
||||
margin: 'auto',
|
||||
width: 22,
|
||||
height: 22,
|
||||
}}
|
||||
disabled
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ marginLeft: 0, marginRight: 8 }}>
|
||||
<FieldLabel title="Cleared" />
|
||||
<BooleanField
|
||||
checked={transaction.cleared}
|
||||
onUpdate={checked =>
|
||||
this.onEdit(transaction, 'cleared', checked)
|
||||
}
|
||||
style={{
|
||||
margin: 'auto',
|
||||
width: 22,
|
||||
height: 22,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FieldLabel title="Notes" />
|
||||
<InputField
|
||||
defaultValue={transaction.notes}
|
||||
onUpdate={value => this.onEdit(transaction, 'notes', value)}
|
||||
onChange={e =>
|
||||
this.onQueueChange(transaction, 'notes', e.target.value)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!adding && (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<Button
|
||||
onClick={() => this.onDelete()}
|
||||
style={{
|
||||
height: 40,
|
||||
borderWidth: 0,
|
||||
marginLeft: styles.mobileEditingPadding,
|
||||
marginRight: styles.mobileEditingPadding,
|
||||
marginTop: 10,
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
type="bare"
|
||||
>
|
||||
<SvgTrash
|
||||
width={17}
|
||||
height={17}
|
||||
style={{ color: theme.errorText }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.errorText,
|
||||
marginLeft: 5,
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
Delete transaction
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
// </KeyboardAvoidingView>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
6
upcoming-release-notes/1993.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
Use Page component for mobile pages
|
||||