Skip to content

Commit de25915

Browse files
committed
Merge branch 'main' into @chrispader/react-native-reanimated-to-4.2.3
2 parents 2cf7775 + b7c38b1 commit de25915

46 files changed

Lines changed: 1346 additions & 256 deletions

Some content is hidden

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

config/eslint/eslint.seatbelt.tsv

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
"../../src/hooks/useNewTransactions.ts" "react-hooks/refs" 2
227227
"../../src/hooks/useOnboardingFlow.ts" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
228228
"../../src/hooks/useOutstandingBalanceGuard.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1
229+
"../../src/hooks/usePaginatedReportActions.ts" "react-hooks/refs" 1
229230
"../../src/hooks/usePaymentOptions.ts" "react-hooks/refs" 1
230231
"../../src/hooks/usePrevious.ts" "react-hooks/refs" 1
231232
"../../src/hooks/useProactiveAppReview.ts" "react-hooks/purity" 1
@@ -487,7 +488,6 @@
487488
"../../src/pages/ScheduleCall/ScheduleCallPage.tsx" "react-hooks/preserve-manual-memoization" 1
488489
"../../src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCardPage.tsx" "react-hooks/set-state-in-effect" 1
489490
"../../src/pages/Search/SearchMoneyRequestReportPage.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
490-
"../../src/pages/Search/SearchPage.tsx" "react-hooks/refs" 31
491491
"../../src/pages/Search/SearchPage.tsx" "react-hooks/set-state-in-effect" 1
492492
"../../src/pages/Search/SearchTransactionsChangeReport.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
493493
"../../src/pages/Share/ShareRootPage.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
@@ -522,9 +522,11 @@
522522
"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/preserve-manual-memoization" 1
523523
"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/refs" 6
524524
"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/set-state-in-effect" 1
525-
"../../src/pages/inbox/report/ReportActionsList.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 4
526-
"../../src/pages/inbox/report/ReportActionsList.tsx" "react-hooks/refs" 5
525+
"../../src/pages/inbox/report/ReportActionsList.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 5
526+
"../../src/pages/inbox/report/ReportActionsList.tsx" "react-hooks/refs" 7
527527
"../../src/pages/inbox/report/ReportActionsList.tsx" "react-hooks/set-state-in-effect" 3
528+
"../../src/pages/inbox/report/ReportActionsView.tsx" "react-hooks/globals" 1
529+
"../../src/pages/inbox/report/ReportActionsView.tsx" "react-hooks/preserve-manual-memoization" 1
528530
"../../src/pages/inbox/report/TripSummary.tsx" "rulesdir/no-default-id-values" 1
529531
"../../src/pages/inbox/report/UserTypingEventListener.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 4
530532
"../../src/pages/inbox/report/shouldUseEmojiPickerSelection/index.website.ts" "no-restricted-syntax" 1

docs/articles/new-expensify/expensify-card/Set-Up-and-Manage-the-Expensify-Card.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ If a card reaches its expiration date, it automatically deactivates and declines
113113

114114
---
115115

116+
## How to export Expensify Card data as CSV
117+
118+
You can bulk-select cards and export their details to a CSV file for reporting or reconciliation.
119+
120+
1. From the navigation tabs (on the left on web, and at the bottom on mobile), select **Workspaces > [Workspace Name] > Expensify Card**.
121+
2. Select the checkboxes next to the cards you want to export. Use the checkbox in the header row to select all cards at once.
122+
3. Click the **selected** dropdown that appears.
123+
4. Select **Export as CSV**.
124+
125+
The downloaded CSV file includes the following columns for each selected card: email, name, last four digits, type (virtual or physical), limit type, and limit amount.
126+
127+
---
128+
116129
# How to freeze or unfreeze an Expensify Card
117130

118131
As a Workspace Admin, you can freeze or unfreeze any card in your workspace without needing to cancel or reissue it.

docs/articles/new-expensify/reports-and-expenses/Adding-Attendees.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
title: Adding Attendees
3-
description: Learn how to add attendees to your expenses and resolve category limit errors by splitting the cost across participants.
4-
keywords: [New Expensify, add attendees, expense attendees, split expense, category limit, attendee allocation]
5-
internalScope: Audience is all members. Covers adding attendees to an expense. Does not cover category limit policies member management.
3+
description: Learn how to add attendees to your expenses, view attendee columns, and sort by attendees in search results.
4+
keywords: [New Expensify, add attendees, expense attendees, split expense, category limit, attendee allocation, sort attendees, per attendee]
5+
internalScope: Audience is all members. Covers adding attendees to an expense and viewing attendee columns. Does not cover category limit policies member management.
66
---
77

