diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 65ac9d320d0c8..0fb9ea9fcce78 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -86,7 +86,19 @@ function TaskPreview({ const {translate} = useLocalize(); const theme = useTheme(); const taskReportID = taskReport?.reportID ?? action?.childReportID; - const taskTitle = action?.childReportName ?? taskReport?.reportName ?? ''; + // Prefer the live task report name so offline title edits are reflected immediately. + const taskTitle = taskReport?.reportName ?? action?.childReportName ?? ''; + const taskContextReport = + taskReport ?? + ({ + reportID: taskReportID, + parentReportID: chatReportID, + parentReportActionID: action?.reportActionID, + ownerAccountID: action?.childOwnerAccountID, + managerID: action?.childManagerAccountID, + stateNum: action?.childStateNum, + statusNum: action?.childStatusNum, + } as Report); const taskTitleWithoutImage = Parser.replace(Parser.htmlToMarkdown(taskTitle), {disabledRules: [...CONST.TASK_TITLE_DISABLED_RULES]}); @@ -96,12 +108,12 @@ function TaskPreview({ const isTaskCompleted = !isEmptyObject(taskReport) ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED : action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED; - const parentReportAction = useParentReportAction(taskReport); - const taskAssigneeAccountID = getTaskAssigneeAccountID(taskReport, parentReportAction) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; - const parentReport = useParentReport(taskReport?.reportID); + const parentReportAction = useParentReportAction(taskContextReport); + const taskAssigneeAccountID = getTaskAssigneeAccountID(taskContextReport, parentReportAction) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; + const parentReport = useParentReport(taskContextReport?.reportID); const isParentReportArchived = useReportIsArchived(parentReport?.reportID); - const hasOutstandingChildTask = useHasOutstandingChildTask(taskReport); - const isTaskActionable = canActionTask(taskReport, parentReportAction, currentUserPersonalDetails.accountID, parentReport, isParentReportArchived); + const hasOutstandingChildTask = useHasOutstandingChildTask(taskContextReport); + const isTaskActionable = canActionTask(taskContextReport, parentReportAction, currentUserPersonalDetails.accountID, parentReport, isParentReportArchived); const hasAssignee = taskAssigneeAccountID > 0; const personalDetails = usePersonalDetails(); const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? icons.FallbackAvatar; @@ -109,7 +121,7 @@ function TaskPreview({ const isDeletedParentAction = isCanceledTaskReport(taskReport, action); const iconWrapperStyle = StyleUtils.getTaskPreviewIconWrapper(hasAssignee ? avatarSize : undefined); - const shouldShowGreenDotIndicator = isOpenTaskReport(taskReport, action) && isReportManager(taskReport); + const shouldShowGreenDotIndicator = isOpenTaskReport(taskContextReport, action) && isReportManager(taskContextReport); if (isDeletedParentAction) { return ${translate('parentReportAction.deletedTask')}`} />; } @@ -150,9 +162,9 @@ function TaskPreview({ disabled={!isTaskActionable} onPress={callFunctionIfActionIsAllowed(() => { if (isTaskCompleted) { - reopenTask(taskReport, parentReport, currentUserPersonalDetails.accountID, taskReportID); + reopenTask(taskContextReport, parentReport, currentUserPersonalDetails.accountID, taskReportID); } else { - completeTask(taskReport, parentReport?.hasOutstandingChildTask ?? false, hasOutstandingChildTask, parentReportAction, taskReportID); + completeTask(taskContextReport, parentReport?.hasOutstandingChildTask ?? false, hasOutstandingChildTask, parentReportAction, taskReportID); } })} accessibilityLabel={translate('task.task')} diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index e3e062a0c98a5..b2bec6982c727 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -41,6 +41,7 @@ import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Log from '@libs/Log'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types'; +import {isCreatedTaskReportAction} from '@libs/ReportActionsUtils'; import {isSplitAction} from '@libs/ReportSecondaryActionUtils'; import {canEditFieldOfMoneyRequest, canHoldUnholdReportAction, canRejectReportAction, isOneTransactionReport, selectFilteredReportActions} from '@libs/ReportUtils'; import {buildCannedSearchQuery, buildSearchQueryString} from '@libs/SearchQueryUtils'; @@ -1056,7 +1057,12 @@ function Search({ } if (isReportActionListItemType(item)) { - const reportActionID = reportActionItem.reportActionID; + // Keep deep-linking for persisted actions, but avoid anchoring to optimistic created-task actions that may not be resolvable offline. + const isOptimisticCreatedTaskAction = reportActionItem.isOptimisticAction ?? false; + const shouldSkipReportActionID = + isCreatedTaskReportAction(reportActionItem) && (isOptimisticCreatedTaskAction || reportActionItem.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + + const reportActionID = shouldSkipReportActionID ? undefined : reportActionItem.reportActionID; Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo})); return; } diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 31907d214b8e0..d3224dfd352c4 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2169,9 +2169,15 @@ function getReportActionsSections(data: OnyxTypes.SearchResults['data'], visible const reportActions = Object.values(data[key]); n += reportActions.length; for (const reportAction of reportActions) { - const reportID = reportAction.reportID ?? reportIDFromKey; + // Always use the container reportID so "In " rows open the parent chat, not a child task/thread report. + const reportID = reportIDFromKey; const from = reportAction.accountID ? (data.personalDetailsList?.[reportAction.accountID] ?? emptyPersonalDetails) : emptyPersonalDetails; - const report = data[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? {}; + const report = + getReportOrDraftReport(reportID) ?? + getReportOrDraftReport(reportAction.reportID) ?? + data[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? + data[`${ONYXKEYS.COLLECTION.REPORT}${reportAction.reportID}`] ?? + {}; const policy = data[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`] ?? {}; const originalMessage = isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction) : undefined; const isSendingMoney = isMoneyRequestAction(reportAction) && originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && originalMessage?.IOUDetails; @@ -2193,6 +2199,7 @@ function getReportActionsSections(data: OnyxTypes.SearchResults['data'], visible reportActionItems.push({ ...reportAction, + reportID, from, // eslint-disable-next-line @typescript-eslint/no-deprecated reportName: getSearchReportName({report, policy, personalDetails: data.personalDetailsList, transactions, invoiceReceiverPolicy, reports, policies, isReportArchived}), diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index f4e03d26c5a28..6d32729fa655f 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -871,7 +871,7 @@ function addActions({ failureReportActions[pregeneratedResponseParams.optimisticConciergeReportActionID] = null; } - const failureData: Array> = [ + const failureData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 30e15174e4aa2..77dd990f452eb 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -210,13 +210,7 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { // task report/action on API failure so the task stays visible until the user dismiss the // error from chat. const failureData: Array< - OnyxUpdate< - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.PERSONAL_DETAILS_LIST - | typeof ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE - | typeof ONYXKEYS.COLLECTION.SNAPSHOT - > + OnyxUpdate > = []; if (assigneeChatReport && assigneeChatReportID) { @@ -424,12 +418,15 @@ function buildTaskData( }, ]; - if (parentReportAction) { + const parentReportID = taskReport?.parentReportID; + const parentReportActionID = parentReportAction?.reportActionID ?? taskReport?.parentReportActionID; + + if (parentReportID && parentReportActionID) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReport?.parentReportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, value: { - [parentReportAction.reportActionID]: { + [parentReportActionID]: { childStateNum: CONST.REPORT.STATE_NUM.APPROVED, childStatusNum: CONST.REPORT.STATUS_NUM.APPROVED, }, @@ -438,9 +435,9 @@ function buildTaskData( failureData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReport?.parentReportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, value: { - [parentReportAction.reportActionID]: { + [parentReportActionID]: { childStateNum: CONST.REPORT.STATE_NUM.OPEN, childStatusNum: CONST.REPORT.STATUS_NUM.OPEN, }, @@ -541,6 +538,19 @@ function reopenTask(taskReport: OnyxEntry, parentReport: OnyxE }, ]; + if (taskReport?.parentReportID && taskReport?.parentReportActionID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReport.parentReportID}`, + value: { + [taskReport.parentReportActionID]: { + childStateNum: CONST.REPORT.STATE_NUM.OPEN, + childStatusNum: CONST.REPORT.STATUS_NUM.OPEN, + }, + }, + }); + } + const successData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -579,6 +589,19 @@ function reopenTask(taskReport: OnyxEntry, parentReport: OnyxE }, ]; + if (taskReport?.parentReportID && taskReport?.parentReportActionID) { + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReport.parentReportID}`, + value: { + [taskReport.parentReportActionID]: { + childStateNum: CONST.REPORT.STATE_NUM.APPROVED, + childStatusNum: CONST.REPORT.STATUS_NUM.APPROVED, + }, + }, + }); + } + const parameters: ReopenTaskParams = { taskReportID, reopenedTaskReportActionID: reopenedTaskReportAction.reportActionID, @@ -621,6 +644,18 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task }, ]; + if (title && report.parentReportID && report.parentReportActionID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, + value: { + [report.parentReportActionID]: { + childReportName: parsedTitle, + }, + }, + }); + } + const successData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -657,6 +692,18 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task }, ]; + if (title && report.parentReportID && report.parentReportActionID) { + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, + value: { + [report.parentReportActionID]: { + childReportName: report.reportName, + }, + }, + }); + } + const parameters: EditTaskParams = { taskReportID: report.reportID, htmlTitle: parsedTitle,