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

Allow Viewing/Editing a category on a Money Request #27459

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
32d1355
delegate category selection to parent
rezkiy37 Sep 14, 2023
d799686
implement edit request category page
rezkiy37 Sep 14, 2023
da95354
preview and navigate to edit a category
rezkiy37 Sep 14, 2023
bfc59ba
prepare api
rezkiy37 Sep 14, 2023
ca8edb1
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 15, 2023
05f020c
do not stop smart scan
rezkiy37 Sep 15, 2023
636924f
handle a selected category alone
rezkiy37 Sep 15, 2023
e13de7f
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 18, 2023
812ea05
do not use modified category
rezkiy37 Sep 18, 2023
3dd7b5c
create getRootParentReport helper
rezkiy37 Sep 18, 2023
d7b6b89
improve conditions of MoneyRequestConfirmationList
rezkiy37 Sep 18, 2023
ddb21c8
improve conditions of MoneyRequestView
rezkiy37 Sep 18, 2023
f8b84a9
improve options helpers
rezkiy37 Sep 18, 2023
b12a93e
check enabled categories
rezkiy37 Sep 18, 2023
9c1815d
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 18, 2023
7a643bf
fix billable condition
rezkiy37 Sep 18, 2023
a4c07d8
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 19, 2023
d9dfaa7
sort prop types
rezkiy37 Sep 19, 2023
4f875ea
simplify helpers
rezkiy37 Sep 19, 2023
d962ce1
create a handler
rezkiy37 Sep 19, 2023
72bd7e8
reuse iuo props
rezkiy37 Sep 19, 2023
37d6770
clarify props
rezkiy37 Sep 19, 2023
3353869
clarify return type
rezkiy37 Sep 19, 2023
70b44d8
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 20, 2023
abd2d25
re-test
rezkiy37 Sep 20, 2023
8912f26
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 20, 2023
5cdc7fb
clarify comment
rezkiy37 Sep 20, 2023
ce2907d
perform conditions
rezkiy37 Sep 20, 2023
96ad0fc
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 21, 2023
d42958e
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 21, 2023
8753fa3
update name of a method
rezkiy37 Sep 21, 2023
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
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,7 @@ const CONST = {
DATE: 'date',
DESCRIPTION: 'description',
MERCHANT: 'merchant',
CATEGORY: 'category',
},
FOOTER: {
EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`,
Expand Down
16 changes: 5 additions & 11 deletions src/components/CategoryPicker/categoryPickerPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import PropTypes from 'prop-types';
import categoryPropTypes from '../categoryPropTypes';

const propTypes = {
/** The report ID of the IOU */
reportID: PropTypes.string.isRequired,

/** The policyID we are getting categories for */
policyID: PropTypes.string,

/** The type of IOU report, i.e. bill, request, send */
iouType: PropTypes.string.isRequired,
/** The selected category of an expense */
selectedCategory: PropTypes.string,

/* Onyx Props */
/** Collection of categories attached to a policy */
Expand All @@ -19,18 +16,15 @@ const propTypes = {
/** Collection of recently used categories attached to a policy */
policyRecentlyUsedCategories: PropTypes.arrayOf(PropTypes.string),

/* Onyx Props */
/** Holds data related to Money Request view state, rather than the underlying Money Request data. */
iou: PropTypes.shape({
category: PropTypes.string.isRequired,
}),
/** Callback to fire when a category is pressed */
onSubmit: PropTypes.func.isRequired,
};

const defaultProps = {
policyID: '',
selectedCategory: '',
policyCategories: {},
policyRecentlyUsedCategories: [],
iou: {},
};

export {propTypes, defaultProps};
30 changes: 5 additions & 25 deletions src/components/CategoryPicker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,31 @@ import lodashGet from 'lodash/get';
import ONYXKEYS from '../../ONYXKEYS';
import {propTypes, defaultProps} from './categoryPickerPropTypes';
import styles from '../../styles/styles';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
import CONST from '../../CONST';
import * as IOU from '../../libs/actions/IOU';
import * as OptionsListUtils from '../../libs/OptionsListUtils';
import OptionsSelector from '../OptionsSelector';
import useLocalize from '../../hooks/useLocalize';

function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentlyUsedCategories}) {
function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}) {
const {translate} = useLocalize();
const [searchValue, setSearchValue] = useState('');

const policyCategoriesCount = _.size(policyCategories);
const isCategoriesCountBelowThreshold = policyCategoriesCount < CONST.CATEGORY_LIST_THRESHOLD;

const selectedOptions = useMemo(() => {
if (!iou.category) {
if (!selectedCategory) {
return [];
}

return [
{
name: iou.category,
name: selectedCategory,
enabled: true,
accountID: null,
},
];
}, [iou.category]);
}, [selectedCategory]);

const initialFocusedIndex = useMemo(() => {
if (isCategoriesCountBelowThreshold && selectedOptions.length > 0) {
Expand All @@ -53,20 +50,6 @@ function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentl
const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, searchValue);
const shouldShowTextInput = !isCategoriesCountBelowThreshold;

const navigateBack = () => {
Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID));
};

const updateCategory = (category) => {
if (category.searchText === iou.category) {
IOU.resetMoneyRequestCategory();
} else {
IOU.setMoneyRequestCategory(category.searchText);
}

navigateBack();
};

return (
<OptionsSelector
optionHoveredStyle={styles.hoveredComponentBG}
Expand All @@ -81,7 +64,7 @@ function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentl
highlightSelectedOptions
isRowMultilineSupported
onChangeText={setSearchValue}
onSelectRow={updateCategory}
onSelectRow={onSubmit}
/>
);
}
Expand All @@ -97,7 +80,4 @@ export default withOnyx({
policyRecentlyUsedCategories: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`,
},
iou: {
key: ONYXKEYS.IOU,
},
})(CategoryPicker);
26 changes: 25 additions & 1 deletion src/components/ReportActionItem/MoneyRequestView.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import reportPropTypes from '../../pages/reportPropTypes';
Expand All @@ -27,6 +28,7 @@ import Image from '../Image';
import ReportActionItemImage from './ReportActionItemImage';
import * as TransactionUtils from '../../libs/TransactionUtils';
import OfflineWithFeedback from '../OfflineWithFeedback';
import categoryPropTypes from '../categoryPropTypes';

