Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/MoneyRequestAmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ type MoneyRequestAmountInputProps = {
/** Whether to allow direct negative input (for split amounts where value is already negative) */
allowNegativeInput?: boolean;

/** Style for the negative symbol */
negativeSymbolStyle?: StyleProp<TextStyle>;

/** The testID of the input. Used to locate this view in end-to-end tests. */
testID?: string;

Expand Down Expand Up @@ -181,6 +184,7 @@ function MoneyRequestAmountInput({
isNegative = false,
allowFlippingAmount = false,
allowNegativeInput = false,
negativeSymbolStyle,
toggleNegative,
clearNegative,
ref,
Expand Down Expand Up @@ -274,6 +278,7 @@ function MoneyRequestAmountInput({
touchableInputWrapperStyle={props.touchableInputWrapperStyle}
contentWidth={contentWidth}
isNegative={isNegative}
negativeSymbolStyle={negativeSymbolStyle}
testID={testID}
errorText={props.errorText}
footer={props.footer}
Expand Down
7 changes: 6 additions & 1 deletion src/components/NumberWithSymbolForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useIsFocused} from '@react-navigation/native';
import type {ForwardedRef} from 'react';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {KeyboardTypeOptions, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native';
import type {KeyboardTypeOptions, NativeSyntheticEvent, StyleProp, TextStyle, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
Expand Down Expand Up @@ -90,6 +90,9 @@ type NumberWithSymbolFormProps = {
/** Whether to allow direct negative input (for split amounts where value is already negative) */
allowNegativeInput?: boolean;

/** Style for the negative symbol */
negativeSymbolStyle?: StyleProp<TextStyle>;

/** Whether to use dynamic font size for the amount input */
shouldUseDynamicFontSize?: boolean;

Expand Down Expand Up @@ -182,6 +185,7 @@ function NumberWithSymbolForm({
isNegative = false,
allowFlippingAmount = false,
allowNegativeInput = false,
negativeSymbolStyle,
toggleNegative,
clearNegative,
ref,
Expand Down Expand Up @@ -577,6 +581,7 @@ function NumberWithSymbolForm({
prefixContainerStyle={props.prefixContainerStyle}
touchableInputWrapperStyle={props.touchableInputWrapperStyle}
isNegative={isNegative}
negativeSymbolStyle={negativeSymbolStyle}
toggleNegative={toggleNegative}
onFocus={props.onFocus}
accessibilityLabel={props.accessibilityLabel}
Expand Down
6 changes: 3 additions & 3 deletions src/components/Table/EditableCell/EditableCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, {useEffect, useId} from 'react';
import type {ReactNode, RefObject} from 'react';
import {View} from 'react-native';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import {useEditingCellActions} from './EditingCellContext';
Expand Down Expand Up @@ -47,8 +47,8 @@ type EditableCellProps = {
*/
function EditableCell({children, editContent, popoverContent, isEditing, canEdit, onStartEditing, anchorRef}: EditableCellProps) {
const styles = useThemeStyles();
const {isLargeScreenWidth} = useResponsiveLayout();
const isEditable = isLargeScreenWidth;
const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayoutOnWideRHP();
const isEditable = isLargeScreenWidth && !shouldUseNarrowLayout;
const cellId = useId();
const {setIsEditingCell, setFocusedCellId} = useEditingCellActions();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function BaseTextInputWithSymbol({
style,
symbolTextStyle,
isNegative = false,
negativeSymbolStyle,
rightHandSideComponent,
ref,
disabled,
Expand All @@ -42,7 +43,7 @@ function BaseTextInputWithSymbol({
onChangeAmount(newAmount);
};

const negativeSymbol = <Text style={[styles.iouAmountText]}>-</Text>;
const negativeSymbol = <Text style={[styles.iouAmountText, negativeSymbolStyle]}>-</Text>;

return (
<>
Expand Down
3 changes: 3 additions & 0 deletions src/components/TextInputWithSymbol/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ type BaseTextInputWithSymbolProps = {
/** Function to toggle the amount to negative */
toggleNegative?: () => void;

/** Style for the negative symbol */
negativeSymbolStyle?: StyleProp<TextStyle>;

/** The test ID of TextInput. Used to locate the view in end-to-end tests. */
testID?: string;

Expand Down
22 changes: 19 additions & 3 deletions src/components/TransactionItemRow/DataCells/TotalCell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useMemo, useRef} from 'react';
import React, {useMemo, useRef, useState} from 'react';
import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput';
import {EditableCell, useInlineEditState} from '@components/Table/EditableCell';
import type {EditableProps} from '@components/Table/EditableCell';
Expand Down Expand Up @@ -32,12 +32,14 @@ function TotalCell({shouldShowTooltip, transactionItem, canEdit, onSave}: TotalC
}

const absoluteAmount = Math.abs(amount ?? 0);
const [isNegative, setIsNegative] = useState((amount ?? 0) < 0);

const handleAmountSave = (amountString: string) => {
const parsedValue = parseFloatAnyLocale(amountString);
if (!Number.isNaN(parsedValue) && parsedValue >= 0) {
const normalizedValue = roundToTwoDecimalPlaces(parsedValue);
onSave?.(convertToBackendAmount(normalizedValue));
const finalAmount = isNegative ? -normalizedValue : normalizedValue;
onSave?.(convertToBackendAmount(finalAmount));
}
};

Expand All @@ -56,6 +58,11 @@ function TotalCell({shouldShowTooltip, transactionItem, canEdit, onSave}: TotalC
ref?.focus();
};

const handleStartEditing = () => {
setIsNegative((amount ?? 0) < 0);
startEditing();
};

const handleAmountChange = (amountString: string) => {
setLocalValue(amountString);
};
Expand All @@ -65,6 +72,10 @@ function TotalCell({shouldShowTooltip, transactionItem, canEdit, onSave}: TotalC
return convertToFrontendAmountAsString(amountAsInt, decimals);
};

const toggleNegative = () => setIsNegative((prev) => !prev);

const clearNegative = () => setIsNegative(false);

const handleEscape = () => {
cancelEditing();
inputRef.current?.blur();
Expand Down Expand Up @@ -99,7 +110,7 @@ function TotalCell({shouldShowTooltip, transactionItem, canEdit, onSave}: TotalC
<EditableCell
canEdit={canEdit}
isEditing={isEditing}
onStartEditing={startEditing}
onStartEditing={handleStartEditing}
editContent={
<MoneyRequestAmountInput
ref={focusOnMount}
Expand All @@ -115,13 +126,18 @@ function TotalCell({shouldShowTooltip, transactionItem, canEdit, onSave}: TotalC
onAmountChange={handleAmountChange}
onFormatAmount={onFormatAmount}
onBlur={save}
allowFlippingAmount
isNegative={isNegative}
toggleNegative={toggleNegative}
clearNegative={clearNegative}
// EditableCell is responsible for the cell's hover and focus styles (border, background).
// Suppress MoneyRequestAmountInput's own border and background to avoid visual conflicts.
containerStyle={[styles.editableCellInputStyle]}
inputStyle={[styles.textAlignRight, styles.pr0]}
touchableInputWrapperStyle={styles.editableCellInputStyle}
scrollViewStyle={[styles.flexRow, styles.justifyContentEnd]}
symbolTextStyle={[styles.editableCellSymbolStyle, hasSymbolSpaceInPreview && styles.pr1]}
negativeSymbolStyle={styles.editableCellSymbolStyle}
/>
}
>
Expand Down
41 changes: 32 additions & 9 deletions src/libs/actions/TransactionInlineEdit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {getTagLists, isMultiLevelTags} from '@libs/PolicyUtils';
import {isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {canEditFieldOfMoneyRequest, canEditMoneyRequest, canUserPerformWriteAction, isArchivedReport, isReportInGroupPolicy} from '@libs/ReportUtils';
import {hasEnabledTags} from '@libs/TagsOptionsListUtils';
import {calculateTaxAmount, getCurrency, getOriginalTransactionWithSplitInfo, getTaxValue, isExpenseUnreported} from '@libs/TransactionUtils';
import {calculateTaxAmount, getCurrency, getOriginalTransactionWithSplitInfo, getTaxValue, isDistanceRequest, isExpenseUnreported} from '@libs/TransactionUtils';
import {isInvalidMerchantValue} from '@libs/ValidationUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {
Expand Down Expand Up @@ -190,9 +191,7 @@ function editTransactionDateInline(params: TransactionInlineEditParams, newDate:

/** Updates the merchant of an expense from the Search results table or the Expense Report page. */
function editTransactionMerchantInline(params: TransactionInlineEditParams, newMerchant: string) {
// Merchant must be a non-empty string. An empty merchant is not a valid
// state and the IOU action would save it as a blank row label.
if (!newMerchant.trim()) {
if (isInvalidMerchantValue(newMerchant)) {
return;
}
const iouParams = getIouParamsForTransaction(params);
Expand Down Expand Up @@ -329,6 +328,33 @@ function getTransactionEditPermissions({
}
}

if (field === CONST.EDIT_REQUEST_FIELD.MERCHANT) {
// Distance expenses cannot have their merchant edited
if (isDistanceRequest(transaction)) {
return false;
}
}

if (field === CONST.EDIT_REQUEST_FIELD.CATEGORY) {
// Matches MoneyRequestView's shouldShowCategory logic
// For policy expenses, check if there's a category or enabled options
if (isPolicyExpenseChat) {
return !!categoryForDisplay || hasEnabledOptions(policyCategories ?? {});
}
// For unreported expenses, allow if no policy or if categories are enabled with options
if (isUnreported) {
return !policy || hasEnabledOptions(policyCategories ?? {});
}
}

if (field === CONST.EDIT_REQUEST_FIELD.TAG) {
// Single-level tags only (multi-level needs a picker UI not available inline)
if (isMultiLevelTags(policyTags)) {
return false;
}
return !!transaction?.tag || hasEnabledTags(policyTagLists);
}

return (
isUnreported ||
canEditFieldOfMoneyRequest({
Expand All @@ -347,12 +373,9 @@ function getTransactionEditPermissions({
canEditMerchant: canEditRestricted(CONST.EDIT_REQUEST_FIELD.MERCHANT),
// Non-restricted; always editable when canEdit is true
canEditDescription: true,
// Matches MoneyRequestView's shouldShowCategory logic
canEditCategory:
(isPolicyExpenseChat && (!!categoryForDisplay || hasEnabledOptions(policyCategories ?? {}))) || (isUnreported && (!policy || hasEnabledOptions(policyCategories ?? {}))),
canEditCategory: canEditRestricted(CONST.EDIT_REQUEST_FIELD.CATEGORY),
canEditAmount: canEditRestricted(CONST.EDIT_REQUEST_FIELD.AMOUNT),
// single-level tags only (multi-level needs a picker UI not available inline).
canEditTag: !isMultiLevelTags(policyTags) && (!!transaction?.tag || hasEnabledTags(policyTagLists)),
canEditTag: canEditRestricted(CONST.EDIT_REQUEST_FIELD.TAG),
};
}

Expand Down
1 change: 1 addition & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,7 @@ const staticStyles = (theme: ThemeColors) =>
...FontUtils.fontFamily.platform.EXP_NEUE,
color: theme.textSupporting,
fontSize: variables.fontSizeNormal,
lineHeight: variables.fontSizeNormalHeight,
},

borderColorFocus: {
Expand Down
Loading