Skip to content

Commit b3f30d9

Browse files
committed
Merge branch 'main' of https://github.com/Expensify/App into fix/64799/negative-sign-expense
2 parents 97f63bb + f832fb5 commit b3f30d9

16 files changed

Lines changed: 91 additions & 26 deletions

File tree

src/CONST/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ const CONST = {
950950
CONFIGURE_EXPENSE_REPORT_RULES_HELP_URL: 'https://help.expensify.com/articles/new-expensify/workspaces/Set-up-rules#configure-expense-report-rules',
951951
SELECT_WORKFLOWS_HELP_URL: 'https://help.expensify.com/articles/new-expensify/workspaces/Set-up-workflows#select-workflows',
952952
COPILOT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Add-or-Act-As-a-Copilot',
953+
BULK_UPLOAD_HELP_URL: 'https://help.expensify.com/articles/new-expensify/reports-and-expenses/Create-an-Expense#option-4-bulk-upload-receipts-desktop-only',
953954
ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security',
954955
PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing',
955956
MERGE_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Merge-Accounts',
@@ -967,7 +968,7 @@ const CONST = {
967968
},
968969
STORYLANE: {
969970
ADMIN_TOUR: 'https://app.storylane.io/demo/bbcreg8vccag?embed=inline',
970-
ADMIN_TOUR_MOBILE: 'https://app.storylane.io/demo/sfzzu3s6l3ov?embed=inline',
971+
ADMIN_TOUR_MOBILE: 'https://app.storylane.io/demo/b6faqcdsxgww?embed=inline',
971972
TRACK_WORKSPACE_TOUR: 'https://app.storylane.io/share/agmsfwgasaed?embed=inline',
972973
TRACK_WORKSPACE_TOUR_MOBILE: 'https://app.storylane.io/share/wq4hiwsqvoho?embed=inline',
973974
},

src/components/DropZone/DualDropZone.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function DualDropZone({isEditing, onAttachmentDrop, onReceiptDrop, shouldAcceptS
2828
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
2929

3030
const shouldStackVertically = shouldUseNarrowLayout || isMediumScreenWidth;
31-
const scanReceiptsText = shouldAcceptSingleReceipt ? 'quickAction.scanReceipt' : 'dropzone.scanReceipts';
31+
const scanReceiptsText = shouldAcceptSingleReceipt ? 'dropzone.addReceipt' : 'dropzone.scanReceipts';
3232

3333
return (
3434
<DragAndDropConsumer>

src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,42 @@ function TransactionPreviewContent({
162162
const shouldShowIOUHeader = !!from && !!to;
163163

164164
// If available, retrieve the split share from the splits object of the transaction, if not, display an even share.
165-
const splitShare = useMemo(
166-
() =>
167-
shouldShowSplitShare
168-
? (transaction?.comment?.splits?.find((split) => split.accountID === sessionAccountID)?.amount ??
169-
calculateAmount(isReportAPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, amount ?? 0, requestCurrency ?? '', action?.actorAccountID === sessionAccountID))
170-
: 0,
171-
[shouldShowSplitShare, isReportAPolicyExpenseChat, action?.actorAccountID, participantAccountIDs.length, transaction?.comment?.splits, amount, requestCurrency, sessionAccountID],
172-
);
165+
const actorAccountID = action?.actorAccountID;
166+
const splitShare = useMemo(() => {
167+
if (!shouldShowSplitShare) {
168+
return 0;
169+
}
170+
171+
const splitAmount = transaction?.comment?.splits?.find((split) => split.accountID === sessionAccountID)?.amount;
172+
if (splitAmount !== undefined) {
173+
return splitAmount;
174+
}
175+
176+
let originalParticipantCount = participantAccountIDs.length;
177+
178+
if (isBillSplit) {
179+
// Try to get the participant count from transaction splits data
180+
const transactionSplitsCount = transaction?.comment?.splits?.length;
181+
if (transactionSplitsCount && transactionSplitsCount > 0) {
182+
originalParticipantCount = transactionSplitsCount;
183+
} else if (isMoneyRequestAction(action)) {
184+
originalParticipantCount = getOriginalMessage(action)?.participantAccountIDs?.length ?? participantAccountIDs.length;
185+
}
186+
}
187+
188+
return calculateAmount(isReportAPolicyExpenseChat ? 1 : originalParticipantCount - 1, amount ?? 0, requestCurrency ?? '', actorAccountID === sessionAccountID);
189+
}, [
190+
shouldShowSplitShare,
191+
isReportAPolicyExpenseChat,
192+
participantAccountIDs.length,
193+
transaction?.comment?.splits,
194+
amount,
195+
requestCurrency,
196+
sessionAccountID,
197+
isBillSplit,
198+
action,
199+
actorAccountID,
200+
]);
173201

174202
const shouldWrapDisplayAmount = !(isBillSplit || shouldShowMerchantOrDescription || isTransactionScanning);
175203
const previewTextViewGap = (shouldShowCategoryOrTag || !shouldWrapDisplayAmount) && styles.gap2;

src/hooks/useFilesValidation.tsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi
4545
const filesToValidate = useRef<FileObject[]>([]);
4646
const collectedErrors = useRef<ErrorObject[]>([]);
4747

48+
const deduplicateErrors = useCallback((errors: ErrorObject[]) => {
49+
const uniqueErrors = new Set<string>();
50+
return errors.filter((error) => {
51+
const key = `${error.error}-${error.fileExtension ?? ''}`;
52+
if (uniqueErrors.has(key)) {
53+
return false;
54+
}
55+
uniqueErrors.add(key);
56+
return true;
57+
});
58+
}, []);
59+
4860
const resetValidationState = useCallback(() => {
4961
setIsErrorModalVisible(false);
5062
setPdfFilesToRender([]);
@@ -120,7 +132,7 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi
120132
}
121133

122134
if (collectedErrors.current.length > 0) {
123-
const uniqueErrors = Array.from(new Set(collectedErrors.current.map((error) => JSON.stringify(error)))).map((errorStr) => JSON.parse(errorStr) as ErrorObject);
135+
const uniqueErrors = deduplicateErrors(collectedErrors.current);
124136
setErrorQueue(uniqueErrors);
125137
setCurrentErrorIndex(0);
126138
const firstError = uniqueErrors.at(0);
@@ -135,9 +147,14 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi
135147
proceedWithFilesAction(validFiles.current);
136148
resetValidationState();
137149
}
138-
}, [pdfFilesToRender.length, proceedWithFilesAction, resetValidationState]);
150+
}, [deduplicateErrors, pdfFilesToRender.length, proceedWithFilesAction, resetValidationState]);
139151

140152
const validateAndResizeFiles = (files: FileObject[]) => {
153+
// Early return for empty files
154+
if (files.length === 0) {
155+
return;
156+
}
157+
141158
// Reset collected errors for new validation
142159
collectedErrors.current = [];
143160

@@ -261,21 +278,21 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi
261278
style={styles.invisiblePDF}
262279
previewSourceURL={file.uri ?? ''}
263280
onLoadSuccess={() => {
264-
validatedPDFs.current = [...(validatedPDFs.current ?? []), file];
265-
validFiles.current = [...(validFiles.current ?? []), file];
281+
validatedPDFs.current.push(file);
282+
validFiles.current.push(file);
266283
checkIfAllValidatedAndProceed();
267284
}}
268285
onPassword={() => {
269-
validatedPDFs.current = [...(validatedPDFs.current ?? []), file];
286+
validatedPDFs.current.push(file);
270287
if (isValidatingReceipts) {
271288
collectedErrors.current.push({error: CONST.FILE_VALIDATION_ERRORS.PROTECTED_FILE});
272289
} else {
273-
validFiles.current = [...(validFiles.current ?? []), file];
290+
validFiles.current.push(file);
274291
}
275292
checkIfAllValidatedAndProceed();
276293
}}
277294
onLoadError={() => {
278-
validatedPDFs.current = [...(validatedPDFs.current ?? []), file];
295+
validatedPDFs.current.push(file);
279296
collectedErrors.current.push({error: CONST.FILE_VALIDATION_ERRORS.FILE_CORRUPTED});
280297
checkIfAllValidatedAndProceed();
281298
}}
@@ -287,21 +304,21 @@ function useFilesValidation(proceedWithFilesAction: (files: FileObject[]) => voi
287304
if (!fileError) {
288305
return '';
289306
}
290-
const prompt = getFileValidationErrorText(fileError, {fileType: invalidFileExtension}).reason;
291-
if (fileError === CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE_MULTIPLE) {
307+
const prompt = getFileValidationErrorText(fileError, {fileType: invalidFileExtension}, isValidatingReceipts).reason;
308+
if (fileError === CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE_MULTIPLE || fileError === CONST.FILE_VALIDATION_ERRORS.WRONG_FILE_TYPE) {
292309
return (
293310
<Text>
294311
{prompt}
295-
<TextLink href=""> {translate('attachmentPicker.learnMoreAboutSupportedFiles')}</TextLink>
312+
<TextLink href={CONST.BULK_UPLOAD_HELP_URL}> {translate('attachmentPicker.learnMoreAboutSupportedFiles')}</TextLink>
296313
</Text>
297314
);
298315
}
299316
return prompt;
300-
}, [fileError, invalidFileExtension, translate]);
317+
}, [fileError, invalidFileExtension, isValidatingReceipts, translate]);
301318

302319
const ErrorModal = (
303320
<ConfirmModal
304-
title={getFileValidationErrorText(fileError, {fileType: invalidFileExtension}).title}
321+
title={getFileValidationErrorText(fileError, {fileType: invalidFileExtension}, isValidatingReceipts).title}
305322
onConfirm={onConfirm}
306323
onCancel={hideModalAndReset}
307324
isVisible={isErrorModalVisible}

src/languages/de.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ const translations = {
678678
},
679679
dropzone: {
680680
addAttachments: 'Anhänge hinzufügen',
681+
addReceipt: 'Beleg hinzufügen',
681682
scanReceipts: 'Belege scannen',
682683
replaceReceipt: 'Beleg ersetzen',
683684
},

src/languages/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ const translations = {
668668
},
669669
dropzone: {
670670
addAttachments: 'Add attachments',
671+
addReceipt: 'Add receipt',
671672
scanReceipts: 'Scan receipts',
672673
replaceReceipt: 'Replace receipt',
673674
},

src/languages/es.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ const translations = {
663663
},
664664
dropzone: {
665665
addAttachments: 'Añadir archivos adjuntos',
666+
addReceipt: 'Añadir recibo',
666667
scanReceipts: 'Escanear recibos',
667668
replaceReceipt: 'Reemplazar recibo',
668669
},

src/languages/fr.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ const translations = {
679679
},
680680
dropzone: {
681681
addAttachments: 'Ajouter des pièces jointes',
682+
addReceipt: 'Ajouter un reçu',
682683
scanReceipts: 'Scanner les reçus',
683684
replaceReceipt: 'Remplacer le reçu',
684685
},

src/languages/it.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ const translations = {
678678
},
679679
dropzone: {
680680
addAttachments: 'Aggiungi allegati',
681+
addReceipt: 'Aggiungi ricevuta',
681682
scanReceipts: 'Scansiona ricevute',
682683
replaceReceipt: 'Sostituisci ricevuta',
683684
},

src/languages/ja.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ const translations = {
678678
},
679679
dropzone: {
680680
addAttachments: '添付ファイルを追加',
681+
addReceipt: '領収書を追加',
681682
scanReceipts: '領収書をスキャンする',
682683
replaceReceipt: '領収書を置き換える',
683684
},

0 commit comments

Comments
 (0)