Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
df224eb
saved-expense-text-inputs
DylanDylann Nov 6, 2026
d276574
saved-expense-text-inputs
DylanDylann Nov 9, 2026
73af02d
Merge branch 'main' into dylann/105904-saved-expense-text-inputs
DylanDylann Nov 9, 2026
a438559
enhance merchant input handling by stripping invisible characters and…
DylanDylann Nov 9, 2026
0733cb7
update attendee handling in MoneyRequest components to use new hooks …
DylanDylann Nov 9, 2026
f960425
Merge branch 'main' into dylann/105904-saved-expense-text-inputs
DylanDylann Nov 10, 2026
ea6b8b0
Refactor Money Request components to utilize new `useMoneyRequestUpda…
DylanDylann Nov 10, 2026
f19eb0a
address comments
DylanDylann Nov 10, 2026
54466ae
address comments
DylanDylann Nov 11, 2026
47a9c2f
Merge branch 'main' into dylann/105904-saved-expense-text-inputs
DylanDylann Nov 11, 2026
6d5c7f6
Refactor TaxAmountInputField to use AmountFormField component instead…
DylanDylann Nov 11, 2026
0379e58
Enhance AmountActionButtons and AmountInputField components by adding…
DylanDylann Nov 11, 2026
491612a
Merge branch 'main' into dylann/105904-saved-expense-text-inputs
DylanDylann Nov 12, 2026
c059c72
update padding
DylanDylann Nov 12, 2026
d405b51
Fix selected tax rate calculation in MoneyRequestView component
DylanDylann Nov 12, 2026
c8d7453
Enhance MenuItemContent to accept custom styles and update ExpenseFie…
DylanDylann Nov 13, 2026
fe8de32
Merge branch 'main' into dylann/105904-saved-expense-text-inputs
DylanDylann Nov 13, 2026
ec532f0
merge main
DylanDylann Nov 13, 2026
c417d65
address comments
DylanDylann Nov 13, 2026
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
13 changes: 13 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11206,6 +11206,19 @@ const CONST = {
GOOGLE_MERCHANT_SEARCH_BUTTON: 'MoneyRequest-GoogleMerchantSearchButton',
REIMBURSABLE_SWITCH: 'MoneyRequest-ReimbursableSwitch',
BILLABLE_SWITCH: 'MoneyRequest-BillableSwitch',
MERCHANT_INPUT: 'MoneyRequest-MerchantInput',
DESCRIPTION_INPUT: 'MoneyRequest-DescriptionInput',
DATE_FIELD_CLEAR_BUTTON: 'MoneyRequest-DateFieldClearButton',
AMOUNT_FIELD: 'MoneyRequest-ViewField-Amount',
DISTANCE_FIELD: 'MoneyRequest-ViewField-Distance',
RATE_FIELD: 'MoneyRequest-ViewField-Rate',
CATEGORY_FIELD: 'MoneyRequest-ViewField-Category',
VENDOR_FIELD: 'MoneyRequest-ViewField-Vendor',
TAG_FIELD: 'MoneyRequest-ViewField-Tag',
TAX_RATE_FIELD: 'MoneyRequest-ViewField-TaxRate',
TAX_AMOUNT_FIELD: 'MoneyRequest-ViewField-TaxAmount',
ATTENDEES_FIELD: 'MoneyRequest-ViewField-Attendees',
REPORT_FIELD: 'MoneyRequest-ViewField-Report',
Comment thread
DylanDylann marked this conversation as resolved.
Resolved
},
SPLIT_EXPENSE: {
ADD_SPLIT_BUTTON: 'SplitExpense-AddSplitButton',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import {convertToBackendAmount, convertToFrontendAmountAsString, getLocalizedCurrencySymbol} from '@libs/CurrencyUtils';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {calculateAmount, isMovingTransactionFromTrackExpense, isP2PRecipient, isParticipantP2P} from '@libs/IOUUtils';
import {isConfirmationAmountMissing} from '@libs/MoneyRequestUtils';
import {getBackendAmountFromInput, isConfirmationAmountMissing} from '@libs/MoneyRequestUtils';
import Navigation from '@libs/Navigation/Navigation';
import navigationRef from '@libs/Navigation/navigationRef';
import {shouldEnableNegative} from '@libs/ReportUtils';
Expand Down Expand Up @@ -189,23 +189,6 @@ function AmountField({
setIsCurrencyPickerVisible(false);
};

const getBackendAmountFromInput = (value: string) => {
const isNegative = value.startsWith('-');
const cleanAmount = value.replace('-', '');

if (!cleanAmount || cleanAmount.trim() === '') {
return null;
}

const numericAmount = Number.parseFloat(cleanAmount);
if (Number.isNaN(numericAmount)) {
return null;
}

const absoluteBackendAmount = convertToBackendAmount(numericAmount);
return isNegative ? -absoluteBackendAmount : absoluteBackendAmount;
};

/**
* When the bill total or currency changes:
* - Editing an existing split bill: recompute shares on the split draft (`SPLIT_TRANSACTION_DRAFT`).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
/**
* The attendees row's dropdown on the expense confirmation form: the attendee list anchored to the row, holding the
* selection until the user applies it to the draft expense.
* The attendees row's dropdown on an expense form: the attendee list anchored to the row, holding the selection until
* the user applies it to the expense.
*/
import AttendeePickerModal from '@components/AttendeePicker/AttendeePickerModal';
import {useConfirmationFields} from '@components/MoneyRequestConfirmationFields/context';

import useOnyx from '@hooks/useOnyx';
import useReportOwnerAsAttendee from '@hooks/useReportOwnerAsAttendee';
import useUpdateTransactionAttendees from '@hooks/useUpdateTransactionAttendees';

import {setMoneyRequestAttendees} from '@libs/actions/IOU/MoneyRequest';
import {getOriginalAttendees} from '@libs/TransactionUtils';

import CONST from '@src/CONST';
import type {IOUType} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Attendee} from '@src/types/onyx/IOU';
Expand All @@ -20,24 +22,29 @@ import React, {useState} from 'react';
import type {ExpenseFieldDropdownRenderProps} from './ExpenseFieldDropdown';

type AttendeeFieldDropdownProps = ExpenseFieldDropdownRenderProps & {
/** ID of the draft expense the attendees are written to */
/** ID of the expense the attendees are written to */
transactionID: string;

/** Type of the expense, which the list's empty state is worded for */
iouType: IOUType;
};

/**
* The attendee list, in the container anchored to the expense form's attendees row. Mounted only once the row is
* opened. The selection is kept here while the user picks and written to the draft when applied, as the full page
* writes it on save; dismissing the container drops the unapplied selection.
* The attendee list, in the container anchored to an expense form's attendees row. Mounted only once the row is
* opened. The selection is kept here while the user picks and written to the expense when applied, through the same
* hook the full page saves with; dismissing the container drops the unapplied selection.
*/
function AttendeeFieldDropdown({transactionID, iouType, isVisible, onClose, ...popoverProps}: AttendeeFieldDropdownProps) {
const [draftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`);
const reportOwnerAsAttendee = useReportOwnerAsAttendee(draftTransaction);
const originalAttendees = getOriginalAttendees(draftTransaction, reportOwnerAsAttendee);
const {action, reportID} = useConfirmationFields();
const isEditing = action === CONST.IOU.ACTION.EDIT;
// A saved expense being edited is read as saved, and anything else from its draft, the same as the full page reads it.
const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`);
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const {updateAttendees} = useUpdateTransactionAttendees({transactionID, report, isEditing});
const reportOwnerAsAttendee = useReportOwnerAsAttendee(transaction);
const originalAttendees = getOriginalAttendees(transaction, reportOwnerAsAttendee);

// The list starts from the draft's attendees, the creator when it has none, as the full page does. The row keeps this
// The list starts from the expense's attendees, the creator when it has none, as the full page does. The row keeps this
// dropdown mounted once it has been opened, so the selection is dropped each time it opens or closes: a selection
// dismissed without Apply must not come back on the next open.
const [pickedAttendees, setPickedAttendees] = useState<Attendee[]>();
Expand All @@ -55,7 +62,7 @@ function AttendeeFieldDropdown({transactionID, iouType, isVisible, onClose, ...p
return;
}
if (!deepEqual(originalAttendees, attendees)) {
setMoneyRequestAttendees(transactionID, attendees, true);
updateAttendees(attendees);
}
onClose();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ type ExpenseFieldRowProps = {
/** Whether the field can be changed at all. A non-interactive row drops its border, caret and press handler */
isInteractive?: boolean;

/** Text a locked row offers to copy, from a button on hover or a long press on touch. Also makes its value selectable */
copyValue?: string;

/**
* Styles for the `Animated.View` wrapping the row, e.g. the highlight animation `useFieldRowHighlightStyle`
* runs. It is a layer of its own because the row paints its own background over anything drawn behind it, and
Expand Down Expand Up @@ -126,6 +129,7 @@ function ExpenseFieldRow({
isExpanded,
isDisabled = false,
isInteractive = true,
copyValue,
backgroundStyle,
sentryLabel,
testID,
Expand All @@ -138,7 +142,8 @@ function ExpenseFieldRow({
// `Required` answers "what goes here", so it goes once the field holds a value. A label that describes the value
// itself has to stay, or a field the app filled in reads as one the user chose.
const shouldShowRightLabel = !!rightLabel && (shouldKeepRightLabelWhenFilled || !hasValue);
const hasTrailing = shouldShowRightLabel || isInteractive;
const shouldShowCopy = !isInteractive && !!copyValue;
const hasTrailing = shouldShowRightLabel || isInteractive || shouldShowCopy;

const row = (
<MenuItem.Row>
Expand All @@ -150,6 +155,7 @@ function ExpenseFieldRow({
<MenuItem.FieldValue
numberOfLines={numberOfLinesValue}
isMuted={!isInteractive}
isSelectable={shouldShowCopy}
>
{value}
</MenuItem.FieldValue>
Expand All @@ -175,6 +181,7 @@ function ExpenseFieldRow({
</>
)}
{isInteractive && <MenuItem.DownCaret />}
{shouldShowCopy && <MenuItem.Copy value={copyValue} />}
</MenuItem.Trailing>
)}
</MenuItem.Row>
Expand Down Expand Up @@ -203,6 +210,7 @@ function ExpenseFieldRow({
// the sign-in prompt rather than the field's selector.
onPress={isInteractive ? callFunctionIfActionIsAllowed(onPress, false, session) : undefined}
isDisabled={isDisabled}
shouldAllowTextSelection={shouldShowCopy}
accessibilityLabel={accessibilityLabel}
accessibilityState={isExpanded === undefined ? undefined : {expanded: isExpanded}}
sentryLabel={sentryLabel}
Expand Down
Loading
Loading