Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ const CONST = {
POPOVER_DROPDOWN_WIDTH: 334,
POPOVER_DROPDOWN_MIN_HEIGHT: 0,
POPOVER_DROPDOWN_MAX_HEIGHT: 416,
POPOVER_REPORT_SUBMIT_TO_CONTENT_HEIGHT: 416,
POPOVER_MENU_MAX_HEIGHT: 496,
POPOVER_MENU_MAX_HEIGHT_MOBILE: 432,
POPOVER_DATE_WIDTH: 338,
Expand Down
15 changes: 12 additions & 3 deletions src/components/ButtonWithDropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
menuHeaderText = '',
customText,
style,
buttonStyle,
disabledStyle,
buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM,
anchorAlignment = defaultAnchorAlignment,
Expand Down Expand Up @@ -146,7 +147,15 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
isActive: useKeyboardShortcuts,
},
);
const splitButtonWrapperStyle = isSplitButton ? [styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter] : {};
const splitButtonWrapperStyle = isSplitButton ? [styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter] : undefined;
let dropdownButtonStyle;
if (isSplitButton) {
dropdownButtonStyle = [splitButtonWrapperStyle, style];
} else if (style) {
dropdownButtonStyle = [styles.w100, style];
}
const defaultStyle = style ? styles.w100 : undefined;
const nonSplitButtonStyle = buttonStyle ? [styles.w100, buttonStyle] : defaultStyle;
const isTextTooLong = customText && customText?.length > 6;

