diff --git a/assets/images/arrows-leftright.svg b/assets/images/arrows-leftright.svg
new file mode 100644
index 000000000000..53c75d411734
--- /dev/null
+++ b/assets/images/arrows-leftright.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/assets/images/chatbubble-slash.svg b/assets/images/chatbubble-slash.svg
new file mode 100644
index 000000000000..09d2b5bd3149
--- /dev/null
+++ b/assets/images/chatbubble-slash.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/CONST.ts b/src/CONST.ts
index ac4b9562672d..f3cf71fef51f 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -446,6 +446,9 @@ const CONST = {
MAX_LENGTH: 83,
},
+ REVERSED_TRANSACTION_ATTRIBUTE: 'is-reversed-transaction',
+ HIDDEN_MESSAGE_ATTRIBUTE: 'is-hidden-message',
+
CALENDAR_PICKER: {
// Numbers were arbitrarily picked.
MIN_YEAR: CURRENT_YEAR - 100,
diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx
index b4002767524f..12b515194928 100755
--- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx
+++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx
@@ -37,6 +37,11 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
mixedUAStyles: {...styles.formError, ...styles.mb0},
contentModel: HTMLContentModel.block,
}),
+ 'deleted-action': HTMLElementModel.fromCustomModel({
+ tagName: 'alert-text',
+ mixedUAStyles: {...styles.formError, ...styles.mb0},
+ contentModel: HTMLContentModel.block,
+ }),
'muted-text': HTMLElementModel.fromCustomModel({
tagName: 'muted-text',
mixedUAStyles: {...styles.colorMuted, ...styles.mb0},
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx
new file mode 100644
index 000000000000..4e6334d90ebd
--- /dev/null
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import {View} from 'react-native';
+import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
+import {TNodeChildrenRenderer} from 'react-native-render-html';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import Text from '@components/Text';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+
+function DeletedActionRenderer({tnode}: CustomRendererProps) {
+ const styles = useThemeStyles();
+ const theme = useTheme();
+ const htmlAttribs = tnode.attributes;
+
+ const reversedTransactionValue = htmlAttribs[CONST.REVERSED_TRANSACTION_ATTRIBUTE];
+ const hiddenMessageValue = htmlAttribs[CONST.HIDDEN_MESSAGE_ATTRIBUTE];
+
+ const getIcon = () => {
+ if (reversedTransactionValue === 'true') {
+ return Expensicons.ArrowsLeftRight;
+ }
+ if (hiddenMessageValue === 'true') {
+ return Expensicons.EyeDisabled;
+ }
+ return Expensicons.Trashcan;
+ };
+
+ return (
+
+
+ {
+ const firstChild = props?.childTnode?.children?.at(0);
+ const data = firstChild && 'data' in firstChild ? firstChild.data : null;
+
+ if (typeof data === 'string') {
+ return {data};
+ }
+ return props.childElement;
+ }}
+ />
+
+ );
+}
+
+DeletedActionRenderer.displayName = 'DeletedActionRenderer';
+
+export default DeletedActionRenderer;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts
index ce24584048b0..91ed66f8b931 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts
@@ -1,6 +1,7 @@
import type {CustomTagRendererRecord} from 'react-native-render-html';
import AnchorRenderer from './AnchorRenderer';
import CodeRenderer from './CodeRenderer';
+import DeletedActionRenderer from './DeletedActionRenderer';
import EditedRenderer from './EditedRenderer';
import EmojiRenderer from './EmojiRenderer';
import ImageRenderer from './ImageRenderer';
@@ -30,6 +31,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = {
'mention-here': MentionHereRenderer,
emoji: EmojiRenderer,
'next-step-email': NextStepEmailRenderer,
+ 'deleted-action': DeletedActionRenderer,
/* eslint-enable @typescript-eslint/naming-convention */
};
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index 02a6843dc11f..e4072504f3d6 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -7,6 +7,7 @@ import ArrowRightLong from '@assets/images/arrow-right-long.svg';
import ArrowRight from '@assets/images/arrow-right.svg';
import ArrowUpLong from '@assets/images/arrow-up-long.svg';
import UpArrow from '@assets/images/arrow-up.svg';
+import ArrowsLeftRight from '@assets/images/arrows-leftright.svg';
import ArrowsUpDown from '@assets/images/arrows-updown.svg';
import AttachmentNotFound from '@assets/images/attachment-not-found.svg';
import AdminRoomAvatar from '@assets/images/avatars/admin-room.svg';
@@ -43,6 +44,7 @@ import Cash from '@assets/images/cash.svg';
import Chair from '@assets/images/chair.svg';
import ChatBubbleAdd from '@assets/images/chatbubble-add.svg';
import ChatBubbleReply from '@assets/images/chatbubble-reply.svg';
+import ChatBubbleSlash from '@assets/images/chatbubble-slash.svg';
import ChatBubbleUnread from '@assets/images/chatbubble-unread.svg';
import ChatBubble from '@assets/images/chatbubble.svg';
import ChatBubbles from '@assets/images/chatbubbles.svg';
@@ -220,6 +222,7 @@ export {
ArrowRight,
ArrowRightLong,
ArrowsUpDown,
+ ArrowsLeftRight,
ArrowUpLong,
ArrowDownLong,
AttachmentNotFound,
@@ -390,6 +393,7 @@ export {
Linkedin,
Instagram,
ChatBubbleAdd,
+ ChatBubbleSlash,
ChatBubbleUnread,
ChatBubbleReply,
Lightbulb,
diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx
index af54e2940d3f..cc64fed1d3e6 100644
--- a/src/components/ReportActionItem/MoneyRequestAction.tsx
+++ b/src/components/ReportActionItem/MoneyRequestAction.tsx
@@ -1,14 +1,13 @@
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import RenderHTML from '@components/RenderHTML';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
-import * as IOUUtils from '@libs/IOUUtils';
+import {isIOUReportPendingCurrencyConversion} from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
-import * as ReportActionsUtils from '@libs/ReportActionsUtils';
+import {isDeletedParentAction, isReversedTransaction, isSplitBillAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
@@ -18,18 +17,7 @@ import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import MoneyRequestPreview from './MoneyRequestPreview';
-type MoneyRequestActionOnyxProps = {
- /** Chat report associated with iouReport */
- chatReport: OnyxEntry;
-
- /** IOU report data object */
- iouReport: OnyxEntry;
-
- /** Report actions for this report */
- reportActions: OnyxEntry;
-};
-
-type MoneyRequestActionProps = MoneyRequestActionOnyxProps & {
+type MoneyRequestActionProps = {
/** All the data of the action */
action: OnyxTypes.ReportAction;
@@ -72,9 +60,6 @@ function MoneyRequestAction({
isMostRecentIOUReportAction,
contextMenuAnchor,
checkIfContextMenuActive = () => {},
- chatReport,
- iouReport,
- reportActions,
isHovered = false,
style,
isWhisper = false,
@@ -83,23 +68,29 @@ function MoneyRequestAction({
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
- const isSplitBillAction = ReportActionsUtils.isSplitBillAction(action);
- const isTrackExpenseAction = ReportActionsUtils.isTrackExpenseAction(action);
+ const isActionSplitBill = isSplitBillAction(action);
+ const isActionTrackExpense = isTrackExpenseAction(action);
+ const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID || CONST.DEFAULT_NUMBER_ID}`, {canEvict: false});
+ const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || CONST.DEFAULT_NUMBER_ID}`);
+ const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`);
const onMoneyRequestPreviewPressed = () => {
- if (isSplitBillAction) {
- const reportActionID = action.reportActionID ?? '-1';
+ if (isActionSplitBill) {
+ const reportActionID = action.reportActionID;
Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, reportActionID, Navigation.getReportRHPActiveRoute()));
return;
}
- const childReportID = action?.childReportID ?? '-1';
+ const childReportID = action?.childReportID;
+ if (!childReportID) {
+ return;
+ }
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID));
};
let shouldShowPendingConversionMessage = false;
- const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action);
- const isReversedTransaction = ReportActionsUtils.isReversedTransaction(action);
+ const isParentActionDeleted = isDeletedParentAction(action);
+ const isTransactionReveresed = isReversedTransaction(action);
if (
!isEmptyObject(iouReport) &&
!isEmptyObject(reportActions) &&
@@ -108,25 +99,25 @@ function MoneyRequestAction({
action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD &&
isOffline
) {
- shouldShowPendingConversionMessage = IOUUtils.isIOUReportPendingCurrencyConversion(iouReport);
+ shouldShowPendingConversionMessage = isIOUReportPendingCurrencyConversion(iouReport);
}
- if (isDeletedParentAction || isReversedTransaction) {
+ if (isParentActionDeleted || isTransactionReveresed) {
let message: TranslationPaths;
- if (isReversedTransaction) {
+ if (isTransactionReveresed) {
message = 'parentReportAction.reversedTransaction';
} else {
message = 'parentReportAction.deletedExpense';
}
- return ${translate(message)}`} />;
+ return ${translate(message)}`} />;
}
return (
({
- chatReport: {
- key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`,
- },
- iouReport: {
- key: ({requestReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`,
- },
- reportActions: {
- key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
- canEvict: false,
- },
-})(MoneyRequestAction);
+export default MoneyRequestAction;
diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx
index 2ea295d16143..91e9cdbbc9c1 100644
--- a/src/components/ReportActionItem/TaskPreview.tsx
+++ b/src/components/ReportActionItem/TaskPreview.tsx
@@ -20,15 +20,15 @@ import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import {checkIfActionIsAllowed} from '@libs/actions/Session';
+import {canActionTask, completeTask, getTaskAssigneeAccountID, reopenTask} from '@libs/actions/Task';
import ControlSelection from '@libs/ControlSelection';
-import * as DeviceCapabilities from '@libs/DeviceCapabilities';
+import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import getButtonState from '@libs/getButtonState';
import Navigation from '@libs/Navigation/Navigation';
-import * as ReportUtils from '@libs/ReportUtils';
-import * as TaskUtils from '@libs/TaskUtils';
+import {isCanceledTaskReport, isOpenTaskReport, isReportManager} from '@libs/ReportUtils';
+import {getTaskTitleFromReport} from '@libs/TaskUtils';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
-import * as Session from '@userActions/Session';
-import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -74,27 +74,27 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che
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 taskTitle = Str.htmlEncode(TaskUtils.getTaskTitleFromReport(taskReport, action?.childReportName ?? ''));
- const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
+ const taskTitle = Str.htmlEncode(getTaskTitleFromReport(taskReport, action?.childReportName ?? ''));
+ const taskAssigneeAccountID = getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
const taskOwnerAccountID = taskReport?.ownerAccountID ?? action?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID;
const hasAssignee = taskAssigneeAccountID > 0;
const personalDetails = usePersonalDetails();
const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? Expensicons.FallbackAvatar;
const avatarSize = CONST.AVATAR_SIZE.SMALL;
- const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action);
+ const isDeletedParentAction = isCanceledTaskReport(taskReport, action);
const iconWrapperStyle = StyleUtils.getTaskPreviewIconWrapper(hasAssignee ? avatarSize : undefined);
const titleStyle = StyleUtils.getTaskPreviewTitleStyle(iconWrapperStyle.height, isTaskCompleted);
- const shouldShowGreenDotIndicator = ReportUtils.isOpenTaskReport(taskReport, action) && ReportUtils.isReportManager(taskReport);
+ const shouldShowGreenDotIndicator = isOpenTaskReport(taskReport, action) && isReportManager(taskReport);
if (isDeletedParentAction) {
- return ${translate('parentReportAction.deletedTask')}`} />;
+ return ${translate('parentReportAction.deletedTask')}`} />;
}
return (
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID))}
- onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
+ onPressIn={() => canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)}
shouldUseHapticsOnLongPress
@@ -107,12 +107,12 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che
{
+ disabled={!canActionTask(taskReport, currentUserPersonalDetails.accountID, taskOwnerAccountID, taskAssigneeAccountID)}
+ onPress={checkIfActionIsAllowed(() => {
if (isTaskCompleted) {
- Task.reopenTask(taskReport, taskReportID);
+ reopenTask(taskReport, taskReportID);
} else {
- Task.completeTask(taskReport, taskReportID);
+ completeTask(taskReport, taskReportID);
}
})}
accessibilityLabel={translate('task.task')}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 7c3bd33c8b2a..3b55d6fbc75c 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -4974,12 +4974,12 @@ const translations = {
viewAttachment: 'View attachment',
},
parentReportAction: {
- deletedReport: '[Deleted report]',
- deletedMessage: '[Deleted message]',
- deletedExpense: '[Deleted expense]',
- reversedTransaction: '[Reversed transaction]',
- deletedTask: '[Deleted task]',
- hiddenMessage: '[Hidden message]',
+ deletedReport: 'Deleted report',
+ deletedMessage: 'Deleted message',
+ deletedExpense: 'Deleted expense',
+ reversedTransaction: 'Reversed transaction',
+ deletedTask: 'Deleted task',
+ hiddenMessage: 'Hidden message',
},
threads: {
thread: 'Thread',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 140891c7a4fa..dff3dcd575c0 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -5486,12 +5486,12 @@ const translations = {
viewAttachment: 'Ver archivo adjunto',
},
parentReportAction: {
- deletedReport: '[Informe eliminado]',
- deletedMessage: '[Mensaje eliminado]',
- deletedExpense: '[Gasto eliminado]',
- reversedTransaction: '[Transacción anulada]',
- deletedTask: '[Tarea eliminada]',
- hiddenMessage: '[Mensaje oculto]',
+ deletedReport: 'Informe eliminado',
+ deletedMessage: 'Mensaje eliminado',
+ deletedExpense: 'Gasto eliminado',
+ reversedTransaction: 'Transacción anulada',
+ deletedTask: 'Tarea eliminada',
+ hiddenMessage: 'Mensaje oculto',
},
threads: {
thread: 'Hilo',
diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx
index ee09576d1a4d..c74531acf317 100644
--- a/src/pages/home/report/PureReportActionItem.tsx
+++ b/src/pages/home/report/PureReportActionItem.tsx
@@ -733,7 +733,7 @@ function PureReportActionItem({
);
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) {
children = isClosedExpenseReportWithNoExpenses ? (
- ${translate('parentReportAction.deletedReport')}`} />
+ ${translate('parentReportAction.deletedReport')}`} />
) : (
@@ -76,13 +75,13 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans
const contextMenuValue = useMemo(() => ({...contextValue, isDisabled: true}), [contextValue]);
- if (ReportActionsUtils.isTransactionThread(parentReportAction)) {
- const isReversedTransaction = ReportActionsUtils.isReversedTransaction(parentReportAction);
+ if (isTransactionThread(parentReportAction)) {
+ const isTransactionReversed = isReversedTransaction(parentReportAction);
- if (ReportActionsUtils.isMessageDeleted(parentReportAction) || isReversedTransaction) {
+ if (isMessageDeleted(parentReportAction) || isTransactionReversed) {
let message: TranslationPaths;
- if (isReversedTransaction) {
+ if (isTransactionReversed) {
message = 'parentReportAction.reversedTransaction';
} else {
message = 'parentReportAction.deletedExpense';
@@ -97,7 +96,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans
showHeader
report={report}
>
- ${translate(message)}`} />
+ ${translate(message)}`} />
@@ -120,8 +119,8 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans
);
}
- if (ReportUtils.isTaskReport(report)) {
- if (ReportUtils.isCanceledTaskReport(report, parentReportAction)) {
+ if (isTaskReport(report)) {
+ if (isCanceledTaskReport(report, parentReportAction)) {
return (
@@ -131,7 +130,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans
showHeader={draftMessage === undefined}
report={report}
>
- ${translate('parentReportAction.deletedTask')}`} />
+ ${translate('parentReportAction.deletedTask')}`} />
@@ -150,7 +149,7 @@ function ReportActionItemContentCreated({contextValue, parentReportAction, trans
);
}
- if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report) || ReportUtils.isInvoiceReport(report)) {
+ if (isExpenseReport(report) || isIOUReport(report) || isInvoiceReport(report)) {
return (
{!isEmptyObject(transactionThreadReport?.reportID) ? (
diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx
index 05cb657b1e54..50721a5ffcad 100644
--- a/src/pages/home/report/ReportActionItemFragment.tsx
+++ b/src/pages/home/report/ReportActionItemFragment.tsx
@@ -6,7 +6,7 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import convertToLTR from '@libs/convertToLTR';
-import * as ReportUtils from '@libs/ReportUtils';
+import isReportMessageAttachment from '@libs/isReportMessageAttachment';
import CONST from '@src/CONST';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type {DecisionName, OriginalMessageSource} from '@src/types/onyx/OriginalMessage';
@@ -106,14 +106,14 @@ function ReportActionItemFragment({
// immediately display "[Deleted message]" while the delete action is pending.
if ((!isOffline && isThreadParentMessage && isPendingDelete) || fragment?.isDeletedParentAction) {
- return ${translate('parentReportAction.deletedMessage')}`} />;
+ return ${translate('parentReportAction.deletedMessage')}`} />;
}
if (isThreadParentMessage && moderationDecision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE) {
- return ${translate('parentReportAction.hiddenMessage')}`} />;
+ return ${translate('parentReportAction.hiddenMessage')}`} />;
}
- if (ReportUtils.isReportMessageAttachment(fragment)) {
+ if (isReportMessageAttachment(fragment)) {
return (