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

fix implement recently used currencies #41834

Merged
merged 31 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2207db4
fix implement recently used currencies
DylanDylann May 8, 2024
0309bbf
fix lint
DylanDylann May 9, 2024
6a1b428
fix merge main
DylanDylann Jun 12, 2024
cfa6401
fix handle p2p request
DylanDylann Jun 12, 2024
fb70418
fix merge main
DylanDylann Jun 18, 2024
4ea13c5
fix type
DylanDylann Jun 18, 2024
8c153dc
fix merge main
DylanDylann Jun 21, 2024
f2e4293
merge main
DylanDylann Jul 17, 2024
2326651
fix fallback to personal policy
DylanDylann Jul 17, 2024
bd0d2bc
fix lint
DylanDylann Jul 17, 2024
e2b4a79
merge main and fix conflicts
DylanDylann Jul 30, 2024
4e0eb11
fix: only display 5 recently used options
DylanDylann Jul 30, 2024
4463701
merge main
DylanDylann Aug 5, 2024
dd91aa3
feat: move selected option to top
DylanDylann Aug 5, 2024
f89aff2
fix lint
DylanDylann Aug 5, 2024
1aefe81
fix conflict
DylanDylann Aug 12, 2024
7d35e6a
fix comment
DylanDylann Aug 12, 2024
b081a80
fix lint
DylanDylann Aug 12, 2024
63584df
fix lint
DylanDylann Aug 12, 2024
d582c63
resolve conflicts
DylanDylann Aug 20, 2024
f02e36a
fix lint
DylanDylann Aug 20, 2024
4db1b2c
merge main
DylanDylann Aug 27, 2024
59aef43
merge main
DylanDylann Sep 5, 2024
3077b27
use nvp_recentlyUsedCurrencies for all
DylanDylann Sep 5, 2024
2125f71
fix type
DylanDylann Sep 5, 2024
381d5b9
fix: use Array.isArray check
DylanDylann Sep 9, 2024
c4bc961
fix rename buildOptimisticRecentlyUsedCurrencies function
DylanDylann Sep 10, 2024
cdeafbd
fix rename variable
DylanDylann Sep 10, 2024
6acba93
fix rename variable
DylanDylann Sep 11, 2024
d9d65fa
fix update buildOptimisticRecentlyUsedCurrencies
DylanDylann Sep 12, 2024
1f7939c
fix lint
DylanDylann Sep 12, 2024
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
5 changes: 4 additions & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ const ONYXKEYS = {
/** Stores the route to open after changing app permission from settings */
LAST_ROUTE: 'lastRoute',

/** Stores recently used currencies */
RECENTLY_USED_CURRENCIES: 'nvp_recentlyUsedCurrencies',

/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
Expand Down Expand Up @@ -797,7 +800,7 @@ type OnyxValuesMapping = {

// ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data
[ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot;

[ONYXKEYS.RECENTLY_USED_CURRENCIES]: string[];
[ONYXKEYS.ACTIVE_CLIENTS]: string[];
[ONYXKEYS.DEVICE_ID]: string;
[ONYXKEYS.IS_SIDEBAR_LOADED]: boolean;
Expand Down
70 changes: 57 additions & 13 deletions src/components/CurrencySelectionList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import {Str} from 'expensify-common';
import React, {useMemo, useState} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import {withOnyx} from 'react-native-onyx';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import SelectableListItem from '@components/SelectionList/SelectableListItem';
import useLocalize from '@hooks/useLocalize';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {CurrencyListItem, CurrencySelectionListOnyxProps, CurrencySelectionListProps} from './types';

function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode, onSelect, currencyList, selectedCurrencies = [], canSelectMultiple = false}: CurrencySelectionListProps) {
function CurrencySelectionList({
searchInputLabel,
initiallySelectedCurrencyCode,
onSelect,
currencyList,
selectedCurrencies = [],
canSelectMultiple = false,
recentlyUsedCurrencies,
}: CurrencySelectionListProps) {
const [searchValue, setSearchValue] = useState('');
const {translate} = useLocalize();

const getUnselectedOptions = useCallback((options: CurrencyListItem[]) => options.filter((option) => !option.isSelected), []);
const {sections, headerMessage} = useMemo(() => {
const currencyOptions: CurrencyListItem[] = Object.entries(currencyList ?? {}).reduce((acc, [currencyCode, currencyInfo]) => {
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode || selectedCurrencies.includes(currencyCode);
Expand All @@ -28,23 +38,57 @@ function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode,
return acc;
}, [] as CurrencyListItem[]);

const policyRecentlyUsedCurrencyOptions: CurrencyListItem[] = Array.isArray(recentlyUsedCurrencies)
DylanDylann marked this conversation as resolved.
Show resolved Hide resolved
? recentlyUsedCurrencies?.map((currencyCode) => {
const currencyInfo = currencyList?.[currencyCode];
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode;
return {
currencyName: currencyInfo?.name ?? '',
text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`,
currencyCode,
keyForList: currencyCode,
isSelected: isSelectedCurrency,
};
})
: [];

const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i');
const filteredCurrencies = currencyOptions.filter((currencyOption) => searchRegex.test(currencyOption.text ?? '') || searchRegex.test(currencyOption.currencyName));
const isEmpty = searchValue.trim() && !filteredCurrencies.length;
const shouldDisplayRecentlyOptions = !isEmptyObject(policyRecentlyUsedCurrencyOptions) && !searchValue;
const selectedOptions = filteredCurrencies.filter((option) => option.isSelected);
const shouldDisplaySelectedOptionOnTop = selectedOptions.length > 0;
const unselectedOptions = getUnselectedOptions(filteredCurrencies);
const result = [];

let computedSections: Array<{data: CurrencyListItem[]}> = [];
if (shouldDisplaySelectedOptionOnTop) {
result.push({
title: '',
data: selectedOptions,
shouldShow: true,
});
}

if (!isEmpty) {
computedSections = canSelectMultiple
? [{data: filteredCurrencies.filter((currency) => currency.isSelected)}, {data: filteredCurrencies.filter((currency) => !currency.isSelected)}]
: [{data: filteredCurrencies}];
if (shouldDisplayRecentlyOptions) {
const cutPolicyRecentlyUsedCurrencyOptions = policyRecentlyUsedCurrencyOptions.slice(0, CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can remove this - this is limited in the BE and will only grow to 5

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we still need this logic in case of offline mode.

Copy link
Contributor

Choose a reason for hiding this comment

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

If that's the case, we should handle that in buildOptimisticRecentlyUsedCurrencies so we don't have to keep this logic so anywhere that uses / shows the recently used currency option can just use it directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated PR

if (!isEmpty) {
result.push(
{
title: translate('common.recents'),
data: shouldDisplaySelectedOptionOnTop ? getUnselectedOptions(cutPolicyRecentlyUsedCurrencyOptions) : cutPolicyRecentlyUsedCurrencyOptions,
shouldShow: shouldDisplayRecentlyOptions,
},
{title: translate('common.all'), data: shouldDisplayRecentlyOptions ? unselectedOptions : filteredCurrencies},
);
}
} else if (!isEmpty) {
result.push({
data: shouldDisplaySelectedOptionOnTop ? unselectedOptions : filteredCurrencies,
});
}

return {
sections: computedSections,
headerMessage: isEmpty ? translate('common.noResultsFound') : '',
};
}, [currencyList, searchValue, canSelectMultiple, translate, initiallySelectedCurrencyCode, selectedCurrencies]);
return {sections: result, headerMessage: isEmpty ? translate('common.noResultsFound') : ''};
}, [currencyList, searchValue, translate, initiallySelectedCurrencyCode, selectedCurrencies, getUnselectedOptions, recentlyUsedCurrencies]);

return (
<SelectionList
Expand Down
3 changes: 3 additions & 0 deletions src/components/CurrencySelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ type CurrencySelectionListProps = CurrencySelectionListOnyxProps & {
/** Currency item to be selected initially */
initiallySelectedCurrencyCode?: string;

/** List of recently used currencies */
recentlyUsedCurrencies?: string[];

/** Callback to fire when a currency is selected */
onSelect: (item: CurrencyListItem) => void;

Expand Down
62 changes: 62 additions & 0 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ function buildOnyxDataForMoneyRequest(
optimisticNextStep?: OnyxTypes.ReportNextStep | null,
isOneOnOneSplit = false,
existingTransactionThreadReportID?: string,
optimisticPolicyRecentlyUsedCurrencies?: string[],
): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] {
const isScanRequest = TransactionUtils.isScanRequest(transaction);
const outstandingChildRequest = ReportUtils.getOutstandingChildRequest(iouReport);
Expand Down Expand Up @@ -634,6 +635,14 @@ function buildOnyxDataForMoneyRequest(
});
}

if (optimisticPolicyRecentlyUsedCurrencies?.length) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
value: optimisticPolicyRecentlyUsedCurrencies,
});
}

if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -912,6 +921,7 @@ function buildOnyxDataForInvoice(
policy?: OnyxEntry<OnyxTypes.Policy>,
policyTagList?: OnyxEntry<OnyxTypes.PolicyTagLists>,
policyCategories?: OnyxEntry<OnyxTypes.PolicyCategories>,
optimisticPolicyRecentlyUsedCurrencies?: string[],
companyName?: string,
companyWebsite?: string,
): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] {
Expand Down Expand Up @@ -1002,6 +1012,14 @@ function buildOnyxDataForInvoice(
});
}

if (optimisticPolicyRecentlyUsedCurrencies?.length) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
value: optimisticPolicyRecentlyUsedCurrencies,
});
}

if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -1890,6 +1908,7 @@ function getSendInvoiceInformation(

const optimisticPolicyRecentlyUsedCategories = Category.buildOptimisticPolicyRecentlyUsedCategories(optimisticInvoiceReport.policyID, category);
const optimisticPolicyRecentlyUsedTags = Tag.buildOptimisticPolicyRecentlyUsedTags(optimisticInvoiceReport.policyID, tag);
const optimisticPolicyRecentlyUsedCurrencies = Policy.buildOptimisticPolicyRecentlyUsedCurrencies(currency);

// STEP 4: Add optimistic personal details for participant
const shouldCreateOptimisticPersonalDetails = isNewChatReport && !allPersonalDetails[receiverAccountID];
Expand Down Expand Up @@ -1943,6 +1962,7 @@ function getSendInvoiceInformation(
policy,
policyTagList,
policyCategories,
optimisticPolicyRecentlyUsedCurrencies,
companyName,
companyWebsite,
);
Expand Down Expand Up @@ -2069,6 +2089,7 @@ function getMoneyRequestInformation(

const optimisticPolicyRecentlyUsedCategories = Category.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, category);
const optimisticPolicyRecentlyUsedTags = Tag.buildOptimisticPolicyRecentlyUsedTags(iouReport.policyID, tag);
const optimisticPolicyRecentluUsedCurrencies = Policy.buildOptimisticPolicyRecentlyUsedCurrencies(currency);

// If there is an existing transaction (which is the case for distance requests), then the data from the existing transaction
// needs to be manually merged into the optimistic transaction. This is because buildOnyxDataForMoneyRequest() uses `Onyx.set()` for the transaction
Expand Down Expand Up @@ -2155,6 +2176,9 @@ function getMoneyRequestInformation(
policyTagList,
policyCategories,
optimisticNextStep,
undefined,
undefined,
optimisticPolicyRecentluUsedCurrencies,
);

return {
Expand Down Expand Up @@ -2660,6 +2684,18 @@ function getUpdateMoneyRequestParams(
}
}

// Update recently used currencies if the category is changed
DylanDylann marked this conversation as resolved.
Show resolved Hide resolved
if ('currency' in transactionChanges) {
const optimisticPolicyRecentlyUsedCurrencies = Policy.buildOptimisticPolicyRecentlyUsedCurrencies(transactionChanges.currency);
if (optimisticPolicyRecentlyUsedCurrencies.length) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
value: optimisticPolicyRecentlyUsedCurrencies,
});
}
}

// Update recently used categories if the tag is changed
if ('tag' in transactionChanges) {
const optimisticPolicyRecentlyUsedTags = Tag.buildOptimisticPolicyRecentlyUsedTags(iouReport?.policyID, transactionChanges.tag);
Expand Down Expand Up @@ -4217,6 +4253,9 @@ function createSplitsAndOnyxData(
// Add category to optimistic policy recently used categories when a participant is a workspace
const optimisticPolicyRecentlyUsedCategories = isPolicyExpenseChat ? Category.buildOptimisticPolicyRecentlyUsedCategories(participant.policyID, category) : [];

// Add currency to optimistic policy recently used currencies when a participant is a workspace
const optimisticPolicyRecentlyUsedCurrencies = isPolicyExpenseChat ? Policy.buildOptimisticPolicyRecentlyUsedCurrencies(currency) : [];
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need this condition? and i think it's better to rename the function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are correct, I removed that condition and renamed the function

Copy link
Contributor

Choose a reason for hiding this comment

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

thanks!


// Add tag to optimistic policy recently used tags when a participant is a workspace
const optimisticPolicyRecentlyUsedTags = isPolicyExpenseChat ? Tag.buildOptimisticPolicyRecentlyUsedTags(participant.policyID, tag) : {};

Expand All @@ -4241,6 +4280,8 @@ function createSplitsAndOnyxData(
null,
null,
true,
undefined,
optimisticPolicyRecentlyUsedCurrencies,
);

const individualSplit = {
Expand Down Expand Up @@ -4707,6 +4748,7 @@ function startSplitBill({

const optimisticPolicyRecentlyUsedCategories = Category.buildOptimisticPolicyRecentlyUsedCategories(participant.policyID, category);
const optimisticPolicyRecentlyUsedTags = Tag.buildOptimisticPolicyRecentlyUsedTags(participant.policyID, tag);
const optimisticPolicyRecentlyUsedCurrencies = Policy.buildOptimisticPolicyRecentlyUsedCurrencies(currency);

if (optimisticPolicyRecentlyUsedCategories.length > 0) {
optimisticData.push({
Expand All @@ -4716,6 +4758,14 @@ function startSplitBill({
});
}

if (optimisticPolicyRecentlyUsedCurrencies.length > 0) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
value: optimisticPolicyRecentlyUsedCurrencies,
});
}

if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -5300,6 +5350,18 @@ function editRegularMoneyRequest(
}
}

// Update recently used currencies if the category is changed
DylanDylann marked this conversation as resolved.
Show resolved Hide resolved
if ('currency' in transactionChanges) {
const optimisticPolicyRecentlyUsedCurrencies = Policy.buildOptimisticPolicyRecentlyUsedCurrencies(transactionChanges.currency);
if (optimisticPolicyRecentlyUsedCurrencies.length) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
value: optimisticPolicyRecentlyUsedCurrencies,
});
}
}

// Update recently used categories if the tag is changed
if ('tag' in transactionChanges) {
const optimisticPolicyRecentlyUsedTags = Tag.buildOptimisticPolicyRecentlyUsedTags(iouReport?.policyID, transactionChanges.tag);
Expand Down
16 changes: 16 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {PUBLIC_DOMAINS, Str} from 'expensify-common';
import {escapeRegExp} from 'lodash';
import lodashClone from 'lodash/clone';
import lodashUnion from 'lodash/union';
import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
Expand Down Expand Up @@ -181,6 +182,12 @@ Onyx.connect({
callback: (val) => (reimbursementAccount = val),
});

let allRecentlyUsedCurrencies: string[];
Onyx.connect({
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
callback: (val) => (allRecentlyUsedCurrencies = val ?? []),
});

/**
* Stores in Onyx the policy ID of the last workspace that was accessed by the user
*/
Expand Down Expand Up @@ -2207,6 +2214,14 @@ function dismissAddedWithPrimaryLoginMessages(policyID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {primaryLoginsInvited: null});
}

function buildOptimisticPolicyRecentlyUsedCurrencies(currency?: string) {
if (!currency) {
return [];
}

return lodashUnion([currency], allRecentlyUsedCurrencies);
}

/**
* This flow is used for bottom up flow converting IOU report to an expense report. When user takes this action,
* we create a Collect type workspace when the person taking the action becomes an owner and an admin, while we
Expand Down Expand Up @@ -3984,6 +3999,7 @@ export {
dismissAddedWithPrimaryLoginMessages,
openDraftWorkspaceRequest,
createDraftInitialWorkspace,
buildOptimisticPolicyRecentlyUsedCurrencies,
setWorkspaceInviteMessageDraft,
setWorkspaceApprovalMode,
setWorkspaceAutoReportingFrequency,
Expand Down
7 changes: 7 additions & 0 deletions src/pages/iou/request/step/IOURequestStepCurrency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNo
type IOURequestStepCurrencyOnyxProps = {
/** The draft transaction object being modified in Onyx */
draftTransaction: OnyxEntry<Transaction>;
/** List of recently used currencies */
recentlyUsedCurrencies: OnyxEntry<string[]>;
};

type IOURequestStepCurrencyProps = IOURequestStepCurrencyOnyxProps & WithFullTransactionOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.STEP_CURRENCY>;
Expand All @@ -31,6 +33,7 @@ function IOURequestStepCurrency({
params: {backTo, iouType, pageIndex, reportID, transactionID, action, currency: selectedCurrency = ''},
},
draftTransaction,
recentlyUsedCurrencies,
}: IOURequestStepCurrencyProps) {
const {translate} = useLocalize();
const {currency: originalCurrency = ''} = ReportUtils.getTransactionDetails(draftTransaction) ?? {};
Expand Down Expand Up @@ -75,6 +78,7 @@ function IOURequestStepCurrency({
>
{({didScreenTransitionEnd}) => (
<CurrencySelectionList
recentlyUsedCurrencies={recentlyUsedCurrencies ?? []}
searchInputLabel={translate('common.search')}
onSelect={(option: CurrencyListItem) => {
if (!didScreenTransitionEnd) {
Expand All @@ -98,6 +102,9 @@ const IOURequestStepCurrencyWithOnyx = withOnyx<IOURequestStepCurrencyProps, IOU
return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`;
},
},
recentlyUsedCurrencies: {
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
},
})(IOURequestStepCurrency);

/* eslint-disable rulesdir/no-negated-variables */
Expand Down
Loading