From a621c6646cf8ca470cd31fe90917a1c7625474bf Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 9 Dec 2025 14:51:54 +0700 Subject: [PATCH 1/3] fix: Red dot is displayed on report preview after dismissed the error messages --- src/components/OfflineWithFeedback.tsx | 4 ++-- src/libs/ErrorUtils.ts | 11 ++++++----- src/libs/ReportUtils.ts | 8 ++++---- src/pages/home/report/PureReportActionItem.tsx | 6 +++--- src/types/utils/EmptyObject.ts | 6 +++++- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 4b0a622879f3..8f9d94ae2974 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -11,7 +11,7 @@ import CONST from '@src/CONST'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {ReceiptErrors} from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {isEmptyValueObject} from '@src/types/utils/EmptyObject'; import CustomStylesForChildrenProvider from './CustomStylesForChildrenProvider'; import ErrorMessageRow from './ErrorMessageRow'; import ImageSVG from './ImageSVG'; @@ -97,7 +97,7 @@ function OfflineWithFeedback({ const StyleUtils = useStyleUtils(); const {isOffline} = useNetwork(); - const hasErrors = !isEmptyObject(errors ?? {}); + const hasErrors = !isEmptyValueObject(errors ?? {}); const isOfflinePendingAction = !!isOffline && !!pendingAction; const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 22d748292f37..1721a433896a 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -5,6 +5,7 @@ import type {TranslationPaths} from '@src/languages/types'; import type {ErrorFields, Errors, TranslationKeyError, TranslationKeyErrors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; import type {ReceiptError} from '@src/types/onyx/Transaction'; +import {isEmptyValueObject} from '@src/types/utils/EmptyObject'; import DateUtils from './DateUtils'; // eslint-disable-next-line @typescript-eslint/no-deprecated import {translateLocal} from './Localize'; @@ -94,7 +95,7 @@ function getLatestErrorMessage(onyxData: O function getLatestErrorMessageField(onyxData: OnyxEntry): Errors { const errors = onyxData?.errors ?? {}; - if (Object.keys(errors).length === 0) { + if (isEmptyValueObject(errors)) { return {}; } @@ -110,7 +111,7 @@ type OnyxDataWithErrorFields = { function getLatestErrorField(onyxData: OnyxEntry, fieldName: string): Errors { const errorsForField = onyxData?.errorFields?.[fieldName] ?? {}; - if (Object.keys(errorsForField).length === 0) { + if (isEmptyValueObject(errorsForField)) { return {}; } @@ -121,7 +122,7 @@ function getLatestErrorField(onyxData function getEarliestErrorField(onyxData: OnyxEntry, fieldName: string): Errors { const errorsForField = onyxData?.errorFields?.[fieldName] ?? {}; - if (Object.keys(errorsForField).length === 0) { + if (isEmptyValueObject(errorsForField)) { return {}; } @@ -135,7 +136,7 @@ function getEarliestErrorField(onyxDa function getLatestErrorFieldForAnyField(onyxData: OnyxEntry): Errors { const errorFields = onyxData?.errorFields ?? {}; - if (Object.keys(errorFields).length === 0) { + if (isEmptyValueObject(errorFields)) { return {}; } @@ -145,7 +146,7 @@ function getLatestErrorFieldForAnyField; for (const action of reportActionsArray) { - if (action && !isEmptyObject(action.errors)) { + if (action && !isEmptyValueObject(action.errors)) { Object.assign(reportActionErrors, action.errors); if (!reportAction) { @@ -11076,11 +11076,11 @@ function hasActionWithErrorsForTransaction(reportID: string | undefined, transac .some((action) => { if (isMoneyRequestAction(action) && getOriginalMessage(action)?.IOUTransactionID) { if (getOriginalMessage(action)?.IOUTransactionID === transaction?.transactionID) { - return !isEmptyObject(action.errors); + return !isEmptyValueObject(action.errors); } return false; } - return !isEmptyObject(action.errors); + return !isEmptyValueObject(action.errors); }); } diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 61ebc9ab3500..c4eb70115182 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -193,7 +193,7 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {JoinWorkspaceResolution, OriginalMessageMovedTransaction} from '@src/types/onyx/OriginalMessage'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {isEmptyObject, isEmptyValueObject} from '@src/types/utils/EmptyObject'; import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; import type {ContextMenuAnchor} from './ContextMenu/ReportActionContextMenu'; @@ -671,7 +671,7 @@ function PureReportActionItem({ const showPopover = useCallback( (event: GestureResponderEvent | MouseEvent) => { // Block menu on the message being Edited or if the report action item has errors - if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { + if (draftMessage !== undefined || !isEmptyValueObject(action.errors) || !shouldDisplayContextMenu) { return; } @@ -1692,7 +1692,7 @@ function PureReportActionItem({ return null; } - const hasErrors = !isEmptyObject(action.errors); + const hasErrors = !isEmptyValueObject(action.errors); const whisperedTo = getWhisperedTo(action); const isMultipleParticipant = whisperedTo.length > 1; diff --git a/src/types/utils/EmptyObject.ts b/src/types/utils/EmptyObject.ts index 8774d515a8e2..f84abf2a8255 100644 --- a/src/types/utils/EmptyObject.ts +++ b/src/types/utils/EmptyObject.ts @@ -12,5 +12,9 @@ function getEmptyObject(): T { return CONST.EMPTY_OBJECT as T; } -export {isEmptyObject, getEmptyObject}; +function isEmptyValueObject(obj: T | EmptyValue) { + return Object.values(obj ?? {}).filter(Boolean).length === 0; +} + +export {isEmptyObject, getEmptyObject, isEmptyValueObject}; export type {EmptyObject}; From 65a24455d8a3fe7a4c3c7dfe2fad673441db8db4 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 9 Dec 2025 15:20:45 +0700 Subject: [PATCH 2/3] fix lint --- src/pages/home/report/PureReportActionItem.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index c4eb70115182..e8e41a4ad3c1 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -14,7 +14,6 @@ import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; import Icon from '@components/Icon'; -import {Eye} from '@components/Icon/Expensicons'; import InlineSystemMessage from '@components/InlineSystemMessage'; import KYCWall from '@components/KYCWall'; import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; @@ -40,6 +39,7 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import UnreadActionIndicator from '@components/UnreadActionIndicator'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import usePreferredPolicy from '@hooks/usePreferredPolicy'; import usePrevious from '@hooks/usePrevious'; @@ -500,6 +500,7 @@ function PureReportActionItem({ isActionableMentionWhisper(action) || isActionableMentionInviteToSubmitExpenseConfirmWhisper(action) || isActionableTrackExpense(action) || isActionableReportMentionWhisper(action); const isReportArchived = useReportIsArchived(reportID); const isOriginalReportArchived = useReportIsArchived(originalReportID); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['Eye'] as const); const highlightedBackgroundColorIfNeeded = useMemo( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -1809,7 +1810,7 @@ function PureReportActionItem({ From 886caa13d1a849962022041b87d899d6dbbdff08 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 15 Dec 2025 14:56:27 +0700 Subject: [PATCH 3/3] use stricter condition --- src/types/utils/EmptyObject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/utils/EmptyObject.ts b/src/types/utils/EmptyObject.ts index f84abf2a8255..9b7f398e06c1 100644 --- a/src/types/utils/EmptyObject.ts +++ b/src/types/utils/EmptyObject.ts @@ -13,7 +13,7 @@ function getEmptyObject(): T { } function isEmptyValueObject(obj: T | EmptyValue) { - return Object.values(obj ?? {}).filter(Boolean).length === 0; + return Object.values(obj ?? {}).filter((value) => value !== undefined && value !== null).length === 0; } export {isEmptyObject, getEmptyObject, isEmptyValueObject};