Skip to content
Merged
29 changes: 14 additions & 15 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@ import {MouseProvider} from '@hooks/useMouseContext';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import blurActiveElement from '@libs/Accessibility/blurActiveElement';
//
import {
adjustRemainingSplitShares,
resetSplitShares,
setCustomUnitRateID,
setIndividualShare,
setMoneyRequestAmount,
setMoneyRequestCategory,
setMoneyRequestMerchant,
setMoneyRequestPendingFields,
setMoneyRequestTag,
setMoneyRequestTaxAmount,
setMoneyRequestTaxRate,
setSplitShares,
} from '@libs/actions/IOU';
import {convertToBackendAmount, convertToDisplayString, convertToDisplayStringWithoutCurrency, getCurrencyDecimals} from '@libs/CurrencyUtils';
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
import {calculateAmount, insertTagIntoTransactionTagsString, isMovingTransactionFromTrackExpense as isMovingTransactionFromTrackExpenseUtil} from '@libs/IOUUtils';
Expand All @@ -36,20 +49,6 @@ import {
isMerchantMissing,
isScanRequest as isScanRequestUtil,
} from '@libs/TransactionUtils';
import {
adjustRemainingSplitShares,
resetSplitShares,
setCustomUnitRateID,
setIndividualShare,
setMoneyRequestAmount,
setMoneyRequestCategory,
setMoneyRequestMerchant,
setMoneyRequestPendingFields,
setMoneyRequestTag,
setMoneyRequestTaxAmount,
setMoneyRequestTaxRate,
setSplitShares,
} from '@userActions/IOU';
import {hasInvoicingDetails} from '@userActions/Policy/Policy';
import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
Expand Down
3 changes: 1 addition & 2 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,7 @@ const translations = {
assignTask: 'Assign task',
header: 'Quick action',
trackManual: 'Create expense',
trackScan: 'Create expense for receipt',
trackScan: 'Scan receipt',
trackDistance: 'Track distance',
noLongerHaveReportAccess: 'You no longer have access to your previous quick action destination. Pick a new one below.',
updateDestination: 'Update destination',
Expand Down Expand Up @@ -1078,7 +1078,6 @@ const translations = {
bookingArchivedDescription: 'This booking is archived because the trip date has passed. Add an expense for the final amount if needed.',
attendees: 'Attendees',
paymentComplete: 'Payment complete',
justTrackIt: 'Just track it (don’t submit it)',
time: 'Time',
startDate: 'Start date',
endDate: 'End date',
Expand Down
3 changes: 1 addition & 2 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ const translations = {
assignTask: 'Assignar tarea',
header: 'Acción rápida',
trackManual: 'Crear gasto',
trackScan: 'Crear gasto por recibo',
trackScan: 'Escanear recibo',
trackDistance: 'Crear gasto por desplazamiento',
noLongerHaveReportAccess: 'Ya no tienes acceso al destino previo de esta acción rápida. Escoge uno nuevo a continuación.',
updateDestination: 'Actualiza el destino',
Expand Down Expand Up @@ -1076,7 +1076,6 @@ const translations = {
bookingArchivedDescription: 'Esta reserva está archivada porque la fecha del viaje ha pasado. Agregue un gasto por el monto final si es necesario.',
attendees: 'Asistentes',
paymentComplete: 'Pago completo',
justTrackIt: 'Solo guardarlo (no enviarlo)',
time: 'Tiempo',
startDate: 'Fecha de inicio',
endDate: 'Fecha de finalización',
Expand Down
107 changes: 104 additions & 3 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ type GetOptionsConfig = {
includeRecentReports?: boolean;
includeSelectedOptions?: boolean;
recentAttendees?: Attendee[];
shouldSeparateWorkspaceChat?: boolean;
shouldSeparateSelfDMChat?: boolean;
} & GetValidReportsConfig;

type GetUserToInviteConfig = {
Expand Down Expand Up @@ -230,6 +232,8 @@ type Options = {
personalDetails: OptionData[];
userToInvite: OptionData | null;
currentUserOption: OptionData | null | undefined;
workspaceChats?: OptionData[];
selfDMChat?: OptionData | undefined;
};

type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: boolean; showPersonalDetails?: boolean};
Expand Down Expand Up @@ -257,7 +261,7 @@ type OrderReportOptionsConfig = {
preferRecentExpenseReports?: boolean;
};

type ReportAndPersonalDetailOptions = Pick<Options, 'recentReports' | 'personalDetails'>;
type ReportAndPersonalDetailOptions = Pick<Options, 'recentReports' | 'personalDetails' | 'workspaceChats'>;

/**
* OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can
Expand Down Expand Up @@ -1104,6 +1108,22 @@ function orderReportOptionsWithSearch(
);
}

function orderWorkspaceOptions(options: OptionData[]): OptionData[] {
return options.sort((a, b) => {
// Check if `a` is the default workspace
if (a.isPolicyExpenseChat && a.policyID === activePolicyID) {
return -1;
}

// Check if `b` is the default workspace
if (b.isPolicyExpenseChat && b.policyID === activePolicyID) {
return 1;
}

return 0;
});
}

function sortComparatorReportOptionByArchivedStatus(option: OptionData) {
return option.private_isArchived ? 1 : 0;
}
Expand Down Expand Up @@ -1131,10 +1151,12 @@ function orderOptions(options: ReportAndPersonalDetailOptions, searchValue?: str
orderedReportOptions = orderReportOptions(options.recentReports);
}
const orderedPersonalDetailsOptions = orderPersonalDetailsOptions(options.personalDetails);
const orderedWorkspaceChats = orderWorkspaceOptions(options?.workspaceChats ?? []);

return {
recentReports: orderedReportOptions,
personalDetails: orderedPersonalDetailsOptions,
workspaceChats: orderedWorkspaceChats,
};
}

Expand Down Expand Up @@ -1388,7 +1410,16 @@ function getValidReports(
*/
function getValidOptions(
options: OptionList,
{excludeLogins = [], includeSelectedOptions = false, includeRecentReports = true, recentAttendees, selectedOptions = [], ...config}: GetOptionsConfig = {},
{
excludeLogins = [],
includeSelectedOptions = false,
includeRecentReports = true,
recentAttendees,
selectedOptions = [],
shouldSeparateSelfDMChat = false,
shouldSeparateWorkspaceChat = false,
...config
}: GetOptionsConfig = {},
): Options {
// Gather shared configs:
const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}];
Expand Down Expand Up @@ -1445,13 +1476,31 @@ function getValidOptions(
}
}

let workspaceChats: OptionData[] = [];

if (shouldSeparateWorkspaceChat) {
workspaceChats = recentReportOptions.filter((option) => option.isOwnPolicyExpenseChat && !option.private_isArchived);
}

let selfDMChat: OptionData | undefined;

if (shouldSeparateWorkspaceChat) {
recentReportOptions = recentReportOptions.filter((option) => !option.isPolicyExpenseChat);
}
if (shouldSeparateSelfDMChat) {
selfDMChat = recentReportOptions.find((option) => option.isSelfDM);
recentReportOptions = recentReportOptions.filter((option) => !option.isSelfDM);
}

return {
personalDetails: personalDetailsOptions,
recentReports: recentReportOptions,
currentUserOption,
// User to invite is generated by the search input of a user.
// As this function isn't concerned with any search input yet, this is null (will be set when using filterOptions).
userToInvite: null,
workspaceChats,
selfDMChat,
};
}

Expand Down Expand Up @@ -1691,6 +1740,7 @@ function formatSectionsFromSearchTerm(
filteredPersonalDetails: OptionData[],
personalDetails: OnyxEntry<PersonalDetailsList> = {},
shouldGetOptionDetails = false,
filteredWorkspaceChats: OptionData[] = [],
): SectionForSearchTerm {
// We show the selected participants at the top of the list when there is no search term or maximum number of participants has already been selected
// However, if there is a search term we remove the selected participants from the top of the list unless they are part of the search results
Expand All @@ -1716,8 +1766,9 @@ function formatSectionsFromSearchTerm(
const selectedParticipantsWithoutDetails = selectedOptions.filter((participant) => {
const accountID = participant.accountID ?? null;
const isPartOfSearchTerm = getPersonalDetailSearchTerms(participant).join(' ').toLowerCase().includes(cleanSearchTerm);
const isReportInRecentReports = filteredRecentReports.some((report) => report.accountID === accountID);
const isReportInRecentReports = filteredRecentReports.some((report) => report.accountID === accountID) || filteredWorkspaceChats.some((report) => report.accountID === accountID);
const isReportInPersonalDetails = filteredPersonalDetails.some((personalDetail) => personalDetail.accountID === accountID);

return isPartOfSearchTerm && !isReportInRecentReports && !isReportInPersonalDetails;
});

Expand Down Expand Up @@ -1803,6 +1854,23 @@ function filterReports(reports: OptionData[], searchTerms: string[]): OptionData
return filteredReports;
}

function filterWorkspaceChats(reports: OptionData[], searchTerms: string[]): OptionData[] {
const filteredReports = searchTerms.reduceRight(
(items, term) =>
filterArrayByMatch(items, term, (item) => {
const values: string[] = [];
if (item.text) {
values.push(item.text);
}
return uniqFast(values);
}),
// We start from all unfiltered reports:
reports,
);

return filteredReports;
}

function filterPersonalDetails(personalDetails: OptionData[], searchTerms: string[]): OptionData[] {
return searchTerms.reduceRight(
(items, term) =>
Expand Down Expand Up @@ -1854,6 +1922,34 @@ function filterUserToInvite(options: Omit<Options, 'userToInvite'>, searchValue:
});
}

function filterSelfDMChat(report: OptionData, searchTerms: string[]): OptionData | undefined {
const isMatch = searchTerms.every((term) => {
const values: string[] = [];

if (report.text) {
values.push(report.text);
}
if (report.login) {
values.push(report.login);
values.push(report.login.replace(CONST.EMAIL_SEARCH_REGEX, ''));
}
if (report.isThread) {
if (report.alternateText) {
values.push(report.alternateText);
}
} else if (!!report.isChatRoom || !!report.isPolicyExpenseChat) {
if (report.subtitle) {
values.push(report.subtitle);
}
}

// Remove duplicate values and check if the term matches any value
return uniqFast(values).some((value) => value.includes(term));
});

return isMatch ? report : undefined;
}

function filterOptions(options: Options, searchInputValue: string, config?: FilterUserToInviteConfig): Options {
const parsedPhoneNumber = parsePhoneNumber(appendCountryCode(Str.removeSMSDomain(searchInputValue)));
const searchValue = parsedPhoneNumber.possible && parsedPhoneNumber.number?.e164 ? parsedPhoneNumber.number.e164 : searchInputValue.toLowerCase();
Expand All @@ -1871,12 +1967,17 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
searchValue,
config,
);
const workspaceChats = filterWorkspaceChats(options.workspaceChats ?? [], searchTerms);

const selfDMChat = options.selfDMChat ? filterSelfDMChat(options.selfDMChat, searchTerms) : undefined;

return {
personalDetails,
recentReports,
userToInvite,
currentUserOption,
workspaceChats,
selfDMChat,
};
}

Expand Down
Loading