Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6893,6 +6893,7 @@ const CONST = {
MERCHANT: 'merchant',
TAG: 'tag',
MONTH: 'month',
WEEK: 'week',
},
get TYPE_CUSTOM_COLUMNS() {
return {
Expand Down Expand Up @@ -6989,6 +6990,11 @@ const CONST = {
EXPENSES: this.TABLE_COLUMNS.GROUP_EXPENSES,
TOTAL: this.TABLE_COLUMNS.GROUP_TOTAL,
},
WEEK: {
WEEK: this.TABLE_COLUMNS.GROUP_WEEK,
EXPENSES: this.TABLE_COLUMNS.GROUP_EXPENSES,
TOTAL: this.TABLE_COLUMNS.GROUP_TOTAL,
},
};
},
get TYPE_DEFAULT_COLUMNS() {
Expand Down Expand Up @@ -7034,6 +7040,7 @@ const CONST = {
MERCHANT: [this.TABLE_COLUMNS.GROUP_MERCHANT, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
TAG: [this.TABLE_COLUMNS.GROUP_TAG, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
MONTH: [this.TABLE_COLUMNS.GROUP_MONTH, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
WEEK: [this.TABLE_COLUMNS.GROUP_WEEK, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
};
},
BOOLEAN: {
Expand Down Expand Up @@ -7133,6 +7140,7 @@ const CONST = {
GROUP_MERCHANT: 'groupMerchant',
GROUP_TAG: 'groupTag',
GROUP_MONTH: 'groupmonth',
GROUP_WEEK: 'groupweek',
},
SYNTAX_OPERATORS: {
AND: 'and',
Expand Down Expand Up @@ -7324,6 +7332,8 @@ const CONST = {
[this.TABLE_COLUMNS.GROUP_CATEGORY]: 'group-category',
[this.TABLE_COLUMNS.GROUP_MERCHANT]: 'group-merchant',
[this.TABLE_COLUMNS.GROUP_TAG]: 'group-tag',
[this.TABLE_COLUMNS.GROUP_MONTH]: 'group-month',
[this.TABLE_COLUMNS.GROUP_WEEK]: 'group-week',
};
},
NOT_MODIFIER: 'Not',
Expand Down
8 changes: 8 additions & 0 deletions src/components/Search/SearchList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type {
TransactionListItemType,
TransactionMerchantGroupListItemType,
TransactionMonthGroupListItemType,
TransactionWeekGroupListItemType,
} from '@components/SelectionListWithSections/types';
import Text from '@components/Text';
import useKeyboardState from '@hooks/useKeyboardState';
Expand Down Expand Up @@ -167,6 +168,13 @@ function isTransactionMatchWithGroupItem(transaction: Transaction, groupItem: Se
const transactionDateString = transaction.modifiedCreated ?? transaction.created ?? '';
return DateUtils.isDateStringInMonth(transactionDateString, monthGroup.year, monthGroup.month);
}
if (groupBy === CONST.SEARCH.GROUP_BY.WEEK) {
const weekGroup = groupItem as TransactionWeekGroupListItemType;
const transactionDateString = transaction.modifiedCreated ?? transaction.created ?? '';
const datePart = transactionDateString.substring(0, 10);
const {start: weekStart, end: weekEnd} = DateUtils.getWeekDateRange(weekGroup.week);
return datePart >= weekStart && datePart <= weekEnd;
}
return false;
}

Expand Down
58 changes: 47 additions & 11 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {isSplitAction} from '@libs/ReportSecondaryActionUtils';
import {canEditFieldOfMoneyRequest, canHoldUnholdReportAction, canRejectReportAction, isOneTransactionReport, selectFilteredReportActions} from '@libs/ReportUtils';
import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
import {
adjustTimeRangeToDateFilters,
createAndOpenSearchTransactionThread,
getColumnsToShow,
getListItem,
Expand All @@ -60,6 +61,7 @@ import {
isTransactionMerchantGroupListItemType,
isTransactionMonthGroupListItemType,
isTransactionTagGroupListItemType,
isTransactionWeekGroupListItemType,
isTransactionWithdrawalIDGroupListItemType,
shouldShowEmptyState,
shouldShowYear as shouldShowYearUtil,
Expand Down Expand Up @@ -545,7 +547,7 @@ function Search({

// For expense reports: when ANY transaction is selected, we want ALL transactions in the report selected.
// This ensures report-level selection persists when new transactions are added.
const hasAnySelected = isExpenseReportType && transactionGroup.transactions.some((transaction) => transaction.transactionID in selectedTransactions);
const hasAnySelected = isExpenseReportType && transactionGroup.transactions.some((transaction: TransactionListItemType) => transaction.transactionID in selectedTransactions);

for (const transactionItem of transactionGroup.transactions) {
const isSelected = transactionItem.transactionID in selectedTransactions;
Expand Down Expand Up @@ -905,13 +907,46 @@ function Search({
return;
}

let reportID = item.reportID;
if (isTransactionItem && item?.reportAction?.childReportID) {
const isFromSelfDM = item.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
const isFromOneTransactionReport = isOneTransactionReport(item.report);
if (isTransactionWeekGroupListItemType(item)) {
if (!item.week) {
return;
}
// Extract the existing date filter to check for year-to-date or other date limits
const existingDateFilter = queryJSON.flatFilters.find((filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
const {start: weekStart, end: weekEnd} = adjustTimeRangeToDateFilters(DateUtils.getWeekDateRange(item.week), existingDateFilter);
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CONSISTENCY-3 (docs)

This code block for creating date filters with week ranges is duplicated in src/libs/SearchUIUtils.ts (lines 2451-2463 in getWeekSections). The logic for filtering and rebuilding flatFilters with date range operators appears in two locations.

Impact: Duplicated logic increases maintenance burden and bug risk. If the filter creation logic needs to change, it must be updated in both places.

Suggested fix: Extract this logic into a reusable utility function:

function createDateFilterForWeek(
    queryJSON: SearchQueryJSON,
    week: string
): SearchQueryJSON | undefined {
    const existingDateFilter = queryJSON.flatFilters.find(
        (filter) => filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE
    );
    const {start: weekStart, end: weekEnd} = adjustTimeRangeToDateFilters(
        DateUtils.getWeekDateRange(week),
        existingDateFilter
    );
    
    const newFlatFilters = queryJSON.flatFilters.filter(
        (filter) => filter.key \!== CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE
    );
    newFlatFilters.push({
        key: CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE,
        filters: [
            {operator: CONST.SEARCH.SYNTAX_OPERATORS.GREATER_THAN_OR_EQUAL_TO, value: weekStart},
            {operator: CONST.SEARCH.SYNTAX_OPERATORS.LOWER_THAN_OR_EQUAL_TO, value: weekEnd},
        ],
    });
    
    const newQueryJSON: SearchQueryJSON = {
        ...queryJSON,
        groupBy: undefined,
        flatFilters: newFlatFilters
    };
    const newQuery = buildSearchQueryString(newQueryJSON);
    return buildSearchQueryJSON(newQuery);
}

Then use it in both locations instead of duplicating the logic.


Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

newFlatFilters.push({
key: CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE,
filters: [
{operator: CONST.SEARCH.SYNTAX_OPERATORS.GREATER_THAN_OR_EQUAL_TO, value: weekStart},
{operator: CONST.SEARCH.SYNTAX_OPERATORS.LOWER_THAN_OR_EQUAL_TO, value: weekEnd},
],
});
const newQueryJSON: SearchQueryJSON = {...queryJSON, groupBy: undefined, flatFilters: newFlatFilters};
const newQuery = buildSearchQueryString(newQueryJSON);
const newQueryJSONWithHash = buildSearchQueryJSON(newQuery);
if (!newQueryJSONWithHash) {
return;
}
handleSearch({queryJSON: newQueryJSONWithHash, searchKey, offset: 0, shouldCalculateTotals: false, isLoading: false});
return;
}

// After handling all group types, item should be TransactionListItemType, ReportActionListItemType, or TransactionGroupListItemType
if (!isTransactionItem && !isReportActionListItemType(item) && !isTransactionGroupListItemType(item)) {
return;
}

const transactionItem = item as TransactionListItemType;
const reportActionItem = item as ReportActionListItemType;

let reportID = transactionItem.reportID ?? reportActionItem.reportID;
if (isTransactionItem && transactionItem?.reportAction?.childReportID) {
const isFromSelfDM = transactionItem.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
const isFromOneTransactionReport = isOneTransactionReport(transactionItem.report);

if (isFromSelfDM || !isFromOneTransactionReport) {
reportID = item?.reportAction?.childReportID;
reportID = transactionItem?.reportAction?.childReportID;
}
}

Expand All @@ -927,16 +962,17 @@ function Search({
});

if (isTransactionGroupListItemType(item)) {
const firstTransaction = item.transactions.at(0);
if (item.isOneTransactionReport && firstTransaction && transactionPreviewData) {
const groupItem = item as TransactionGroupListItemType;
const firstTransaction = groupItem.transactions.at(0);
if (groupItem.isOneTransactionReport && firstTransaction && transactionPreviewData) {
if (!firstTransaction?.reportAction?.childReportID) {
createAndOpenSearchTransactionThread(firstTransaction, backTo, firstTransaction?.reportAction?.childReportID, transactionPreviewData, false);
} else {
setOptimisticDataForTransactionThreadPreview(firstTransaction, transactionPreviewData, firstTransaction?.reportAction?.childReportID);
}
}

if (item.transactions.length > 1) {
if (groupItem.transactions.length > 1) {
markReportIDAsMultiTransactionExpense(reportID);
} else {
unmarkReportIDAsMultiTransactionExpense(reportID);
Expand All @@ -947,15 +983,15 @@ function Search({
}

if (isReportActionListItemType(item)) {
const reportActionID = item.reportActionID;
const reportActionID = reportActionItem.reportActionID;
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo}));
return;
}

markReportIDAsExpense(reportID);

if (isTransactionItem && transactionPreviewData) {
setOptimisticDataForTransactionThreadPreview(item, transactionPreviewData, item?.reportAction?.childReportID);
setOptimisticDataForTransactionThreadPreview(transactionItem, transactionPreviewData, transactionItem?.reportAction?.childReportID);
}

requestAnimationFrame(() => Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})));
Expand Down
3 changes: 2 additions & 1 deletion src/components/Search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ type SearchCustomColumnIds =
| ValueOf<typeof CONST.SEARCH.GROUP_CUSTOM_COLUMNS.CATEGORY>
| ValueOf<typeof CONST.SEARCH.GROUP_CUSTOM_COLUMNS.MERCHANT>
| ValueOf<typeof CONST.SEARCH.GROUP_CUSTOM_COLUMNS.TAG>
| ValueOf<typeof CONST.SEARCH.GROUP_CUSTOM_COLUMNS.MONTH>;
| ValueOf<typeof CONST.SEARCH.GROUP_CUSTOM_COLUMNS.MONTH>
| ValueOf<typeof CONST.SEARCH.GROUP_CUSTOM_COLUMNS.WEEK>;

type SearchContextData = {
currentSearchHash: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ type GroupColumnKey =
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_CATEGORY
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_MERCHANT
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_TAG
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_MONTH;
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_MONTH
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_WEEK;

/** Supported column style keys for sizing */
type ColumnStyleKey =
| typeof CONST.SEARCH.TABLE_COLUMNS.CATEGORY
| typeof CONST.SEARCH.TABLE_COLUMNS.MERCHANT
| typeof CONST.SEARCH.TABLE_COLUMNS.TAG
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_MONTH;
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_MONTH
| typeof CONST.SEARCH.TABLE_COLUMNS.GROUP_WEEK;

type BaseListItemHeaderProps<TItem extends ListItem> = {
/** The group item being rendered */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
TransactionMonthGroupListItemType,
TransactionReportGroupListItemType,
TransactionTagGroupListItemType,
TransactionWeekGroupListItemType,
TransactionWithdrawalIDGroupListItemType,
} from '@components/SelectionListWithSections/types';
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
Expand Down Expand Up @@ -50,6 +51,7 @@ import MonthListItemHeader from './MonthListItemHeader';
import ReportListItemHeader from './ReportListItemHeader';
import TagListItemHeader from './TagListItemHeader';
import TransactionGroupListExpandedItem from './TransactionGroupListExpanded';
import WeekListItemHeader from './WeekListItemHeader';
import WithdrawalIDListItemHeader from './WithdrawalIDListItemHeader';

function TransactionGroupListItem<TItem extends ListItem>({
Expand Down Expand Up @@ -340,6 +342,19 @@ function TransactionGroupListItem<TItem extends ListItem>({
isExpanded={isExpanded}
/>
),
[CONST.SEARCH.GROUP_BY.WEEK]: (
<WeekListItemHeader
week={groupItem as TransactionWeekGroupListItemType}
onCheckboxPress={onCheckboxPress}
isDisabled={isDisabledOrEmpty}
columns={columns}
canSelectMultiple={canSelectMultiple}
isSelectAllChecked={isSelectAllChecked}
isIndeterminate={isIndeterminate}
onDownArrowClick={onExpandIconPress}
isExpanded={isExpanded}
/>
),
};

if (searchType === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import type {ListItem, TransactionWeekGroupListItemType} from '@components/SelectionListWithSections/types';
import CONST from '@src/CONST';
import type {BaseListItemHeaderProps} from './BaseListItemHeader';
import BaseListItemHeader from './BaseListItemHeader';

type WeekListItemHeaderProps<TItem extends ListItem> = Omit<BaseListItemHeaderProps<TItem>, 'item' | 'displayName' | 'groupColumnKey' | 'columnStyleKey'> & {
/** The week group currently being looked at */
week: TransactionWeekGroupListItemType;
};

function WeekListItemHeader<TItem extends ListItem>({
week: weekItem,
onCheckboxPress,
isDisabled,
canSelectMultiple,
isSelectAllChecked,
isIndeterminate,
isExpanded,
onDownArrowClick,
columns,
}: WeekListItemHeaderProps<TItem>) {
const weekName = weekItem.formattedWeek;

return (
<BaseListItemHeader
item={weekItem}
displayName={weekName}
groupColumnKey={CONST.SEARCH.TABLE_COLUMNS.GROUP_WEEK}
columnStyleKey={CONST.SEARCH.TABLE_COLUMNS.GROUP_WEEK}
onCheckboxPress={onCheckboxPress}
isDisabled={isDisabled}
canSelectMultiple={canSelectMultiple}
isSelectAllChecked={isSelectAllChecked}
isIndeterminate={isIndeterminate}
isExpanded={isExpanded}
onDownArrowClick={onDownArrowClick}
columns={columns}
/>
);
}

export default WeekListItemHeader;
18 changes: 18 additions & 0 deletions src/components/SelectionListWithSections/SearchTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,24 @@ const getTransactionGroupHeaders = (groupBy: SearchGroupBy, icons: SearchHeaderI
isColumnSortable: true,
},
];
case CONST.SEARCH.GROUP_BY.WEEK:
return [
{
columnName: CONST.SEARCH.TABLE_COLUMNS.GROUP_WEEK,
translationKey: 'common.week',
isColumnSortable: true,
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.GROUP_EXPENSES,
translationKey: 'common.expenses',
isColumnSortable: true,
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL,
translationKey: 'common.total',
isColumnSortable: true,
},
];
default:
return [];
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/SelectionListWithSections/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {
SearchTagGroup,
SearchTask,
SearchTransactionAction,
SearchWeekGroup,
SearchWithdrawalIDGroup,
} from '@src/types/onyx/SearchResults';
import type {ReceiptErrors} from '@src/types/onyx/Transaction';
Expand Down Expand Up @@ -523,6 +524,11 @@ type TransactionTagGroupListItemType = TransactionGroupListItemType & {groupedBy
formattedTag?: string;
};

type TransactionWeekGroupListItemType = TransactionGroupListItemType & {groupedBy: typeof CONST.SEARCH.GROUP_BY.WEEK} & SearchWeekGroup & {
/** Final and formatted "week" value used for displaying */
formattedWeek: string;
};

type ListItemProps<TItem extends ListItem> = CommonListItemProps<TItem> & {
/** The section list item */
item: TItem;
Expand Down Expand Up @@ -1168,6 +1174,7 @@ export type {
TransactionCategoryGroupListItemType,
TransactionMerchantGroupListItemType,
TransactionTagGroupListItemType,
TransactionWeekGroupListItemType,
Section,
SectionListDataType,
SectionWithIndexOffset,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ const translations: TranslationDeepObject<typeof en> = {
newFeature: 'Neue Funktion',
month: 'Monat',
home: 'Startseite',
week: 'Woche',
},
supportalNoAccess: {
title: 'Nicht so schnell',
Expand Down Expand Up @@ -7002,6 +7003,7 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard
[CONST.SEARCH.GROUP_BY.MERCHANT]: 'Händler',
[CONST.SEARCH.GROUP_BY.TAG]: 'Stichwort',
[CONST.SEARCH.GROUP_BY.MONTH]: 'Monat',
[CONST.SEARCH.GROUP_BY.WEEK]: 'Woche',
},
feed: 'Feed',
withdrawalType: {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ const translations = {
reimbursableTotal: 'Reimbursable total',
nonReimbursableTotal: 'Non-reimbursable total',
month: 'Month',
week: 'Week',
},
supportalNoAccess: {
title: 'Not so fast',
Expand Down Expand Up @@ -6893,6 +6894,7 @@ const translations = {
[CONST.SEARCH.GROUP_BY.MERCHANT]: 'Merchant',
[CONST.SEARCH.GROUP_BY.TAG]: 'Tag',
[CONST.SEARCH.GROUP_BY.MONTH]: 'Month',
[CONST.SEARCH.GROUP_BY.WEEK]: 'Week',
},
feed: 'Feed',
withdrawalType: {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: 'Total reembolsable',
nonReimbursableTotal: 'Total no reembolsable',
month: 'Monat',
week: 'Semana',
},
supportalNoAccess: {
title: 'No tan rápido',
Expand Down Expand Up @@ -6644,6 +6645,7 @@ ${amount} para ${merchant} - ${date}`,
[CONST.SEARCH.GROUP_BY.MERCHANT]: 'Comerciante',
[CONST.SEARCH.GROUP_BY.TAG]: 'Etiqueta',
[CONST.SEARCH.GROUP_BY.MONTH]: 'Mes',
[CONST.SEARCH.GROUP_BY.WEEK]: 'Semana',
},
feed: 'Feed',
withdrawalType: {
Expand Down
Loading
Loading