88
# Adding Attendees
@@ -39,14 +39,20 @@ The selected attendees are added to the expense.
3939

4040
---
4141

42-
# How to view attendees on a report
42+
## How to view attendees on a report
4343

44-
When any expense in a report has attendees, the report automatically shows two additional columns:
44+
When any expense in a report has attendees, the report shows two additional columns:
4545

4646
- **Attendees:** displays the attendees added to each expense.
4747
- **Per attendee:** shows the total amount divided equally among all attendees.
4848

49-
These columns appear in both the report and search results.
49+
In search results (**Reports > Expenses**), these columns are not shown by default. To enable them:
50+
51+
1. Go to **Reports > Expenses**.
52+
2. Click **Columns**.
53+
3. Select **Attendees** and **Per attendee amount**.
54+
55+
Once enabled, you can click the column header to sort expenses by that column.
5056

5157
---
5258

docs/articles/new-expensify/reports-and-expenses/Managing-Expenses-in-a-Report.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ The table displays:
9191
- Date
9292
- Merchant
9393
- Category
94-
- Attendees (if any expense has attendees)
95-
- Per attendee (if any expense has attendees)
9694
- Amount
9795
- Workspace violations (if applicable)
9896

97+
Additional columns such as **Attendees** and **Per attendee** can be enabled via the **Columns** picker when attendee tracking is available.
98+
9999
Clicking a row opens the full expense in a side panel (web) or details screen (mobile).
100100

101101
---

docs/articles/new-expensify/workspaces/Create-expense-categories.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
title: Create Expense Categories
33
description: Add categories to use for coding expenses.
4-
keywords: [New Expensify, expense categories, GL codes, payroll codes, chart of accounts, import categories, expense coding]
5-
internalScope: Audience is Workspace Admins. Covers creating, importing, enabling, and managing expense categories, including GL and payroll codes. Does not cover personal expense rules or accounting integration setup.
4+
keywords: [New Expensify, expense categories, GL codes, payroll codes, chart of accounts, import categories, expense coding, add category from expense, create category inline]
5+
internalScope: Audience is Workspace Admins. Covers creating, importing, enabling, and managing expense categories, including GL and payroll codes and inline category creation from the expense flow. Does not cover personal expense rules or accounting integration setup.
66
---
77

88

@@ -31,6 +31,19 @@ To delete a category:
3131

3232
---
3333

34+
## How to add a category while creating or editing an expense
35+
36+
Workspace Admins can also create a new category directly from the category picker when creating or editing an expense, without navigating to workspace settings first. This option is available when no accounting integration is connected to the workspace.
37+
38+
1. While creating or editing an expense, tap the **Category** field.
39+
2. Tap the **+** icon in the top-right corner.
40+
3. Enter a category name.
41+
4. Tap **Save**.
42+
43+
The new category is immediately applied to the expense and added to the workspace's category list.
44+
45+
---
46+
3447
## How to upload categories using a CSV file
3548

3649
1. In the **navigation tabs** (on the left on web, and at the bottom on mobile), click **Workspaces**.

