Skip to content

Commit 95cc648

Browse files
authored
Merge pull request #54804 from Krishna2323/krishna2323/issue/53617
Tweak UI for deleted message and expense
2 parents b883a37 + 5f2028c commit 95cc648

14 files changed

+152
-92
lines changed

assets/images/arrows-leftright.svg

+6
Loading

assets/images/chatbubble-slash.svg

+4
Loading

src/CONST.ts

+3
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,9 @@ const CONST = {
446446
MAX_LENGTH: 83,
447447
},
448448

449+
REVERSED_TRANSACTION_ATTRIBUTE: 'is-reversed-transaction',
450+
HIDDEN_MESSAGE_ATTRIBUTE: 'is-hidden-message',
451+
449452
CALENDAR_PICKER: {
450453
// Numbers were arbitrarily picked.
451454
MIN_YEAR: CURRENT_YEAR - 100,

src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
3737
mixedUAStyles: {...styles.formError, ...styles.mb0},
3838
contentModel: HTMLContentModel.block,
3939
}),
40+
'deleted-action': HTMLElementModel.fromCustomModel({
41+
tagName: 'alert-text',
42+
mixedUAStyles: {...styles.formError, ...styles.mb0},
43+
contentModel: HTMLContentModel.block,
44+
}),
4045
'muted-text': HTMLElementModel.fromCustomModel({
4146
tagName: 'muted-text',
4247
mixedUAStyles: {...styles.colorMuted, ...styles.mb0},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
4+
import {TNodeChildrenRenderer} from 'react-native-render-html';
5+
import Icon from '@components/Icon';
6+
import * as Expensicons from '@components/Icon/Expensicons';
7+
import Text from '@components/Text';
8+
import useTheme from '@hooks/useTheme';
9+
import useThemeStyles from '@hooks/useThemeStyles';
10+
import variables from '@styles/variables';
11+
import CONST from '@src/CONST';
12+
13+
function DeletedActionRenderer({tnode}: CustomRendererProps<TText | TPhrasing>) {
14+
const styles = useThemeStyles();
15+
const theme = useTheme();
16+
const htmlAttribs = tnode.attributes;
17+
18+
const reversedTransactionValue = htmlAttribs[CONST.REVERSED_TRANSACTION_ATTRIBUTE];
19+
const hiddenMessageValue = htmlAttribs[CONST.HIDDEN_MESSAGE_ATTRIBUTE];
20+
21+
const getIcon = () => {
22+
if (reversedTransactionValue === 'true') {
23+
return Expensicons.ArrowsLeftRight;
24+
}
25+
if (hiddenMessageValue === 'true') {
26+
return Expensicons.EyeDisabled;
27+
}
28+
return Expensicons.Trashcan;
29+
};
30+
31+
return (
32+
<View style={[styles.p4, styles.mt1, styles.appBG, styles.border, {borderColor: theme.border}, styles.flexRow, styles.justifyContentCenter, styles.alignItemsCenter, styles.gap2]}>
33+
<Icon
34+
width={variables.iconSizeMedium}
35+
height={variables.iconSizeMedium}
36+
fill={theme.icon}
37+
src={getIcon()}
38+
/>
39+
<TNodeChildrenRenderer
40+
tnode={tnode}
41+
renderChild={(props) => {
42+
const firstChild = props?.childTnode?.children?.at(0);
43+
const data = firstChild && 'data' in firstChild ? firstChild.data : null;
44+
45+
if (typeof data === 'string') {
46+
return <Text style={(styles.textLabelSupporting, styles.textStrong)}>{data}</Text>;
47+
}
48+
return props.childElement;
49+
}}
50+
/>
51+
</View>
52+
);
53+
}
54+
55+
DeletedActionRenderer.displayName = 'DeletedActionRenderer';
56+
57+
export default DeletedActionRenderer;

src/components/HTMLEngineProvider/HTMLRenderers/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {CustomTagRendererRecord} from 'react-native-render-html';
22
import AnchorRenderer from './AnchorRenderer';
33
import CodeRenderer from './CodeRenderer';
4+
import DeletedActionRenderer from './DeletedActionRenderer';
45
import EditedRenderer from './EditedRenderer';
56
import EmojiRenderer from './EmojiRenderer';
67
import ImageRenderer from './ImageRenderer';
@@ -30,6 +31,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = {
3031
'mention-here': MentionHereRenderer,
3132
emoji: EmojiRenderer,
3233
'next-step-email': NextStepEmailRenderer,
34+
'deleted-action': DeletedActionRenderer,
3335
/* eslint-enable @typescript-eslint/naming-convention */
3436
};
3537

src/components/Icon/Expensicons.ts

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ArrowRightLong from '@assets/images/arrow-right-long.svg';
77
import ArrowRight from '@assets/images/arrow-right.svg';
88
import ArrowUpLong from '@assets/images/arrow-up-long.svg';
99
import UpArrow from '@assets/images/arrow-up.svg';
10+
import ArrowsLeftRight from '@assets/images/arrows-leftright.svg';
1011
import ArrowsUpDown from '@assets/images/arrows-updown.svg';
1112
import AttachmentNotFound from '@assets/images/attachment-not-found.svg';
1213
import AdminRoomAvatar from '@assets/images/avatars/admin-room.svg';
@@ -43,6 +44,7 @@ import Cash from '@assets/images/cash.svg';
4344
import Chair from '@assets/images/chair.svg';
4445
import ChatBubbleAdd from '@assets/images/chatbubble-add.svg';
4546
import ChatBubbleReply from '@assets/images/chatbubble-reply.svg';
47+
import ChatBubbleSlash from '@assets/images/chatbubble-slash.svg';
4648
import ChatBubbleUnread from '@assets/images/chatbubble-unread.svg';
4749
import ChatBubble from '@assets/images/chatbubble.svg';
4850
import ChatBubbles from '@assets/images/chatbubbles.svg';
@@ -220,6 +222,7 @@ export {
220222
ArrowRight,
221223
ArrowRightLong,
222224
ArrowsUpDown,
225+
ArrowsLeftRight,
223226
ArrowUpLong,
224227
ArrowDownLong,
225228
AttachmentNotFound,
@@ -390,6 +393,7 @@ export {
390393
Linkedin,
391394
Instagram,
392395
ChatBubbleAdd,
396+
ChatBubbleSlash,
393397
ChatBubbleUnread,
394398
ChatBubbleReply,
395399
Lightbulb,

src/components/ReportActionItem/MoneyRequestAction.tsx

+24-44
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import React from 'react';
22
import type {StyleProp, ViewStyle} from 'react-native';
3-
import {withOnyx} from 'react-native-onyx';
4-
import type {OnyxEntry} from 'react-native-onyx';
3+
import {useOnyx} from 'react-native-onyx';
54
import RenderHTML from '@components/RenderHTML';
65
import useLocalize from '@hooks/useLocalize';
76
import useNetwork from '@hooks/useNetwork';
87
import useThemeStyles from '@hooks/useThemeStyles';
9-
import * as IOUUtils from '@libs/IOUUtils';
8+
import {isIOUReportPendingCurrencyConversion} from '@libs/IOUUtils';
109
import Navigation from '@libs/Navigation/Navigation';
11-
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
10+
import {isDeletedParentAction, isReversedTransaction, isSplitBillAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
1211
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
1312
import CONST from '@src/CONST';
1413
import type {TranslationPaths} from '@src/languages/types';
@@ -18,18 +17,7 @@ import type * as OnyxTypes from '@src/types/onyx';
1817
import {isEmptyObject} from '@src/types/utils/EmptyObject';
1918
import MoneyRequestPreview from './MoneyRequestPreview';
2019

21-
type MoneyRequestActionOnyxProps = {
22-
/** Chat report associated with iouReport */
23-
chatReport: OnyxEntry<OnyxTypes.Report>;
24-
25-
/** IOU report data object */
26-
iouReport: OnyxEntry<OnyxTypes.Report>;
27-
28-
/** Report actions for this report */
29-
reportActions: OnyxEntry<OnyxTypes.ReportActions>;
30-
};
31-
32-
type MoneyRequestActionProps = MoneyRequestActionOnyxProps & {
20+
type MoneyRequestActionProps = {
3321
/** All the data of the action */
3422
action: OnyxTypes.ReportAction;
3523

@@ -72,9 +60,6 @@ function MoneyRequestAction({
7260
isMostRecentIOUReportAction,
7361
contextMenuAnchor,
7462
checkIfContextMenuActive = () => {},
75-
chatReport,
76-
iouReport,
77-
reportActions,
7863
isHovered = false,
7964
style,
8065
isWhisper = false,
@@ -83,23 +68,29 @@ function MoneyRequestAction({
8368
const styles = useThemeStyles();
8469
const {translate} = useLocalize();
8570
const {isOffline} = useNetwork();
86-
const isSplitBillAction = ReportActionsUtils.isSplitBillAction(action);
87-
const isTrackExpenseAction = ReportActionsUtils.isTrackExpenseAction(action);
71+
const isActionSplitBill = isSplitBillAction(action);
72+
const isActionTrackExpense = isTrackExpenseAction(action);
73+
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID || CONST.DEFAULT_NUMBER_ID}`, {canEvict: false});
74+
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || CONST.DEFAULT_NUMBER_ID}`);
75+
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`);
8876

8977
const onMoneyRequestPreviewPressed = () => {
90-
if (isSplitBillAction) {
91-
const reportActionID = action.reportActionID ?? '-1';
78+
if (isActionSplitBill) {
79+
const reportActionID = action.reportActionID;
9280
Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, reportActionID, Navigation.getReportRHPActiveRoute()));
9381
return;
9482
}
9583

96-
const childReportID = action?.childReportID ?? '-1';
84+
const childReportID = action?.childReportID;
85+
if (!childReportID) {
86+
return;
87+
}
9788
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID));
9889
};
9990

10091
let shouldShowPendingConversionMessage = false;
101-
const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action);
102-
const isReversedTransaction = ReportActionsUtils.isReversedTransaction(action);
92+
const isParentActionDeleted = isDeletedParentAction(action);
93+
const isTransactionReveresed = isReversedTransaction(action);
10394
if (
10495
!isEmptyObject(iouReport) &&
10596
!isEmptyObject(reportActions) &&
@@ -108,25 +99,25 @@ function MoneyRequestAction({
10899
action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD &&
109100
isOffline
110101
) {
111-
shouldShowPendingConversionMessage = IOUUtils.isIOUReportPendingCurrencyConversion(iouReport);
102+
shouldShowPendingConversionMessage = isIOUReportPendingCurrencyConversion(iouReport);
112103
}
113104

114-
if (isDeletedParentAction || isReversedTransaction) {
105+
if (isParentActionDeleted || isTransactionReveresed) {
115106
let message: TranslationPaths;
116-
if (isReversedTransaction) {
107+
if (isTransactionReveresed) {
117108
message = 'parentReportAction.reversedTransaction';
118109
} else {
119110
message = 'parentReportAction.deletedExpense';
120111
}
121-
return <RenderHTML html={`<comment>${translate(message)}</comment>`} />;
112+
return <RenderHTML html={`<deleted-action ${CONST.REVERSED_TRANSACTION_ATTRIBUTE}="${isTransactionReveresed}">${translate(message)}</deleted-action>`} />;
122113
}
123114
return (
124115
<MoneyRequestPreview
125116
iouReportID={requestReportID}
126117
chatReportID={chatReportID}
127118
reportID={reportID}
128-
isBillSplit={isSplitBillAction}
129-
isTrackExpense={isTrackExpenseAction}
119+
isBillSplit={isActionSplitBill}
120+
isTrackExpense={isActionTrackExpense}
130121
action={action}
131122
contextMenuAnchor={contextMenuAnchor}
132123
checkIfContextMenuActive={checkIfContextMenuActive}
@@ -142,15 +133,4 @@ function MoneyRequestAction({
142133

143134
MoneyRequestAction.displayName = 'MoneyRequestAction';
144135

145-
export default withOnyx<MoneyRequestActionProps, MoneyRequestActionOnyxProps>({
146-
chatReport: {
147-
key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`,
148-
},
149-
iouReport: {
150-
key: ({requestReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`,
151-
},
152-
reportActions: {
153-
key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
154-
canEvict: false,
155-
},
156-
})(MoneyRequestAction);
136+
export default MoneyRequestAction;

src/components/ReportActionItem/TaskPreview.tsx

+15-15
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ import useLocalize from '@hooks/useLocalize';
2020
import useStyleUtils from '@hooks/useStyleUtils';
2121
import useTheme from '@hooks/useTheme';
2222
import useThemeStyles from '@hooks/useThemeStyles';
23+
import {checkIfActionIsAllowed} from '@libs/actions/Session';
24+
import {canActionTask, completeTask, getTaskAssigneeAccountID, reopenTask} from '@libs/actions/Task';
2325
import ControlSelection from '@libs/ControlSelection';
24-
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
26+
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
2527
import getButtonState from '@libs/getButtonState';
2628
import Navigation from '@libs/Navigation/Navigation';
27-
import * as ReportUtils from '@libs/ReportUtils';
28-
import * as TaskUtils from '@libs/TaskUtils';
29+
import {isCanceledTaskReport, isOpenTaskReport, isReportManager} from '@libs/ReportUtils';
30+
import {getTaskTitleFromReport} from '@libs/TaskUtils';
2931
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
30-
import * as Session from '@userActions/Session';
31-
import * as Task from '@userActions/Task';
3232
import CONST from '@src/CONST';
3333
import ONYXKEYS from '@src/ONYXKEYS';
3434
import ROUTES from '@src/ROUTES';
@@ -74,27 +74,27 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che
7474
const isTaskCompleted = !isEmptyObject(taskReport)
7575
? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED
7676
: action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED;
77-
const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitleFromReport(taskReport, action?.childReportName ?? ''));
78-
const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
77+
const taskTitle = Str.htmlEncode(getTaskTitleFromReport(taskReport, action?.childReportName ?? ''));
78+
const taskAssigneeAccountID = getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
7979
const taskOwnerAccountID = taskReport?.ownerAccountID ?? action?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID;
8080
const hasAssignee = taskAssigneeAccountID > 0;
8181
const personalDetails = usePersonalDetails();
8282
const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? Expensicons.FallbackAvatar;
8383
const avatarSize = CONST.AVATAR_SIZE.SMALL;
84-
const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action);
84+
const isDeletedParentAction = isCanceledTaskReport(taskReport, action);
8585
const iconWrapperStyle = StyleUtils.getTaskPreviewIconWrapper(hasAssignee ? avatarSize : undefined);
8686
const titleStyle = StyleUtils.getTaskPreviewTitleStyle(iconWrapperStyle.height, isTaskCompleted);
8787

88-
const shouldShowGreenDotIndicator = ReportUtils.isOpenTaskReport(taskReport, action) && ReportUtils.isReportManager(taskReport);
88+
const shouldShowGreenDotIndicator = isOpenTaskReport(taskReport, action) && isReportManager(taskReport);
8989
if (isDeletedParentAction) {
90-
return <RenderHTML html={`<comment>${translate('parentReportAction.deletedTask')}</comment>`} />;
90+
return <RenderHTML html={`<deleted-action>${translate('parentReportAction.deletedTask')}</deleted-action>`} />;
9191
}
9292

9393
return (
9494
<View style={[styles.chatItemMessage, !hasAssignee && styles.mv1]}>
9595
<PressableWithoutFeedback
9696
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID))}
97-
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
97+
onPressIn={() => canUseTouchScreen() && ControlSelection.block()}
9898
onPressOut={() => ControlSelection.unblock()}
9999
onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)}
100100
shouldUseHapticsOnLongPress
@@ -107,12 +107,12 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che
107107
<Checkbox
108108
style={[styles.mr2]}
109109
isChecked={isTaskCompleted}
110-
disabled={!Task.canActionTask(taskReport, currentUserPersonalDetails.accountID, taskOwnerAccountID, taskAssigneeAccountID)}
111-
onPress={Session.checkIfActionIsAllowed(() => {
110+
disabled={!canActionTask(taskReport, currentUserPersonalDetails.accountID, taskOwnerAccountID, taskAssigneeAccountID)}
111+
onPress={checkIfActionIsAllowed(() => {
112112
if (isTaskCompleted) {
113-
Task.reopenTask(taskReport, taskReportID);
113+
reopenTask(taskReport, taskReportID);
114114
} else {
115-
Task.completeTask(taskReport, taskReportID);
115+
completeTask(taskReport, taskReportID);
116116
}
117117
})}
118118
accessibilityLabel={translate('task.task')}

src/languages/en.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -4974,12 +4974,12 @@ const translations = {
49744974
viewAttachment: 'View attachment',
49754975
},
49764976
parentReportAction: {
4977-
deletedReport: '[Deleted report]',
4978-
deletedMessage: '[Deleted message]',
4979-
deletedExpense: '[Deleted expense]',
4980-
reversedTransaction: '[Reversed transaction]',
4981-
deletedTask: '[Deleted task]',
4982-
hiddenMessage: '[Hidden message]',
4977+
deletedReport: 'Deleted report',
4978+
deletedMessage: 'Deleted message',
4979+
deletedExpense: 'Deleted expense',
4980+
reversedTransaction: 'Reversed transaction',
4981+
deletedTask: 'Deleted task',
4982+
hiddenMessage: 'Hidden message',
49834983
},
49844984
threads: {
49854985
thread: 'Thread',

src/languages/es.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -5486,12 +5486,12 @@ const translations = {
54865486
viewAttachment: 'Ver archivo adjunto',
54875487
},
54885488
parentReportAction: {
5489-
deletedReport: '[Informe eliminado]',
5490-
deletedMessage: '[Mensaje eliminado]',
5491-
deletedExpense: '[Gasto eliminado]',
5492-
reversedTransaction: '[Transacción anulada]',
5493-
deletedTask: '[Tarea eliminada]',
5494-
hiddenMessage: '[Mensaje oculto]',
5489+
deletedReport: 'Informe eliminado',
5490+
deletedMessage: 'Mensaje eliminado',
5491+
deletedExpense: 'Gasto eliminado',
5492+
reversedTransaction: 'Transacción anulada',
5493+
deletedTask: 'Tarea eliminada',
5494+
hiddenMessage: 'Mensaje oculto',
54955495
},
54965496
threads: {
54975497
thread: 'Hilo',

src/pages/home/report/PureReportActionItem.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ function PureReportActionItem({
733733
);
734734
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) {
735735
children = isClosedExpenseReportWithNoExpenses ? (
736-
<RenderHTML html={`<comment>${translate('parentReportAction.deletedReport')}</comment>`} />
736+
<RenderHTML html={`<deleted-action>${translate('parentReportAction.deletedReport')}</deleted-action>`} />
737737
) : (
738738
<ReportPreview
739739
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style

0 commit comments

Comments
 (0)