Skip to content

Commit 38db9e3

Browse files
authored
Merge pull request Expensify#72457 from callstack-internal/feat/71784-category-field
Enable category field on unreported expenses
2 parents d4a8a04 + 122babf commit 38db9e3

16 files changed

Lines changed: 199 additions & 48 deletions

File tree

src/ROUTES.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,11 @@ const ROUTES = {
457457
return getUrlWithBackToParam(baseRoute, backTo);
458458
},
459459
},
460+
SET_DEFAULT_WORKSPACE: {
461+
route: 'set-default-workspace',
462+
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
463+
getRoute: (backTo?: string) => getUrlWithBackToParam('set-default-workspace', backTo),
464+
},
460465
REPORT: 'r',
461466
REPORT_WITH_ID: {
462467
route: 'r/:reportID?/:reportActionID?',

src/SCREENS.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ const SCREENS = {
211211
DETAILS: 'Details',
212212
PROFILE: 'Profile',
213213
NEW_REPORT_WORKSPACE_SELECTION: 'New_Report_Workspace_Selection',
214+
SET_DEFAULT_WORKSPACE: 'Set_Default_Workspace',
214215
REPORT_DETAILS: 'Report_Details',
215216
REPORT_CHANGE_WORKSPACE: 'ReportChangeWorkspace',
216217
WORKSPACE_CONFIRMATION: 'Workspace_Confirmation',
@@ -400,6 +401,10 @@ const SCREENS = {
400401
ROOT: 'NewReportWorkspaceSelection_Root',
401402
},
402403

404+
SET_DEFAULT_WORKSPACE: {
405+
ROOT: 'SetDefaultWorkspace_Root',
406+
},
407+
403408
REPORT_DETAILS: {
404409
ROOT: 'Report_Details_Root',
405410
SHARE_CODE: 'Report_Details_Share_Code',

src/components/ReportActionItem/MoneyRequestView.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ function MoneyRequestView({
268268
// Flags for showing categories and tags
269269
// transactionCategory can be an empty string
270270
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
271-
const shouldShowCategory = isPolicyExpenseChat && (categoryForDisplay || hasEnabledOptions(policyCategories ?? {}));
271+
const shouldShowCategory = (isPolicyExpenseChat && (categoryForDisplay || hasEnabledOptions(policyCategories ?? {}))) || isExpenseUnreported;
272272
// transactionTag can be an empty string
273273
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
274274
const shouldShowTag = isPolicyExpenseChat && (transactionTag || hasEnabledTags(policyTagLists));
@@ -307,6 +307,8 @@ function MoneyRequestView({
307307
amountTitle = translate('iou.receiptStatusTitle');
308308
}
309309

310+
const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy;
311+
310312
const updatedTransactionDescription = useMemo(() => {
311313
if (!updatedTransaction) {
312314
return undefined;
@@ -711,17 +713,28 @@ function MoneyRequestView({
711713
shouldShowRightIcon={canEdit}
712714
titleStyle={styles.flex1}
713715
onPress={() => {
714-
if (!policy) {
716+
if (shouldNavigateToUpgradePath) {
715717
Navigation.navigate(
716718
ROUTES.MONEY_REQUEST_UPGRADE.getRoute({
717719
action: CONST.IOU.ACTION.EDIT,
718720
iouType,
719721
transactionID: transaction.transactionID,
720722
reportID: report.reportID,
721-
backTo: getReportRHPActiveRoute(),
722723
upgradePath: CONST.UPGRADE_PATHS.CATEGORIES,
723724
}),
724725
);
726+
} else if (shouldSelectPolicy) {
727+
Navigation.navigate(
728+
ROUTES.SET_DEFAULT_WORKSPACE.getRoute(
729+
ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(
730+
CONST.IOU.ACTION.EDIT,
731+
iouType,
732+
transaction.transactionID,
733+
report.reportID,
734+
getReportRHPActiveRoute(),
735+
),
736+
),
737+
);
725738
} else {
726739
Navigation.navigate(
727740
ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction.transactionID, report.reportID, getReportRHPActiveRoute()),
@@ -852,7 +865,7 @@ function MoneyRequestView({
852865
if (!canEditReport) {
853866
return;
854867
}
855-
if (!policyForMovingExpenses && !shouldSelectPolicy) {
868+
if (shouldNavigateToUpgradePath) {
856869
Navigation.navigate(
857870
ROUTES.MONEY_REQUEST_UPGRADE.getRoute({
858871
iouType,

src/components/Search/index.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {accountIDSelector} from '@selectors/Session';
33
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
44
import type {NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native';
55
import {View} from 'react-native';
6-
import type {OnyxEntry} from 'react-native-onyx';
6+
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
77
import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
88
import FullPageErrorView from '@components/BlockingViews/FullPageErrorView';
99
import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
@@ -12,6 +12,7 @@ import type {ReportActionListItemType, SearchListItem, SelectionListHandle, Tran
1212
import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
1313
import {WideRHPContext} from '@components/WideRHPContextProvider';
1414
import useCardFeedsForDisplay from '@hooks/useCardFeedsForDisplay';
15+
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
1516
import useLocalize from '@hooks/useLocalize';
1617
import useNetwork from '@hooks/useNetwork';
1718
import useOnyx from '@hooks/useOnyx';
@@ -29,6 +30,7 @@ import Log from '@libs/Log';
2930
import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute';
3031
import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types';
3132
import Performance from '@libs/Performance';
33+
import {getPolicyRole, isPaidGroupPolicy as isPaidGroupPolicyPolicyUtils} from '@libs/PolicyUtils';
3234
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
3335
import {canEditFieldOfMoneyRequest, selectArchivedReportsIdSet, selectFilteredReportActions} from '@libs/ReportUtils';
3436
import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
@@ -90,6 +92,7 @@ function mapTransactionItemToSelectedEntry(
9092
item: TransactionListItemType,
9193
reportActions: ReportAction[],
9294
outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue,
95+
canMoveExpense = true,
9396
): [string, SelectedTransactionInfo] {
9497
return [
9598
item.keyForList,
@@ -105,7 +108,7 @@ function mapTransactionItemToSelectedEntry(
105108
undefined,
106109
undefined,
107110
outstandingReportsByPolicyID,
108-
true,
111+
canMoveExpense,
109112
),
110113
action: item.action,
111114
reportID: item.reportID,
@@ -166,6 +169,7 @@ function prepareTransactionsList(
166169
selectedTransactions: SelectedTransactions,
167170
reportActions: ReportAction[],
168171
outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue,
172+
canMoveExpense = true,
169173
) {
170174
if (selectedTransactions[item.keyForList]?.isSelected) {
171175
const {[item.keyForList]: omittedTransaction, ...transactions} = selectedTransactions;
@@ -187,7 +191,7 @@ function prepareTransactionsList(
187191
undefined,
188192
undefined,
189193
outstandingReportsByPolicyID,
190-
true,
194+
canMoveExpense,
191195
),
192196
action: item.action,
193197
reportID: item.reportID,
@@ -201,6 +205,10 @@ function prepareTransactionsList(
201205
};
202206
}
203207

208+
const activePaidPoliciesSelector = (allPolicies: OnyxCollection<Policy>): OnyxCollection<Policy> => {
209+
return Object.fromEntries(Object.entries(allPolicies ?? {}).filter(([, userPolicy]) => isPaidGroupPolicyPolicyUtils(userPolicy)));
210+
};
211+
204212
function Search({queryJSON, searchResults, onSearchListScroll, contentContainerStyle, handleSearch, isMobileSelectionModeEnabled, onSortPressedCallback}: SearchProps) {
205213
const {type, status, sortBy, sortOrder, hash, similarSearchHash, groupBy} = queryJSON;
206214
const {isOffline} = useNetwork();
@@ -235,6 +243,14 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
235243
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true});
236244
const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
237245

246+
const [activePaidPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
247+
selector: activePaidPoliciesSelector,
248+
canBeMissing: true,
249+
});
250+
251+
const {login: currentUserLogin} = useCurrentUserPersonalDetails();
252+
const isUserWorkspaceMember = !!Object.values(activePaidPolicies ?? {}).filter((userPolicy) => getPolicyRole(userPolicy, currentUserLogin)).length;
253+
238254
// Filter violations based on user visibility
239255
const filteredViolations = useMemo(() => {
240256
if (!violations || !searchResults?.data) {
@@ -444,7 +460,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
444460
undefined,
445461
undefined,
446462
outstandingReportsByPolicyID,
447-
true,
463+
isUserWorkspaceMember,
448464
),
449465
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
450466
isSelected: areAllMatchingItemsSelected || selectedTransactions[transaction.transactionID].isSelected,
@@ -477,7 +493,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
477493
undefined,
478494
undefined,
479495
outstandingReportsByPolicyID,
480-
true,
496+
isUserWorkspaceMember,
481497
),
482498
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
483499
isSelected: areAllMatchingItemsSelected || selectedTransactions[transaction.transactionID].isSelected,
@@ -499,7 +515,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
499515

500516
isRefreshingSelection.current = true;
501517
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
502-
}, [data, setSelectedTransactions, areAllMatchingItemsSelected, isFocused, outstandingReportsByPolicyID]);
518+
}, [data, setSelectedTransactions, areAllMatchingItemsSelected, isFocused, outstandingReportsByPolicyID, isUserWorkspaceMember]);
503519

504520
useEffect(() => {
505521
if (!isSearchResultsEmpty || prevIsSearchResultEmpty) {
@@ -559,7 +575,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
559575
if (isTransactionPendingDelete(item)) {
560576
return;
561577
}
562-
setSelectedTransactions(prepareTransactionsList(item, selectedTransactions, reportActionsArray, outstandingReportsByPolicyID), data);
578+
setSelectedTransactions(prepareTransactionsList(item, selectedTransactions, reportActionsArray, outstandingReportsByPolicyID, isUserWorkspaceMember), data);
563579
return;
564580
}
565581

@@ -581,13 +597,13 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
581597
...Object.fromEntries(
582598
currentTransactions
583599
.filter((t) => !isTransactionPendingDelete(t))
584-
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray, outstandingReportsByPolicyID)),
600+
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray, outstandingReportsByPolicyID, isUserWorkspaceMember)),
585601
),
586602
},
587603
data,
588604
);
589605
},
590-
[data, reportActionsArray, selectedTransactions, outstandingReportsByPolicyID, setSelectedTransactions],
606+
[setSelectedTransactions, selectedTransactions, data, reportActionsArray, outstandingReportsByPolicyID, isUserWorkspaceMember],
591607
);
592608

593609
const onSelectRow = useCallback(
@@ -794,7 +810,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
794810
(data as TransactionGroupListItemType[]).flatMap((item) =>
795811
item.transactions
796812
.filter((t) => !isTransactionPendingDelete(t))
797-
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray, outstandingReportsByPolicyID)),
813+
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray, outstandingReportsByPolicyID, isUserWorkspaceMember)),
798814
),
799815
),
800816
data,
@@ -807,11 +823,11 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS
807823
Object.fromEntries(
808824
(data as TransactionListItemType[])
809825
.filter((t) => !isTransactionPendingDelete(t))
810-
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray, outstandingReportsByPolicyID)),
826+
.map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray, outstandingReportsByPolicyID, isUserWorkspaceMember)),
811827
),
812828
data,
813829
);
814-
}, [clearSelectedTransactions, data, groupBy, reportActionsArray, selectedTransactions, setSelectedTransactions, outstandingReportsByPolicyID]);
830+
}, [groupBy, selectedTransactions, setSelectedTransactions, data, clearSelectedTransactions, reportActionsArray, outstandingReportsByPolicyID, isUserWorkspaceMember]);
815831

816832
const onLayout = useCallback(() => handleSelectionListScroll(sortedSelectedData, searchListRef.current), [handleSelectionListScroll, sortedSelectedData]);
817833

src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import type {
3737
SearchAdvancedFiltersParamList,
3838
SearchReportParamList,
3939
SearchSavedSearchParamList,
40+
SetDefaultWorkspaceNavigatorParamList,
4041
SettingsNavigatorParamList,
4142
ShareNavigatorParamList,
4243
SignInNavigatorParamList,
@@ -206,6 +207,10 @@ const NewReportWorkspaceSelectionModalStackNavigator = createModalStackNavigator
206207
[SCREENS.NEW_REPORT_WORKSPACE_SELECTION.ROOT]: () => require<ReactComponentModule>('../../../../pages/NewReportWorkspaceSelectionPage').default,
207208
});
208209

210+
const SetDefaultWorkspaceModalStackNavigator = createModalStackNavigator<SetDefaultWorkspaceNavigatorParamList>({
211+
[SCREENS.SET_DEFAULT_WORKSPACE.ROOT]: () => require<ReactComponentModule>('../../../../pages/SetDefaultWorkspacePage').default,
212+
});
213+
209214
const ReportDetailsModalStackNavigator = createModalStackNavigator<ReportDetailsNavigatorParamList>({
210215
[SCREENS.REPORT_DETAILS.ROOT]: () => require<ReactComponentModule>('../../../../pages/ReportDetailsPage').default,
211216
[SCREENS.REPORT_DETAILS.SHARE_CODE]: () => require<ReactComponentModule>('../../../../pages/home/report/ReportDetailsShareCodePage').default,
@@ -918,6 +923,7 @@ export {
918923
ReferralModalStackNavigator,
919924
TravelModalStackNavigator,
920925
NewReportWorkspaceSelectionModalStackNavigator,
926+
SetDefaultWorkspaceModalStackNavigator,
921927
ReportDescriptionModalStackNavigator,
922928
ReportDetailsModalStackNavigator,
923929
ReportChangeWorkspaceModalStackNavigator,

src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) {
107107
name={SCREENS.RIGHT_MODAL.NEW_REPORT_WORKSPACE_SELECTION}
108108
component={ModalStackNavigators.NewReportWorkspaceSelectionModalStackNavigator}
109109
/>
110+
<Stack.Screen
111+
name={SCREENS.RIGHT_MODAL.SET_DEFAULT_WORKSPACE}
112+
component={ModalStackNavigators.SetDefaultWorkspaceModalStackNavigator}
113+
/>
110114
<Stack.Screen
111115
name={SCREENS.RIGHT_MODAL.REPORT_DETAILS}
112116
component={ModalStackNavigators.ReportDetailsModalStackNavigator}

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,11 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
11111111
[SCREENS.NEW_REPORT_WORKSPACE_SELECTION.ROOT]: ROUTES.NEW_REPORT_WORKSPACE_SELECTION.route,
11121112
},
11131113
},
1114+
[SCREENS.RIGHT_MODAL.SET_DEFAULT_WORKSPACE]: {
1115+
screens: {
1116+
[SCREENS.SET_DEFAULT_WORKSPACE.ROOT]: ROUTES.SET_DEFAULT_WORKSPACE.route,
1117+
},
1118+
},
11141119
[SCREENS.RIGHT_MODAL.REPORT_DETAILS]: {
11151120
screens: {
11161121
[SCREENS.REPORT_DETAILS.ROOT]: ROUTES.REPORT_WITH_ID_DETAILS.route,

src/libs/Navigation/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,12 @@ type NewReportWorkspaceSelectionNavigatorParamList = {
12131213
};
12141214
};
12151215

1216+
type SetDefaultWorkspaceNavigatorParamList = {
1217+
[SCREENS.SET_DEFAULT_WORKSPACE.ROOT]: {
1218+
backTo?: Routes;
1219+
};
1220+
};
1221+
12161222
type ReportDetailsNavigatorParamList = {
12171223
[SCREENS.REPORT_DETAILS.ROOT]: {
12181224
reportID: string;
@@ -1819,6 +1825,7 @@ type RightModalNavigatorParamList = {
18191825
[SCREENS.RIGHT_MODAL.PROFILE]: NavigatorScreenParams<ProfileNavigatorParamList>;
18201826
[SCREENS.SETTINGS.SHARE_CODE]: undefined;
18211827
[SCREENS.RIGHT_MODAL.NEW_REPORT_WORKSPACE_SELECTION]: NavigatorScreenParams<NewReportWorkspaceSelectionNavigatorParamList>;
1828+
[SCREENS.RIGHT_MODAL.SET_DEFAULT_WORKSPACE]: NavigatorScreenParams<SetDefaultWorkspaceNavigatorParamList>;
18221829
[SCREENS.RIGHT_MODAL.REPORT_DETAILS]: NavigatorScreenParams<ReportDetailsNavigatorParamList>;
18231830
[SCREENS.RIGHT_MODAL.REPORT_CHANGE_WORKSPACE]: NavigatorScreenParams<ReportChangeWorkspaceNavigatorParamList>;
18241831
[SCREENS.RIGHT_MODAL.REPORT_SETTINGS]: NavigatorScreenParams<ReportSettingsNavigatorParamList>;
@@ -2457,6 +2464,7 @@ export type {
24572464
ReimbursementAccountNavigatorParamList,
24582465
ReimbursementAccountEnterSignerInfoNavigatorParamList,
24592466
NewReportWorkspaceSelectionNavigatorParamList,
2467+
SetDefaultWorkspaceNavigatorParamList,
24602468
ReportDescriptionNavigatorParamList,
24612469
ReportDetailsNavigatorParamList,
24622470
ReportChangeWorkspaceNavigatorParamList,

src/libs/ReportUtils.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4395,7 +4395,7 @@ function canEditFieldOfMoneyRequest(
43954395
isDeleteAction?: boolean,
43964396
isChatReportArchived = false,
43974397
outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue,
4398-
isSearchPageOption?: boolean,
4398+
canMoveExpense = true,
43994399
): boolean {
44004400
// A list of fields that cannot be edited by anyone, once an expense has been settled
44014401
const restrictedFields: string[] = [
@@ -4480,13 +4480,7 @@ function canEditFieldOfMoneyRequest(
44804480
// Unreported transaction from OldDot can have the reportID as an empty string
44814481
const isUnreportedExpense = !transaction?.reportID || transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
44824482

4483-
const isUserWorkspaceMember = getActivePolicies(allPolicies ?? {}, currentUserEmail).filter((userPolicy) => isPaidGroupPolicyPolicyUtils(userPolicy)).length;
4484-
4485-
if (isUnreportedExpense && isSearchPageOption && isUserWorkspaceMember) {
4486-
return true;
4487-
}
4488-
4489-
if (isUnreportedExpense && !isSearchPageOption) {
4483+
if (isUnreportedExpense && canMoveExpense) {
44904484
return true;
44914485
}
44924486

0 commit comments

Comments
 (0)