src/CONST/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3660,6 +3660,7 @@ const CONST = {
36603660
DOWNLOAD_CSV: 'downloadCSV',
36613661
SETTINGS: 'settings',
36623662
EXPORT: 'export',
3663+
SYNC_WITH_GUSTO: 'syncWithGusto',
36633664
},
36643665
MEMBERS_BULK_ACTION_TYPES: {
36653666
REMOVE: 'remove',
@@ -7581,6 +7582,7 @@ const CONST = {
75817582

75827583
DOWNLOADS_PATH: '/Downloads',
75837584
DOWNLOADS_TIMEOUT: 5000,
7585+
75847586
NEW_EXPENSIFY_PATH: '/New Expensify',
75857587
RECEIPTS_UPLOAD_PATH: '/Receipts-Upload',
75867588

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import Icon from '@components/Icon';
4+
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
5+
import useLocalize from '@hooks/useLocalize';
6+
import useTheme from '@hooks/useTheme';
7+
import useThemeStyles from '@hooks/useThemeStyles';
8+
import variables from '@styles/variables';
9+
10+
type DraftIndicatorProps = {
11+
hasDraftComment: boolean;
12+
isAllowedToComment: boolean | null | undefined;
13+
};
14+
15+
function DraftIndicator({hasDraftComment, isAllowedToComment}: DraftIndicatorProps) {
16+
const theme = useTheme();
17+
const styles = useThemeStyles();
18+
const {translate} = useLocalize();
19+
const {Pencil} = useMemoizedLazyExpensifyIcons(['Pencil']);
20+
21+
if (!hasDraftComment || !isAllowedToComment) {
22+
return null;
23+
}
24+
25+
return (
26+
<View
27+
style={styles.ml2}
28+
accessibilityLabel={translate('sidebarScreen.draftedMessage')}
29+
>
30+
<Icon
31+
testID="Pencil Icon"
32+
fill={theme.icon}
33+
src={Pencil}
34+
width={variables.iconSizeSmall}
35+
height={variables.iconSizeSmall}
36+
/>
37+
</View>
38+
);
39+
}
40+
41+
DraftIndicator.displayName = 'OptionRow.DraftIndicator';
42+
43+
export default DraftIndicator;

src/components/LHNOptionsList/OptionRowLHN/OptionRowLHNCore.tsx

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import variables from '@styles/variables';
2626
import CONST from '@src/CONST';
2727
import ONYXKEYS from '@src/ONYXKEYS';
2828
import {isEmptyObject} from '@src/types/utils/EmptyObject';
29+
import DraftIndicator from './OptionRow/DraftIndicator';
2930
import OptionRowAlternateText from './OptionRowAlternateText';
3031
import OptionRowAvatar from './OptionRowAvatar';
3132
import OptionRowErrorBadge from './OptionRowErrorBadge';
@@ -50,7 +51,7 @@ function OptionRowLHN({
5051
const styles = useThemeStyles();
5152
const popoverAnchor = useRef<View>(null);
5253
const StyleUtils = useStyleUtils();
53-
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Pencil', 'Pin']);
54+
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Pin']);
5455

5556
const {onboardingPurpose, onboarding, isScreenFocused} = useLHNTooltipContext();
5657
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
@@ -204,20 +205,10 @@ function OptionRowLHN({
204205
brickRoadIndicator={brickRoadIndicator}
205206
actionBadgeText={actionBadgeText}
206207
/>
207-
{hasDraftComment && !!optionItem.isAllowedToComment && (
208-
<View
209-
style={styles.ml2}
210-
accessibilityLabel={translate('sidebarScreen.draftedMessage')}
211-
>
212-
<Icon
213-
testID="Pencil Icon"
214-
fill={theme.icon}
215-
src={expensifyIcons.Pencil}
216-
width={variables.iconSizeSmall}
217-
height={variables.iconSizeSmall}
218-
/>
219-
</View>
220-
)}
208+
<DraftIndicator
209+
hasDraftComment={hasDraftComment}
210+
isAllowedToComment={optionItem.isAllowedToComment}
211+
/>
221212
{!brickRoadIndicator && !!optionItem.isPinned && (
222213
<View
223214
style={styles.ml2}

src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
2626
import useWindowDimensions from '@hooks/useWindowDimensions';
2727
import {isConsecutiveChronosAutomaticTimerAction} from '@libs/ChronosUtils';
2828
import DateUtils from '@libs/DateUtils';
29+
import {hasDeferredWriteForReport} from '@libs/deferredLayoutWrite';
2930
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
3031
import {getAllNonDeletedTransactions, isActionVisibleOnMoneyRequestReport} from '@libs/MoneyRequestReportUtils';
3132
import Navigation from '@libs/Navigation/Navigation';
@@ -662,6 +663,16 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
662663
return numToRender || undefined;
663664
}, [styles.chatItem.paddingBottom, styles.chatItem.paddingTop, windowHeight, linkedReportActionID]);
664665

666+
const isReportEmpty = isEmpty(visibleReportActions) && isEmpty(transactions) && !showReportActionsLoadingState;
667+
// hasDeferredWriteForReport is non-reactive (reads a module-level Map, not tracked by React).
668+
// This is intentional: we only check on the initial render after the RHP dismisses.
669+
// Once the deferred write flushes and createTransaction runs, Onyx updates make
670+
// transactions non-empty, which drives the transition away from the skeleton.
671+
// Scoping to `report.reportID` ensures an unrelated submit flow's pending dismiss doesn't keep
672+
// *this* report stuck on the skeleton.
673+
const isAwaitingDeferredTransaction = isReportEmpty && hasDeferredWriteForReport(CONST.DEFERRED_LAYOUT_WRITE_KEYS.DISMISS_MODAL, report?.reportID);
674+
const showEmptyState = isReportEmpty && !isAwaitingDeferredTransaction;
675+
665676
if (!report) {
666677
return null;
667678
}
@@ -682,7 +693,12 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
682693
isActive={isFloatingMessageCounterVisible}
683694
onClick={scrollToBottomAndMarkReportAsRead}
684695
/>
685-
{isEmpty(visibleReportActions) && isEmpty(transactions) && !showReportActionsLoadingState ? (
696+
{/* Exactly one of these three branches is active at a time:
697+
1. isAwaitingDeferredTransaction — skeleton while dismiss-first creates the transaction
698+
2. showEmptyState — genuinely empty report
699+
3. !isReportEmpty — report has data, render the FlatList */}
700+
{isAwaitingDeferredTransaction && <ReportActionsListLoadingSkeleton reasonAttributes={skeletonReasonAttributes} />}
701+
{showEmptyState && (
686702
<ScrollView contentContainerStyle={styles.flexGrow1}>
687703
<MoneyRequestViewReportFields
688704
report={report}
@@ -694,7 +710,8 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
694710
policy={policy}
695711
/>
696712
</ScrollView>
697-
) : (
713+
)}
714+
{!isReportEmpty && (
698715
<FlatListWithScrollKey
699716
initialNumToRender={initialNumToRender}
700717
accessibilityLabel={translate('sidebarScreen.listOfChatMessages')}

src/components/ReportActionItem/MoneyReportView.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ type MoneyReportViewProps = {
7070

7171
/** Whether we should display the animated banner above the component */
7272
shouldShowAnimatedBackground?: boolean;
73+
74+
/**
75+
* When true, the Total amount is rendered as a loading indicator regardless of `isOffline`.
76+
* Use this when the caller knows the underlying total is being recomputed and a
77+
* network-independent update is expected, so falling back to the (stale) amount while offline
78+
* would be misleading.
79+
*/
80+
isTotalPending?: boolean;
7381
};
7482

7583
function MoneyReportView({
@@ -80,6 +88,7 @@ function MoneyReportView({
8088
shouldHideThreadDividerLine,
8189
pendingAction,
8290
shouldShowAnimatedBackground = true,
91+
isTotalPending = false,
8392
}: MoneyReportViewProps) {
8493
const theme = useTheme();
8594
const styles = useThemeStyles();
@@ -89,7 +98,7 @@ function MoneyReportView({
8998
const {convertToDisplayString} = useCurrencyListActions();
9099
const {isOffline} = useNetwork();
91100
const isSettled = isSettledReportUtils(report?.reportID);
92-
const isTotalUpdated = hasUpdatedTotal(report, policy);
101+
const isTotalUpdated = hasUpdatedTotal(report, policy) && !isTotalPending;
93102

94103
const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = getMoneyRequestSpendBreakdown(report);
95104
const transactions = useReportTransactions(report?.reportID);
@@ -107,6 +116,7 @@ function MoneyReportView({
107116
context: 'MoneyReportView.Total',
108117
isTotalUpdated,
109118
isOffline,
119+
isTotalPending,
110120
};
111121

112122
const subAmountTextStyles: StyleProp<TextStyle> = [
@@ -228,7 +238,7 @@ function MoneyReportView({
228238
/>
229239
</View>
230240
)}
231-
{!isTotalUpdated && !isOffline ? (
241+
{!isTotalUpdated && (!isOffline || isTotalPending) ? (
232242
<ActivityIndicator
233243
style={[styles.moneyRequestLoadingHeight]}
234244
color={theme.textSupporting}

0 commit comments

Comments
 (0)