Skip to content

Commit c4c889f

Browse files
authored
Merge pull request #60887 from software-mansion-labs/@szymczak/create-buttons
Add Report creation related options to Simplified actions button
2 parents 53d9bc4 + 4b57fd4 commit c4c889f

10 files changed

+94
-37
lines changed

src/CONST.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,7 @@ const CONST = {
11961196
CHANGE_WORKSPACE: 'changeWorkspace',
11971197
VIEW_DETAILS: 'viewDetails',
11981198
DELETE: 'delete',
1199+
ADD_EXPENSE: 'addExpense',
11991200
},
12001201
PRIMARY_ACTIONS: {
12011202
SUBMIT: 'submit',
@@ -1205,6 +1206,7 @@ const CONST = {
12051206
REMOVE_HOLD: 'removeHold',
12061207
REVIEW_DUPLICATES: 'reviewDuplicates',
12071208
MARK_AS_CASH: 'markAsCash',
1209+
ADD_EXPENSE: 'addExpense',
12081210
},
12091211
TRANSACTION_PRIMARY_ACTIONS: {
12101212
REMOVE_HOLD: 'removeHold',

src/components/MoneyReportHeader.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
getNextApproverAccountID,
6161
payInvoice,
6262
payMoneyRequest,
63+
startMoneyRequest,
6364
submitReport,
6465
unapproveExpenseReport,
6566
} from '@userActions/IOU';
@@ -184,6 +185,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
184185
const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction);
185186
const isArchivedReport = isArchivedReportWithID(moneyRequestReport?.reportID);
186187
const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID}`, {selector: getArchiveReason, canBeMissing: true});
188+
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${moneyRequestReport?.reportID}`, {canBeMissing: true});
187189

188190
const getCanIOUBePaid = useCallback(
189191
(onlyShowPayElsewhere = false, shouldCheckApprovedState = true) =>
@@ -360,8 +362,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
360362
if (!moneyRequestReport) {
361363
return '';
362364
}
363-
return getReportPrimaryAction(moneyRequestReport, transactions, violations, policy);
364-
}, [isPaidAnimationRunning, moneyRequestReport, policy, transactions, violations]);
365+
return getReportPrimaryAction(moneyRequestReport, transactions, violations, policy, reportNameValuePairs);
366+
}, [isPaidAnimationRunning, moneyRequestReport, policy, reportNameValuePairs, transactions, violations]);
365367

366368
const primaryActionsImplementation = {
367369
[CONST.REPORT.PRIMARY_ACTIONS.SUBMIT]: (
@@ -454,6 +456,18 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
454456
}}
455457
/>
456458
),
459+
[CONST.REPORT.PRIMARY_ACTIONS.ADD_EXPENSE]: (
460+
<Button
461+
success
462+
text={translate('iou.addExpense')}
463+
onPress={() => {
464+
if (!moneyRequestReport?.reportID) {
465+
return;
466+
}
467+
startMoneyRequest(CONST.IOU.TYPE.SUBMIT, moneyRequestReport?.reportID);
468+
}}
469+
/>
470+
),
457471
};
458472

459473
const secondaryActions = useMemo(() => {
@@ -589,6 +603,17 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
589603
setIsDeleteModalVisible(true);
590604
},
591605
},
606+
[CONST.REPORT.SECONDARY_ACTIONS.ADD_EXPENSE]: {
607+
text: translate('iou.addExpense'),
608+
icon: Expensicons.Plus,
609+
value: CONST.REPORT.SECONDARY_ACTIONS.ADD_EXPENSE,
610+
onSelected: () => {
611+
if (!moneyRequestReport?.reportID) {
612+
return;
613+
}
614+
startMoneyRequest(CONST.IOU.TYPE.SUBMIT, moneyRequestReport?.reportID);
615+
},
616+
},
592617
};
593618

594619
const applicableSecondaryActions = secondaryActions.map((action) => secondaryActionsImplemenation[action]);

