Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweaks to the expense flow #55016

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
Loading