Skip to content

Commit 1da9ea0

Browse files
committed
Merge branch 'main' into issue-83884-2
2 parents 97151da + 48b076c commit 1da9ea0

52 files changed

Lines changed: 1275 additions & 377 deletions

File tree

Some content is hidden

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

src/CONST/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6052,7 +6052,7 @@ const CONST = {
60526052
PM: 'PM',
60536053
},
60546054
INDENTS: ' ',
6055-
PARENT_CHILD_SEPARATOR: ': ',
6055+
PARENT_CHILD_SEPARATOR: ':',
60566056
DISTANCE_MERCHANT_SEPARATOR: '@',
60576057
COLON: ':',
60586058
MAPBOX: {

src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {createContext} from 'react';
33
type MentionReportContextProps = {
44
currentReportID: string | undefined;
55
exactlyMatch?: boolean;
6+
policyID?: string;
67
};
78

89
const MentionReportContext = createContext<MentionReportContextProps>({

src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function MentionReportRenderer({style, tnode, TDefaultRenderer, ...defaultRender
2323
const styles = useThemeStyles();
2424
const StyleUtils = useStyleUtils();
2525
const htmlAttributeReportID = tnode.attributes.reportid;
26-
const {currentReportID: currentReportIDContext, exactlyMatch} = useContext(MentionReportContext);
26+
const {currentReportID: currentReportIDContext, exactlyMatch, policyID} = useContext(MentionReportContext);
2727
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
2828

2929
const {currentReportID} = useCurrentReportIDState();
@@ -32,9 +32,12 @@ function MentionReportRenderer({style, tnode, TDefaultRenderer, ...defaultRender
3232
const [currentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentReportIDValue}`);
3333

3434
// When we invite someone to a room they don't have the policy object, but we still want them to be able to see and click on report mentions, so we only check if the policyID in the report is from a workspace
35-
const isGroupPolicyReport = useMemo(() => currentReport && !isEmptyObject(currentReport) && !!currentReport.policyID && currentReport.policyID !== CONST.POLICY.ID_FAKE, [currentReport]);
35+
const isGroupPolicyReport = useMemo(
36+
() => (!!currentReport && !isEmptyObject(currentReport) && !!currentReport.policyID && currentReport.policyID !== CONST.POLICY.ID_FAKE) || !!policyID,
37+
[currentReport, policyID],
38+
);
3639

37-
const mentionDetails = getReportMentionDetails(htmlAttributeReportID, currentReport, reports, tnode);
40+
const mentionDetails = getReportMentionDetails(htmlAttributeReportID, currentReport, reports, tnode, policyID);
3841
if (!mentionDetails) {
3942
return null;
4043
}

src/components/MoneyReportHeader.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import {
8888
getAllReportActionsErrorsAndReportActionThatRequiresAttention,
8989
getIntegrationIcon,
9090
getIntegrationNameFromExportMessage as getIntegrationNameFromExportMessageUtils,
91+
getLinkedIOUTransaction,
9192
getNextApproverAccountID,
9293
getNonHeldAndFullAmount,
9394
getPolicyExpenseChat,
@@ -1087,15 +1088,15 @@ function MoneyReportHeader({
10871088
setIsHoldEducationalModalVisible(false);
10881089
setNameValuePair(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, true, false, !shouldFailAllRequests);
10891090
if (requestParentReportAction) {
1090-
changeMoneyRequestHoldStatus(requestParentReportAction);
1091+
changeMoneyRequestHoldStatus(requestParentReportAction, transaction);
10911092
}
10921093
};
10931094

10941095
const dismissRejectModalBasedOnAction = () => {
10951096
if (rejectModalAction === CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.HOLD) {
10961097
dismissRejectUseExplanation();
10971098
if (requestParentReportAction) {
1098-
changeMoneyRequestHoldStatus(requestParentReportAction);
1099+
changeMoneyRequestHoldStatus(requestParentReportAction, transaction);
10991100
}
11001101
} else if (rejectModalAction === CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT_BULK) {
11011102
dismissRejectUseExplanation();
@@ -1419,7 +1420,7 @@ function MoneyReportHeader({
14191420

14201421
if (IOUActions.length) {
14211422
for (const action of IOUActions) {
1422-
changeMoneyRequestHoldStatus(action);
1423+
changeMoneyRequestHoldStatus(action, getLinkedIOUTransaction(action, transactions));
14231424
}
14241425
return;
14251426
}
@@ -1428,8 +1429,7 @@ function MoneyReportHeader({
14281429
if (!moneyRequestAction) {
14291430
return;
14301431
}
1431-
1432-
changeMoneyRequestHoldStatus(moneyRequestAction);
1432+
changeMoneyRequestHoldStatus(moneyRequestAction, getLinkedIOUTransaction(moneyRequestAction, transactions));
14331433
}}
14341434
/>
14351435
),
@@ -1779,7 +1779,7 @@ function MoneyReportHeader({
17791779
const isDismissed = isReportSubmitter ? dismissedHoldUseExplanation : dismissedRejectUseExplanation;
17801780

17811781
if (isDismissed || isChatReportDM) {
1782-
changeMoneyRequestHoldStatus(requestParentReportAction);
1782+
changeMoneyRequestHoldStatus(requestParentReportAction, transaction);
17831783
} else if (isReportSubmitter) {
17841784
setIsHoldEducationalModalVisible(true);
17851785
} else {
@@ -1802,7 +1802,7 @@ function MoneyReportHeader({
18021802
return;
18031803
}
18041804

1805-
changeMoneyRequestHoldStatus(requestParentReportAction);
1805+
changeMoneyRequestHoldStatus(requestParentReportAction, transaction);
18061806
},
18071807
},
18081808
[CONST.REPORT.SECONDARY_ACTIONS.SPLIT]: {

src/components/MoneyRequestHeader.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
367367
return;
368368
}
369369

370-
changeMoneyRequestHoldStatus(parentReportAction);
370+
changeMoneyRequestHoldStatus(parentReportAction, transaction);
371371
}}
372372
/>
373373
),
@@ -449,15 +449,15 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
449449
setIsHoldEducationalModalVisible(false);
450450
setNameValuePair(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, true, false, !shouldFailAllRequests);
451451
if (parentReportAction) {
452-
changeMoneyRequestHoldStatus(parentReportAction);
452+
changeMoneyRequestHoldStatus(parentReportAction, transaction);
453453
}
454454
};
455455

456456
const dismissRejectModalBasedOnAction = () => {
457457
if (rejectModalAction === CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.HOLD) {
458458
dismissRejectUseExplanation();
459459
if (parentReportAction) {
460-
changeMoneyRequestHoldStatus(parentReportAction);
460+
changeMoneyRequestHoldStatus(parentReportAction, transaction);
461461
}
462462
} else {
463463
dismissRejectUseExplanation();
@@ -487,7 +487,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
487487

488488
const isDismissed = isReportSubmitter ? dismissedHoldUseExplanation : dismissedRejectUseExplanation;
489489
if (isDismissed || isParentChatReportDM) {
490-
changeMoneyRequestHoldStatus(parentReportAction);
490+
changeMoneyRequestHoldStatus(parentReportAction, transaction);
491491
} else if (isReportSubmitter) {
492492
setIsHoldEducationalModalVisible(true);
493493
} else {
@@ -509,7 +509,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
509509
return;
510510
}
511511

512-
changeMoneyRequestHoldStatus(parentReportAction);
512+
changeMoneyRequestHoldStatus(parentReportAction, transaction);
513513
},
514514
},
515515
[CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.SPLIT]: {

src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ function MoneyRequestReportActionsList({
167167
const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT);
168168
const isTryNewDotNVPDismissed = !!tryNewDot?.classicRedirect?.dismissed;
169169
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
170+
const [betas] = useOnyx(ONYXKEYS.BETAS);
170171
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
171172
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
172173

@@ -732,15 +733,15 @@ function MoneyRequestReportActionsList({
732733
setIsFloatingMessageCounterVisible(false);
733734

734735
if (!hasNewestReportAction) {
735-
openReport({reportID: report.reportID, introSelected});
736+
openReport({reportID: report.reportID, introSelected, betas});
736737
reportScrollManager.scrollToEnd();
737738
return;
738739
}
739740

740741
reportScrollManager.scrollToEnd();
741742
readActionSkipped.current = false;
742743
readNewestAction(report.reportID, !!reportMetadata?.hasOnceLoadedReportActions);
743-
}, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, reportMetadata?.hasOnceLoadedReportActions]);
744+
}, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID, reportMetadata?.hasOnceLoadedReportActions, introSelected, betas]);
744745

745746
const scrollToNewTransaction = useCallback(
746747
(pageY: number) => {

src/components/Search/SearchAutocompleteList.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,15 @@ function SearchAutocompleteList({
241241
prevQueryRef.current = autocompleteQueryValue;
242242

243243
if (queryChanged) {
244-
// When query changes, focus on the search query item (index 0) and scroll to top
245-
// onHighlightFirstItem will switch focus to the first result when there's a good match
246-
innerListRef.current?.updateAndScrollToFocusedIndex(0, true);
244+
if (autocompleteQueryValue === '') {
245+
// When query is cleared, reset the initial focus guard so the initial focus
246+
// effect can re-fire and correctly focus the first focusable item (skipping section headers).
247+
hasSetInitialFocusRef.current = false;
248+
} else {
249+
// When query changes to a non-empty value, focus on the search query item (index 0) and scroll to top
250+
// onHighlightFirstItem will switch focus to the first result when there's a good match
251+
innerListRef.current?.updateAndScrollToFocusedIndex(0, true);
252+
}
247253
}
248254
}, [autocompleteQueryValue, isInitialRender]);
249255

src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ function BaseSelectionListWithSections<TItem extends ListItem>({
8585
const innerTextInputRef = useRef<BaseTextInputRef | null>(null);
8686
const isTextInputFocusedRef = useRef<boolean>(false);
8787
const hasKeyBeenPressed = useRef(false);
88+
const suppressNextFocusScrollRef = useRef(false);
8889
const activeElementRole = useActiveElementRole();
8990
const {isKeyboardShown} = useKeyboardState();
9091
const {safeAreaPaddingBottomStyle} = useSafeAreaPaddings();
@@ -126,6 +127,10 @@ function BaseSelectionListWithSections<TItem extends ListItem>({
126127
disabledIndexes,
127128
isActive: isScreenFocused && itemsCount > 0,
128129
onFocusedIndexChange: (index: number) => {
130+
if (suppressNextFocusScrollRef.current) {
131+
suppressNextFocusScrollRef.current = false;
132+
return;
133+
}
129134
if (!shouldScrollToFocusedIndex) {
130135
return;
131136
}
@@ -184,6 +189,9 @@ function BaseSelectionListWithSections<TItem extends ListItem>({
184189
};
185190

186191
const updateAndScrollToFocusedIndex = (index: number, shouldScroll = true) => {
192+
if (!shouldScroll) {
193+
suppressNextFocusScrollRef.current = true;
194+
}
187195
setFocusedIndex(index);
188196
if (shouldScroll) {
189197
scrollToIndex(index);

src/hooks/usePolicyForMovingExpenses.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {activePolicySelector} from '@selectors/Policy';
22
import type {OnyxEntry} from 'react-native-onyx';
33
import {useSession} from '@components/OnyxListItemProvider';
4-
import {canSubmitPerDiemExpenseFromWorkspace, isPaidGroupPolicy, isPolicyMemberWithoutPendingDelete} from '@libs/PolicyUtils';
4+
import {canSubmitPerDiemExpenseFromWorkspace, isPaidGroupPolicy, isPolicyMemberWithoutPendingDelete, isTimeTrackingEnabled} from '@libs/PolicyUtils';
55
import CONST from '@src/CONST';
66
import ONYXKEYS from '@src/ONYXKEYS';
77
import type {Policy} from '@src/types/onyx';
@@ -20,17 +20,18 @@ function isPolicyMemberByRole(policy: OnyxEntry<Policy>) {
2020
return !!policy?.role && Object.values(CONST.POLICY.ROLE).includes(policy.role);
2121
}
2222

23-
function isPolicyValidForMovingExpenses(policy: OnyxEntry<Policy>, login: string, isPerDiemRequest?: boolean) {
23+
function isPolicyValidForMovingExpenses(policy: OnyxEntry<Policy>, login: string, isPerDiemRequest?: boolean, isTimeRequest?: boolean) {
2424
return (
2525
checkForUserPendingDelete(login, policy) &&
2626
isPolicyMemberByRole(policy) &&
2727
isPaidGroupPolicy(policy) &&
2828
policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE &&
29-
(!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(policy))
29+
(!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(policy)) &&
30+
(!isTimeRequest || isTimeTrackingEnabled(policy))
3031
);
3132
}
3233

33-
function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, expensePolicyID?: string) {
34+
function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: boolean, expensePolicyID?: string) {
3435
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
3536
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
3637
const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {
@@ -44,7 +45,7 @@ function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, expensePolicyID?
4445
let singleUserPolicy;
4546
let isMemberOfMoreThanOnePolicy = false;
4647
for (const policy of Object.values(allPolicies ?? {})) {
47-
if (!isPolicyValidForMovingExpenses(policy, login, isPerDiemRequest)) {
48+
if (!isPolicyValidForMovingExpenses(policy, login, isPerDiemRequest, isTimeRequest)) {
4849
continue;
4950
}
5051

@@ -61,12 +62,12 @@ function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, expensePolicyID?
6162
// even if the user's default workspace is A
6263
if (expensePolicyID) {
6364
const expensePolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${expensePolicyID}`];
64-
if (expensePolicy && isPolicyValidForMovingExpenses(expensePolicy, login, isPerDiemRequest)) {
65+
if (expensePolicy && isPolicyValidForMovingExpenses(expensePolicy, login, isPerDiemRequest, isTimeRequest)) {
6566
return {policyForMovingExpensesID: expensePolicyID, policyForMovingExpenses: expensePolicy, shouldSelectPolicy: false};
6667
}
6768
}
6869

69-
if (activePolicy && (!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(activePolicy))) {
70+
if (activePolicy && (!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(activePolicy)) && (!isTimeRequest || isTimeTrackingEnabled(activePolicy))) {
7071
return {policyForMovingExpensesID: activePolicyID, policyForMovingExpenses: activePolicy, shouldSelectPolicy: false};
7172
}
7273

src/libs/CategoryOptionListUtils.ts

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,10 @@ type Hierarchy = Record<string, Category & {[key: string]: Hierarchy & Category}
2929
* @param options - an initial object array
3030
* @param options[].enabled - a flag to enable/disable option in a list
3131
* @param options[].name - a name of an option
32-
* @param [isOneLine] - a flag to determine if text should be one line
3332
*/
34-
function getCategoryOptionTree(options: Record<string, Category> | Category[], isOneLine = false, selectedOptions: Category[] = []): OptionTree[] {
33+
function getCategoryOptionTree(options: Record<string, Category> | Category[], selectedOptions: Category[] = []): OptionTree[] {
3534
const optionCollection = new Map<string, OptionTree>();
3635
for (const option of Object.values(options)) {
37-
if (isOneLine) {
38-
if (optionCollection.has(option.name)) {
39-
continue;
40-
}
41-
42-
const decodedCategoryName = getDecodedCategoryName(option.name);
43-
optionCollection.set(option.name, {
44-
text: decodedCategoryName,
45-
keyForList: option.name,
46-
searchText: option.name,
47-
tooltipText: decodedCategoryName,
48-
isDisabled: !option.enabled || option.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
49-
isSelected: !!option.isSelected,
50-
pendingAction: option.pendingAction,
51-
});
52-
53-
continue;
54-
}
55-
5636
const array = option.name.split(CONST.PARENT_CHILD_SEPARATOR);
5737

5838
for (let index = 0; index < array.length; index++) {
@@ -70,13 +50,14 @@ function getCategoryOptionTree(options: Record<string, Category> | Category[], i
7050
if (optionCollection.has(searchText)) {
7151
continue;
7252
}
73-
74-
const decodedCategoryName = getDecodedCategoryName(optionName);
53+
const leafName = getDecodedCategoryName(optionName.trim());
54+
const decodedCategoryName = getDecodedCategoryName(option.name);
55+
const tooltipText = isChild ? decodedCategoryName : getDecodedCategoryName(searchText);
7556
optionCollection.set(searchText, {
76-
text: `${indents}${decodedCategoryName}`,
57+
text: `${indents}${leafName}`,
7758
keyForList: searchText,
7859
searchText,
79-
tooltipText: decodedCategoryName,
60+
tooltipText,
8061
isDisabled: isChild ? !option.enabled || option.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : isParentOptionDisabled,
8162
isSelected: isChild ? !!option.isSelected : !!selectedParentOption,
8263
pendingAction: option.pendingAction,
@@ -124,7 +105,7 @@ function getCategoryListSections({
124105
}
125106

126107
if (numberOfEnabledCategories === 0 && selectedOptions.length > 0) {
127-
const data = getCategoryOptionTree(selectedOptionsWithDisabledState, true);
108+
const data = getCategoryOptionTree(selectedOptionsWithDisabledState);
128109
categorySections.push({
129110
// "Selected" section
130111
title: '',
@@ -143,7 +124,7 @@ function getCategoryListSections({
143124
isSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name),
144125
}));
145126

146-
const data = getCategoryOptionTree(searchCategories, true);
127+
const data = getCategoryOptionTree(searchCategories);
147128
categorySections.push({
148129
// "Search" section
149130
title: '',
@@ -155,7 +136,7 @@ function getCategoryListSections({
155136
}
156137

157138
if (selectedOptions.length > 0) {
158-
const data = getCategoryOptionTree(selectedOptionsWithDisabledState, true);
139+
const data = getCategoryOptionTree(selectedOptionsWithDisabledState);
159140
categorySections.push({
160141
// "Selected" section
161142
title: '',
@@ -168,7 +149,7 @@ function getCategoryListSections({
168149
const filteredCategories = enabledCategories.filter((category) => !selectedOptionNames.has(category.name));
169150

170151
if (numberOfEnabledCategories < CONST.STANDARD_LIST_ITEM_LIMIT) {
171-
const data = getCategoryOptionTree(filteredCategories, false, selectedOptionsWithDisabledState);
152+
const data = getCategoryOptionTree(filteredCategories, selectedOptionsWithDisabledState);
172153
categorySections.push({
173154
// "All" section when items amount less than the threshold
174155
title: '',
@@ -192,7 +173,7 @@ function getCategoryListSections({
192173
if (filteredRecentlyUsedCategories.length > 0) {
193174
const cutRecentlyUsedCategories = filteredRecentlyUsedCategories.slice(0, maxRecentReportsToShow);
194175

195-
const data = getCategoryOptionTree(cutRecentlyUsedCategories, true);
176+
const data = getCategoryOptionTree(cutRecentlyUsedCategories);
196177
categorySections.push({
197178
// "Recent" section
198179
title: translate('common.recent'),
@@ -201,7 +182,7 @@ function getCategoryListSections({
201182
});
202183
}
203184

204-
const data = getCategoryOptionTree(filteredCategories, false, selectedOptionsWithDisabledState);
185+
const data = getCategoryOptionTree(filteredCategories, selectedOptionsWithDisabledState);
205186
categorySections.push({
206187
// "All" section when items amount more than the threshold
207188
title: translate('common.all'),

0 commit comments

Comments
 (0)