diff --git a/src/CONST.ts b/src/CONST.ts index 6de65ae74f63..98a373239526 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1122,6 +1122,7 @@ const CONST = { TRACK_DISTANCE: 'trackDistance', ASSIGN_TASK: 'assignTask', SEND_MONEY: 'sendMoney', + CREATE_REPORT: 'createReport', }, RECEIPT: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 8b3c22ea0c6b..7d9fb5874316 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -885,6 +885,7 @@ const translations = { header: 'Quick action', noLongerHaveReportAccess: 'You no longer have access to your previous quick action destination. Pick a new one below.', updateDestination: 'Update destination', + createReport: 'Create report', }, iou: { amount: 'Amount', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6e94abdce292..ba06a72a6ecd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -880,6 +880,7 @@ const translations = { header: 'Acción rápida', noLongerHaveReportAccess: 'Ya no tienes acceso al destino previo de esta acción rápida. Escoge uno nuevo a continuación.', updateDestination: 'Actualiza el destino', + createReport: 'Crear informe', }, iou: { amount: 'Importe', diff --git a/src/libs/API/parameters/CreateAppReportParams.ts b/src/libs/API/parameters/CreateAppReportParams.ts index 6ac0152ad7a6..02390a69cde1 100644 --- a/src/libs/API/parameters/CreateAppReportParams.ts +++ b/src/libs/API/parameters/CreateAppReportParams.ts @@ -5,5 +5,6 @@ type CreateAppReportParams = { reportID: string; reportActionID: string; reportPreviewReportActionID: string; + shouldUpdateQAB: boolean; }; export default CreateAppReportParams; diff --git a/src/libs/actions/QuickActionNavigation.ts b/src/libs/actions/QuickActionNavigation.ts index cb05c42b2fa3..5e82988abd94 100644 --- a/src/libs/actions/QuickActionNavigation.ts +++ b/src/libs/actions/QuickActionNavigation.ts @@ -1,9 +1,13 @@ +import Navigation from '@libs/Navigation/Navigation'; import {generateReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {PersonalDetails} from '@src/types/onyx'; import type {QuickActionName} from '@src/types/onyx/QuickAction'; import type QuickAction from '@src/types/onyx/QuickAction'; import type {IOURequestType} from './IOU'; import {startMoneyRequest} from './IOU'; +import {createNewReport} from './Report'; import {startOutCreateTaskQuickAction} from './Task'; function getQuickActionRequestType(action: QuickActionName | undefined): IOURequestType | undefined { @@ -25,8 +29,14 @@ function getQuickActionRequestType(action: QuickActionName | undefined): IOURequ return requestType; } -function navigateToQuickAction(isValidReport: boolean, quickActionReportID: string, quickAction: QuickAction, selectOption: (onSelected: () => void, shouldRestrictAction: boolean) => void) { - const reportID = isValidReport ? quickActionReportID : generateReportID(); +function navigateToQuickAction( + isValidReport: boolean, + quickAction: QuickAction, + currentUserPersonalDetails: PersonalDetails, + policyID: string | undefined, + selectOption: (onSelected: () => void, shouldRestrictAction: boolean) => void, +) { + const reportID = isValidReport && quickAction?.chatReportID ? quickAction?.chatReportID : generateReportID(); const requestType = getQuickActionRequestType(quickAction?.action); switch (quickAction?.action) { @@ -35,15 +45,15 @@ function navigateToQuickAction(isValidReport: boolean, quickActionReportID: stri case CONST.QUICK_ACTIONS.REQUEST_DISTANCE: case CONST.QUICK_ACTIONS.PER_DIEM: selectOption(() => startMoneyRequest(CONST.IOU.TYPE.SUBMIT, reportID, requestType, true), true); - return; + break; case CONST.QUICK_ACTIONS.SPLIT_MANUAL: case CONST.QUICK_ACTIONS.SPLIT_SCAN: case CONST.QUICK_ACTIONS.SPLIT_DISTANCE: selectOption(() => startMoneyRequest(CONST.IOU.TYPE.SPLIT, reportID, requestType, true), true); - return; + break; case CONST.QUICK_ACTIONS.SEND_MONEY: selectOption(() => startMoneyRequest(CONST.IOU.TYPE.PAY, reportID, undefined, true), false); - return; + break; case CONST.QUICK_ACTIONS.ASSIGN_TASK: selectOption(() => startOutCreateTaskQuickAction(isValidReport ? reportID : '', quickAction.targetAccountID ?? CONST.DEFAULT_NUMBER_ID), false); break; @@ -52,6 +62,14 @@ function navigateToQuickAction(isValidReport: boolean, quickActionReportID: stri case CONST.QUICK_ACTIONS.TRACK_DISTANCE: selectOption(() => startMoneyRequest(CONST.IOU.TYPE.TRACK, reportID, requestType, true), false); break; + case CONST.QUICK_ACTIONS.CREATE_REPORT: + selectOption(() => { + const optimisticReportID = createNewReport(currentUserPersonalDetails, policyID); + Navigation.setNavigationActionToMicrotaskQueue(() => { + Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID: optimisticReportID, backTo: Navigation.getActiveRoute()})); + }); + }, true); + break; default: } } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 446c9c5561ec..7bdabcf4b4e2 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2546,6 +2546,15 @@ function buildNewReportOptimisticData(policy: OnyxEntry, reportID: strin key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`, value: optimisticNextStep, }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, + value: { + action: CONST.QUICK_ACTIONS.CREATE_REPORT, + chatReportID: parentReport?.reportID, + isFirstQuickAction: isEmptyObject(quickAction), + }, + }, ]; const failureData: OnyxUpdate[] = [ @@ -2559,6 +2568,11 @@ function buildNewReportOptimisticData(policy: OnyxEntry, reportID: strin key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: {[reportActionID]: {errorFields: {create: getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage')}}}, }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, + value: quickAction ?? null, + }, ]; const successData: OnyxUpdate[] = [ @@ -2606,7 +2620,7 @@ function createNewReport(creatorPersonalDetails: PersonalDetails, policyID?: str API.write( WRITE_COMMANDS.CREATE_APP_REPORT, - {reportName: optimisticReportName, type: CONST.REPORT.TYPE.EXPENSE, policyID, reportID: optimisticReportID, reportActionID, reportPreviewReportActionID}, + {reportName: optimisticReportName, type: CONST.REPORT.TYPE.EXPENSE, policyID, reportID: optimisticReportID, reportActionID, reportPreviewReportActionID, shouldUpdateQAB: true}, {optimisticData, successData, failureData}, ); return optimisticReportID; diff --git a/src/pages/NewReportWorkspaceSelectionPage.tsx b/src/pages/NewReportWorkspaceSelectionPage.tsx index 554a3a378893..1414702635d0 100644 --- a/src/pages/NewReportWorkspaceSelectionPage.tsx +++ b/src/pages/NewReportWorkspaceSelectionPage.tsx @@ -45,8 +45,10 @@ function NewReportWorkspaceSelectionPage() { if (!policyID) { return; } - const createdReportID = createNewReport(currentUserPersonalDetails, policyID); - Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID: createdReportID, backTo: Navigation.getActiveRoute()}), {forceReplace: true}); + const optimisticReportID = createNewReport(currentUserPersonalDetails, policyID); + Navigation.setNavigationActionToMicrotaskQueue(() => { + Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID: optimisticReportID}), {forceReplace: true}); + }); }, [currentUserPersonalDetails], ); diff --git a/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx index b51b93519063..635c2effe72e 100644 --- a/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx @@ -110,6 +110,8 @@ const getQuickActionIcon = (action: QuickActionName): React.FC => { return getIconForAction(CONST.IOU.TYPE.TRACK); case CONST.QUICK_ACTIONS.TRACK_SCAN: return Expensicons.ReceiptScan; + case CONST.QUICK_ACTIONS.CREATE_REPORT: + return Expensicons.Document; default: return Expensicons.MoneyCircle; } @@ -160,6 +162,8 @@ const getQuickActionTitle = (action: QuickActionName): TranslationPaths => { return 'quickAction.paySomeone'; case CONST.QUICK_ACTIONS.ASSIGN_TASK: return 'quickAction.assignTask'; + case CONST.QUICK_ACTIONS.CREATE_REPORT: + return 'quickAction.createReport'; default: return '' as TranslationPaths; } @@ -191,6 +195,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT }, [activePolicy, activePolicyID, session?.accountID, allReports]); const [quickActionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${quickActionReport?.policyID}`); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector)}); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); const [modalVisible, setModalVisible] = useState(false); @@ -267,6 +272,13 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT return quickAction?.action === CONST.QUICK_ACTIONS.SEND_MONEY && displayName.length === 0; }, [isValidReport, quickActionAvatars, personalDetails, quickAction?.action]); + const quickActionSubtitle = useMemo(() => { + if (quickAction?.action === CONST.QUICK_ACTIONS.CREATE_REPORT) { + return quickActionPolicy?.name; + } + return !hideQABSubtitle ? getReportName(quickActionReport) ?? translate('quickAction.updateDestination') : ''; + }, [hideQABSubtitle, quickAction?.action, quickActionPolicy?.name, quickActionReport, translate]); + const selectOption = useCallback( (onSelected: () => void, shouldRestrictAction: boolean) => { if (shouldRestrictAction && quickActionReport?.policyID && shouldRestrictUserBillableActions(quickActionReport.policyID)) { @@ -393,7 +405,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT } const onSelected = () => { interceptAnonymousUser(() => { - navigateToQuickAction(isValidReport, `${quickActionReport?.reportID ?? CONST.DEFAULT_NUMBER_ID}`, quickAction, selectOption); + navigateToQuickAction(isValidReport, quickAction, currentUserPersonalDetails, quickActionPolicy?.id, selectOption); }); }; return [ @@ -401,7 +413,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT ...baseQuickAction, icon: getQuickActionIcon(quickAction?.action), text: quickActionTitle, - description: !hideQABSubtitle ? getReportName(quickActionReport) ?? translate('quickAction.updateDestination') : '', + description: quickActionSubtitle, onSelected, shouldShowSubscriptRightAvatar: isPolicyExpenseChat(quickActionReport), }, @@ -440,17 +452,18 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT quickActionAvatars, quickAction, policyChatForActivePolicy, - quickActionReport, - quickActionPolicy, quickActionTitle, - hideQABSubtitle, + quickActionSubtitle, + currentUserPersonalDetails, + quickActionPolicy, + quickActionReport, isValidReport, selectOption, ]); const viewTourTaskReportID = introSelected?.viewTour; const [viewTourTaskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${viewTourTaskReportID}`); - const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const canModifyTask = canModifyTaskUtils(viewTourTaskReport, currentUserPersonalDetails.accountID); const canActionTask = canActionTaskUtils(viewTourTaskReport, currentUserPersonalDetails.accountID); diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 3b2ee21b31e3..a0c982f3fbfb 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -1485,4 +1485,37 @@ describe('actions/Report', () => { expect(report?.lastMentionedTime).toBeUndefined(); }); + + it('should create new report and "create report" quick action, when createNewReport gets called', async () => { + const reportID = Report.createNewReport({accountID: 1234}, '5678'); + await new Promise((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (reports) => { + Onyx.disconnect(connection); + const createdReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + + // assert correctness of crucial onyx data + expect(createdReport?.reportID).toBe(reportID); + expect(createdReport?.total).toBe(0); + + resolve(); + }, + }); + }); + + await new Promise((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, + callback: (quickAction) => { + Onyx.disconnect(connection); + + // Then the quickAction.action should be set to CREATE_REPORT + expect(quickAction?.action).toBe(CONST.QUICK_ACTIONS.CREATE_REPORT); + resolve(); + }, + }); + }); + }); }); diff --git a/tests/unit/QuickActionNavigationTest.ts b/tests/unit/QuickActionNavigationTest.ts index 86c54ca51685..e37cb1153403 100644 --- a/tests/unit/QuickActionNavigationTest.ts +++ b/tests/unit/QuickActionNavigationTest.ts @@ -1,11 +1,19 @@ import {startMoneyRequest} from '@libs/actions/IOU'; import {navigateToQuickAction} from '@libs/actions/QuickActionNavigation'; +import {createNewReport} from '@libs/actions/Report'; +import {startOutCreateTaskQuickAction} from '@libs/actions/Task'; import {generateReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; jest.mock('@libs/actions/IOU', () => ({ startMoneyRequest: jest.fn(), })); +jest.mock('@libs/actions/Report', () => ({ + createNewReport: jest.fn(), +})); +jest.mock('@libs/actions/Task', () => ({ + startOutCreateTaskQuickAction: jest.fn(), +})); describe('IOU Utils', () => { // Given navigateToQuickAction is called with quick action argument when clicking on quick action button from Global create menu @@ -14,7 +22,7 @@ describe('IOU Utils', () => { it('should be navigated to Manual Submit Expense', () => { // When the quick action is REQUEST_MANUAL - navigateToQuickAction(true, reportID, {action: CONST.QUICK_ACTIONS.REQUEST_MANUAL}, (onSelected: () => void) => { + navigateToQuickAction(true, {action: CONST.QUICK_ACTIONS.REQUEST_MANUAL, chatReportID: reportID}, {accountID: 1234}, undefined, (onSelected: () => void) => { onSelected(); }); // Then we should start manual submit request flow @@ -23,7 +31,7 @@ describe('IOU Utils', () => { it('should be navigated to Scan receipt Split Expense', () => { // When the quick action is SPLIT_SCAN - navigateToQuickAction(true, reportID, {action: CONST.QUICK_ACTIONS.SPLIT_SCAN}, (onSelected: () => void) => { + navigateToQuickAction(true, {action: CONST.QUICK_ACTIONS.SPLIT_SCAN, chatReportID: reportID}, {accountID: 1234}, undefined, (onSelected: () => void) => { onSelected(); }); // Then we should start scan split request flow @@ -32,7 +40,7 @@ describe('IOU Utils', () => { it('should be navigated to Track distance Expense', () => { // When the quick action is TRACK_DISTANCE - navigateToQuickAction(true, reportID, {action: CONST.QUICK_ACTIONS.TRACK_DISTANCE}, (onSelected: () => void) => { + navigateToQuickAction(true, {action: CONST.QUICK_ACTIONS.TRACK_DISTANCE, chatReportID: reportID}, {accountID: 1234}, undefined, (onSelected: () => void) => { onSelected(); }); // Then we should start distance track request flow @@ -41,7 +49,7 @@ describe('IOU Utils', () => { it('should be navigated to Per Diem Expense', () => { // When the quick action is PER_DIEM - navigateToQuickAction(true, reportID, {action: CONST.QUICK_ACTIONS.PER_DIEM}, (onSelected: () => void) => { + navigateToQuickAction(true, {action: CONST.QUICK_ACTIONS.PER_DIEM, chatReportID: reportID}, {accountID: 1234}, undefined, (onSelected: () => void) => { onSelected(); }); // Then we should start per diem request flow @@ -49,3 +57,22 @@ describe('IOU Utils', () => { }); }); }); + +describe('Non IOU quickActions test:', () => { + const reportID = generateReportID(); + + describe('navigateToQuickAction', () => { + it('creates new report for "createReport" quick action', () => { + navigateToQuickAction(true, {action: CONST.QUICK_ACTIONS.CREATE_REPORT, chatReportID: reportID}, {accountID: 1234}, undefined, (onSelected: () => void) => { + onSelected(); + }); + expect(createNewReport).toHaveBeenCalled(); + }); + it('starts create task flow for "assignTask" quick action', () => { + navigateToQuickAction(true, {action: CONST.QUICK_ACTIONS.ASSIGN_TASK, targetAccountID: 123}, {accountID: 1234}, undefined, (onSelected: () => void) => { + onSelected(); + }); + expect(startOutCreateTaskQuickAction).toHaveBeenCalled(); + }); + }); +});