const propTypes = {
/** The report currently being looked at */
Expand All @@ -35,6 +37,10 @@ const propTypes = {
/** The expense report or iou report (only will have a value if this is a transaction thread) */
parentReport: iouReportPropTypes,

/* Onyx Props */
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
/** Collection of categories attached to a policy */
policyCategories: PropTypes.objectOf(categoryPropTypes),

/** The transaction associated with the transactionThread */
transaction: transactionPropTypes,

Expand All @@ -46,14 +52,15 @@ const propTypes = {

const defaultProps = {
parentReport: {},
policyCategories: {},
transaction: {
amount: 0,
currency: CONST.CURRENCY.USD,
comment: {comment: ''},
},
};

function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, transaction}) {
function MoneyRequestView({report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) {
const {isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();

Expand All @@ -65,13 +72,15 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans
currency: transactionCurrency,
comment: transactionDescription,
merchant: transactionMerchant,
category: transactionCategory,
} = ReportUtils.getTransactionDetails(transaction);
const isEmptyMerchant =
transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
const formattedTransactionAmount = transactionAmount && transactionCurrency && CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency);

const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction);
const shouldShowCategory = !_.isEmpty(policyCategories) || !_.isEmpty(transactionCategory);

let description = `${translate('iou.amount')} • ${translate('iou.cash')}`;
if (isSettled) {
Expand Down Expand Up @@ -165,6 +174,18 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans
subtitleTextStyle={styles.textLabelError}
/>
</OfflineWithFeedback>
{shouldShowCategory && (
<OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.category') || lodashGet(transaction, 'pendingAction')}>
<MenuItemWithTopDescription
description={translate('common.category')}
title={transactionCategory}
interactive={canEdit}
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.getEditRequestRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))}
/>
</OfflineWithFeedback>
)}
{shouldShowHorizontalRule && <View style={styles.reportHorizontalRule} />}
</View>
);
Expand All @@ -183,6 +204,9 @@ export default compose(
policy: {
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`,
},
policyCategories: {
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report.policyID}`,
},
session: {
key: ONYXKEYS.SESSION,
},
Expand Down
12 changes: 12 additions & 0 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,18 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt
const numberOfCategories = _.size(categoriesValues);
let indexOffset = 0;

if (numberOfCategories === 0 && selectedOptions.length > 0) {
categorySections.push({
// "Selected" section
title: '',
shouldShow: false,
indexOffset,
data: getCategoryOptionTree(selectedOptions, true),
});

return categorySections;
}

if (!_.isEmpty(searchInputValue)) {
const searchCategories = _.filter(categoriesValues, (category) => category.name.toLowerCase().includes(searchInputValue.toLowerCase()));

Expand Down
14 changes: 13 additions & 1 deletion src/libs/TransactionUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep
shouldStopSmartscan = true;
}

if (_.has(transactionChanges, 'category')) {
updatedTransaction.modifiedCategory = transactionChanges.category;
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
shouldStopSmartscan = false;
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
}

if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) {
updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN;
}
Expand All @@ -151,6 +156,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep
...(_.has(transactionChanges, 'amount') && {amount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(_.has(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(_.has(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(_.has(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
};

return updatedTransaction;
Expand Down Expand Up @@ -232,12 +238,18 @@ function getMerchant(transaction) {
}

/**
* Return the category from the transaction. This "category" field has no "modified" complement.
* Return the category from the transaction, return the modifiedCategory if present.
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
*
* @param {Object} transaction
* @return {String}
*/
function getCategory(transaction) {
const modifiedCategory = lodashGet(transaction, 'modifiedCategory', null);
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved

if (!_.isNull(modifiedCategory)) {
return modifiedCategory;
}

return lodashGet(transaction, 'category', '');
}

Expand Down
13 changes: 13 additions & 0 deletions src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
created: null,
currency: null,
merchant: null,
category: null,
},
},
},
Expand Down Expand Up @@ -1157,6 +1158,18 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
},
];

// STEP 5: Use the modifiedCategory as a category on success
if (!_.isUndefined(updatedTransaction.modifiedCategory)) {
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
value: {
category: updatedTransaction.modifiedCategory,
modifiedCategory: null,
},
});
}