const handlePress = useCallback(
Expand All @@ -167,7 +176,7 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
return (
<View style={wrapperStyle}>
{shouldAlwaysShowDropdownMenu || options.length > 1 ? (
<View style={[splitButtonWrapperStyle, style]}>
<View style={dropdownButtonStyle}>
<Button
success={success}
pressOnEnter={pressOnEnter}
Expand All @@ -179,7 +188,7 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
shouldStayNormalOnDisable={shouldStayNormalOnDisable}
isLoading={isLoading}
shouldRemoveRightBorderRadius
style={isSplitButton ? [styles.pr0, styles.flexGrow1, styles.flexShrink1] : {}}
style={isSplitButton ? [styles.pr0, styles.flexGrow1, styles.flexShrink1] : nonSplitButtonStyle}
extraSmall={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.EXTRA_SMALL}
large={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE}
medium={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
Expand Down
3 changes: 3 additions & 0 deletions src/components/ButtonWithDropdownMenu/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ type ButtonWithDropdownMenuProps<TValueType> = WithSentryLabel & {
/** Additional styles to add to the component */
style?: StyleProp<ViewStyle>;

/** Additional styles for the inner button when isSplitButton is false (wrapper uses style). */
buttonStyle?: StyleProp<ViewStyle>;

/** Additional styles to add to the component when it's disabled */
disabledStyle?: StyleProp<ViewStyle>;

Expand Down
17 changes: 10 additions & 7 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,17 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt
)}
</HeaderWithBackButton>
{!shouldShowHeaderButtonsInHeaderRow && (
<MoneyReportHeaderActions
reportID={reportIDProp}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
/>
<View style={[styles.w100, styles.flexColumn]}>
<MoneyReportHeaderActions
reportID={reportIDProp}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
/>
<MoneyReportHeaderMoreContent reportID={reportIDProp} />
</View>
)}
<MoneyReportHeaderMoreContent reportID={reportIDProp} />
{shouldShowHeaderButtonsInHeaderRow && <MoneyReportHeaderMoreContent reportID={reportIDProp} />}
<HeaderLoadingBar />
</View>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {useMoneyReportHeaderModals} from '@components/MoneyReportHeaderModalsCon
import NavigationDeferredMount from '@components/NavigationDeferredMount';
import {usePaymentAnimationsContext} from '@components/PaymentAnimationsContext';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import {ReportSubmitToPopoverAnchor} from '@components/ReportSubmitToPopoverAnchor';
import {useSearchQueryContext, useSearchResultsContext} from '@components/Search/SearchContext';
import type {PaymentActionParams} from '@components/SettlementButton/types';
import useActiveAdminPolicies from '@hooks/useActiveAdminPolicies';
Expand Down Expand Up @@ -77,6 +78,11 @@ type MoneyReportHeaderSecondaryActionsProps = {
dropdownMenuRef?: React.RefObject<ButtonWithDropdownMenuRef>;
};

const MORE_MENU_SUBMIT_TO_POPOVER_ANCHOR_ALIGNMENT = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
};

function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isReportInSearch, backTo, dropdownMenuRef}: MoneyReportHeaderSecondaryActionsProps) {
const {isPaidAnimationRunning, isApprovedAnimationRunning, startAnimation, startApprovedAnimation, startSubmittingAnimation} = usePaymentAnimationsContext();
const {openHoldMenu, openPDFDownload, openHoldEducational, openRejectModal} = useMoneyReportHeaderModals();
Expand Down Expand Up @@ -284,7 +290,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo
}

// Domain hooks
const lifecycleActions = useLifecycleActions({
const {actions: lifecycleActionEntries, confirmApproval} = useLifecycleActions({
reportID,
startApprovedAnimation,
startAnimation,
Expand Down Expand Up @@ -352,7 +358,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo
: [];

// Merge all action implementations
const secondaryActionsImplementation: Record<string, (typeof lifecycleActions.actions)[string]> = {
const secondaryActionsImplementation: Record<string, (typeof lifecycleActionEntries)[string]> = {
[CONST.REPORT.SECONDARY_ACTIONS.VIEW_DETAILS]: {
value: CONST.REPORT.SECONDARY_ACTIONS.VIEW_DETAILS,
text: translate('iou.viewDetails'),
Expand All @@ -363,7 +369,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo
},
},
...exportActionEntries,
...lifecycleActions.actions,
...lifecycleActionEntries,
...expenseActions,
...holdRejectActions,
[CONST.REPORT.SECONDARY_ACTIONS.PAY]: {
Expand Down Expand Up @@ -397,7 +403,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo
hasViolations,
isASAPSubmitBetaEnabled,
isUserValidated,
confirmApproval: () => lifecycleActions.confirmApproval(),
confirmApproval: () => confirmApproval(),
iouReport: moneyRequestReport,
iouReportNextStep: nextStep,
betas,
Expand Down Expand Up @@ -437,7 +443,7 @@ function MoneyReportHeaderSecondaryActionsPlaceholder({primaryAction}: {primaryA
const {shouldUseNarrowLayout, isMediumScreenWidth, isInLandscapeMode} = useResponsiveLayout();
const icons = useMemoizedLazyExpensifyIcons(['DownArrow']);
const shouldTakeRemainingWidth = (shouldUseNarrowLayout || isMediumScreenWidth) && !primaryAction && !isInLandscapeMode;
const wrapperStyle = shouldTakeRemainingWidth ? styles.flex1 : undefined;
const wrapperStyle = shouldTakeRemainingWidth ? [styles.flex1, styles.w100, styles.mnw0] : undefined;
// Match the inner styles the real ButtonWithDropdownMenu applies when isSplitButton=false so text placement stays put on swap.
const innerStyles = [StyleUtils.getDropDownButtonHeight(CONST.DROPDOWN_BUTTON_SIZE.MEDIUM), styles.dropDownButtonCartIconView];
return (
Expand All @@ -447,28 +453,42 @@ function MoneyReportHeaderSecondaryActionsPlaceholder({primaryAction}: {primaryA
iconRight={icons.DownArrow}
shouldShowRightIcon
innerStyles={innerStyles}
style={shouldTakeRemainingWidth ? styles.w100 : undefined}
onPress={() => {}}
/>
</View>
);
}

function MoneyReportHeaderSecondaryActions({reportID, primaryAction, isReportInSearch, backTo, dropdownMenuRef}: MoneyReportHeaderSecondaryActionsProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout, isMediumScreenWidth, isInLandscapeMode} = useResponsiveLayout();
const shouldTakeRemainingWidth = (shouldUseNarrowLayout || isMediumScreenWidth) && !primaryAction && !isInLandscapeMode;
const layoutWrapperStyle = shouldTakeRemainingWidth ? [styles.flex1, styles.w100, styles.mnw0] : undefined;

return (
<NavigationDeferredMount
placeholder={<MoneyReportHeaderSecondaryActionsPlaceholder primaryAction={primaryAction} />}
// RHPReportScreen remounts this tree on setParams arrow-nav without firing a transition,
// so we must not wait for one — see https://github.com/Expensify/App/issues/88931.
waitForUpcomingTransition={false}
>
<MoneyReportHeaderSecondaryActionsInner
<View style={layoutWrapperStyle}>
<ReportSubmitToPopoverAnchor
reportID={reportID}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
dropdownMenuRef={dropdownMenuRef}
/>
</NavigationDeferredMount>
wrapperStyle={styles.w100}
anchorAlignment={MORE_MENU_SUBMIT_TO_POPOVER_ANCHOR_ALIGNMENT}
>
<NavigationDeferredMount
placeholder={<MoneyReportHeaderSecondaryActionsPlaceholder primaryAction={primaryAction} />}
// RHPReportScreen remounts this tree on setParams arrow-nav without firing a transition,
// so we must not wait for one — see https://github.com/Expensify/App/issues/88931.
waitForUpcomingTransition={false}
>
<MoneyReportHeaderSecondaryActionsInner
reportID={reportID}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
dropdownMenuRef={dropdownMenuRef}
/>
</NavigationDeferredMount>
</ReportSubmitToPopoverAnchor>
</View>
);
}

Expand Down
1 change: 1 addition & 0 deletions src/components/MoneyReportHeaderKYCDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ function MoneyReportHeaderKYCDropdown({
options={applicableSecondaryActions}
isSplitButton={false}
wrapperStyle={shouldDisplayNarrowVersion && [!primaryAction && !customText && !isInLandscapeMode && styles.flex1, !!customText && styles.w100]}
style={shouldDisplayNarrowVersion && !primaryAction && !customText && !isInLandscapeMode ? styles.w100 : undefined}
shouldUseModalPaddingStyle
onOptionsMenuHide={onOptionsMenuHide}
sentryLabel={CONST.SENTRY_LABEL.MORE_MENU.MORE_BUTTON}
Expand Down
2 changes: 1 addition & 1 deletion src/components/MoneyReportHeaderMoreContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function MoneyReportHeaderMoreContentBody({moneyRequestReport, statusBarType, is
const {iouTransactionID} = useMoneyReportTransactionThread();

return (
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3]}>
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3, shouldShowNextStep && styles.pt0]}>
<View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
{shouldShowNextStep && <MoneyReportHeaderNextStep reportID={reportID} />}
<MoneyReportHeaderStatusBarSection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {isTrackIntentUserSelector} from '@selectors/Onboarding';
import React from 'react';
import AnimatedSubmitButton from '@components/AnimatedSubmitButton';
import {usePaymentAnimationsContext} from '@components/PaymentAnimationsContext';
import {ReportSubmitToPopoverAnchor, useOpenReportSubmitToPopover} from '@components/ReportSubmitToPopoverAnchor';
import {useSearchQueryContext, useSearchResultsContext} from '@components/Search/SearchContext';
import useConfirmModal from '@hooks/useConfirmModal';
import useConfirmPendingRTERAndProceed from '@hooks/useConfirmPendingRTERAndProceed';
Expand All @@ -17,7 +18,7 @@ import useStrictPolicyRules from '@hooks/useStrictPolicyRules';
import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';
import {search} from '@libs/actions/Search';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {hasDynamicExternalWorkflow} from '@libs/PolicyUtils';
import {hasDynamicExternalWorkflow, isSubmitPolicy} from '@libs/PolicyUtils';
import {getFilteredReportActionsForReportView} from '@libs/ReportActionsUtils';
import {hasViolations as hasViolationsReportUtils, shouldBlockSubmitDueToPreventSelfApproval, shouldBlockSubmitDueToStrictPolicyRules, shouldShowMarkAsDone} from '@libs/ReportUtils';
import {hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils, hasOnlyPendingCardTransactions, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
Expand All @@ -27,17 +28,37 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';

const ANCHOR_ALIGNMENT = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
};

type SubmitPrimaryActionProps = {
reportID: string | undefined;
};

function SubmitPrimaryAction({reportID}: SubmitPrimaryActionProps) {
const {startSubmittingAnimation} = usePaymentAnimationsContext();

return (
<ReportSubmitToPopoverAnchor
reportID={reportID}
onSubmitSuccess={startSubmittingAnimation}
anchorAlignment={ANCHOR_ALIGNMENT}
>
<SubmitPrimaryActionContent reportID={reportID} />
</ReportSubmitToPopoverAnchor>
);
}

function SubmitPrimaryActionContent({reportID}: SubmitPrimaryActionProps) {
const {isSubmittingAnimationRunning, stopAnimation, startSubmittingAnimation} = usePaymentAnimationsContext();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const {accountID, email} = useCurrentUserPersonalDetails();
const {isBetaEnabled} = usePermissions();
const {areStrictPolicyRulesEnabled} = useStrictPolicyRules();
const openReportSubmitToPopover = useOpenReportSubmitToPopover();

const [moneyRequestReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(moneyRequestReport?.policyID)}`);
Expand Down Expand Up @@ -97,6 +118,11 @@ function SubmitPrimaryAction({reportID}: SubmitPrimaryActionProps) {
}

confirmPendingRTERAndProceed(() => {
if (isSubmitPolicy(policy) && reportID) {
openReportSubmitToPopover();
return;
}

submitReport({
expenseReport: moneyRequestReport,
policy,
Expand Down
39 changes: 21 additions & 18 deletions src/components/MoneyRequestReportView/SelectionToolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import HoldOrRejectEducationalModal from '@components/HoldOrRejectEducationalMod
import {ModalActions} from '@components/Modal/Global/ModalContext';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ProcessMoneyReportHoldMenu from '@components/ProcessMoneyReportHoldMenu';
import {ReportSubmitToPopoverAnchor} from '@components/ReportSubmitToPopoverAnchor';
import BulkDuplicateHandler from '@components/Search/BulkDuplicateHandler';
import {useSearchSelectionActions, useSearchSelectionContext} from '@components/Search/SearchContext';
import useConfirmModal from '@hooks/useConfirmModal';
Expand Down Expand Up @@ -247,24 +248,26 @@ function SelectionToolbar({reportID, transactions, reportActions}: SelectionTool
<View
style={[isInLandscapeMode ? [styles.flexRowReverse, styles.justifyContentBetween, styles.alignItemsCenter, styles.gap6, styles.pb3, styles.ph5] : styles.flexColumn]}
>
<SelectionDropdown
hasPayInSelectionMode={hasPayInSelectionMode}
chatReport={chatReport}
report={report}
onSelectionModePaymentSelect={onSelectionModePaymentSelect}
selectionModeKYCSuccess={selectionModeKYCSuccess}
onWorkspacePolicySelect={(selectedPolicy, triggerKYCFlow) => {
if (shouldBlockAction()) {
return;
}
triggerKYCFlow({policy: selectedPolicy});
}}
primaryAction={primaryAction}
selectedTransactionsOptions={selectedTransactionsOptions}
selectedTransactionIDs={selectedTransactionIDs}
kycWallRef={kycWallRef}
shouldPopoverUseScrollView={popoverUseScrollView}
/>
<ReportSubmitToPopoverAnchor reportID={reportID}>
<SelectionDropdown
hasPayInSelectionMode={hasPayInSelectionMode}
chatReport={chatReport}
report={report}
onSelectionModePaymentSelect={onSelectionModePaymentSelect}
selectionModeKYCSuccess={selectionModeKYCSuccess}
onWorkspacePolicySelect={(selectedPolicy, triggerKYCFlow) => {
if (shouldBlockAction()) {
return;
}
triggerKYCFlow({policy: selectedPolicy});
}}
primaryAction={primaryAction}
selectedTransactionsOptions={selectedTransactionsOptions}
selectedTransactionIDs={selectedTransactionIDs}
kycWallRef={kycWallRef}
shouldPopoverUseScrollView={popoverUseScrollView}
/>
</ReportSubmitToPopoverAnchor>

<SelectAllCheckbox
isSelectAllChecked={isSelectAllChecked}
Expand Down
Loading
Loading