diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index e241f65bc646..8c43ae542932 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -17,6 +17,7 @@ import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as DraftCommentUtils from '@libs/DraftCommentUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -112,7 +113,7 @@ function LHNOptionsList({ const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; - const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + const hasDraftComment = DraftCommentUtils.isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); const lastReportAction = sortedReportActions[0]; @@ -139,7 +140,7 @@ function LHNOptionsList({ isFocused={!shouldDisableFocusOptions} onSelectRow={onSelectRow} preferredLocale={preferredLocale} - comment={itemComment} + hasDraftComment={hasDraftComment} transactionViolations={transactionViolations} canUseViolations={canUseViolations} onLayout={onLayoutItem} @@ -163,7 +164,10 @@ function LHNOptionsList({ ], ); - const extraData = useMemo(() => [reportActions, reports, policy, personalDetails, data.length], [reportActions, reports, policy, personalDetails, data.length]); + const extraData = useMemo( + () => [reportActions, reports, policy, personalDetails, data.length, draftComments], + [reportActions, reports, policy, personalDetails, data.length, draftComments], + ); const previousOptionMode = usePrevious(optionMode); diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index b1abaf3f0b5b..fbcb7d4b373d 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -27,7 +27,7 @@ import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; -function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}}: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -248,7 +248,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti /> )} - {optionItem.hasDraftComment && optionItem.isAllowedToComment && ( + {hasDraftComment && optionItem.isAllowedToComment && ( { - if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { - return; - } - Report.setReportWithDraft(reportID, true); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - return ( ; - /** Comment added to report */ - comment: string; + /** Whether a report contains a draft */ + hasDraftComment: boolean; /** The receipt transaction from the parent report action */ receiptTransactions: OnyxCollection; @@ -133,6 +133,9 @@ type OptionRowLHNProps = { /** The item that should be rendered */ optionItem?: OptionData; + /** Whether a report contains a draft */ + hasDraftComment: boolean; + onLayout?: (event: LayoutChangeEvent) => void; }; diff --git a/src/components/optionPropTypes.js b/src/components/optionPropTypes.js index 4cfadea33d60..53d8e86b02d6 100644 --- a/src/components/optionPropTypes.js +++ b/src/components/optionPropTypes.js @@ -25,9 +25,6 @@ export default PropTypes.shape({ // reportID (only present when there is a matching report) reportID: PropTypes.string, - // Whether the report has a draft comment or not - hasDraftComment: PropTypes.bool, - // Key used internally by React keyForList: PropTypes.string, diff --git a/src/libs/ComposerUtils/getDraftComment.ts b/src/libs/ComposerUtils/getDraftComment.ts deleted file mode 100644 index 7f11825004a1..000000000000 --- a/src/libs/ComposerUtils/getDraftComment.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type {OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; - -const draftCommentMap: Record> = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - callback: (value, key) => { - if (!key) { - return; - } - - const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, ''); - draftCommentMap[reportID] = value; - }, -}); - -/** - * Returns a draft comment from the onyx collection. - * Note: You should use the HOCs/hooks to get onyx data, instead of using this directly. - * A valid use case to use this is if the value is only needed once for an initial value. - */ -export default function getDraftComment(reportID: string): OnyxEntry { - return draftCommentMap[reportID]; -} diff --git a/src/libs/DraftCommentUtils.ts b/src/libs/DraftCommentUtils.ts new file mode 100644 index 000000000000..325da93d235e --- /dev/null +++ b/src/libs/DraftCommentUtils.ts @@ -0,0 +1,47 @@ +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; + +let draftCommentCollection: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + callback: (nextVal) => { + draftCommentCollection = nextVal; + }, + waitForCollectionCallback: true, +}); + +/** + * Returns a draft comment from the onyx collection for given reportID. + * Note: You should use the HOCs/hooks to get onyx data, instead of using this directly. + * A valid use-case of this function is outside React components, like in utility functions. + */ +function getDraftComment(reportID: string): OnyxEntry | null | undefined { + return draftCommentCollection?.[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT + reportID]; +} + +/** + * Returns true if the report has a valid draft comment. + * A valid draft comment is a non-empty string. + */ +function isValidDraftComment(comment?: string | null): boolean { + return !!comment?.trim(); +} + +/** + * Returns true if the report has a valid draft comment. + */ +function hasValidDraftComment(reportID: string): boolean { + return isValidDraftComment(getDraftComment(reportID)); +} + +/** + * Prepares a draft comment by trimming it and returning null if it's empty. + */ +function prepareDraftComment(comment: string | null) { + // logical OR is used to convert empty string to null + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return comment?.trim() || null; +} + +export {getDraftComment, isValidDraftComment, hasValidDraftComment, prepareDraftComment}; diff --git a/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts b/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts deleted file mode 100644 index 8305fa217f79..000000000000 --- a/src/libs/OnyxSelectors/reportWithoutHasDraftSelector.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type {OnyxEntry} from 'react-native-onyx'; -import type {Report} from '@src/types/onyx'; - -type ReportWithoutHasDraft = Omit; - -export default function reportWithoutHasDraftSelector(report: OnyxEntry): OnyxEntry { - if (!report) { - return null; - } - const {hasDraft, ...reportWithoutHasDraft} = report; - return reportWithoutHasDraft; -} - -export type {ReportWithoutHasDraft}; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 21c178684bfe..bd8b799bdc52 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -654,7 +654,6 @@ function createOption( login: null, reportID: '', phoneNumber: null, - hasDraftComment: false, keyForList: null, searchText: null, isDefaultRoom: false, @@ -699,7 +698,6 @@ function createOption( result.ownerAccountID = report.ownerAccountID; result.reportID = report.reportID; result.isUnread = ReportUtils.isUnread(report); - result.hasDraftComment = report.hasDraft; result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 23d42efef01c..9d7b6b1d6549 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -56,6 +56,7 @@ import * as store from './actions/ReimbursementAccount/store'; import * as CollectionUtils from './CollectionUtils'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; +import {hasValidDraftComment} from './DraftCommentUtils'; import originalGetReportPolicyID from './getReportPolicyID'; import isReportMessageAttachment from './isReportMessageAttachment'; import localeCompare from './LocaleCompare'; @@ -391,7 +392,6 @@ type OptionData = { phoneNumber?: string | null; isUnread?: boolean | null; isUnreadWithMention?: boolean | null; - hasDraftComment?: boolean | null; keyForList?: string | null; searchText?: string | null; isIOUReportOwner?: boolean | null; @@ -4469,9 +4469,12 @@ function shouldReportBeInOptionList({ return true; } + // Retrieve the draft comment for the report and convert it to a boolean + const hasDraftComment = hasValidDraftComment(report.reportID); + // Include reports that are relevant to the user in any view mode. Criteria include having a draft or having a GBR showing. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (report.hasDraft || requiresAttentionFromCurrentUser(report)) { + if (hasDraftComment || requiresAttentionFromCurrentUser(report)) { return true; } const lastVisibleMessage = ReportActionsUtils.getLastVisibleMessage(report.reportID); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2c628f397390..789029779b55 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -13,6 +13,7 @@ import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import * as CollectionUtils from './CollectionUtils'; +import {hasValidDraftComment} from './DraftCommentUtils'; import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; @@ -139,7 +140,7 @@ function getOrderedReportIDs( const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? ''); if (isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { pinnedAndGBRReports.push(report); - } else if (report.hasDraft) { + } else if (hasValidDraftComment(report.reportID)) { draftReports.push(report); } else if (ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); @@ -214,7 +215,6 @@ function getOptionData({ phoneNumber: null, isUnread: null, isUnreadWithMention: null, - hasDraftComment: false, keyForList: null, searchText: null, isPinned: false, @@ -263,7 +263,6 @@ function getOptionData({ // setting it Unread so we add additional condition here to avoid empty chat LHN from being bold. result.isUnread = ReportUtils.isUnread(report) && !!report.lastActorAccountID; result.isUnreadWithMention = ReportUtils.isUnreadWithMention(report); - result.hasDraftComment = report.hasDraft; result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 778b519b9180..42a2852e42a6 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -160,7 +160,6 @@ Onyx.connect({ return; } const {reportID} = policyReport; - cleanUpMergeQueries[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] = {hasDraft: false}; cleanUpSetQueries[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] = null; cleanUpSetQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}`] = null; }); @@ -362,7 +361,6 @@ function deleteWorkspace(policyID: string, policyName: string) { value: { stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, - hasDraft: false, oldPolicyName: allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.name ?? '', policyName: '', }, @@ -412,14 +410,13 @@ function deleteWorkspace(policyID: string, policyName: string) { ]; reportsToArchive.forEach((report) => { - const {reportID, stateNum, statusNum, hasDraft, oldPolicyName} = report ?? {}; + const {reportID, stateNum, statusNum, oldPolicyName} = report ?? {}; failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { stateNum, statusNum, - hasDraft, oldPolicyName, policyName: report?.policyName, }, @@ -881,7 +878,6 @@ function removeMembers(accountIDs: number[], policyID: string) { statusNum: CONST.REPORT.STATUS_NUM.CLOSED, stateNum: CONST.REPORT.STATE_NUM.APPROVED, oldPolicyName: policy.name, - hasDraft: false, pendingChatMembers, }, }); @@ -936,14 +932,13 @@ function removeMembers(accountIDs: number[], policyID: string) { const filteredWorkspaceChats = workspaceChats.filter((report): report is Report => report !== null); - filteredWorkspaceChats.forEach(({reportID, stateNum, statusNum, hasDraft, oldPolicyName = null}) => { + filteredWorkspaceChats.forEach(({reportID, stateNum, statusNum, oldPolicyName = null}) => { failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { stateNum, statusNum, - hasDraft, oldPolicyName, }, }); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 3605f8c39962..179ee87862ff 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -44,6 +44,7 @@ import type UpdateRoomVisibilityParams from '@libs/API/parameters/UpdateRoomVisi import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; +import {prepareDraftComment} from '@libs/DraftCommentUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as Environment from '@libs/Environment/Environment'; import * as ErrorUtils from '@libs/ErrorUtils'; @@ -1073,9 +1074,10 @@ function togglePinnedState(reportID: string, isPinnedChat: boolean) { /** * Saves the comment left by the user as they are typing. By saving this data the user can switch between chats, close * tab, refresh etc without worrying about loosing what they typed out. + * When empty string or null is passed, it will delete the draft comment from Onyx store. */ -function saveReportComment(reportID: string, comment: string) { - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, comment); +function saveReportDraftComment(reportID: string, comment: string | null) { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, prepareDraftComment(comment)); } /** Saves the number of lines for the comment */ @@ -1083,11 +1085,6 @@ function saveReportCommentNumberOfLines(reportID: string, numberOfLines: number) Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES}${reportID}`, numberOfLines); } -/** Immediate indication whether the report has a draft comment. */ -function setReportWithDraft(reportID: string, hasDraft: boolean): Promise { - return Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {hasDraft}); -} - /** Broadcasts whether or not a user is typing on a report over the report's private pusher channel. */ function broadcastUserIsTyping(reportID: string) { const privateReportChannelName = getReportChannelName(reportID); @@ -2908,17 +2905,6 @@ function clearNewRoomFormError() { }); } -function getReportDraftStatus(reportID: string) { - if (!allReports) { - return false; - } - - if (!allReports[reportID]) { - return false; - } - return allReports[reportID]?.hasDraft; -} - function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry, resolution: ValueOf) { const message = reportAction?.message?.[0]; if (!message) { @@ -2973,7 +2959,6 @@ function setGroupDraft(participants: Array<{login: string; accountID: number}>, } export { - getReportDraftStatus, searchInServer, addComment, addAttachment, @@ -2984,7 +2969,7 @@ export { subscribeToReportLeavingEvents, unsubscribeFromReportChannel, unsubscribeFromLeavingRoomReportChannel, - saveReportComment, + saveReportDraftComment, saveReportCommentNumberOfLines, broadcastUserIsTyping, broadcastUserIsLeavingRoom, @@ -2996,7 +2981,6 @@ export { saveReportActionDraftNumberOfLines, deleteReportComment, navigateToConciergeChat, - setReportWithDraft, addPolicyReport, deleteReport, navigateToConciergeChatAndDeleteReport, diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 515a63aa5265..1b8957b833b0 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -23,8 +23,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as HeaderUtils from '@libs/HeaderUtils'; import Navigation from '@libs/Navigation/Navigation'; -import type {ReportWithoutHasDraft} from '@libs/OnyxSelectors/reportWithoutHasDraftSelector'; -import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -49,7 +47,7 @@ type HeaderViewOnyxProps = { personalDetails: OnyxEntry; /** Parent report */ - parentReport: OnyxEntry; + parentReport: OnyxEntry; /** The current policy of the report */ policy: OnyxEntry; @@ -377,8 +375,7 @@ export default memo( initialValue: null, }, parentReport: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? report?.reportID}`, - selector: reportWithoutHasDraftSelector, + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID ?? report?.reportID}`, }, session: { key: ONYXKEYS.SESSION, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index ef277984b4e9..940cba181db7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -30,8 +30,6 @@ import Timing from '@libs/actions/Timing'; import * as Browser from '@libs/Browser'; import Navigation from '@libs/Navigation/Navigation'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; -import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector'; -import type {ReportWithoutHasDraft} from '@libs/OnyxSelectors/reportWithoutHasDraftSelector'; import Performance from '@libs/Performance'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -79,7 +77,7 @@ type ReportScreenOnyxPropsWithoutParentReportAction = { sortedAllReportActions: OnyxTypes.ReportAction[]; /** The report currently being looked at */ - report: OnyxEntry; + report: OnyxEntry; /** The report metadata loading states */ reportMetadata: OnyxEntry; @@ -711,7 +709,6 @@ export default withCurrentReportID( report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${getReportID(route)}`, allowStaleData: true, - selector: reportWithoutHasDraftSelector, }, reportMetadata: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${getReportID(route)}`, diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index ab3ed32f5ee8..bfcef66e7c54 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -29,8 +29,8 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import * as ComposerUtils from '@libs/ComposerUtils'; -import getDraftComment from '@libs/ComposerUtils/getDraftComment'; import convertToLTRForComposer from '@libs/convertToLTRForComposer'; +import {getDraftComment} from '@libs/DraftCommentUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import getPlatform from '@libs/getPlatform'; @@ -333,7 +333,7 @@ function ComposerWithSuggestions( const debouncedSaveReportComment = useMemo( () => lodashDebounce((selectedReportID, newComment) => { - Report.saveReportComment(selectedReportID, newComment || ''); + Report.saveReportDraftComment(selectedReportID, newComment); isCommentPendingSaved.current = false; }, 1000), [], @@ -426,29 +426,12 @@ function ComposerWithSuggestions( }); } - // Indicate that draft has been created. - if (commentRef.current.length === 0 && newCommentConverted.length !== 0) { - Report.setReportWithDraft(reportID, true); - } - - const hasDraftStatus = Report.getReportDraftStatus(reportID); - - /** - * The extra `!hasDraftStatus` check is to prevent the draft being set - * when the user navigates to the ReportScreen. This doesn't alter anything - * in terms of functionality. - */ - // The draft has been deleted. - if (newCommentConverted.length === 0 && hasDraftStatus) { - Report.setReportWithDraft(reportID, false); - } - commentRef.current = newCommentConverted; if (shouldDebounceSaveComment) { isCommentPendingSaved.current = true; debouncedSaveReportComment(reportID, newCommentConverted); } else { - Report.saveReportComment(reportID, newCommentConverted || ''); + Report.saveReportDraftComment(reportID, newCommentConverted); } if (newCommentConverted) { debouncedBroadcastUserIsTyping(reportID); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 70340e9e1fec..5f7f5adebfc9 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -24,8 +24,8 @@ import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; -import getDraftComment from '@libs/ComposerUtils/getDraftComment'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {getDraftComment} from '@libs/DraftCommentUtils'; import getModalState from '@libs/getModalState'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; diff --git a/src/pages/home/report/ReportActionItemCreated.tsx b/src/pages/home/report/ReportActionItemCreated.tsx index 4fe52f6adf41..899575a9aa3a 100644 --- a/src/pages/home/report/ReportActionItemCreated.tsx +++ b/src/pages/home/report/ReportActionItemCreated.tsx @@ -10,7 +10,6 @@ import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector'; import * as ReportUtils from '@libs/ReportUtils'; import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report'; import CONST from '@src/CONST'; @@ -97,7 +96,6 @@ ReportActionItemCreated.displayName = 'ReportActionItemCreated'; export default withOnyx({ report: { key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - selector: reportWithoutHasDraftSelector, }, policy: { diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 0c1f8b8c5972..34af75308ac4 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -93,6 +93,8 @@ const propTypes = { ), }), + reportsDrafts: PropTypes.objectOf(PropTypes.string), + ...withCurrentUserPersonalDetailsPropTypes, }; @@ -105,6 +107,7 @@ const defaultProps = { policyMembers: {}, transactionViolations: {}, allReportActions: {}, + reportsDrafts: {}, ...withCurrentUserPersonalDetailsDefaultProps, }; @@ -123,6 +126,7 @@ function SidebarLinksData({ policyMembers, transactionViolations, currentUserPersonalDetails, + reportsDrafts, }) { const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); @@ -138,8 +142,20 @@ function SidebarLinksData({ const isLoading = isLoadingApp; const optionItemsMemoized = useMemo( - () => SidebarUtils.getOrderedReportIDs(null, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs), - [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs], + () => + SidebarUtils.getOrderedReportIDs( + null, + chatReports, + betas, + policies, + priorityMode, + allReportActions, + transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + reportsDrafts, + ), + [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, reportsDrafts], ); const optionListItems = useMemo(() => { @@ -219,7 +235,6 @@ const chatReportSelector = (report) => report && { reportID: report.reportID, participantAccountIDs: report.participantAccountIDs, - hasDraft: report.hasDraft, isPinned: report.isPinned, isHidden: report.isHidden, notificationPreference: report.notificationPreference, @@ -329,6 +344,10 @@ export default compose( key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, initialValue: {}, }, + reportsDrafts: { + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + initialValue: {}, + }, }), )( /* @@ -351,6 +370,7 @@ export default compose( _.isEqual(prevProps.policyMembers, nextProps.policyMembers) && _.isEqual(prevProps.transactionViolations, nextProps.transactionViolations) && _.isEqual(prevProps.currentUserPersonalDetails, nextProps.currentUserPersonalDetails) && - prevProps.currentReportID === nextProps.currentReportID, + prevProps.currentReportID === nextProps.currentReportID && + _.isEqual(prevProps.reportsDrafts, nextProps.reportsDrafts), ), ); diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index ce20462df372..7e39e7f65a22 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -132,7 +132,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< parentReportID?: string; parentReportActionID?: string; isOptimisticReport?: boolean; - hasDraft?: boolean; managerID?: number; lastVisibleActionLastModified?: string; displayName?: string; diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index f95fe3e484e9..d590236e5256 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -17,7 +17,6 @@ describe('OptionsListUtils', () => { participantAccountIDs: [2, 1], visibleChatMemberAccountIDs: [2, 1], reportName: 'Iron Man, Mister Fantastic', - hasDraft: true, type: CONST.REPORT.TYPE.CHAT, }, 2: { diff --git a/tests/unit/SidebarFilterTest.ts b/tests/unit/SidebarFilterTest.ts index 05771980dcf3..3f8678fa8cf9 100644 --- a/tests/unit/SidebarFilterTest.ts +++ b/tests/unit/SidebarFilterTest.ts @@ -24,6 +24,7 @@ const ONYXKEYS = { REPORT: 'report_', REPORT_ACTIONS: 'reportActions_', POLICY: 'policy_', + REPORT_DRAFT_COMMENT: 'reportDraftComment_', }, NETWORK: 'network', } as const; @@ -110,7 +111,6 @@ xdescribe('Sidebar', () => { // Given a new report with a draft text const report: Report = { ...LHNTestUtils.getFakeReport([1, 2], 0), - hasDraft: true, }; const reportCollectionDataSet: ReportCollectionDataSet = { @@ -124,6 +124,7 @@ xdescribe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]: 'This is a draft message', ...reportCollectionDataSet, }), ) @@ -330,9 +331,9 @@ xdescribe('Sidebar', () => { // const boolArr = [false, false, false, false, false]; it(`the booleans ${JSON.stringify(boolArr)}`, () => { - const [isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned, hasDraft] = boolArr; + const [isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned] = boolArr; const report2: Report = { - ...LHNTestUtils.getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned, hasDraft), + ...LHNTestUtils.getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned), policyID: policy.policyID, }; LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID); @@ -453,7 +454,6 @@ xdescribe('Sidebar', () => { // Given a draft report and a pinned report const draftReport = { ...LHNTestUtils.getFakeReport([1, 2]), - hasDraft: true, }; const pinnedReport = { ...LHNTestUtils.getFakeReport([3, 4]), @@ -474,6 +474,7 @@ xdescribe('Sidebar', () => { [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${draftReport.reportID}`]: 'draft report message', ...reportCollectionDataSet, }), ) @@ -676,7 +677,7 @@ xdescribe('Sidebar', () => { it(`the booleans ${JSON.stringify(boolArr)}`, () => { const [isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned, hasDraft] = boolArr; const report2 = { - ...LHNTestUtils.getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned, hasDraft), + ...LHNTestUtils.getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned), policyID: policy.policyID, }; LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID); @@ -696,6 +697,7 @@ xdescribe('Sidebar', () => { [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy, + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report2.reportID}`]: hasDraft ? 'report2 draft' : null, ...reportCollectionDataSet, }), ) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 27da8348f43d..2758d43fb81e 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -25,6 +25,7 @@ const ONYXKEYS = { REPORT: 'report_', REPORT_ACTIONS: 'reportActions_', POLICY: 'policy_', + REPORT_DRAFT_COMMENT: 'reportDraftComment_', }, NETWORK: 'network', IS_LOADING_REPORT_DATA: 'isLoadingReportData', @@ -163,7 +164,6 @@ describe('Sidebar', () => { // And the currently viewed report is the first report const report1 = { ...LHNTestUtils.getFakeReport([1, 2], 3), - hasDraft: true, }; const report2 = LHNTestUtils.getFakeReport([3, 4], 2); const report3 = LHNTestUtils.getFakeReport([5, 6], 1); @@ -190,6 +190,7 @@ describe('Sidebar', () => { [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report1.reportID}`]: 'report1 draft', ...reportCollectionDataSet, }), ) @@ -455,7 +456,6 @@ describe('Sidebar', () => { const report1 = LHNTestUtils.getFakeReport([1, 2], 3); const report2: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([3, 4], 2), - hasDraft: true, }; const report3 = LHNTestUtils.getFakeReport([5, 6], 1); @@ -481,6 +481,7 @@ describe('Sidebar', () => { [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, + [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT + report2.reportID]: 'This is a draft', ...reportCollectionDataSet, }), ) @@ -513,7 +514,6 @@ describe('Sidebar', () => { // And the report has a draft const report: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([1, 2]), - hasDraft: true, }; const reportCollectionDataSet: ReportCollectionDataSet = { @@ -528,6 +528,7 @@ describe('Sidebar', () => { [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]: 'This is a draft', ...reportCollectionDataSet, }), ) @@ -538,7 +539,7 @@ describe('Sidebar', () => { }) // When the draft is removed - .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, {hasDraft: null})) + .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`, null)) // Then the pencil icon goes away .then(() => { @@ -599,7 +600,6 @@ describe('Sidebar', () => { }; const report2: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([3, 4], 2), - hasDraft: true, }; const report3: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([5, 6], 1), @@ -643,6 +643,7 @@ describe('Sidebar', () => { [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, [ONYXKEYS.SESSION]: {accountID: currentlyLoggedInUserAccountID}, + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report2.reportID}`]: 'Report2 draft comment', ...reportCollectionDataSet, }), ) @@ -734,19 +735,15 @@ describe('Sidebar', () => { // and they all have drafts const report1: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([1, 2], 3), - hasDraft: true, }; const report2: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([3, 4], 2), - hasDraft: true, }; const report3: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([5, 6], 1), - hasDraft: true, }; const report4: OnyxTypes.Report = { ...LHNTestUtils.getFakeReport([7, 8], 0), - hasDraft: true, }; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); @@ -757,6 +754,12 @@ describe('Sidebar', () => { [`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3, }; + const reportDraftCommentCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report1.reportID}`]: 'report1 draft', + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report2.reportID}`]: 'report2 draft', + [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report3.reportID}`]: 'report3 draft', + }; + return ( waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders @@ -765,6 +768,7 @@ describe('Sidebar', () => { [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_APP]: false, + ...reportDraftCommentCollectionDataSet, ...reportCollectionDataSet, }), ) @@ -780,7 +784,12 @@ describe('Sidebar', () => { }) // When a new report is added - .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report4.reportID}`, report4)) + .then(() => + Promise.all([ + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report4.reportID}`, report4), + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report4.reportID}`, 'report4 draft'), + ]), + ) // Then they are still in alphabetical order .then(() => { diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index f6eee590313b..2dfb31f7cfe4 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -217,7 +217,7 @@ function getFakeReportAction(actor = 'email1@test.com', millisecondsInThePast = }; } -function getAdvancedFakeReport(isArchived: boolean, isUserCreatedPolicyRoom: boolean, hasAddWorkspaceError: boolean, isUnread: boolean, isPinned: boolean, hasDraft: boolean): Report { +function getAdvancedFakeReport(isArchived: boolean, isUserCreatedPolicyRoom: boolean, hasAddWorkspaceError: boolean, isUnread: boolean, isPinned: boolean): Report { return { ...getFakeReport([1, 2], 0, isUnread), type: CONST.REPORT.TYPE.CHAT, @@ -226,7 +226,6 @@ function getAdvancedFakeReport(isArchived: boolean, isUserCreatedPolicyRoom: boo stateNum: isArchived ? CONST.REPORT.STATE_NUM.APPROVED : 0, errorFields: hasAddWorkspaceError ? {1708946640843000: {addWorkspaceRoom: 'blah'}} : undefined, isPinned, - hasDraft, }; } diff --git a/tests/utils/collections/reports.ts b/tests/utils/collections/reports.ts index 34ce66ab495d..60908e72c2de 100644 --- a/tests/utils/collections/reports.ts +++ b/tests/utils/collections/reports.ts @@ -8,7 +8,6 @@ export default function createRandomReport(index: number): Report { chatType: rand(Object.values(CONST.REPORT.CHAT_TYPE)), currency: randCurrencyCode(), displayName: randWord(), - hasDraft: randBoolean(), ownerAccountID: index, isPinned: randBoolean(), isOptimisticReport: randBoolean(),