// STEP 6: Call the API endpoint
const {created, amount, currency, comment, merchant, category} = ReportUtils.getTransactionDetails(updatedTransaction);
API.write(
Expand Down
49 changes: 49 additions & 0 deletions src/pages/EditRequestCategoryPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import ScreenWrapper from '../components/ScreenWrapper';
import HeaderWithBackButton from '../components/HeaderWithBackButton';
import Navigation from '../libs/Navigation/Navigation';
import useLocalize from '../hooks/useLocalize';
import CategoryPicker from '../components/CategoryPicker';

const propTypes = {
/** Transaction default category value */
defaultCategory: PropTypes.string.isRequired,

/** The policyID we are getting categories for */
policyID: PropTypes.string.isRequired,

/** Callback to fire when the Save button is pressed */
onSubmit: PropTypes.func.isRequired,
};

function EditRequestCategoryPage({defaultCategory, policyID, onSubmit}) {
const {translate} = useLocalize();

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
>
<HeaderWithBackButton
title={translate('common.category')}
onBackButtonPress={Navigation.goBack}
/>

<CategoryPicker
selectedCategory={defaultCategory}
policyID={policyID}
onSubmit={(category) =>
onSubmit({
category: category.searchText,
})
}
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
/>
</ScreenWrapper>
);
}

EditRequestCategoryPage.propTypes = propTypes;
EditRequestCategoryPage.displayName = 'EditRequestCategoryPage';

export default EditRequestCategoryPage;
26 changes: 25 additions & 1 deletion src/pages/EditRequestPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import reportPropTypes from './reportPropTypes';
import * as IOU from '../libs/actions/IOU';
import * as CurrencyUtils from '../libs/CurrencyUtils';
import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView';
import EditRequestCategoryPage from './EditRequestCategoryPage';

const propTypes = {
/** Route from navigation */
Expand Down Expand Up @@ -69,7 +70,13 @@ const defaultProps = {
function EditRequestPage({report, route, parentReport, policy, session}) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);
const transaction = TransactionUtils.getLinkedTransaction(parentReportAction);
const {amount: transactionAmount, currency: transactionCurrency, comment: transactionDescription, merchant: transactionMerchant} = ReportUtils.getTransactionDetails(transaction);
const {
amount: transactionAmount,
currency: transactionCurrency,
comment: transactionDescription,
merchant: transactionMerchant,
category: transactionCategory,
} = ReportUtils.getTransactionDetails(transaction);

const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency;

Expand Down Expand Up @@ -171,6 +178,23 @@ function EditRequestPage({report, route, parentReport, policy, session}) {
);
}

if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.CATEGORY) {
return (
<EditRequestCategoryPage
defaultCategory={transactionCategory}
policyID={lodashGet(report, 'policyID', '')}
onSubmit={(transactionChanges) => {
let updatedCategory = transactionChanges.category;
// In case the same category has been selected, do reset of the category.
if (transactionCategory === updatedCategory) {
updatedCategory = '';
}
editMoneyRequest({category: updatedCategory});
}}
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
/>
);
}

return <FullPageNotFoundView shouldShow />;
}

Expand Down
Loading
Loading