From a621c6646cf8ca470cd31fe90917a1c7625474bf Mon Sep 17 00:00:00 2001
From: nkdengineer <nkdengineer@outlook.com>
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<TOnyxData extends OnyxDataWithErrors>(onyxData: O
 function getLatestErrorMessageField<TOnyxData extends OnyxDataWithErrors>(onyxData: OnyxEntry<TOnyxData>): Errors {
     const errors = onyxData?.errors ?? {};
 
-    if (Object.keys(errors).length === 0) {
+    if (isEmptyValueObject(errors)) {
         return {};
     }
 
@@ -110,7 +111,7 @@ type OnyxDataWithErrorFields = {
 function getLatestErrorField<TOnyxData extends OnyxDataWithErrorFields>(onyxData: OnyxEntry<TOnyxData>, fieldName: string): Errors {
     const errorsForField = onyxData?.errorFields?.[fieldName] ?? {};
 
-    if (Object.keys(errorsForField).length === 0) {
+    if (isEmptyValueObject(errorsForField)) {
         return {};
     }
 
@@ -121,7 +122,7 @@ function getLatestErrorField<TOnyxData extends OnyxDataWithErrorFields>(onyxData
 function getEarliestErrorField<TOnyxData extends OnyxDataWithErrorFields>(onyxData: OnyxEntry<TOnyxData>, fieldName: string): Errors {
     const errorsForField = onyxData?.errorFields?.[fieldName] ?? {};
 
-    if (Object.keys(errorsForField).length === 0) {
+    if (isEmptyValueObject(errorsForField)) {
         return {};
     }
 
@@ -135,7 +136,7 @@ function getEarliestErrorField<TOnyxData extends OnyxDataWithErrorFields>(onyxDa
 function getLatestErrorFieldForAnyField<TOnyxData extends OnyxDataWithErrorFields>(onyxData: OnyxEntry<TOnyxData>): Errors {
     const errorFields = onyxData?.errorFields ?? {};
 
-    if (Object.keys(errorFields).length === 0) {
+    if (isEmptyValueObject(errorFields)) {
         return {};
     }
 
@@ -145,7 +146,7 @@ function getLatestErrorFieldForAnyField<TOnyxData extends OnyxDataWithErrorField
 }
 
 function getLatestError(errors?: Errors): Errors {
-    if (!errors || Object.keys(errors).length === 0) {
+    if (!errors || isEmptyValueObject(errors)) {
         return {};
     }
 
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index a605f50a9aaf..f507414a72aa 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -77,7 +77,7 @@ import type {PendingChatMember} from '@src/types/onyx/ReportMetadata';
 import type {OnyxData} from '@src/types/onyx/Request';
 import type {Comment, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction';
 import type {FileObject} from '@src/types/utils/Attachment';
-import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import {isEmptyObject, isEmptyValueObject} from '@src/types/utils/EmptyObject';
 import type IconAsset from '@src/types/utils/IconAsset';
 import {getBankAccountFromID} from './actions/BankAccounts';
 import {
@@ -9336,7 +9336,7 @@ function getAllReportActionsErrorsAndReportActionThatRequiresAttention(
     let reportAction: OnyxEntry<ReportAction>;
 
     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>(): T {
     return CONST.EMPTY_OBJECT as T;
 }
 
-export {isEmptyObject, getEmptyObject};
+function isEmptyValueObject<T>(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 <nkdengineer@outlook.com>
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({
                                                 <View style={[styles.pl6, styles.mr3]}>
                                                     <Icon
                                                         fill={theme.icon}
-                                                        src={Eye}
+                                                        src={expensifyIcons.Eye}
                                                         small
                                                     />
                                                 </View>

From 886caa13d1a849962022041b87d899d6dbbdff08 Mon Sep 17 00:00:00 2001
From: nkdengineer <nkdengineer@outlook.com>
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>(): T {
 }
 
 function isEmptyValueObject<T>(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};