src/languages/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ const translations = {
923923
share: 'Share',
924924
participants: 'Participants',
925925
createExpense: 'Create expense',
926+
addExpense: 'Add expense',
926927
chooseRecipient: 'Choose recipient',
927928
createExpenseWithAmount: ({amount}: {amount: string}) => `Create ${amount} expense`,
928929
confirmDetails: 'Confirm details',

src/languages/es.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,7 @@ const translations = {
912912
original: 'Original',
913913
split: 'Dividir',
914914
splitExpense: 'Dividir gasto',
915+
addExpense: 'Agregar gasto',
915916
expense: 'Gasto',
916917
categorize: 'Categorizar',
917918
share: 'Compartir',

src/libs/ReportPrimaryActionUtils.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {OnyxCollection} from 'react-native-onyx';
22
import type {ValueOf} from 'type-fest';
33
import CONST from '@src/CONST';
4-
import type {Policy, Report, Transaction, TransactionViolation} from '@src/types/onyx';
4+
import type {Policy, Report, ReportNameValuePairs, Transaction, TransactionViolation} from '@src/types/onyx';
55
import {isApprover as isApproverUtils} from './actions/Policy/Member';
66
import {getCurrentUserAccountID} from './actions/Report';
77
import {
@@ -15,9 +15,9 @@ import {
1515
} from './PolicyUtils';
1616
import {getAllReportActions, getOneTransactionThreadReportID} from './ReportActionsUtils';
1717
import {
18+
canAddTransaction as canAddTransactionUtil,
1819
getMoneyRequestSpendBreakdown,
1920
getParentReport,
20-
getReportNameValuePairs,
2121
isArchivedReport,
2222
isClosedReport as isClosedReportUtils,
2323
isCurrentUserSubmitter,
@@ -42,6 +42,14 @@ import {
4242
shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils,
4343
} from './TransactionUtils';
4444

45+
function isAddExpenseAction(report: Report, reportTransactions: Transaction[]) {
46+
const isExpenseReport = isExpenseReportUtils(report);
47+
const isReportSubmitter = isCurrentUserSubmitter(report.reportID);
48+
const canAddTransaction = canAddTransactionUtil(report);
49+
50+
return isExpenseReport && canAddTransaction && isReportSubmitter && reportTransactions.length === 0;
51+
}
52+
4553
function isSubmitAction(report: Report, reportTransactions: Transaction[], policy?: Policy) {
4654
const isExpenseReport = isExpenseReportUtils(report);
4755
const isReportSubmitter = isCurrentUserSubmitter(report.reportID);
@@ -92,7 +100,7 @@ function isApproveAction(report: Report, reportTransactions: Transaction[], poli
92100
return false;
93101
}
94102

95-
function isPayAction(report: Report, policy?: Policy) {
103+
function isPayAction(report: Report, policy?: Policy, reportNameValuePairs?: ReportNameValuePairs) {
96104
const isExpenseReport = isExpenseReportUtils(report);
97105
const isReportPayer = isPayer(getSession(), report, false, policy);
98106
const arePaymentsEnabled = arePaymentsEnabledUtils(policy);
@@ -106,7 +114,6 @@ function isPayAction(report: Report, policy?: Policy) {
106114
const isReportFinished = (isReportApproved && !report.isWaitingOnBankAccount) || isSubmittedWithoutApprovalsEnabled || isReportClosed;
107115
const {reimbursableSpend} = getMoneyRequestSpendBreakdown(report);
108116

109-
const reportNameValuePairs = getReportNameValuePairs(report.chatReportID);
110117
const isChatReportArchived = isArchivedReport(reportNameValuePairs);
111118

112119
if (isChatReportArchived) {
@@ -248,7 +255,12 @@ function getReportPrimaryAction(
248255
reportTransactions: Transaction[],
249256
violations: OnyxCollection<TransactionViolation[]>,
250257
policy?: Policy,
258+
reportNameValuePairs?: ReportNameValuePairs,
251259
): ValueOf<typeof CONST.REPORT.PRIMARY_ACTIONS> | '' {
260+
if (isAddExpenseAction(report, reportTransactions)) {
261+
return CONST.REPORT.PRIMARY_ACTIONS.ADD_EXPENSE;
262+
}
263+
252264
if (isReviewDuplicatesAction(report, reportTransactions, policy)) {
253265
return CONST.REPORT.PRIMARY_ACTIONS.REVIEW_DUPLICATES;
254266
}
@@ -265,7 +277,7 @@ function getReportPrimaryAction(
265277
return CONST.REPORT.PRIMARY_ACTIONS.APPROVE;
266278
}
267279

268-
if (isPayAction(report, policy)) {
280+
if (isPayAction(report, policy, reportNameValuePairs)) {
269281
return CONST.REPORT.PRIMARY_ACTIONS.PAY;
270282
}
271283

src/libs/ReportSecondaryActionUtils.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from './PolicyUtils';
1919
import {getIOUActionForReportID, getReportActions, isPayAction} from './ReportActionsUtils';
2020
import {
21+
canAddTransaction,
2122
isClosedReport as isClosedReportUtils,
2223
isCurrentUserSubmitter,
2324
isExpenseReport as isExpenseReportUtils,
@@ -34,6 +35,16 @@ import {
3435
import {getSession} from './SessionUtils';
3536
import {allHavePendingRTERViolation, isDuplicate, isOnHold as isOnHoldTransactionUtils, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils';
3637

38+
function isAddExpenseAction(report: Report, reportTransactions: Transaction[]) {
39+
const isReportSubmitter = isCurrentUserSubmitter(report.reportID);
40+
41+
if (!isReportSubmitter || reportTransactions.length === 0) {
42+
return false;
43+
}
44+
45+
return canAddTransaction(report);
46+
}
47+
3748
function isSubmitAction(report: Report, reportTransactions: Transaction[], policy?: Policy): boolean {
3849
const transactionAreComplete = reportTransactions.every((transaction) => transaction.amount !== 0 || transaction.modifiedAmount !== 0);
3950

@@ -49,25 +60,21 @@ function isSubmitAction(report: Report, reportTransactions: Transaction[], polic
4960

5061
const isReportSubmitter = isCurrentUserSubmitter(report.reportID);
5162
const isReportApprover = isApproverUtils(policy, getCurrentUserAccountID());
52-
5363
if (!isReportSubmitter && !isReportApprover) {
5464
return false;
5565
}
5666

5767
const isOpenReport = isOpenReportUtils(report);
58-
5968
if (!isOpenReport) {
6069
return false;
6170
}
6271

6372
const submitToAccountID = getSubmitToAccountID(policy, report);
64-
6573
if (submitToAccountID === report.ownerAccountID && policy?.preventSelfApproval) {
6674
return false;
6775
}
6876

6977
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
70-
7178
if (isAdmin) {
7279
return true;
7380
}
@@ -398,6 +405,10 @@ function getSecondaryReportActions(
398405
): Array<ValueOf<typeof CONST.REPORT.SECONDARY_ACTIONS>> {
399406
const options: Array<ValueOf<typeof CONST.REPORT.SECONDARY_ACTIONS>> = [];
400407

408+
if (isAddExpenseAction(report, reportTransactions)) {
409+
options.push(CONST.REPORT.SECONDARY_ACTIONS.ADD_EXPENSE);
410+
}
411+
401412
if (isSubmitAction(report, reportTransactions, policy)) {
402413
options.push(CONST.REPORT.SECONDARY_ACTIONS.SUBMIT);
403414
}

src/libs/ReportUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8485,9 +8485,9 @@ function canCreateRequest(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>,
84858485
return requestOptions.includes(iouType);
84868486
}
84878487

8488-
function getWorkspaceChats(policyID: string, accountIDs: number[], reports: OnyxCollection<Report> = allReports): Array<OnyxEntry<Report>> {
8488+
function getWorkspaceChats(policyID: string | undefined, accountIDs: number[], reports: OnyxCollection<Report> = allReports): Array<OnyxEntry<Report>> {
84898489
return Object.values(reports ?? {}).filter(
8490-
(report) => isPolicyExpenseChat(report) && report?.policyID === policyID && report?.ownerAccountID && accountIDs.includes(report?.ownerAccountID),
8490+
(report) => isPolicyExpenseChat(report) && !!policyID && report?.policyID === policyID && report?.ownerAccountID && accountIDs.includes(report?.ownerAccountID),
84918491
);
84928492
}
84938493

src/pages/NewReportWorkspaceSelectionPage.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Navigation from '@libs/Navigation/Navigation';
1919
import {getHeaderMessageForNonUserList} from '@libs/OptionsListUtils';
2020
import {isPolicyAdmin, shouldShowPolicy} from '@libs/PolicyUtils';
2121
import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils';
22+
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
2223
import CONST from '@src/CONST';
2324
import ONYXKEYS from '@src/ONYXKEYS';
2425
import ROUTES from '@src/ROUTES';
@@ -37,9 +38,9 @@ function NewReportWorkspaceSelectionPage() {
3738
const {translate} = useLocalize();
3839
const {shouldUseNarrowLayout} = useResponsiveLayout();
3940

40-
const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
41+
const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
4142
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
42-
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
43+
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: true});
4344
const shouldShowLoadingIndicator = isLoadingApp && !isOffline;
4445

4546
const navigateToNewReport = useCallback(
@@ -66,6 +67,10 @@ function NewReportWorkspaceSelectionPage() {
6667
if (!policyID) {
6768
return;
6869
}
70+
if (shouldRestrictUserBillableActions(policyID)) {
71+
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policyID));
72+
return;
73+
}
6974
const optimisticReportID = createNewReport(currentUserPersonalDetails, policyID);
7075
navigateToNewReport(optimisticReportID);
7176
},

src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,21 @@ function AttachmentPickerWithMenuItems({
131131
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true});
132132
const {canUseTableReportView, canUseLeftHandBar} = usePermissions();
133133

134-
/**
135-
* Returns the list of IOU Options
136-
*/
137-
const moneyRequestOptions = useMemo(() => {
138-
const selectOption = (onSelected: () => void, shouldRestrictAction: boolean) => {
134+
const selectOption = useCallback(
135+
(onSelected: () => void, shouldRestrictAction: boolean) => {
139136
if (shouldRestrictAction && policy && shouldRestrictUserBillableActions(policy.id)) {
140137
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id));
141138
return;
142139
}
143140

144141
onSelected();
145-
};
146-
142+
},
143+
[policy],
144+
);
145+
/**
146+
* Returns the list of IOU Options
147+
*/
148+
const moneyRequestOptions = useMemo(() => {
147149
const options: MoneyRequestOptions = {
148150
[CONST.IOU.TYPE.SPLIT]: {
149151
icon: Expensicons.Transfer,
@@ -190,7 +192,7 @@ function AttachmentPickerWithMenuItems({
190192
}));
191193

192194
return moneyRequestOptionsList.filter((item, index, self) => index === self.findIndex((t) => t.text === item.text));
193-
}, [translate, report, policy, reportParticipantIDs, isDelegateAccessRestricted, shouldUseNarrowLayout]);
195+
}, [translate, shouldUseNarrowLayout, report, policy, reportParticipantIDs, selectOption, isDelegateAccessRestricted]);
194196

195197
const createReportOption: PopoverMenuItem[] = useMemo(() => {
196198
if (!canUseTableReportView || !isPolicyExpenseChat(report) || !isPaidGroupPolicy(report) || !isReportOwner(report)) {
@@ -201,12 +203,10 @@ function AttachmentPickerWithMenuItems({
201203
{
202204
icon: Expensicons.Document,
203205
text: translate('report.newReport.createReport'),
204-
onSelected: () => {
205-
createNewReport(currentUserPersonalDetails, report?.policyID);
206-
},
206+
onSelected: () => selectOption(() => createNewReport(currentUserPersonalDetails, report?.policyID), true),
207207
},
208208
];
209-
}, [canUseTableReportView, currentUserPersonalDetails, report, translate]);
209+
}, [canUseTableReportView, currentUserPersonalDetails, report, selectOption, translate]);
210210

211211
/**
212212
* Determines if we can show the task option

src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,13 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT
184184
const [quickActionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${quickAction?.chatReportID}`, {canBeMissing: true});
185185
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${quickActionReport?.reportID}`, {canBeMissing: true});
186186
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false});
187-
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
187+
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
188188
const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: false});
189189
const policyChatForActivePolicy = useMemo(() => {
190190
if (isEmptyObject(activePolicy) || !activePolicy?.isPolicyExpenseChatEnabled) {
191191
return {} as OnyxTypes.Report;
192192
}
193-
const policyChatsForActivePolicy = getWorkspaceChats(`${activePolicyID ?? CONST.DEFAULT_NUMBER_ID}`, [session?.accountID ?? CONST.DEFAULT_NUMBER_ID], allReports);
193+
const policyChatsForActivePolicy = getWorkspaceChats(activePolicyID, [session?.accountID ?? CONST.DEFAULT_NUMBER_ID], allReports);
194194
return policyChatsForActivePolicy.length > 0 ? policyChatsForActivePolicy.at(0) : ({} as OnyxTypes.Report);
195195
}, [activePolicy, activePolicyID, session?.accountID, allReports]);
196196
const [quickActionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${quickActionReport?.policyID}`, {canBeMissing: true});
@@ -513,24 +513,24 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT
513513
return;
514514
}
515515

516+
let workspaceIDForReportCreation: string | undefined;
517+
516518
// If the user's default workspace is a paid group workspace with chat enabled, we create a report with it by default
517519
if (activePolicy && activePolicy.isPolicyExpenseChatEnabled && isPaidGroupPolicy(activePolicy)) {
518-
const createdReportID = createNewReport(currentUserPersonalDetails, activePolicyID);
519-
Navigation.setNavigationActionToMicrotaskQueue(() => {
520-
Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID: createdReportID, backTo: Navigation.getActiveRoute()}));
521-
});
522-
return;
520+
workspaceIDForReportCreation = activePolicyID;
521+
} else if (groupPoliciesWithChatEnabled.length === 1) {
522+
workspaceIDForReportCreation = groupPoliciesWithChatEnabled.at(0)?.id;
523523
}
524524

525-
if (groupPoliciesWithChatEnabled.length === 1) {
526-
const createdReportID = createNewReport(currentUserPersonalDetails, groupPoliciesWithChatEnabled.at(0)?.id);
525+
if (workspaceIDForReportCreation && !shouldRestrictUserBillableActions(workspaceIDForReportCreation)) {
526+
const createdReportID = createNewReport(currentUserPersonalDetails, workspaceIDForReportCreation);
527527
Navigation.setNavigationActionToMicrotaskQueue(() => {
528528
Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID: createdReportID, backTo: Navigation.getActiveRoute()}));
529529
});
530530
return;
531531
}
532532

533-
// If the user's default workspace is personal and the user has more than one group workspace which is paid and has chat enabled, we need to redirect them to the workspace selection screen
533+
// If the user's default workspace is personal and the user has more than one group workspace, which is paid and has chat enabled, or a chosen workspace is past the grace period, we need to redirect them to the workspace selection screen
534534
Navigation.navigate(ROUTES.NEW_REPORT_WORKSPACE_SELECTION);
535535
});
536536
},

0 commit comments

Comments
 (0)