diff --git a/assets/images/sparkles.svg b/assets/images/sparkles.svg
new file mode 100644
index 0000000000000..d878a4e49f1b1
--- /dev/null
+++ b/assets/images/sparkles.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index 1a885a21901c2..aebba30cc1590 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -192,6 +192,7 @@ import Instagram from '@assets/images/social-instagram.svg';
import Linkedin from '@assets/images/social-linkedin.svg';
import Podcast from '@assets/images/social-podcast.svg';
import Twitter from '@assets/images/social-twitter.svg';
+import Sparkles from '@assets/images/sparkles.svg';
import SpreadsheetComputer from '@assets/images/spreadsheet-computer.svg';
import Star from '@assets/images/Star.svg';
import Stopwatch from '@assets/images/stopwatch.svg';
@@ -379,6 +380,7 @@ export {
Send,
Shield,
SmartScan,
+ Sparkles,
Stopwatch,
Suitcase,
Sync,
diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx
index 7964b53496720..8d658c97af362 100644
--- a/src/components/Image/index.tsx
+++ b/src/components/Image/index.tsx
@@ -18,6 +18,7 @@ function Image({
loadingIconSize,
loadingIndicatorStyles,
imageWidthToCalculateHeight,
+ shouldUseFullHeight,
...forwardedProps
}: ImageProps) {
const [aspectRatio, setAspectRatio] = useState(null);
@@ -31,6 +32,12 @@ function Image({
return {};
}
+ if (shouldUseFullHeight) {
+ return {
+ height: '100%',
+ };
+ }
+
if (!!imageWidthToCalculateHeight && typeof aspectRatio === 'number') {
return {
width: '100%',
@@ -39,11 +46,11 @@ function Image({
}
return {aspectRatio, height: 'auto'};
- }, [shouldSetAspectRatioInStyle, aspectRatio, imageWidthToCalculateHeight]);
+ }, [shouldSetAspectRatioInStyle, aspectRatio, imageWidthToCalculateHeight, shouldUseFullHeight]);
const updateAspectRatio = useCallback(
(width: number, height: number) => {
- if (!isObjectPositionTop) {
+ if (!isObjectPositionTop || shouldUseFullHeight) {
return;
}
@@ -54,7 +61,7 @@ function Image({
setAspectRatio(height ? width / height : 'auto');
},
- [isObjectPositionTop, shouldCalculateAspectRatioForWideImage],
+ [isObjectPositionTop, shouldCalculateAspectRatioForWideImage, shouldUseFullHeight],
);
const handleLoad = useCallback(
@@ -150,7 +157,7 @@ function Image({
/**
* If the image fails to load and the object position is top, we should hide the image by setting the opacity to 0.
*/
- const shouldOpacityBeZero = isObjectPositionTop && !aspectRatio;
+ const shouldOpacityBeZero = isObjectPositionTop && !aspectRatio && !shouldUseFullHeight;
if (source === undefined && !!forwardedProps?.waitForSession) {
return undefined;
diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts
index 7a57250b98484..45178ece3629b 100644
--- a/src/components/Image/types.ts
+++ b/src/components/Image/types.ts
@@ -69,6 +69,9 @@ type ImageOwnProps = BaseImageProps & {
/** If you want to calculate the image height dynamically instead of using aspectRatio, pass the width in this property */
imageWidthToCalculateHeight?: number;
+
+ /** Whether the image should use the full height of the container */
+ shouldUseFullHeight?: boolean;
};
type ImageProps = ImageOwnProps;
diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx
index 59af932c72b7c..69c4dae99bd4b 100644
--- a/src/components/ImageWithSizeCalculation.tsx
+++ b/src/components/ImageWithSizeCalculation.tsx
@@ -44,6 +44,9 @@ type ImageWithSizeCalculationProps = {
/** The style of the loading indicator */
loadingIndicatorStyles?: StyleProp;
+ /** Whether the image should use the full height of the container */
+ shouldUseFullHeight?: boolean;
+
/** Callback to be called when the image loads */
onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void;
};
@@ -64,6 +67,7 @@ function ImageWithSizeCalculation({
objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL,
loadingIconSize,
loadingIndicatorStyles,
+ shouldUseFullHeight,
onLoad,
}: ImageWithSizeCalculationProps) {
const styles = useThemeStyles();
@@ -94,6 +98,7 @@ function ImageWithSizeCalculation({
objectPosition={objectPosition}
loadingIconSize={loadingIconSize}
loadingIndicatorStyles={loadingIndicatorStyles}
+ shouldUseFullHeight={shouldUseFullHeight}
/>
);
}
diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx
index b92385c5aa6f0..9346293741e98 100644
--- a/src/components/MenuItem.tsx
+++ b/src/components/MenuItem.tsx
@@ -196,6 +196,9 @@ type MenuItemBaseProps = {
/** Label to be displayed on the right */
rightLabel?: string;
+ /** Icon to be displayed next to the right label */
+ rightLabelIcon?: IconAsset;
+
/** Text to display for the item */
title?: string;
@@ -449,6 +452,7 @@ function MenuItem({
titleContainerStyle,
subtitle,
shouldShowBasicTitle,
+ rightLabelIcon,
label,
shouldTruncateTitle = false,
characterLimit = 200,
@@ -934,7 +938,15 @@ function MenuItem({
)}
{!title && !!rightLabel && !errorText && (
-
+
+ {!!rightLabelIcon && (
+
+ )}
{rightLabel}
)}
diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx
index 3b76306348980..e9bd45d7f20e7 100755
--- a/src/components/MoneyRequestConfirmationList.tsx
+++ b/src/components/MoneyRequestConfirmationList.tsx
@@ -12,6 +12,7 @@ import usePermissions from '@hooks/usePermissions';
import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses';
import usePreferredPolicy from '@hooks/usePreferredPolicy';
import usePrevious from '@hooks/usePrevious';
+import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import blurActiveElement from '@libs/Accessibility/blurActiveElement';
import {
@@ -286,6 +287,7 @@ function MoneyRequestConfirmationList({
const defaultMileageRate = defaultMileageRateDraft ?? defaultMileageRateReal;
const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
const {translate, toLocaleDigit} = useLocalize();
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const {isRestrictedToPreferredPolicy} = usePreferredPolicy();
@@ -383,6 +385,7 @@ function MoneyRequestConfirmationList({
const [didConfirm, setDidConfirm] = useState(isConfirmed);
const [didConfirmSplit, setDidConfirmSplit] = useState(false);
+ const [showMoreFields, setShowMoreFields] = useState(false);
// Clear the form error if it's set to one among the list passed as an argument
const clearFormErrors = useCallback(
@@ -1093,6 +1096,9 @@ function MoneyRequestConfirmationList({
reportID,
]);
+ const isScan = isScanRequestUtil(transaction);
+ const shouldRestrictHeight = useMemo(() => !showMoreFields && isScan, [isScan, showMoreFields]);
+
const listFooterContent = (
);
@@ -1164,6 +1172,8 @@ function MoneyRequestConfirmationList({
containerStyle={[styles.flexBasisAuto]}
removeClippedSubviews={false}
disableKeyboardShortcuts
+ contentContainerStyle={shouldRestrictHeight ? [StyleUtils.getReceiptContainerStyles()] : undefined}
+ ListFooterComponentStyle={shouldRestrictHeight ? [styles.flex1] : undefined}
/>
);
diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx
index 86d8c030a51e6..38911428afd1b 100644
--- a/src/components/MoneyRequestConfirmationListFooter.tsx
+++ b/src/components/MoneyRequestConfirmationListFooter.tsx
@@ -2,15 +2,17 @@ import {emailSelector} from '@selectors/Session';
import {format} from 'date-fns';
import {Str} from 'expensify-common';
import {deepEqual} from 'fast-equals';
-import React, {memo, useMemo} from 'react';
+import React, {memo, useCallback, useMemo} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
+import Animated, {Easing, FadeInDown, FadeOutUp, LinearTransition} from 'react-native-reanimated';
import type {ValueOf} from 'type-fest';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses';
import usePrevious from '@hooks/usePrevious';
+import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {getDecodedCategoryName} from '@libs/CategoryUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
@@ -31,10 +33,13 @@ import {
isCreatedMissing,
isFetchingWaypointsFromServer,
isManagedCardTransaction,
+ isScanRequest,
shouldShowAttendees as shouldShowAttendeesTransactionUtils,
+ willFieldBeAutomaticallyFilled,
} from '@libs/TransactionUtils';
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
+import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {IOUAction, IOUType} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -44,8 +49,10 @@ import type {Attendee, Participant} from '@src/types/onyx/IOU';
import type {Unit} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import Badge from './Badge';
+import Button from './Button';
import ConfirmedRoute from './ConfirmedRoute';
import MentionReportContext from './HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext';
+import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import MenuItem from './MenuItem';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
@@ -54,6 +61,7 @@ import PressableWithoutFocus from './Pressable/PressableWithoutFocus';
import ReceiptEmptyState from './ReceiptEmptyState';
import ReceiptImage from './ReceiptImage';
import {ShowContextMenuContext} from './ShowContextMenuContext';
+import Text from './Text';
type MoneyRequestConfirmationListFooterProps = {
/** The action to perform */
@@ -206,6 +214,12 @@ type MoneyRequestConfirmationListFooterProps = {
/** Flag indicating if the IOU is reimbursable */
iouIsReimbursable: boolean;
+ /** Whether to show more fields */
+ showMoreFields: boolean;
+
+ /** Function to set the show more fields */
+ setShowMoreFields: (showMoreFields: boolean) => void;
+
/** Flag indicating if the description is required */
isDescriptionRequired: boolean;
};
@@ -261,11 +275,14 @@ function MoneyRequestConfirmationListFooter({
iouIsReimbursable,
onToggleReimbursable,
isReceiptEditable = false,
+ showMoreFields,
+ setShowMoreFields,
isDescriptionRequired = false,
}: MoneyRequestConfirmationListFooterProps) {
const styles = useThemeStyles();
const {translate, toLocaleDigit, localeCompare} = useLocalize();
const {isOffline} = useNetwork();
+ const theme = useTheme();
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
@@ -276,6 +293,7 @@ function MoneyRequestConfirmationListFooter({
const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses();
const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector, canBeMissing: true});
+ const isScan = isScanRequest(transaction);
const isUnreported = transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
const isCreatingTrackExpense = action === CONST.IOU.ACTION.CREATE && iouType === CONST.IOU.TYPE.TRACK;
@@ -413,6 +431,26 @@ function MoneyRequestConfirmationListFooter({
const mentionReportContextValue = useMemo(() => ({currentReportID: reportID, exactlyMatch: true}), [reportID]);
+ const getRightLabelIcon = useCallback(
+ (fieldType: 'amount' | 'merchant' | 'date' | 'category') => {
+ return willFieldBeAutomaticallyFilled(transaction, fieldType) ? Expensicons.Sparkles : undefined;
+ },
+ [transaction],
+ );
+
+ const getRightLabel = useCallback(
+ (fieldType: 'amount' | 'merchant' | 'date' | 'category', isRequiredField = false) => {
+ if (willFieldBeAutomaticallyFilled(transaction, fieldType)) {
+ return translate('common.automatic');
+ }
+ if (isRequiredField) {
+ return translate('common.required');
+ }
+ return '';
+ },
+ [transaction, translate],
+ );
+
const fields = [
{
item: (
@@ -439,6 +477,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: shouldShowSmartScanFields && shouldShowAmountField,
+ isRequired: true,
},
{
item: (
@@ -472,6 +511,7 @@ function MoneyRequestConfirmationListFooter({
),
shouldShow: true,
+ isRequired: true,
},
{
item: (
@@ -499,6 +539,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: isDistanceRequest,
+ isRequired: true,
},
{
item: (
@@ -536,6 +577,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: isDistanceRequest,
+ isRequired: false,
},
{
item: (
@@ -557,11 +599,13 @@ function MoneyRequestConfirmationListFooter({
interactive={!isReadOnly}
brickRoadIndicator={shouldDisplayMerchantError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={shouldDisplayMerchantError ? translate('common.error.fieldRequired') : ''}
- rightLabel={isMerchantRequired && !shouldDisplayMerchantError ? translate('common.required') : ''}
+ rightLabel={getRightLabel('merchant', !!isMerchantRequired && !shouldDisplayMerchantError)}
+ rightLabelIcon={getRightLabelIcon('merchant')}
numberOfLinesTitle={2}
/>
),
shouldShow: shouldShowMerchant,
+ isRequired: false,
},
{
item: (
@@ -601,10 +645,12 @@ function MoneyRequestConfirmationListFooter({
titleStyle={styles.flex1}
disabled={didConfirm}
interactive={!isReadOnly}
- rightLabel={isCategoryRequired ? translate('common.required') : ''}
+ rightLabel={getRightLabel('category', isCategoryRequired)}
+ rightLabelIcon={getRightLabelIcon('category')}
/>
),
shouldShow: shouldShowCategories,
+ isRequired: false,
},
{
item: (
@@ -630,6 +676,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: shouldShowDate,
+ isRequired: false,
},
...policyTagLists.map(({name}, index) => {
const tagVisibilityItem = tagVisibility.at(index);
@@ -663,6 +710,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow,
+ isRequired: false,
};
}),
{
@@ -686,6 +734,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: shouldShowTax,
+ isRequired: false,
},
{
item: (
@@ -708,6 +757,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: shouldShowTax,
+ isRequired: false,
},
{
item: (
@@ -732,6 +782,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: shouldShowAttendees,
+ isRequired: false,
},
{
item: (
@@ -751,6 +802,7 @@ function MoneyRequestConfirmationListFooter({
),
shouldShow: shouldShowReimbursable,
isSupplementary: true,
+ isRequired: false,
},
{
item: (
@@ -769,6 +821,7 @@ function MoneyRequestConfirmationListFooter({
),
shouldShow: shouldShowBillable,
+ isRequired: false,
},
{
item: (
@@ -790,6 +843,7 @@ function MoneyRequestConfirmationListFooter({
/>
),
shouldShow: isPolicyExpenseChat,
+ isRequired: false,
},
];
@@ -852,9 +906,14 @@ function MoneyRequestConfirmationListFooter({
return badges;
}, [firstDay, lastDay, translate, tripDays]);
+ const shouldRestrictHeight = useMemo(() => !showMoreFields && isScan, [isScan, showMoreFields]);
+
const receiptThumbnailContent = useMemo(
() => (
-
+
{isLocalFile && Str.isPDF(receiptFilename) ? (
{
@@ -910,17 +969,20 @@ function MoneyRequestConfirmationListFooter({
fileExtension={fileExtension}
shouldUseThumbnailImage
shouldUseInitialObjectPosition={isDistanceRequest}
+ shouldUseFullHeight
/>
)}
-
+
),
[
styles.moneyRequestImage,
- styles.expenseViewImageSmall,
+ styles.receiptPreviewAspectRatio,
styles.cursorDefault,
styles.h100,
styles.flex1,
+ styles.expenseViewImageSmall,
+ shouldRestrictHeight,
isLocalFile,
receiptFilename,
translate,
@@ -934,10 +996,10 @@ function MoneyRequestConfirmationListFooter({
fileExtension,
isDistanceRequest,
transactionID,
+ isReceiptEditable,
+ reportID,
action,
iouType,
- reportID,
- isReceiptEditable,
],
);
@@ -1016,7 +1078,7 @@ function MoneyRequestConfirmationListFooter({
>
)}
{(!shouldShowMap || isManualDistanceRequest) && (
-
+
{hasReceiptImageOrThumbnail
? receiptThumbnailContent
: showReceiptEmptyState && (
@@ -1028,12 +1090,59 @@ function MoneyRequestConfirmationListFooter({
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRoute()));
}}
- style={styles.expenseViewImageSmall}
+ style={[styles.expenseViewImageSmall, !shouldRestrictHeight && styles.receiptPreviewAspectRatio]}
/>
)}
)}
- {fields.filter((field) => field.shouldShow).map((field) => field.item)}
+
+
+ {isScan && (
+
+
+ {translate('iou.automaticallyEnterExpenseDetails')}
+
+ )}
+
+ {fields.filter((field) => field.shouldShow && (field.isRequired ?? false)).map((field) => field.item)}
+
+ {!shouldRestrictHeight &&
+ fields
+ .filter((field) => field.shouldShow && !(field.isRequired ?? false))
+ .map((field) => (
+
+ {field.item}
+
+ ))}
+
+ {shouldRestrictHeight && fields.some((field) => field.shouldShow && !(field.isRequired ?? false)) && (
+
+
+
+ )}
+
>
);
}
@@ -1081,5 +1190,6 @@ export default memo(
prevProps.shouldShowTax === nextProps.shouldShowTax &&
deepEqual(prevProps.transaction, nextProps.transaction) &&
prevProps.transactionID === nextProps.transactionID &&
- prevProps.unit === nextProps.unit,
+ prevProps.unit === nextProps.unit &&
+ prevProps.showMoreFields === nextProps.showMoreFields,
);
diff --git a/src/components/ReceiptImage/index.tsx b/src/components/ReceiptImage/index.tsx
index 1453f02212881..8e4eacec98821 100644
--- a/src/components/ReceiptImage/index.tsx
+++ b/src/components/ReceiptImage/index.tsx
@@ -221,6 +221,7 @@ function ReceiptImage({
fallbackIconBackground={fallbackIconBackground}
objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP}
onLoad={onLoad}
+ shouldUseFullHeight={shouldUseFullHeight}
onLoadFailure={onLoadFailure}
/>
);
diff --git a/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx b/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx
index 88d22cb0f4504..f78f83383427b 100644
--- a/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx
+++ b/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx
@@ -141,6 +141,7 @@ function BaseSelectionListWithSections({
isSelected,
canShowProductTrainingTooltip,
renderScrollComponent,
+ ListFooterComponentStyle,
shouldShowRightCaret,
shouldHighlightSelectedItem = true,
ref,
@@ -1049,6 +1050,7 @@ function BaseSelectionListWithSections({
{listFooterContent}
>
}
+ ListFooterComponentStyle={ListFooterComponentStyle}
onEndReached={handleOnEndReached}
onEndReachedThreshold={onEndReachedThreshold}
scrollEventThrottle={scrollEventThrottle}
diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts
index be3f9a598dcb8..71b9e590d4a57 100644
--- a/src/components/SelectionListWithSections/types.ts
+++ b/src/components/SelectionListWithSections/types.ts
@@ -963,6 +963,9 @@ type SelectionListProps = Partial & {
/** Custom scroll component to use instead of the default ScrollView */
renderScrollComponent?: (props: ScrollViewProps) => ReactElement>;
+ /** Styles to apply to the list footer component */
+ ListFooterComponentStyle?: StyleProp;
+
/** Whether to show the right caret icon */
shouldShowRightCaret?: boolean;
diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx
index 3e5f521e2daf6..38fd184fddfbf 100644
--- a/src/components/ThumbnailImage.tsx
+++ b/src/components/ThumbnailImage.tsx
@@ -76,6 +76,9 @@ type ThumbnailImageProps = {
/** Callback to be called when the image loads */
onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void;
+
+ /** Whether the image should use the full height of the container */
+ shouldUseFullHeight?: boolean;
};
function ThumbnailImage({
@@ -96,6 +99,7 @@ function ThumbnailImage({
onLoadFailure,
onMeasure,
loadingIndicatorStyles,
+ shouldUseFullHeight,
onLoad,
}: ThumbnailImageProps) {
const styles = useThemeStyles();
@@ -173,6 +177,7 @@ function ThumbnailImage({
loadingIconSize={loadingIconSize}
loadingIndicatorStyles={loadingIndicatorStyles}
onLoad={onLoad}
+ shouldUseFullHeight={shouldUseFullHeight}
/>
diff --git a/src/hooks/useStyleUtils.ts b/src/hooks/useStyleUtils.ts
index 8e6ad31396d50..0fafab3b82236 100644
--- a/src/hooks/useStyleUtils.ts
+++ b/src/hooks/useStyleUtils.ts
@@ -1,7 +1,8 @@
import {useContext} from 'react';
import ThemeStylesContext from '@styles/theme/context/ThemeStylesContext';
+import type {StyleUtilsType} from '@styles/utils';
-function useStyleUtils() {
+function useStyleUtils(): StyleUtilsType {
const themeStylesContext = useContext(ThemeStylesContext);
if (!themeStylesContext) {
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 01a87c718bc2d..1ef89b036b791 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: 'Karte',
whyDoWeAskForThis: 'Warum fragen wir danach?',
required: 'Erforderlich',
+ automatic: 'Automatisch',
showing: 'Anzeigen',
of: 'von',
default: 'Standardmäßig',
@@ -1180,6 +1181,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: 'Beleg wartet auf Abgleich mit Kartentransaktion. Als Barzahlung markieren, um abzubrechen.',
markAsCash: 'Als Barzahlung markieren',
routePending: 'Route wird bearbeitet...',
+ automaticallyEnterExpenseDetails: 'Concierge wird automatisch die Ausgabendetails für Sie eingeben, oder Sie können sie manuell hinzufügen.',
receiptScanning: () => ({
one: 'Beleg scannen...',
other: 'Belege werden gescannt...',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index dad58f2fea12a..f528df2f5f786 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -559,6 +559,7 @@ const translations = {
card: 'Card',
whyDoWeAskForThis: 'Why do we ask for this?',
required: 'Required',
+ automatic: 'Automatic',
showing: 'Showing',
of: 'of',
default: 'Default',
@@ -1166,6 +1167,7 @@ const translations = {
pendingMatchWithCreditCardDescription: 'Receipt pending match with card transaction. Mark as cash to cancel.',
markAsCash: 'Mark as cash',
routePending: 'Route pending...',
+ automaticallyEnterExpenseDetails: 'Concierge will automatically enter the expense details for you, or you can add them manually.',
receiptScanning: () => ({
one: 'Receipt scanning...',
other: 'Receipts scanning...',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index c94388bdf1554..17c529f343a70 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -244,6 +244,7 @@ const translations: TranslationDeepObject = {
card: 'Tarjeta',
whyDoWeAskForThis: '¿Por qué pedimos esto?',
required: 'Obligatorio',
+ automatic: 'Automático',
showing: 'Mostrando',
of: 'de',
default: 'Predeterminado',
@@ -834,6 +835,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: 'Recibo pendiente de adjuntar con la transacción de la tarjeta. Márcalo como efectivo para cancelar.',
markAsCash: 'Marcar como efectivo',
routePending: 'Ruta pendiente...',
+ automaticallyEnterExpenseDetails: 'Concierge introducirá automáticamente los detalles del gasto por ti, o puedes añadirlos manualmente.',
findExpense: 'Buscar gasto',
deletedTransaction: ({amount, merchant}) => `eliminó un gasto (${amount} para ${merchant})`,
movedFromReport: ({reportName}) => `movió un gasto${reportName ? ` desde ${reportName}` : ''}`,
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 1c3a3bde56c53..7fe5c477296a8 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: 'Carte',
whyDoWeAskForThis: 'Pourquoi demandons-nous cela ?',
required: 'Requis',
+ automatic: 'Automatique',
showing: 'Affichage',
of: 'de',
default: 'Par défaut',
@@ -1182,6 +1183,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: 'Reçu en attente de correspondance avec une transaction par carte. Marquer comme espèce pour annuler.',
markAsCash: 'Marquer comme espèces',
routePending: 'Itinéraire en attente...',
+ automaticallyEnterExpenseDetails: 'Concierge saisira automatiquement les détails de la dépense pour vous, ou vous pouvez les ajouter manuellement.',
receiptScanning: () => ({
one: 'Numérisation du reçu...',
other: 'Numérisation des reçus...',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 2beabe982c4e4..7c2a23e8d58a5 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: 'Carta',
whyDoWeAskForThis: 'Perché lo chiediamo?',
required: 'Richiesto',
+ automatic: 'Automatico',
showing: 'Mostrando',
of: 'di',
default: 'Predefinito',
@@ -1177,6 +1178,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: 'Ricevuta in attesa di abbinamento con transazione della carta. Segna come contante per annullare.',
markAsCash: 'Segna come contante',
routePending: 'Instradamento in corso...',
+ automaticallyEnterExpenseDetails: 'Concierge inserirà automaticamente i dettagli della spesa per te, oppure puoi aggiungerli manualmente.',
receiptScanning: () => ({
one: 'Scansione della ricevuta...',
other: 'Scansione delle ricevute...',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index b441f6bbe6166..4ab70dbd2e02f 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: 'カード',
whyDoWeAskForThis: 'なぜこれを尋ねるのですか?',
required: '必須',
+ automatic: '自動',
showing: '表示中',
of: 'の',
default: 'デフォルト',
@@ -1179,6 +1180,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: '領収書がカード取引と一致待ちです。現金としてマークしてキャンセルしてください。',
markAsCash: '現金としてマーク',
routePending: 'ルートを保留中...',
+ automaticallyEnterExpenseDetails: 'コンシェルジュが自動的に経費の詳細を入力するか、手動で追加することができます。',
receiptScanning: () => ({
one: '領収書をスキャン中...',
other: '領収書をスキャン中...',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 12ec42ec4e89a..857228923f761 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: 'Kaart',
whyDoWeAskForThis: 'Waarom vragen we hierom?',
required: 'Vereist',
+ automatic: 'Automatisch',
showing: 'Weergeven',
of: 'of',
default: 'Standaard',
@@ -1177,6 +1178,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: 'Ontvangst in afwachting van overeenkomst met kaarttransactie. Markeer als contant om te annuleren.',
markAsCash: 'Als contant markeren',
routePending: 'Route in behandeling...',
+ automaticallyEnterExpenseDetails: 'Concierge zal automatisch de uitgavendetails voor je invoeren, of je kunt ze handmatig toevoegen.',
receiptScanning: () => ({
one: 'Bonnetjes scannen...',
other: 'Bonnen scannen...',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 1ed301049f106..5f5b36181093d 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: 'Karta',
whyDoWeAskForThis: 'Dlaczego o to prosimy?',
required: 'Wymagane',
+ automatic: 'Automatyczny',
showing: 'Pokazywanie',
of: 'of',
default: 'Domyślny',
@@ -1176,6 +1177,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: 'Paragon oczekuje na dopasowanie z transakcją kartą. Oznacz jako gotówka, aby anulować.',
markAsCash: 'Oznacz jako gotówka',
routePending: 'Trasa w toku...',
+ automaticallyEnterExpenseDetails: 'Concierge automatycznie wprowadzi szczegóły wydatku za Ciebie lub możesz dodać je ręcznie.',
receiptScanning: () => ({
one: 'Skanowanie paragonu...',
other: 'Skanowanie paragonów...',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 7fb8aa7d06370..829d4d8e21c9c 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: 'Cartão',
whyDoWeAskForThis: 'Por que pedimos isso?',
required: 'Obrigatório',
+ automatic: 'Automático',
showing: 'Mostrando',
of: 'of',
default: 'Padrão',
@@ -1177,6 +1178,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: 'Recibo pendente de correspondência com transação do cartão. Marcar como dinheiro para cancelar.',
markAsCash: 'Marcar como dinheiro',
routePending: 'Rota pendente...',
+ automaticallyEnterExpenseDetails: 'O Concierge inserirá automaticamente os detalhes da despesa para você, ou você pode adicioná-los manualmente.',
receiptScanning: () => ({
one: 'Escaneando recibo...',
other: 'Digitalização de recibos...',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index b7b5babb7294e..ba1d4ca3e6178 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -566,6 +566,7 @@ const translations: TranslationDeepObject = {
card: '卡片',
whyDoWeAskForThis: '我们为什么要求这个?',
required: '必填',
+ automatic: '自动',
showing: '显示中',
of: '的',
default: '默认',
@@ -1165,6 +1166,7 @@ const translations: TranslationDeepObject = {
pendingMatchWithCreditCardDescription: '收据待与卡交易匹配。标记为现金以取消。',
markAsCash: '标记为现金',
routePending: '路由处理中...',
+ automaticallyEnterExpenseDetails: 'Concierge 将自动为您输入费用详情,或者您可以手动添加。',
receiptScanning: () => ({
one: '收据扫描中...',
other: '正在扫描收据...',
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index da4424cb42800..f3ce5de126414 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -2030,6 +2030,21 @@ function createUnreportedExpenseSections(transactions: Array, fieldType: 'amount' | 'merchant' | 'date' | 'category'): boolean {
+ if (!transaction?.receipt) {
+ return false;
+ }
+
+ const isSmartScanActive = isScanRequest(transaction);
+
+ if (!isSmartScanActive) {
+ return false;
+ }
+
+ const autoFillableFields = ['amount', 'merchant', 'date', 'category'];
+ return autoFillableFields.includes(fieldType);
+}
+
function isExpenseUnreported(transaction?: Transaction): transaction is UnreportedTransaction {
return transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
}
@@ -2097,6 +2112,7 @@ export {
isCreatedMissing,
areRequiredFieldsEmpty,
hasMissingSmartscanFields,
+ willFieldBeAutomaticallyFilled,
hasPendingRTERViolation,
allHavePendingRTERViolation,
hasPendingUI,
diff --git a/src/styles/utils/getReceiptContainerStyles/index.native.ts b/src/styles/utils/getReceiptContainerStyles/index.native.ts
new file mode 100644
index 0000000000000..f1811dcb349d9
--- /dev/null
+++ b/src/styles/utils/getReceiptContainerStyles/index.native.ts
@@ -0,0 +1,5 @@
+const getReceiptContainerStyles = () => ({
+ flexGrow: 1,
+});
+
+export default getReceiptContainerStyles;
diff --git a/src/styles/utils/getReceiptContainerStyles/index.ts b/src/styles/utils/getReceiptContainerStyles/index.ts
new file mode 100644
index 0000000000000..afdc0bca0bfa7
--- /dev/null
+++ b/src/styles/utils/getReceiptContainerStyles/index.ts
@@ -0,0 +1,5 @@
+const getReceiptContainerStyles = () => ({
+ flex: 1,
+});
+
+export default getReceiptContainerStyles;
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 31f4e4341dd74..66a5f79e4b2f6 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -31,6 +31,7 @@ import getHighResolutionInfoWrapperStyle from './getHighResolutionInfoWrapperSty
import getMoneyRequestReportPreviewStyle from './getMoneyRequestReportPreviewStyle';
import getNavigationBarType from './getNavigationBarType/index';
import getNavigationModalCardStyle from './getNavigationModalCardStyles';
+import getReceiptContainerStyles from './getReceiptContainerStyles';
import getSafeAreaInsets from './getSafeAreaInsets';
import getSuccessReportCardLostIllustrationStyle from './getSuccessReportCardLostIllustrationStyle';
import {compactContentContainerStyles} from './optionRowStyles';
@@ -1345,6 +1346,7 @@ const staticStyleUtils = {
getNavigationBarType,
getSuccessReportCardLostIllustrationStyle,
getOptionMargin,
+ getReceiptContainerStyles,
};
const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({