Skip to content

Commit d0d37ea

Browse files
committed
Merge remote-tracking branch 'origin/main' into perf/workspaces-tab-button-rerenders
2 parents 0866854 + dfe7f59 commit d0d37ea

46 files changed

Lines changed: 811 additions & 204 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/articles/new-expensify/workspaces/Workspace-Rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Once enabled, go to the **Rules** tab in the left menu to manage expense-level s
4848

4949
When an expense breaks a Workspace Rule or Category Rule, the expense is flagged with a violation and the approver is prompted to manually review it before approval.
5050

51-
![Expense showing violations]({{site.url}}/assets/images/ExpensifyHelp-FlagExpensesMissing|temizedReceipts_02.png){:width="100%"}
51+
![Expense showing violations]({{site.url}}/assets/images/ExpensifyHelp-FlagExpensesMissingItemizedReceipts_02.png){:width="100%"}
5252

5353
---
5454

src/ROUTES.ts

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -298,51 +298,6 @@ const DYNAMIC_ROUTES = {
298298
path: 'category-edit',
299299
entryScreens: [SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
300300
},
301-
WORKSPACE_CATEGORY_SETTINGS: {
302-
path: 'category/:categoryName',
303-
entryScreens: [SCREENS.WORKSPACE.CATEGORIES],
304-
getRoute: (categoryName: string) => `category/${encodeURIComponent(categoryName)}` as const,
305-
},
306-
WORKSPACE_CATEGORY_EDIT: {
307-
path: 'edit',
308-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS],
309-
},
310-
WORKSPACE_CATEGORY_PAYROLL_CODE: {
311-
path: 'payroll-code',
312-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS],
313-
},
314-
WORKSPACE_CATEGORY_GL_CODE: {
315-
path: 'gl-code',
316-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS],
317-
},
318-
WORKSPACE_CATEGORY_DEFAULT_TAX_RATE: {
319-
path: 'tax-rate',
320-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
321-
},
322-
WORKSPACE_CATEGORY_FLAG_AMOUNTS_OVER: {
323-
path: 'flag-amounts',
324-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
325-
},
326-
WORKSPACE_CATEGORY_DESCRIPTION_HINT: {
327-
path: 'description-hint',
328-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
329-
},
330-
WORKSPACE_CATEGORY_REQUIRED_FIELDS: {
331-
path: 'required-fields',
332-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
333-
},
334-
WORKSPACE_CATEGORY_APPROVER: {
335-
path: 'approver',
336-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
337-
},
338-
WORKSPACE_CATEGORY_REQUIRE_RECEIPTS_OVER: {
339-
path: 'require-receipts-over',
340-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
341-
},
342-
WORKSPACE_CATEGORY_REQUIRE_ITEMIZED_RECEIPTS_OVER: {
343-
path: 'require-itemized-receipts-over',
344-
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_SETTINGS],
345-
},
346301
NOTIFICATION_PREFERENCES: {
347302
path: 'notification-preferences',
348303
entryScreens: [SCREENS.REPORT_SETTINGS.ROOT, SCREENS.PROFILE_ROOT],
@@ -2404,6 +2359,10 @@ const ROUTES = {
24042359
return `workspaces/${policyID}/categories` as const;
24052360
},
24062361
},
2362+
WORKSPACE_CATEGORY_SETTINGS: {
2363+
route: 'workspaces/:policyID/category/:categoryName',
2364+
getRoute: (policyID: string, categoryName: string) => `workspaces/${policyID}/category/${encodeURIComponent(categoryName)}` as const,
2365+
},
24072366
WORKSPACE_UPGRADE: {
24082367
route: 'workspaces/:policyID?/upgrade/:featureName?',
24092368
getRoute: (policyID?: string, featureName?: string, backTo?: string) =>
@@ -2422,14 +2381,6 @@ const ROUTES = {
24222381
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
24232382
getRoute: (backTo?: string) => getUrlWithBackToParam(`workspaces/pay-and-downgrade` as const, backTo),
24242383
},
2425-
WORKSPACE_CATEGORIES_SETTINGS: {
2426-
route: 'workspaces/:policyID/categories/settings',
2427-
getRoute: (policyID: string) => `workspaces/${policyID}/categories/settings` as const,
2428-
},
2429-
WORKSPACE_CATEGORY_CREATE: {
2430-
route: 'workspaces/:policyID/categories/new',
2431-
getRoute: (policyID: string) => `workspaces/${policyID}/categories/new` as const,
2432-
},
24332384
WORKSPACE_CATEGORY_EDIT: {
24342385
route: 'workspaces/:policyID/category/:categoryName/edit',
24352386
getRoute: (policyID: string, categoryName: string) => `workspaces/${policyID}/category/${encodeURIComponent(categoryName)}/edit` as const,

src/SCREENS.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -782,11 +782,11 @@ const SCREENS = {
782782
NAME: 'Workspace_Overview_Name',
783783
CLIENT_ID: 'Workspace_Overview_Client_ID',
784784
CATEGORY_CREATE: 'Category_Create',
785-
DYNAMIC_CATEGORY_EDIT: 'Dynamic_Category_Edit',
786-
DYNAMIC_CATEGORY_PAYROLL_CODE: 'Dynamic_Category_Payroll_Code',
787-
DYNAMIC_CATEGORY_GL_CODE: 'Dynamic_Category_GL_Code',
788-
DYNAMIC_CATEGORY_SETTINGS: 'Dynamic_Category_Settings',
789785
DYNAMIC_CATEGORY_CREATE: 'Dynamic_Category_Create',
786+
CATEGORY_EDIT: 'Category_Edit',
787+
CATEGORY_PAYROLL_CODE: 'Category_Payroll_Code',
788+
CATEGORY_GL_CODE: 'Category_GL_Code',
789+
CATEGORY_SETTINGS: 'Category_Settings',
790790
CATEGORY_DEFAULT_TAX_RATE: 'Category_Default_Tax_Rate',
791791
CATEGORY_FLAG_AMOUNTS_OVER: 'Category_Flag_Amounts_Over',
792792
DYNAMIC_EXPENSE_LIMIT_TYPE_SELECTOR: 'Dynamic_Expense_Limit_Type_Selector',

src/components/AddPaymentMethodMenu.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,17 @@ function AddPaymentMethodMenu({
7575

7676
const isPersonalOnlyOption = canUsePersonalBankAccount && !canUseBusinessBankAccount;
7777

78+
const isLoadingIntroSelected = isLoadingOnyxValue(introSelectedStatus);
79+
7880
// We temporarily disabled P2P debit cards so we will automatically select the personal bank account option if there is no other option to select.
7981
useEffect(() => {
80-
if (!isVisible || !isPersonalOnlyOption || isLoadingOnyxValue(introSelectedStatus)) {
82+
if (!isVisible || !isPersonalOnlyOption || isLoadingIntroSelected) {
8183
return;
8284
}
8385

8486
completePaymentOnboarding(CONST.PAYMENT_SELECTED.PBA, introSelected, isSelfTourViewed, betas);
8587
onItemSelected(CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT);
86-
}, [betas, introSelected, introSelectedStatus, introSelectedStatus.status, isPersonalOnlyOption, isVisible, onItemSelected, isSelfTourViewed]);
88+
}, [betas, introSelected, isLoadingIntroSelected, isPersonalOnlyOption, isVisible, onItemSelected, isSelfTourViewed]);
8789

8890
if (isPersonalOnlyOption) {
8991
return null;

src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
import type {SortableColumnName} from '@libs/ReportUtils';
6363
import {compareValues, getColumnsToShow, getTableMinWidth, isTransactionAmountTooLong, isTransactionTaxAmountTooLong} from '@libs/SearchUIUtils';
6464
import {getPendingSubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction';
65+
import {compareByRBR} from '@libs/TransactionPreviewUtils';
6566
import {getTransactionPendingAction, isTransactionPendingDelete, shouldShowExpenseBreakdown} from '@libs/TransactionUtils';
6667
import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear';
6768
import isReportOpenInSuperWideRHP from '@navigation/helpers/isReportOpenInSuperWideRHP';
@@ -181,6 +182,7 @@ function MoneyRequestReportTransactionList({
181182
const [nonPersonalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST);
182183
const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);
183184
const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
185+
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
184186

185187
const shouldShowGroupedTransactions = isExpenseReport(report) && !isIOUReport(report);
186188

@@ -286,12 +288,32 @@ function MoneyRequestReportTransactionList({
286288
});
287289

288290
const {sortBy, sortOrder} = sortConfig;
291+
const isDefaultSort = sortBy === CONST.SEARCH.TABLE_COLUMNS.DATE && sortOrder === CONST.SEARCH.SORT_ORDER.ASC;
292+
293+
// Convert reportActions array to a record keyed by reportActionID for compareByRBR
294+
const reportActionsMap = useMemo(() => Object.fromEntries(reportActions.map((ra) => [ra.reportActionID, ra])), [reportActions]);
289295

290296
const sortedTransactions: TransactionWithOptionalHighlight[] = useMemo(() => {
291-
return [...transactions].sort((a, b) =>
292-
compareValues(getTransactionSortValue(a, sortBy, report, policy), getTransactionSortValue(b, sortBy, report, policy), sortOrder, sortBy, localeCompare, true),
293-
);
294-
}, [sortBy, sortOrder, transactions, localeCompare, report, policy]);
297+
return [...transactions].sort((a, b) => {
298+
// When on default sort (Date/ASC), prioritize RBR-flagged transactions
299+
if (isDefaultSort && allTransactionViolations) {
300+
const rbrComparison = compareByRBR(
301+
a,
302+
b,
303+
allTransactionViolations,
304+
currentUserDetails?.login ?? '',
305+
currentUserDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID,
306+
report,
307+
policy,
308+
reportActionsMap,
309+
);
310+
if (rbrComparison !== 0) {
311+
return rbrComparison;
312+
}
313+
}
314+
return compareValues(getTransactionSortValue(a, sortBy, report, policy), getTransactionSortValue(b, sortBy, report, policy), sortOrder, sortBy, localeCompare, true);
315+
});
316+
}, [sortBy, sortOrder, transactions, localeCompare, report, policy, isDefaultSort, allTransactionViolations, currentUserDetails?.login, currentUserDetails?.accountID, reportActionsMap]);
295317

296318
const resolvedTransactions = useMemo(() => resolveTransactionCardFields(sortedTransactions, cardList, translate), [sortedTransactions, cardList, translate]);
297319

src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {showContextMenuForReport} from '@components/ShowContextMenuContext';
1919
import StatusBadge from '@components/StatusBadge';
2020
import Text from '@components/Text';
2121
import {useCurrencyListActions} from '@hooks/useCurrencyList';
22+
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
2223
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
2324
import useLocalize from '@hooks/useLocalize';
2425
import useOnyx from '@hooks/useOnyx';
@@ -55,6 +56,7 @@ import shouldAdjustScroll from '@libs/shouldAdjustScroll';
5556
import {startSpan} from '@libs/telemetry/activeSpans';
5657
import {getPendingSubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction';
5758
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
59+
import {compareByRBR} from '@libs/TransactionPreviewUtils';
5860
import {hasPendingUI, isManagedCardTransaction, isPending} from '@libs/TransactionUtils';
5961
import colors from '@styles/theme/colors';
6062
import variables from '@styles/variables';
@@ -132,6 +134,7 @@ function MoneyRequestReportPreviewContent({
132134
const shouldShowLoading =
133135
chatReportLoadingState != null && chatReportLoadingState.hasOnceLoadedReportActions !== true && transactions.length === 0 && !chatReportMetadata?.isOptimisticReport;
134136
// `hasOnceLoadedReportActions` becomes true before transactions populate fully,
137+
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
135138
// so we defer the loading state update to ensure transactions are loaded
136139
const shouldShowLoadingDeferred = useDeferredValue(shouldShowLoading);
137140
const lastTransaction = transactions?.at(0);
@@ -158,6 +161,7 @@ function MoneyRequestReportPreviewContent({
158161
const theme = useTheme();
159162
const styles = useThemeStyles();
160163
const StyleUtils = useStyleUtils();
164+
const currentUserDetails = useCurrentUserPersonalDetails();
161165
const {translate, formatPhoneNumber} = useLocalize();
162166
const {convertToDisplayString} = useCurrencyListActions();
163167
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
@@ -351,7 +355,15 @@ function MoneyRequestReportPreviewContent({
351355
thumbsUpScale.set(isApprovedAnimationRunning ? withDelay(CONST.ANIMATION_THUMBS_UP_DELAY, withSpring(1, {duration: CONST.ANIMATION_THUMBS_UP_DURATION})) : 1);
352356
}, [isApproved, isApprovedAnimationRunning, thumbsUpScale]);
353357

354-
const carouselTransactions = useMemo(() => (shouldShowAccessPlaceHolder ? [] : transactions.slice(0, 11)), [shouldShowAccessPlaceHolder, transactions]);
358+
const carouselTransactions = useMemo(() => {
359+
if (shouldShowAccessPlaceHolder) {
360+
return [];
361+
}
362+
const sorted = [...transactions].sort((a, b) =>
363+
compareByRBR(a, b, transactionViolations, currentUserDetails?.login ?? '', currentUserDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID, iouReport, policy),
364+
);
365+
return sorted.slice(0, 11);
366+
}, [shouldShowAccessPlaceHolder, transactions, transactionViolations, currentUserDetails?.login, currentUserDetails?.accountID, iouReport, policy]);
355367
const prevCarouselTransactionLength = useRef(0);
356368

357369
useEffect(() => {

src/components/TestDrive/TestDriveDemo.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
1818
import useThemeStyles from '@hooks/useThemeStyles';
1919
import {openReport} from '@libs/actions/Report';
2020
import {completeTestDriveTask} from '@libs/actions/Task';
21+
import {setSelfTourViewed} from '@libs/actions/Welcome';
2122
import Log from '@libs/Log';
2223
import Navigation from '@libs/Navigation/Navigation';
2324
import {isAdminRoom} from '@libs/ReportUtils';
@@ -58,6 +59,8 @@ function TestDriveDemo() {
5859
return;
5960
}
6061
if (!viewTourTaskReport) {
62+
// Fallback for accounts with no viewTour task — otherwise selfTourViewed never gets set.
63+
setSelfTourViewed();
6164
if (conciergeReportID && !hasCalledOpenReportRef.current) {
6265
hasCalledOpenReportRef.current = true;
6366
openReport({reportID: conciergeReportID, introSelected, betas});

src/languages/de.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9236,6 +9236,12 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
92369236
restrictDefaultLoginSelection: 'Standard-Login-Auswahl einschränken',
92379237
restrictDefaultLoginSelectionDescription:
92389238
'Verhindert, dass Mitglieder ihre Login-E-Mail-Adresse außerhalb der Unternehmensdomäne ändern, um Richtlinienbeschränkungen zu umgehen.',
9239+
expensifyCardPreferredWorkspace: 'Bevorzugter Workspace für die Expensify Card',
9240+
expensifyCardPreferredWorkspaceDescription:
9241+
'Alle Expensify-Card-Transaktionen werden im bevorzugten Workspace für die Expensify Card statt im bevorzugten Workspace erstellt. Die Aktivierung dieser Funktion überschreibt die Einstellung des bevorzugten Workspace ausschließlich für Expensify-Card-Transaktionen.',
9242+
expensifyCardPreferredWorkspaceDisabledMessage:
9243+
'Um diese Einstellung zu verwenden, muss der bevorzugte Workspace aktiviert und für die Domain die Expensify Card eingerichtet sein.',
9244+
findGroup: 'Gruppe suchen',
92399245
},
92409246
},
92419247
proactiveAppReview: {

src/languages/en.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9241,6 +9241,11 @@ const translations = {
92419241
noWorkspacesMessage: 'There are no workspaces on this domain. A workspace is required to enable this restriction.',
92429242
restrictDefaultLoginSelection: 'Restrict default login selection',
92439243
restrictDefaultLoginSelectionDescription: 'Prevent members from changing their login email away from their company domain to avoid policy restrictions.',
9244+
expensifyCardPreferredWorkspace: 'Expensify Card Preferred Workspace',
9245+
expensifyCardPreferredWorkspaceDescription:
9246+
'All Expensify Card transactions will be created on the Expensify Card Preferred Workspace instead of the Preferred Workspace. Enabling this feature will override the Preferred Workspace setting for Expensify Card transactions only.',
9247+
expensifyCardPreferredWorkspaceDisabledMessage: 'To use this setting, Preferred Workspace must be enabled and the domain must have Expensify Card set up.',
9248+
findGroup: 'Find group',
92449249
},
92459250
},
92469251
};

src/languages/es.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9476,6 +9476,12 @@ ${amount} para ${merchant} - ${date}`,
94769476
noWorkspacesMessage: 'No hay espacios de trabajo en este dominio. Se requiere un espacio de trabajo para habilitar esta restricción.',
94779477
restrictDefaultLoginSelection: 'Restringir el inicio de sesión predeterminado',
94789478
restrictDefaultLoginSelectionDescription: 'Evita que los miembros cambien su correo de inicio de sesión fuera del dominio de la empresa para eludir restricciones de políticas.',
9479+
expensifyCardPreferredWorkspace: 'Espacio de trabajo preferido para Expensify Card',
9480+
expensifyCardPreferredWorkspaceDescription:
9481+
'Todas las transacciones de Expensify Card se crearán en el espacio de trabajo preferido de Expensify Card en lugar del espacio de trabajo preferido. Al habilitar esta función, la configuración de espacio de trabajo preferido se aplicará solo a las transacciones de Expensify Card.',
9482+
expensifyCardPreferredWorkspaceDisabledMessage:
9483+
'Para usar esta configuración, el espacio de trabajo preferido debe estar habilitado y el dominio debe tener Expensify Card configurada.',
9484+
findGroup: 'Encontrar grupo',
94799485
},
94809486
},
94819487
gps: {

0 commit comments

Comments
 (0)