diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index 3fdcecffb04c..75bdd48c2894 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -6687,6 +6687,7 @@ const CONST = {
EXPENSE: {
RECEIPT: this.TABLE_COLUMNS.RECEIPT,
DATE: this.TABLE_COLUMNS.DATE,
+ SUBMITTED: this.TABLE_COLUMNS.SUBMITTED,
MERCHANT: this.TABLE_COLUMNS.MERCHANT,
FROM: this.TABLE_COLUMNS.FROM,
TO: this.TABLE_COLUMNS.TO,
@@ -6696,6 +6697,7 @@ const CONST = {
},
EXPENSE_REPORT: {
DATE: this.TABLE_COLUMNS.DATE,
+ SUBMITTED: this.TABLE_COLUMNS.SUBMITTED,
STATUS: this.TABLE_COLUMNS.STATUS,
TITLE: this.TABLE_COLUMNS.TITLE,
FROM: this.TABLE_COLUMNS.FROM,
@@ -6773,6 +6775,7 @@ const CONST = {
TABLE_COLUMNS: {
RECEIPT: 'receipt',
DATE: 'date',
+ SUBMITTED: 'submitted',
MERCHANT: 'merchant',
DESCRIPTION: 'description',
FROM: 'from',
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 586154caa388..fda71d655181 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -1037,7 +1037,7 @@ function Search({
navigation.setParams({q: newQuery, rawQuery: undefined});
};
- const shouldShowYear = shouldShowYearUtil(searchResults?.data, isExpenseReportType ?? false);
+ const {shouldShowYearCreated, shouldShowYearSubmitted} = shouldShowYearUtil(searchResults?.data, isExpenseReportType ?? false);
const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(searchResults?.data);
const shouldShowSorting = !validGroupBy;
const shouldShowTableHeader = isLargeScreenWidth && !isChat && !validGroupBy;
@@ -1067,7 +1067,8 @@ function Search({
onSortPress={onSortPress}
sortOrder={sortOrder}
sortBy={sortBy}
- shouldShowYear={shouldShowYear}
+ shouldShowYear={shouldShowYearCreated}
+ shouldShowYearSubmitted={shouldShowYearSubmitted}
isAmountColumnWide={shouldShowAmountInWideColumn}
isTaxAmountColumnWide={shouldShowTaxAmountInWideColumn}
shouldShowSorting={shouldShowSorting}
diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
index 53547f29416e..558671578da9 100644
--- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
+++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
@@ -88,6 +88,15 @@ function ExpenseReportListItemRow({
/>
),
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: (
+
+
+
+ ),
[CONST.SEARCH.TABLE_COLUMNS.STATUS]: (
({
backgroundColor: theme.highlightBG,
});
- const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => {
+ const {amountColumnSize, dateColumnSize, taxAmountColumnSize, submittedColumnSize} = useMemo(() => {
return {
amountColumnSize: transactionItem.isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
taxAmountColumnSize: transactionItem.isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
dateColumnSize: transactionItem.shouldShowYear ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
+ submittedColumnSize: transactionItem.shouldShowYearSubmitted ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
};
- }, [transactionItem.isAmountColumnWide, transactionItem.isTaxAmountColumnWide, transactionItem.shouldShowYear]);
+ }, [transactionItem.isAmountColumnWide, transactionItem.isTaxAmountColumnWide, transactionItem.shouldShowYear, transactionItem.shouldShowYearSubmitted]);
const transactionViolations = useMemo(() => {
return (violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionItem.transactionID}`] ?? []).filter(
@@ -183,6 +184,7 @@ function TransactionListItem({
isActionLoading={isLoading ?? isActionLoading}
isSelected={!!transactionItem.isSelected}
dateColumnSize={dateColumnSize}
+ submittedColumnSize={submittedColumnSize}
amountColumnSize={amountColumnSize}
taxAmountColumnSize={taxAmountColumnSize}
shouldShowCheckbox={!!canSelectMultiple}
diff --git a/src/components/SelectionListWithSections/SearchTableHeader.tsx b/src/components/SelectionListWithSections/SearchTableHeader.tsx
index 2672d1225dee..8ab5da2c19a3 100644
--- a/src/components/SelectionListWithSections/SearchTableHeader.tsx
+++ b/src/components/SelectionListWithSections/SearchTableHeader.tsx
@@ -34,6 +34,10 @@ const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [
columnName: CONST.SEARCH.TABLE_COLUMNS.DATE,
translationKey: 'common.date',
},
+ {
+ columnName: CONST.SEARCH.TABLE_COLUMNS.SUBMITTED,
+ translationKey: 'common.submitted',
+ },
{
columnName: CONST.SEARCH.TABLE_COLUMNS.MERCHANT,
translationKey: 'common.merchant',
@@ -140,6 +144,10 @@ const getExpenseReportHeaders = (profileIcon?: IconAsset): SearchColumnConfig[]
columnName: CONST.SEARCH.TABLE_COLUMNS.DATE,
translationKey: 'common.date',
},
+ {
+ columnName: CONST.SEARCH.TABLE_COLUMNS.SUBMITTED,
+ translationKey: 'common.submitted',
+ },
{
columnName: CONST.SEARCH.TABLE_COLUMNS.STATUS,
translationKey: 'common.status',
@@ -192,6 +200,7 @@ type SearchTableHeaderProps = {
sortOrder?: SortOrder;
onSortPress: (column: SearchColumnType, order: SortOrder) => void;
shouldShowYear: boolean;
+ shouldShowYearSubmitted?: boolean;
isAmountColumnWide: boolean;
isTaxAmountColumnWide: boolean;
shouldShowSorting: boolean;
@@ -207,6 +216,7 @@ function SearchTableHeader({
sortOrder,
onSortPress,
shouldShowYear,
+ shouldShowYearSubmitted,
shouldShowSorting,
canSelectMultiple,
isAmountColumnWide,
@@ -245,6 +255,7 @@ function SearchTableHeader({
areAllOptionalColumnsHidden={areAllOptionalColumnsHidden}
shouldShowColumn={shouldShowColumn}
dateColumnSize={shouldShowYear ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL}
+ submittedColumnSize={shouldShowYearSubmitted ? 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}
shouldShowSorting={shouldShowSorting}
diff --git a/src/components/SelectionListWithSections/SortableTableHeader.tsx b/src/components/SelectionListWithSections/SortableTableHeader.tsx
index 1d1ff42243b7..6157f929ec04 100644
--- a/src/components/SelectionListWithSections/SortableTableHeader.tsx
+++ b/src/components/SelectionListWithSections/SortableTableHeader.tsx
@@ -25,6 +25,7 @@ type SearchTableHeaderProps = {
sortOrder?: SortOrder;
shouldShowSorting: boolean;
dateColumnSize: TableColumnSize;
+ submittedColumnSize?: TableColumnSize;
amountColumnSize: TableColumnSize;
taxAmountColumnSize: TableColumnSize;
containerStyles?: StyleProp;
@@ -39,6 +40,7 @@ function SortableTableHeader({
sortOrder,
shouldShowColumn,
dateColumnSize,
+ submittedColumnSize,
containerStyles,
shouldShowSorting,
onSortPress,
@@ -77,6 +79,7 @@ function SortableTableHeader({
amountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE,
taxAmountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE,
!!areAllOptionalColumnsHidden,
+ submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE,
),
]}
isSortable={isSortable}
diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts
index 85a1c84d0168..61c3af8db33d 100644
--- a/src/components/SelectionListWithSections/types.ts
+++ b/src/components/SelectionListWithSections/types.ts
@@ -243,6 +243,9 @@ type TransactionListItemType = ListItem &
/** Report to which the transaction belongs */
report: Report | undefined;
+ /** The date the report was submitted */
+ submitted?: string;
+
/** Policy to which the transaction belongs */
policy: Policy | undefined;
@@ -281,6 +284,11 @@ type TransactionListItemType = ListItem &
*/
shouldShowYear: boolean;
+ /** Whether we should show the year for the submitted date.
+ * This is true if at least one transaction in the dataset was submitted in past years
+ */
+ shouldShowYearSubmitted: boolean;
+
isAmountColumnWide: boolean;
isTaxAmountColumnWide: boolean;
@@ -399,6 +407,12 @@ type TransactionReportGroupListItemType = TransactionGroupListItemType & {groupe
*/
shouldShowYear: boolean;
+ /**
+ * Whether we should show the year for the submitted date.
+ * This is true if at least one report in the dataset was submitted in past years
+ */
+ shouldShowYearSubmitted: boolean;
+
/** The main action that can be performed for the report */
action: SearchTransactionAction | undefined;
diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx
index 729ccb358f5d..bd06a2d3f912 100644
--- a/src/components/TransactionItemRow/index.tsx
+++ b/src/components/TransactionItemRow/index.tsx
@@ -89,6 +89,7 @@ type TransactionItemRowProps = {
isSelected: boolean;
shouldShowTooltip: boolean;
dateColumnSize: TableColumnSize;
+ submittedColumnSize?: TableColumnSize;
amountColumnSize: TableColumnSize;
taxAmountColumnSize: TableColumnSize;
onCheckboxPress?: (transactionID: string) => void;
@@ -131,6 +132,7 @@ function TransactionItemRow({
isSelected,
shouldShowTooltip,
dateColumnSize,
+ submittedColumnSize,
amountColumnSize,
taxAmountColumnSize,
onCheckboxPress = () => {},
@@ -162,6 +164,7 @@ function TransactionItemRow({
const expensicons = useMemoizedLazyExpensifyIcons(['ArrowRight']);
const isDateColumnWide = dateColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE;
+ const isSubmittedColumnWide = submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE;
const isAmountColumnWide = amountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE;
const isTaxAmountColumnWide = taxAmountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE;
@@ -253,6 +256,18 @@ function TransactionItemRow({
/>
),
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: (
+
+
+
+ ),
[CONST.SEARCH.TABLE_COLUMNS.CATEGORY]: (
= Partial>;
@@ -139,6 +138,7 @@ const transactionColumnNamesToSortingProperty: TransactionSorting = {
[CONST.SEARCH.TABLE_COLUMNS.TO]: 'formattedTo' as const,
[CONST.SEARCH.TABLE_COLUMNS.FROM]: 'formattedFrom' as const,
[CONST.SEARCH.TABLE_COLUMNS.DATE]: 'date' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: 'submitted' as const,
[CONST.SEARCH.TABLE_COLUMNS.TAG]: 'tag' as const,
[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: 'formattedMerchant' as const,
[CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: 'formattedTotal' as const,
@@ -162,6 +162,7 @@ const taskColumnNamesToSortingProperty: TaskSorting = {
const expenseReportColumnNamesToSortingProperty: ExpenseReportSorting = {
[CONST.SEARCH.TABLE_COLUMNS.AVATAR]: null,
[CONST.SEARCH.TABLE_COLUMNS.DATE]: 'created' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: 'submitted' as const,
[CONST.SEARCH.TABLE_COLUMNS.STATUS]: 'formattedStatus' as const,
[CONST.SEARCH.TABLE_COLUMNS.TITLE]: 'reportName' as const,
[CONST.SEARCH.TABLE_COLUMNS.FROM]: 'formattedFrom' as const,
@@ -646,7 +647,7 @@ function getTransactionItemCommonFormattedProperties(
policy: OnyxTypes.Policy,
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
report: OnyxTypes.Report | undefined,
-): Pick {
+): Pick {
const isExpenseReport = report?.type === CONST.REPORT.TYPE.EXPENSE;
const fromName = getDisplayNameOrDefault(from);
@@ -664,11 +665,13 @@ function getTransactionItemCommonFormattedProperties(
const date = transactionItem?.modifiedCreated ? transactionItem.modifiedCreated : transactionItem?.created;
const merchant = getTransactionMerchant(transactionItem, policy);
const formattedMerchant = merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ? '' : merchant;
+ const submitted = report?.submitted;
return {
formattedFrom,
formattedTo,
date,
+ submitted,
formattedTotal,
formattedMerchant,
};
@@ -847,59 +850,98 @@ function getWideAmountIndicators(data: TransactionListItemType[] | TransactionGr
};
}
+type ShouldShowYearResult = {
+ shouldShowYearCreated: boolean;
+ shouldShowYearSubmitted: boolean;
+};
+
/**
* Checks if the date of transactions or reports indicate the need to display the year because they are from a past year.
* @param data - The search results data (array or object)
* @param checkOnlyReports - When true and data is an object, only check report dates (skip transactions and report actions)
+ * @returns An object indicating which date fields should display the full year
*/
-function shouldShowYear(data: TransactionListItemType[] | TransactionGroupListItemType[] | TaskListItemType[] | OnyxTypes.SearchResults['data'], checkOnlyReports = false) {
+function shouldShowYear(
+ data: TransactionListItemType[] | TransactionGroupListItemType[] | TaskListItemType[] | OnyxTypes.SearchResults['data'],
+ checkOnlyReports = false,
+): ShouldShowYearResult {
+ const result: ShouldShowYearResult = {
+ shouldShowYearCreated: false,
+ shouldShowYearSubmitted: false,
+ };
+
const currentYear = new Date().getFullYear();
if (Array.isArray(data)) {
- return data.some((item: TransactionListItemType | TransactionGroupListItemType | TaskListItemType) => {
+ for (const item of data) {
if (isTaskListItemType(item)) {
const taskYear = new Date(item.created).getFullYear();
- return taskYear !== currentYear;
+ if (taskYear !== currentYear) {
+ result.shouldShowYearCreated = true;
+ }
}
-
- if (isTransactionGroupListItemType(item)) {
- // If the item is a TransactionGroupListItemType, iterate over its transactions and check them
- return item.transactions.some((transaction) => {
- const transactionYear = new Date(getTransactionCreatedDate(transaction)).getFullYear();
- return transactionYear !== currentYear;
- });
+ if (isTransactionReportGroupListItemType(item)) {
+ if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) {
+ result.shouldShowYearCreated = true;
+ }
+ if (item.submitted && DateUtils.doesDateBelongToAPastYear(item.submitted)) {
+ result.shouldShowYearSubmitted = true;
+ }
+ }
+ if (isTransactionListItemType(item)) {
+ const transactionCreated = getTransactionCreatedDate(item);
+ if (transactionCreated && DateUtils.doesDateBelongToAPastYear(transactionCreated)) {
+ result.shouldShowYearCreated = true;
+ }
+ if (item.submitted && DateUtils.doesDateBelongToAPastYear(item.submitted)) {
+ result.shouldShowYearSubmitted = true;
+ }
}
- const createdYear = new Date(item?.modifiedCreated ? item.modifiedCreated : item?.created || '').getFullYear();
- return createdYear !== currentYear;
- });
+ // Early exit if all flags are true
+ if (result.shouldShowYearCreated && result.shouldShowYearSubmitted) {
+ return result;
+ }
+ }
+ return result;
}
- for (const key in data) {
+ for (const key of Object.keys(data)) {
if (!checkOnlyReports && isTransactionEntry(key)) {
const item = data[key];
- if (shouldShowTransactionYear(item)) {
- return true;
+ if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) {
+ result.shouldShowYearCreated = true;
+ }
+ const report = data[`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`];
+ if (report?.submitted && DateUtils.doesDateBelongToAPastYear(report.submitted)) {
+ result.shouldShowYearSubmitted = true;
}
} else if (!checkOnlyReports && isReportActionEntry(key)) {
const item = data[key];
for (const action of Object.values(item)) {
const date = action.created;
-
if (DateUtils.doesDateBelongToAPastYear(date)) {
- return true;
+ result.shouldShowYearCreated = true;
}
}
} else if (isReportEntry(key)) {
const item = data[key];
- const date = item.created;
- if (date && DateUtils.doesDateBelongToAPastYear(date)) {
- return true;
+ if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) {
+ result.shouldShowYearCreated = true;
+ }
+ if (item.submitted && DateUtils.doesDateBelongToAPastYear(item.submitted)) {
+ result.shouldShowYearSubmitted = true;
}
}
+
+ // Early exit if all flags are true
+ if (result.shouldShowYearCreated && result.shouldShowYearSubmitted) {
+ return result;
+ }
}
- return false;
+
+ return result;
}
/**
@@ -1051,7 +1093,7 @@ function getTransactionsSections(
isActionLoadingSet: ReadonlySet | undefined,
): [TransactionListItemType[], number] {
const shouldShowMerchant = getShouldShowMerchant(data);
- const doesDataContainAPastYearTransaction = shouldShowYear(data);
+ const {shouldShowYearCreated, shouldShowYearSubmitted} = shouldShowYear(data);
const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(data);
// Pre-filter transaction keys to avoid repeated checks
@@ -1100,7 +1142,7 @@ function getTransactionsSections(
const from = reportAction?.actorAccountID ? (personalDetailsMap.get(reportAction.actorAccountID.toString()) ?? emptyPersonalDetails) : emptyPersonalDetails;
const to = getToFieldValueForTransaction(transactionItem, report, data.personalDetailsList, reportAction);
- const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(
+ const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date, submitted} = getTransactionItemCommonFormattedProperties(
transactionItem,
from,
to,
@@ -1125,8 +1167,10 @@ function getTransactionsSections(
formattedTotal,
formattedMerchant,
date,
+ submitted,
shouldShowMerchant,
- shouldShowYear: doesDataContainAPastYearTransaction,
+ shouldShowYear: shouldShowYearCreated,
+ shouldShowYearSubmitted,
isAmountColumnWide: shouldShowAmountInWideColumn,
isTaxAmountColumnWide: shouldShowTaxAmountInWideColumn,
violations: transactionViolations,
@@ -1338,7 +1382,7 @@ function getTaskSections(
const report = getReportOrDraftReport(taskItem.reportID) ?? taskItem;
const parentReport = getReportOrDraftReport(taskItem.parentReportID);
- const doesDataContainAPastYearTransaction = shouldShowYear(data);
+ const {shouldShowYearCreated} = shouldShowYear(data);
const reportName = StringUtils.lineBreaksToSpaces(Parser.htmlToText(taskItem.reportName));
const description = StringUtils.lineBreaksToSpaces(Parser.htmlToText(taskItem.description));
@@ -1351,7 +1395,7 @@ function getTaskSections(
createdBy,
formattedCreatedBy,
keyForList: taskItem.reportID,
- shouldShowYear: doesDataContainAPastYearTransaction,
+ shouldShowYear: shouldShowYearCreated,
};
if (parentReport && personalDetails) {
@@ -1498,7 +1542,7 @@ function getReportSections(
): [TransactionGroupListItemType[], number] {
const shouldShowMerchant = getShouldShowMerchant(data);
- const doesDataContainAPastYearTransaction = shouldShowYear(data);
+ const {shouldShowYearCreated: shouldShowYearCreatedTransaction, shouldShowYearSubmitted: shouldShowYearSubmittedTransaction} = shouldShowYear(data);
const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(data);
const {moneyRequestReportActionsByTransactionID, holdReportActionsByTransactionID} = createReportActionsLookupMaps(data);
@@ -1521,7 +1565,7 @@ function getReportSections(
);
const orderedKeys: string[] = [...reportKeys, ...transactionKeys];
- const doesDataContainAPastYearReport = shouldShowYear(data, true);
+ const {shouldShowYearCreated: shouldShowYearCreatedReport, shouldShowYearSubmitted: shouldShowYearSubmittedReport} = shouldShowYear(data, true);
for (const key of orderedKeys) {
if (isReportEntry(key) && (data[key].type === CONST.REPORT.TYPE.IOU || data[key].type === CONST.REPORT.TYPE.EXPENSE || data[key].type === CONST.REPORT.TYPE.INVOICE)) {
@@ -1594,7 +1638,8 @@ function getReportSections(
formattedStatus,
transactions,
...(reportPendingAction ? {pendingAction: reportPendingAction} : {}),
- shouldShowYear: doesDataContainAPastYearReport,
+ shouldShowYear: shouldShowYearCreatedReport,
+ shouldShowYearSubmitted: shouldShowYearSubmittedReport,
hasVisibleViolations: hasVisibleViolationsForReport,
};
@@ -1640,7 +1685,8 @@ function getReportSections(
formattedMerchant,
date,
shouldShowMerchant,
- shouldShowYear: doesDataContainAPastYearTransaction,
+ shouldShowYear: shouldShowYearCreatedTransaction,
+ shouldShowYearSubmitted: shouldShowYearSubmittedTransaction,
keyForList: transactionItem.transactionID,
violations: transactionViolations,
isAmountColumnWide: shouldShowAmountInWideColumn,
@@ -1877,11 +1923,21 @@ function getSortedSections(
*/
function compareValues(a: unknown, b: unknown, sortOrder: SortOrder, sortBy: string, localeCompare: LocaleContextProps['localeCompare'], shouldCompareOriginalValue = false): number {
const isAsc = sortOrder === CONST.SEARCH.SORT_ORDER.ASC;
+ const aIsEmpty = a === undefined || a === null || a === '';
+ const bIsEmpty = b === undefined || b === null || b === '';
- if (a === undefined || b === undefined) {
+ if (aIsEmpty && bIsEmpty) {
return 0;
}
+ if (aIsEmpty) {
+ return isAsc ? -1 : 1;
+ }
+
+ if (bIsEmpty) {
+ return isAsc ? 1 : -1;
+ }
+
if (typeof a === 'string' && typeof b === 'string') {
return isAsc ? localeCompare(a, b) : localeCompare(b, a);
}
@@ -2091,6 +2147,8 @@ function getSearchColumnTranslationKey(columnId: SearchCustomColumnIds): Transla
switch (columnId) {
case CONST.SEARCH.TABLE_COLUMNS.DATE:
return 'common.date';
+ case CONST.SEARCH.TABLE_COLUMNS.SUBMITTED:
+ return 'common.submitted';
case CONST.SEARCH.TABLE_COLUMNS.MERCHANT:
return 'common.merchant';
case CONST.SEARCH.TABLE_COLUMNS.FROM:
@@ -2486,20 +2544,21 @@ function getColumnsToShow(
type?: SearchDataTypes,
): ColumnVisibility {
if (type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
- const reportColumns = [
- CONST.SEARCH.TABLE_COLUMNS.AVATAR,
- CONST.SEARCH.TABLE_COLUMNS.DATE,
- CONST.SEARCH.TABLE_COLUMNS.STATUS,
- CONST.SEARCH.TABLE_COLUMNS.TITLE,
- CONST.SEARCH.TABLE_COLUMNS.FROM,
- CONST.SEARCH.TABLE_COLUMNS.TO,
- CONST.SEARCH.TABLE_COLUMNS.TOTAL,
- CONST.SEARCH.TABLE_COLUMNS.ACTION,
- ];
+ const reportColumns: ColumnVisibility = {
+ [CONST.SEARCH.TABLE_COLUMNS.AVATAR]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.DATE]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false,
+ [CONST.SEARCH.TABLE_COLUMNS.STATUS]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.TITLE]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.FROM]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.TO]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.TOTAL]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.ACTION]: true,
+ };
// If there are no visible columns, everything should be visible
if (!visibleColumns.length) {
- return Object.fromEntries(reportColumns.map((column) => [column, true]));
+ return reportColumns;
}
// If the user has set custom columns, toggle the visible columns on, with all other
@@ -2507,7 +2566,7 @@ function getColumnsToShow(
const columns: ColumnVisibility = {};
const requiredColumns = new Set([CONST.SEARCH.TABLE_COLUMNS.AVATAR, CONST.SEARCH.TABLE_COLUMNS.TOTAL]);
- for (const columnId of reportColumns) {
+ for (const columnId of Object.keys(reportColumns) as SearchColumnType[]) {
columns[columnId] = requiredColumns.has(columnId);
}
@@ -2557,6 +2616,7 @@ function getColumnsToShow(
[CONST.SEARCH.TABLE_COLUMNS.RECEIPT]: true,
[CONST.SEARCH.TABLE_COLUMNS.TYPE]: true,
[CONST.SEARCH.TABLE_COLUMNS.DATE]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false,
[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: false,
[CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]: false,
[CONST.SEARCH.TABLE_COLUMNS.FROM]: false,
@@ -2766,6 +2826,8 @@ function getTableMinWidth(columns: SearchColumnType[]) {
minWidth += 80;
} else if (column === CONST.SEARCH.TABLE_COLUMNS.DATE) {
minWidth += 48;
+ } else if (column === CONST.SEARCH.TABLE_COLUMNS.SUBMITTED) {
+ minWidth += 72;
} else if (column === CONST.SEARCH.TABLE_COLUMNS.TYPE) {
minWidth += 20;
} else {
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 9002658e60be..a82d65ac4835 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -1718,7 +1718,14 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
return isDragging ? styles.cursorGrabbing : styles.cursorZoomOut;
},
- getReportTableColumnStyles: (columnName: string, isDateColumnWide = false, isAmountColumnWide = false, isTaxAmountColumnWide = false, isDateColumnFullWidth = false): ViewStyle => {
+ getReportTableColumnStyles: (
+ columnName: string,
+ isDateColumnWide = false,
+ isAmountColumnWide = false,
+ isTaxAmountColumnWide = false,
+ isDateColumnFullWidth = false,
+ isSubmittedColumnWide = false,
+ ): ViewStyle => {
let columnWidth;
switch (columnName) {
case CONST.SEARCH.TABLE_COLUMNS.COMMENTS:
@@ -1731,6 +1738,9 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
case CONST.SEARCH.TABLE_COLUMNS.STATUS:
columnWidth = {...getWidthStyle(variables.w80), ...styles.alignItemsCenter};
break;
+ case CONST.SEARCH.TABLE_COLUMNS.SUBMITTED:
+ columnWidth = {...getWidthStyle(isSubmittedColumnWide ? variables.w92 : variables.w72)};
+ break;
case CONST.SEARCH.TABLE_COLUMNS.DATE:
// We will remove this variable & param, but in a follow up PR. We are duplicating the logic here to "use" the variable
// to prevent eslint errors. This will be removed
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index 1e2235392ed9..5e2fca054ae2 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -89,6 +89,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback<
/** The date the report was created */
created?: string;
+ /** The date the report was submitted */
+ submitted?: string;
+
/** The specific type of chat */
chatType?: ValueOf;
diff --git a/src/types/utils/whitelistedReportKeys.ts b/src/types/utils/whitelistedReportKeys.ts
index 0a6b4100ebbc..c3f10b4da0e2 100644
--- a/src/types/utils/whitelistedReportKeys.ts
+++ b/src/types/utils/whitelistedReportKeys.ts
@@ -32,6 +32,7 @@ type WhitelistedReport = OnyxCommon.OnyxValueWithOfflineFeedback<
writeCapability: unknown;
type: unknown;
created: unknown;
+ submitted: unknown;
visibility: unknown;
invoiceReceiver: unknown;
parentReportID: unknown;
diff --git a/tests/unit/MoneyRequestReportUtilsTest.ts b/tests/unit/MoneyRequestReportUtilsTest.ts
index 9cae1e021d32..616603708fea 100644
--- a/tests/unit/MoneyRequestReportUtilsTest.ts
+++ b/tests/unit/MoneyRequestReportUtilsTest.ts
@@ -16,6 +16,7 @@ const policyBaseMock: Policy = {
const reportBaseMock: Report = {
chatReportID: '1706144653204915',
created: '2024-12-21 13:05:20',
+ submitted: '2024-12-21 13:05:20',
currency: 'USD',
isWaitingOnBankAccount: false,
managerID: 100,
@@ -60,6 +61,7 @@ const transactionItemBaseMock: TransactionListItemType = {
category: '',
comment: {comment: ''},
created: '2024-12-21',
+ submitted: '2024-12-21',
currency: 'USD',
date: '2024-12-21',
formattedFrom: 'Admin',
@@ -84,6 +86,7 @@ const transactionItemBaseMock: TransactionListItemType = {
reportID: reportBaseMock.reportID,
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
tag: '',
diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts
index b5b16c076022..fd11d68d88f3 100644
--- a/tests/unit/Search/SearchUIUtilsTest.ts
+++ b/tests/unit/Search/SearchUIUtilsTest.ts
@@ -110,6 +110,7 @@ const report2 = {
action: 'view',
chatReportID: '1706144653204915',
created: '2024-12-21 13:05:20',
+ submitted: '2024-12-21 13:05:20',
currency: 'USD',
isOneTransactionReport: true,
isWaitingOnBankAccount: false,
@@ -132,6 +133,7 @@ const report3 = {
chatReportID: '6155022250251839',
chatType: undefined,
created: '2025-03-05 16:34:27',
+ submitted: '2025-03-05',
currency: 'VND',
isOneTransactionReport: false,
isOwnPolicyExpenseChat: false,
@@ -764,6 +766,7 @@ const transactionsListItems = [
category: '',
comment: {comment: ''},
created: '2024-12-21',
+ submitted: undefined,
currency: 'USD',
date: '2024-12-21',
formattedFrom: 'Admin',
@@ -788,6 +791,7 @@ const transactionsListItems = [
reportID: '123456789',
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
tag: '',
@@ -815,6 +819,7 @@ const transactionsListItems = [
category: '',
comment: {comment: ''},
created: '2024-12-21',
+ submitted: '2024-12-21 13:05:20',
currency: 'USD',
date: '2024-12-21',
formattedFrom: 'Admin',
@@ -838,6 +843,7 @@ const transactionsListItems = [
reportID: '11111',
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
tag: '',
@@ -876,6 +882,7 @@ const transactionsListItems = [
category: '',
comment: {comment: ''},
created: '2025-03-05',
+ submitted: '2025-03-05',
currency: 'VND',
hasEReceipt: false,
merchant: '(none)',
@@ -906,6 +913,7 @@ const transactionsListItems = [
date: '2025-03-05',
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
keyForList: '3',
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
@@ -932,6 +940,7 @@ const transactionsListItems = [
category: '',
comment: {comment: ''},
created: '2025-03-05',
+ submitted: '2025-03-05',
currency: 'VND',
hasEReceipt: false,
merchant: '(none)',
@@ -962,6 +971,7 @@ const transactionsListItems = [
date: '2025-03-05',
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
keyForList: '4',
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
@@ -1006,6 +1016,7 @@ const transactionReportGroupListItems = [
reportID: '123456789',
reportName: 'Expense Report #123',
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
stateNum: 0,
statusNum: 0,
to: emptyPersonalDetails,
@@ -1049,6 +1060,7 @@ const transactionReportGroupListItems = [
reportID: '123456789',
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
tag: '',
@@ -1074,6 +1086,7 @@ const transactionReportGroupListItems = [
allActions: ['approve'],
chatReportID: '1706144653204915',
created: '2024-12-21 13:05:20',
+ submitted: '2024-12-21 13:05:20',
currency: 'USD',
formattedFrom: 'Admin',
formattedStatus: 'Outstanding',
@@ -1095,6 +1108,7 @@ const transactionReportGroupListItems = [
reportID: '11111',
reportName: 'Expense Report #123',
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
stateNum: 1,
statusNum: 1,
to: {
@@ -1148,6 +1162,7 @@ const transactionReportGroupListItems = [
reportID: '11111',
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
tag: '',
@@ -1177,6 +1192,7 @@ const transactionReportGroupListItems = [
chatReportID: '6155022250251839',
chatType: undefined,
created: '2025-03-05 16:34:27',
+ submitted: '2025-03-05',
currency: 'VND',
formattedFrom: 'Admin',
formattedStatus: 'Outstanding',
@@ -1194,6 +1210,7 @@ const transactionReportGroupListItems = [
reportID: '99999',
reportName: 'Approver owes ₫44.00',
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
stateNum: 1,
statusNum: 1,
total: 4400,
@@ -1215,7 +1232,122 @@ const transactionReportGroupListItems = [
displayName: 'Approver',
login: 'approver@policy.com',
},
- transactions: [transactionsListItems.at(2), transactionsListItems.at(3)],
+ transactions: [
+ {
+ amount: 1200,
+ action: 'view',
+ allActions: ['view'],
+ report: report3,
+ policy,
+ reportAction: reportAction3,
+ holdReportAction: undefined,
+ cardID: undefined,
+ cardName: undefined,
+ category: '',
+ comment: {comment: ''},
+ created: '2025-03-05',
+ currency: 'VND',
+ hasEReceipt: false,
+ merchant: '(none)',
+ modifiedAmount: 0,
+ modifiedCreated: '',
+ modifiedCurrency: '',
+ modifiedMerchant: '',
+ parentTransactionID: '',
+ reportID: '99999',
+ tag: '',
+ transactionID: '3',
+ from: {
+ accountID: 18439984,
+ avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
+ displayName: 'Admin',
+ login: 'admin@policy.com',
+ },
+ to: {
+ accountID: 1111111,
+ avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
+ displayName: 'Approver',
+ login: 'approver@policy.com',
+ },
+ formattedFrom: 'Admin',
+ formattedTo: 'Approver',
+ formattedTotal: 1200,
+ formattedMerchant: '',
+ date: '2025-03-05',
+ shouldShowMerchant: true,
+ shouldShowYear: true,
+ shouldShowYearSubmitted: true,
+ keyForList: '3',
+ isAmountColumnWide: false,
+ isTaxAmountColumnWide: false,
+ receipt: undefined,
+ taxAmount: undefined,
+ mccGroup: undefined,
+ modifiedMCCGroup: undefined,
+ pendingAction: undefined,
+ errors: undefined,
+ violations: [],
+ groupAmount: -5000,
+ groupCurrency: 'USD',
+ },
+ {
+ amount: 3200,
+ action: 'view',
+ allActions: ['view'],
+ report: report3,
+ policy,
+ reportAction: reportAction4,
+ holdReportAction: undefined,
+ cardID: undefined,
+ cardName: undefined,
+ category: '',
+ comment: {comment: ''},
+ created: '2025-03-05',
+ currency: 'VND',
+ hasEReceipt: false,
+ merchant: '(none)',
+ modifiedAmount: 0,
+ modifiedCreated: '',
+ modifiedCurrency: '',
+ modifiedMerchant: '',
+ parentTransactionID: '',
+ reportID: '99999',
+ tag: '',
+ transactionID: '4',
+ from: {
+ accountID: 18439984,
+ avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
+ displayName: 'Admin',
+ login: 'admin@policy.com',
+ },
+ to: {
+ accountID: 1111111,
+ avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
+ displayName: 'Approver',
+ login: 'approver@policy.com',
+ },
+ formattedFrom: 'Admin',
+ formattedTo: 'Approver',
+ formattedTotal: 3200,
+ formattedMerchant: '',
+ date: '2025-03-05',
+ shouldShowMerchant: true,
+ shouldShowYear: true,
+ shouldShowYearSubmitted: true,
+ keyForList: '4',
+ isAmountColumnWide: false,
+ isTaxAmountColumnWide: false,
+ receipt: undefined,
+ taxAmount: undefined,
+ mccGroup: undefined,
+ modifiedMCCGroup: undefined,
+ pendingAction: undefined,
+ errors: undefined,
+ violations: [],
+ groupAmount: -5000,
+ groupCurrency: 'USD',
+ },
+ ],
},
{
groupedBy: 'expense-report',
@@ -1245,6 +1377,7 @@ const transactionReportGroupListItems = [
reportID: reportID5,
reportName: 'Expense Report #123',
shouldShowYear: true,
+ shouldShowYearSubmitted: true,
stateNum: 0,
statusNum: 0,
to: emptyPersonalDetails,
@@ -1696,7 +1829,7 @@ describe('SearchUIUtils', () => {
expect(distanceTransaction).toBeDefined();
expect(distanceTransaction?.iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.DISTANCE);
- const expectedPropertyCount = 45;
+ const expectedPropertyCount = 47;
expect(Object.keys(distanceTransaction ?? {}).length).toBe(expectedPropertyCount);
});
@@ -1729,7 +1862,7 @@ describe('SearchUIUtils', () => {
expect(distanceTransaction).toBeDefined();
expect(distanceTransaction?.iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.DISTANCE);
- const expectedPropertyCount = 45;
+ const expectedPropertyCount = 46;
expect(Object.keys(distanceTransaction ?? {}).length).toBe(expectedPropertyCount);
});
@@ -2570,6 +2703,7 @@ describe('SearchUIUtils', () => {
expect(SearchUIUtils.getColumnsToShow(1, [], [], false, CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT)).toEqual({
[CONST.SEARCH.TABLE_COLUMNS.AVATAR]: true,
[CONST.SEARCH.TABLE_COLUMNS.DATE]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false,
[CONST.SEARCH.TABLE_COLUMNS.STATUS]: true,
[CONST.SEARCH.TABLE_COLUMNS.TITLE]: true,
[CONST.SEARCH.TABLE_COLUMNS.FROM]: true,
@@ -2586,6 +2720,7 @@ describe('SearchUIUtils', () => {
// Avatar should always be visible
[CONST.SEARCH.TABLE_COLUMNS.AVATAR]: true,
[CONST.SEARCH.TABLE_COLUMNS.DATE]: true,
+ [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false,
[CONST.SEARCH.TABLE_COLUMNS.STATUS]: true,
[CONST.SEARCH.TABLE_COLUMNS.TITLE]: true,
[CONST.SEARCH.TABLE_COLUMNS.FROM]: false,
diff --git a/tests/unit/Search/handleActionButtonPressTest.ts b/tests/unit/Search/handleActionButtonPressTest.ts
index 5bb137e4228e..6adf6279af0b 100644
--- a/tests/unit/Search/handleActionButtonPressTest.ts
+++ b/tests/unit/Search/handleActionButtonPressTest.ts
@@ -16,6 +16,7 @@ const mockReportItemWithHold = {
allActions: ['approve'],
chatReportID: '2108006919825366',
created: '2024-12-04 23:18:33',
+ submitted: '2024-12-04',
currency: 'USD',
isOneTransactionReport: false,
isPolicyExpenseChat: false,
@@ -64,6 +65,7 @@ const mockReportItemWithHold = {
validated: false,
},
shouldShowYear: false,
+ shouldShowYearSubmitted: false,
transactions: [
{
report: {
@@ -107,6 +109,7 @@ const mockReportItemWithHold = {
modifiedCurrency: '',
modifiedMerchant: '',
parentTransactionID: '',
+ submitted: '2024-12-04',
policyID: '48D7178DE42EE9F9',
reportID: '1350959062018695',
reportType: 'expense',
@@ -150,6 +153,7 @@ const mockReportItemWithHold = {
date: '2024-12-04',
shouldShowMerchant: true,
shouldShowYear: false,
+ shouldShowYearSubmitted: false,
keyForList: '1049531721038862176',
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
@@ -185,6 +189,7 @@ const mockReportItemWithHold = {
comment: '',
},
created: '2024-12-04',
+ submitted: '2024-12-04',
currency: 'USD',
hasEReceipt: false,
merchant: 'Forbes',
@@ -215,6 +220,7 @@ const mockReportItemWithHold = {
date: '2024-12-04',
shouldShowMerchant: true,
shouldShowYear: false,
+ shouldShowYearSubmitted: false,
keyForList: '5345995386715609966',
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
diff --git a/tests/unit/TransactionGroupListItemTest.tsx b/tests/unit/TransactionGroupListItemTest.tsx
index 05cc0ab57376..6e951836b48b 100644
--- a/tests/unit/TransactionGroupListItemTest.tsx
+++ b/tests/unit/TransactionGroupListItemTest.tsx
@@ -30,6 +30,7 @@ const mockTransaction: TransactionListItemType = {
groupAmount: 1284,
groupCurrency: 'USD',
created: '2025-09-19',
+ submitted: '2025-09-19',
currency: 'USD',
policy: {
id: '06F34677820A4D07',
@@ -64,6 +65,7 @@ const mockTransaction: TransactionListItemType = {
date: '2025-09-07',
shouldShowMerchant: true,
shouldShowYear: true,
+ shouldShowYearSubmitted: false,
keyForList: '1',
isAmountColumnWide: false,
isTaxAmountColumnWide: false,
@@ -88,6 +90,7 @@ const mockReport: TransactionReportGroupListItemType = {
chatReportID: '4735435600700077',
chatType: undefined,
created: '2025-09-19 20:00:47',
+ submitted: '2025-09-19',
currency: 'USD',
isOneTransactionReport: true,
isOwnPolicyExpenseChat: false,
@@ -117,6 +120,7 @@ const mockReport: TransactionReportGroupListItemType = {
displayName: 'Main Applause QA',
},
shouldShowYear: false,
+ shouldShowYearSubmitted: false,
action: 'view',
transactions: [],
groupedBy: 'expense-report',