From db3ba8634abff75a51981e81d1527e6fc4fc2115 Mon Sep 17 00:00:00 2001 From: Hubert Sosinski Date: Tue, 15 Sep 2026 14:15:51 +0200 Subject: [PATCH 1/2] Extract useMoneyRequestReportColumns and useMoneyRequestReportLayout --- .../MoneyRequestReportTransactionList.tsx | 91 ++------------ .../useMoneyRequestReportColumns.ts | 115 ++++++++++++++++++ .../useMoneyRequestReportLayout.ts | 67 ++++++++++ 3 files changed, 191 insertions(+), 82 deletions(-) create mode 100644 src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts create mode 100644 src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 2ce48fa2bba1..6963cca5807a 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -1,7 +1,7 @@ import LinkButton from '@components/Button/composed/LinkButton'; import type FlatListRefType from '@components/FlashList/types'; import {useSearchSelectionActions, useSearchSelectionContext} from '@components/Search/SearchContext'; -import type {SearchCustomColumnIds, SortOrder} from '@components/Search/types'; +import type {SortOrder} from '@components/Search/types'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -16,14 +16,10 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import {getReportLayoutGroupBy, getReportLayoutSelection, setReportLayout} from '@libs/actions/ReportLayout'; import {clearActiveTransactionIDs, getActiveTransactionIDs, setActiveTransactionIDs} from '@libs/actions/TransactionThreadNavigation'; import {resolveTransactionCardFields} from '@libs/CardUtils'; -import {isBillableEnabledOnPolicy} from '@libs/MoneyRequestReportUtils'; import {navigationRef} from '@libs/Navigation/Navigation'; -import {isPolicyTaxEnabled} from '@libs/PolicyUtils'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {groupTransactionsByCategory, groupTransactionsByTag} from '@libs/ReportLayoutUtils'; import { @@ -36,12 +32,10 @@ import { isSortableColumnName, } from '@libs/ReportUtils'; import type {SortableColumnName} from '@libs/ReportUtils'; -import {compareValues, getColumnsToShow, getTableMinWidth, isTransactionAmountTooLong, isTransactionTaxAmountTooLong} from '@libs/SearchUIUtils'; +import {compareValues} from '@libs/SearchUIUtils'; import {getPendingSubmitFollowUpAction} from '@libs/telemetry/submitFollowUpAction'; import {transactionHasRBR} from '@libs/TransactionPreviewUtils'; -import {getTransactionPendingAction, getVisibleTransactionViolations, hasNonReimbursableTransactions, isTransactionPendingDelete} from '@libs/TransactionUtils'; -import shouldShowTransactionPostedYear from '@libs/TransactionUtils/shouldShowTransactionPostedYear'; -import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear'; +import {getTransactionPendingAction, getVisibleTransactionViolations, isTransactionPendingDelete} from '@libs/TransactionUtils'; import isReportOpenInSuperWideRHP from '@navigation/helpers/isReportOpenInSuperWideRHP'; import Navigation from '@navigation/Navigation'; @@ -74,6 +68,8 @@ import MoneyRequestReportTransactionItem from './MoneyRequestReportTransactionIt import MoneyRequestReportTransactionLongPressModal from './MoneyRequestReportTransactionLongPressModal'; import MoneyRequestReportUnifiedList from './MoneyRequestReportUnifiedList'; import SearchMoneyRequestReportEmptyState from './SearchMoneyRequestReportEmptyState'; +import useMoneyRequestReportColumns from './useMoneyRequestReportColumns'; +import useMoneyRequestReportLayout from './useMoneyRequestReportLayout'; type TransactionWithOptionalHighlight = OnyxTypes.Transaction & { /** Whether the transaction should be highlighted, when it is added to the report */ @@ -270,15 +266,10 @@ function MoneyRequestReportTransactionList({ const {reportPendingAction} = getReportOfflinePendingActionAndErrors(report); const {isOffline} = useNetwork(); - const isTaxEnabled = isPolicyTaxEnabled(policy); const {totalDisplaySpend} = getMoneyRequestSpendBreakdown(report); - const shouldShowExpenseReportBreakDown = hasNonReimbursableTransactions(transactions); const currentUserDetails = useCurrentUserPersonalDetails(); const ownerLoginSelector = useMemo(() => personalDetailsLoginSelector(report?.ownerAccountID), [report?.ownerAccountID]); const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: ownerLoginSelector}); - const [reportLayoutGroupBy] = useOnyx(ONYXKEYS.NVP_REPORT_LAYOUT_GROUP_BY); - const [reportLayoutOption] = useOnyx(ONYXKEYS.NVP_REPORT_LAYOUT_OPTION); - const [reportDetailsColumns] = useOnyx(ONYXKEYS.NVP_REPORT_DETAILS_COLUMNS); const [nonPersonalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST); const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); @@ -442,58 +433,10 @@ function MoneyRequestReportTransactionList({ const highlightedTransactionIDs = useMemo(() => new Set(newTransactions.map(({transactionID}) => transactionID)), [newTransactions]); - // Always use default columns for money request report view (don't use user-customized search columns) - const isExpenseReportViewFromIOUReport = isIOUReport(report); - const shouldShowBillableColumn = isBillableEnabledOnPolicy(policy); - const shouldShowCommentsColumn = useMemo(() => Object.values(reportActions ?? {}).some((action) => (action?.childVisibleActionCount ?? 0) > 0), [reportActions]); - const columnsToShow = useMemo(() => { - return getColumnsToShow({ - currentAccountID: currentUserDetails?.accountID, - data: transactions, - report, - visibleColumns: (isExpenseReportViewFromIOUReport ? [] : (reportDetailsColumns ?? [])) as SearchCustomColumnIds[], - isExpenseReportView: true, - isExpenseReportViewFromIOUReport, - shouldShowBillableColumn, - shouldShowCommentsColumn, - shouldShowReimbursableColumn: shouldShowExpenseReportBreakDown, - reportCurrency: report?.currency, - isPolicyTaxEnabled: isTaxEnabled, - }); - }, [ - transactions, - currentUserDetails?.accountID, - isExpenseReportViewFromIOUReport, - shouldShowBillableColumn, - shouldShowCommentsColumn, - reportDetailsColumns, - report, - isTaxEnabled, - shouldShowExpenseReportBreakDown, - ]); - - const {windowWidth} = useWindowDimensions(); - const minTableWidth = getTableMinWidth(columnsToShow); - const shouldScrollHorizontally = !shouldUseNarrowLayout && minTableWidth > windowWidth; - - // Latch the user's most recent selection so the popover label and grouping mode never flick through the - // (layoutOption=null, groupByOption=null) → CATEGORY default while the two NVPs settle in separate render passes. - // Drop the latch once Onyx reaches the clicked value, so later authoritative updates (failureData rollback, - // another client changing the layout) flow through instead of staying masked by stale local state. - const [pendingLayoutSelection, setPendingLayoutSelection] = useState(null); - const onyxLayoutSelection = getReportLayoutSelection(reportLayoutOption, reportLayoutGroupBy); - const currentSelection: OnyxTypes.ReportLayoutSelection = pendingLayoutSelection ?? onyxLayoutSelection; - useEffect(() => { - if (pendingLayoutSelection === null || pendingLayoutSelection !== onyxLayoutSelection) { - return; - } - // eslint-disable-next-line react-hooks/set-state-in-effect -- syncs the click latch to Onyx so subsequent authoritative updates aren't masked by stale local state - setPendingLayoutSelection(null); - }, [pendingLayoutSelection, onyxLayoutSelection]); + const {columnsToShow, dateColumnSize, postedColumnSize, amountColumnSize, taxAmountColumnSize, minTableWidth, shouldScrollHorizontally, isExpenseReportViewFromIOUReport} = + useMoneyRequestReportColumns({report, policy, transactions, reportActions}); - const isLayoutMatrixSelected = currentSelection === CONST.REPORT_LAYOUT.LAYOUT_OPTION.MATRIX; - const currentGroupBy: OnyxTypes.ReportLayoutGroupBy = currentSelection !== CONST.REPORT_LAYOUT.LAYOUT_OPTION.MATRIX ? currentSelection : getReportLayoutGroupBy(reportLayoutGroupBy); - const shouldGroupTransactions = shouldShowGroupedTransactions && !isLayoutMatrixSelected; + const {currentSelection, currentGroupBy, shouldGroupTransactions, selectLayout} = useMoneyRequestReportLayout(shouldShowGroupedTransactions); const groupedTransactions = useMemo(() => { if (!shouldGroupTransactions) { @@ -610,19 +553,6 @@ function MoneyRequestReportTransactionList({ [navigateToTransactionThread, reportActions, sortedTransactions, report, visualOrderTransactionIDs], ); - const {amountColumnSize, dateColumnSize, postedColumnSize, taxAmountColumnSize} = useMemo(() => { - const isAmountColumnWide = transactions.some((transaction) => isTransactionAmountTooLong(transaction)); - const isTaxAmountColumnWide = transactions.some((transaction) => isTransactionTaxAmountTooLong(transaction)); - const shouldShowYearForSomeTransaction = transactions.some((transaction) => shouldShowTransactionYear(transaction)); - const shouldShowPostedYearForSomeTransaction = transactions.some((transaction) => shouldShowTransactionPostedYear(transaction)); - return { - amountColumnSize: isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, - taxAmountColumnSize: isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, - dateColumnSize: shouldShowYearForSomeTransaction ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, - postedColumnSize: shouldShowPostedYearForSomeTransaction ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, - }; - }, [transactions]); - const isEmptyTransactions = isEmpty(transactions); const handleLongPress = useCallback( @@ -780,10 +710,7 @@ function MoneyRequestReportTransactionList({ {shouldShowGroupedTransactions && ( { - setPendingLayoutSelection(selection); - setReportLayout(selection, reportLayoutOption, reportLayoutGroupBy); - }} + onSelect={selectLayout} /> )} {!shouldUseNarrowLayout && !isExpenseReportViewFromIOUReport && ( diff --git a/src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts b/src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts new file mode 100644 index 000000000000..4161c96f2d58 --- /dev/null +++ b/src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts @@ -0,0 +1,115 @@ +import type {SearchColumnType, SearchCustomColumnIds, TableColumnSize} from '@components/Search/types'; + +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useOnyx from '@hooks/useOnyx'; +import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP'; +import useWindowDimensions from '@hooks/useWindowDimensions'; + +import {isBillableEnabledOnPolicy} from '@libs/MoneyRequestReportUtils'; +import {isPolicyTaxEnabled} from '@libs/PolicyUtils'; +import {isIOUReport} from '@libs/ReportUtils'; +import {getColumnsToShow, getTableMinWidth, isTransactionAmountTooLong, isTransactionTaxAmountTooLong} from '@libs/SearchUIUtils'; +import {hasNonReimbursableTransactions} from '@libs/TransactionUtils'; +import shouldShowTransactionPostedYear from '@libs/TransactionUtils/shouldShowTransactionPostedYear'; +import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear'; + +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {StableReport} from '@src/selectors/Report'; +import type * as OnyxTypes from '@src/types/onyx'; + +const REPORT_DETAILS_CUSTOM_COLUMNS = Object.values(CONST.SEARCH.REPORT_DETAILS_CUSTOM_COLUMNS); + +function isReportDetailsCustomColumn(column: string): column is SearchCustomColumnIds { + return REPORT_DETAILS_CUSTOM_COLUMNS.some((customColumn) => customColumn === column); +} + +type UseMoneyRequestReportColumnsParams = { + /** The money request report containing the transactions */ + report: StableReport; + + /** The workspace to which the report belongs */ + policy: OnyxTypes.Policy | undefined; + + /** List of transactions belonging to one report */ + transactions: OnyxTypes.Transaction[]; + + /** Report actions of the report, used to decide whether the comments column shows */ + reportActions: OnyxTypes.ReportAction[]; +}; + +type UseMoneyRequestReportColumnsResult = { + /** The columns the table renders, in order */ + columnsToShow: SearchColumnType[]; + + /** Width bucket of the date column */ + dateColumnSize: TableColumnSize; + + /** Width bucket of the posted-date column */ + postedColumnSize: TableColumnSize; + + /** Width bucket of the amount column */ + amountColumnSize: TableColumnSize; + + /** Width bucket of the tax-amount column */ + taxAmountColumnSize: TableColumnSize; + + /** Pixel width of the table at full column visibility */ + minTableWidth: number; + + /** True when the rendered table is wider than the viewport and needs its own horizontal scroller */ + shouldScrollHorizontally: boolean; + + /** Whether this expense-report view was opened from an IOU report — hides column customization */ + isExpenseReportViewFromIOUReport: boolean; +}; + +/** + * Derives which columns the transaction table shows and how wide they are, from the report's + * transactions and the user's saved column preferences. + */ +function useMoneyRequestReportColumns({report, policy, transactions, reportActions}: UseMoneyRequestReportColumnsParams): UseMoneyRequestReportColumnsResult { + const currentUserDetails = useCurrentUserPersonalDetails(); + const [reportDetailsColumns] = useOnyx(ONYXKEYS.NVP_REPORT_DETAILS_COLUMNS); + const {windowWidth} = useWindowDimensions(); + const {shouldUseNarrowLayout} = useResponsiveLayoutOnWideRHP(); + + // Always use default columns for money request report view (don't use user-customized search columns) + const isExpenseReportViewFromIOUReport = isIOUReport(report); + const shouldShowBillableColumn = isBillableEnabledOnPolicy(policy); + const shouldShowCommentsColumn = Object.values(reportActions ?? {}).some((action) => (action?.childVisibleActionCount ?? 0) > 0); + const columnsToShow = getColumnsToShow({ + currentAccountID: currentUserDetails?.accountID, + data: transactions, + report, + visibleColumns: isExpenseReportViewFromIOUReport ? [] : (reportDetailsColumns ?? []).filter(isReportDetailsCustomColumn), + isExpenseReportView: true, + isExpenseReportViewFromIOUReport, + shouldShowBillableColumn, + shouldShowCommentsColumn, + shouldShowReimbursableColumn: hasNonReimbursableTransactions(transactions), + reportCurrency: report?.currency, + isPolicyTaxEnabled: isPolicyTaxEnabled(policy), + }); + + const isAmountColumnWide = transactions.some((transaction) => isTransactionAmountTooLong(transaction)); + const isTaxAmountColumnWide = transactions.some((transaction) => isTransactionTaxAmountTooLong(transaction)); + const shouldShowYearForSomeTransaction = transactions.some((transaction) => shouldShowTransactionYear(transaction)); + const shouldShowPostedYearForSomeTransaction = transactions.some((transaction) => shouldShowTransactionPostedYear(transaction)); + + const minTableWidth = getTableMinWidth(columnsToShow); + const shouldScrollHorizontally = !shouldUseNarrowLayout && minTableWidth > windowWidth; + + return { + columnsToShow, + dateColumnSize: shouldShowYearForSomeTransaction ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, + postedColumnSize: shouldShowPostedYearForSomeTransaction ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, + amountColumnSize: isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, + taxAmountColumnSize: isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, + minTableWidth, + shouldScrollHorizontally, + isExpenseReportViewFromIOUReport, + }; +} + +export default useMoneyRequestReportColumns; diff --git a/src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts b/src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts new file mode 100644 index 000000000000..005d854845d1 --- /dev/null +++ b/src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts @@ -0,0 +1,67 @@ +import useOnyx from '@hooks/useOnyx'; + +import {getReportLayoutGroupBy, getReportLayoutSelection, setReportLayout} from '@libs/actions/ReportLayout'; + +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; + +import {useEffect, useState} from 'react'; + +type UseMoneyRequestReportLayoutResult = { + /** The layout selection currently shown to the user (latched click or the Onyx value) */ + currentSelection: OnyxTypes.ReportLayoutSelection; + + /** The attribute transactions are grouped by while grouping is on */ + currentGroupBy: OnyxTypes.ReportLayoutGroupBy; + + /** Whether the transactions should be rendered in groups */ + shouldGroupTransactions: boolean; + + /** Persists a layout selection and latches it locally until Onyx settles */ + selectLayout: (selection: OnyxTypes.ReportLayoutSelection) => void; +}; + +/** + * Owns the report-layout (group-by) selection: reads the two NVPs, exposes the effective selection and + * grouping mode, and persists user picks. + * + * Latches the user's most recent selection so the popover label and grouping mode never flick through the + * (layoutOption=null, groupByOption=null) → CATEGORY default while the two NVPs settle in separate render passes. + * Drops the latch once Onyx reaches the clicked value, so later authoritative updates (failureData rollback, + * another client changing the layout) flow through instead of staying masked by stale local state. + */ +function useMoneyRequestReportLayout(shouldShowGroupedTransactions: boolean): UseMoneyRequestReportLayoutResult { + const [reportLayoutGroupBy] = useOnyx(ONYXKEYS.NVP_REPORT_LAYOUT_GROUP_BY); + const [reportLayoutOption] = useOnyx(ONYXKEYS.NVP_REPORT_LAYOUT_OPTION); + const [pendingLayoutSelection, setPendingLayoutSelection] = useState(null); + + const onyxLayoutSelection = getReportLayoutSelection(reportLayoutOption, reportLayoutGroupBy); + const currentSelection: OnyxTypes.ReportLayoutSelection = pendingLayoutSelection ?? onyxLayoutSelection; + + useEffect(() => { + if (pendingLayoutSelection === null || pendingLayoutSelection !== onyxLayoutSelection) { + return; + } + // eslint-disable-next-line react-hooks/set-state-in-effect -- syncs the click latch to Onyx so subsequent authoritative updates aren't masked by stale local state + setPendingLayoutSelection(null); + }, [pendingLayoutSelection, onyxLayoutSelection]); + + const isLayoutMatrixSelected = currentSelection === CONST.REPORT_LAYOUT.LAYOUT_OPTION.MATRIX; + const currentGroupBy: OnyxTypes.ReportLayoutGroupBy = currentSelection !== CONST.REPORT_LAYOUT.LAYOUT_OPTION.MATRIX ? currentSelection : getReportLayoutGroupBy(reportLayoutGroupBy); + const shouldGroupTransactions = shouldShowGroupedTransactions && !isLayoutMatrixSelected; + + const selectLayout = (selection: OnyxTypes.ReportLayoutSelection) => { + setPendingLayoutSelection(selection); + setReportLayout(selection, reportLayoutOption, reportLayoutGroupBy); + }; + + return { + currentSelection, + currentGroupBy, + shouldGroupTransactions, + selectLayout, + }; +} + +export default useMoneyRequestReportLayout; From 9811b13c38760fa3b722a74c30c6012e6c7d5b4b Mon Sep 17 00:00:00 2001 From: Hubert Sosinski Date: Fri, 18 Sep 2026 09:32:42 +0200 Subject: [PATCH 2/2] comments edit' --- .../MoneyRequestReportView/useMoneyRequestReportColumns.ts | 2 +- .../MoneyRequestReportView/useMoneyRequestReportLayout.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts b/src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts index 4161c96f2d58..98bcb4bc3fd2 100644 --- a/src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts +++ b/src/components/MoneyRequestReportView/useMoneyRequestReportColumns.ts @@ -60,7 +60,7 @@ type UseMoneyRequestReportColumnsResult = { /** True when the rendered table is wider than the viewport and needs its own horizontal scroller */ shouldScrollHorizontally: boolean; - /** Whether this expense-report view was opened from an IOU report — hides column customization */ + /** True when opened from an IOU report, which hides column customization */ isExpenseReportViewFromIOUReport: boolean; }; diff --git a/src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts b/src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts index 005d854845d1..665473db72d1 100644 --- a/src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts +++ b/src/components/MoneyRequestReportView/useMoneyRequestReportLayout.ts @@ -26,10 +26,9 @@ type UseMoneyRequestReportLayoutResult = { * Owns the report-layout (group-by) selection: reads the two NVPs, exposes the effective selection and * grouping mode, and persists user picks. * - * Latches the user's most recent selection so the popover label and grouping mode never flick through the - * (layoutOption=null, groupByOption=null) → CATEGORY default while the two NVPs settle in separate render passes. - * Drops the latch once Onyx reaches the clicked value, so later authoritative updates (failureData rollback, - * another client changing the layout) flow through instead of staying masked by stale local state. + * Latches the last clicked selection so the label and grouping never flicker through the CATEGORY default while + * the two NVPs settle in separate render passes. Drops the latch once Onyx reaches that value, so rollbacks and + * updates from other clients flow through. */ function useMoneyRequestReportLayout(shouldShowGroupedTransactions: boolean): UseMoneyRequestReportLayoutResult { const [reportLayoutGroupBy] = useOnyx(ONYXKEYS.NVP_REPORT_LAYOUT_GROUP_BY);