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

Category list helpers #25777

Merged
merged 20 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2606,7 +2606,9 @@ const CONST = {
NAVIGATE: 'NAVIGATE',
},
},

INDENTS: ' ',
PARENT_CHILD_SEPARATOR: ': ',
CATEGORY_LIST_THRESHOLD: 8,
rezkiy37 marked this conversation as resolved.
Show resolved Hide resolved
DEMO_PAGES: {
SAASTR: 'SaaStrDemoSetup',
SBE: 'SbeDemoSetup',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ export default {
receipt: 'Receipt',
replace: 'Replace',
distance: 'Distance',
recent: 'Recent',
all: 'All',
},
anonymousReportFooter: {
logoTagline: 'Join the discussion.',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export default {
receipt: 'Recibo',
replace: 'Sustituir',
distance: 'Distancia',
recent: 'Reciente',
all: 'Todo',
},
anonymousReportFooter: {
logoTagline: 'Únete a la discusión.',
Expand Down
164 changes: 164 additions & 0 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,143 @@ function isCurrentUser(userDetails) {
return _.some(_.keys(loginList), (login) => login.toLowerCase() === userDetailsLogin.toLowerCase());
}

/**
* Build the options for the category tree hierarchy via indents
*
* @param {Object[]} options - an initial strings array
* @param {Boolean} options[].enabled - a flag to enable/disable option in a list
* @param {String} options[].name - a name of an option
* @param {Boolean} [isOneLine] - a flag to determine if text should be one line
* @returns {Array<Object>}
*/
function getCategoryOptionTree(options, isOneLine = false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming from #29281

This operator didn't order the sub-categories correctly

const optionCollection = {};

_.each(options, (option) => {
if (isOneLine) {
if (_.has(optionCollection, option.name)) {
return;
}

optionCollection[option.name] = {
text: option.name,
keyForList: option.name,
searchText: option.name,
tooltipText: option.name,
isDisabled: !option.enabled,
};

return;
}

option.name.split(CONST.PARENT_CHILD_SEPARATOR).forEach((optionName, index, array) => {
const indents = _.times(index, () => CONST.INDENTS).join('');
const isChild = array.length - 1 === index;

if (_.has(optionCollection, optionName)) {
return;
}

optionCollection[optionName] = {
text: `${indents}${optionName}`,
keyForList: optionName,
searchText: array.slice(0, index + 1).join(CONST.PARENT_CHILD_SEPARATOR),
tooltipText: optionName,
isDisabled: isChild ? !option.enabled : true,
Copy link
Contributor

Choose a reason for hiding this comment

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

@rezkiy37 I wonder why do we need to check isChild here? Can you help explain when you have a chance? Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, as you can see above there is a definition for isChild - = array.length - 1 === index. So for example there is an option parent:child. It automatically sets isDisabled as true for parent. However, it checks option.enabled for child. Because only child is an option that can be disabled or enabled. parent is an option that can be disabled with this example. To enable parent the app should have a specific parent option.

Copy link
Contributor

Choose a reason for hiding this comment

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

TY for your explanation. That makes sense!

Copy link
Contributor

Choose a reason for hiding this comment

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

(BugZero Checklist) Coming from #42936, We ended up modifying the isDisabled condion to have hover effect over selected parent category in the list

};
});
});

return _.values(optionCollection);
}

/**
* Build the section list for categories
*
* @param {Object[]} categories
* @param {String} categories[].name
* @param {Boolean} categories[].enabled
* @param {Object[]} recentlyUsedCategories
* @param {String} recentlyUsedCategories[].name
* @param {Boolean} recentlyUsedCategories[].enabled
* @param {Object[]} selectedOptions
* @param {String} selectedOptions[].name
* @param {String} searchInputValue
* @param {Number} maxRecentReportsToShow
* @returns {Array<Object>}
*/
function getCategoryListSections(categories, recentlyUsedCategories, selectedOptions, searchInputValue, maxRecentReportsToShow) {
const categorySections = [];
const numberOfCategories = _.size(categories);
let indexOffset = 0;

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

categorySections.push({
// "Search" section
title: '',
shouldShow: false,
indexOffset,
data: getCategoryOptionTree(searchCategories, true),
});

return categorySections;
}

if (numberOfCategories < CONST.CATEGORY_LIST_THRESHOLD) {
categorySections.push({
// "All" section when items amount less than the threshold
title: '',
shouldShow: false,
indexOffset,
data: getCategoryOptionTree(categories),
});

return categorySections;
}

const selectedOptionNames = _.map(selectedOptions, (selectedOption) => selectedOption.name);
const filteredRecentlyUsedCategories = _.filter(recentlyUsedCategories, (category) => !_.includes(selectedOptionNames, category.name));
const filteredCategories = _.filter(categories, (category) => !_.includes(selectedOptionNames, category.name));

if (!_.isEmpty(selectedOptions)) {
categorySections.push({
// "Selected" section
title: '',
shouldShow: false,
indexOffset,
data: getCategoryOptionTree(selectedOptions, true),
});

indexOffset += selectedOptions.length;
}

if (!_.isEmpty(filteredRecentlyUsedCategories)) {
const cutRecentlyUsedCategories = filteredRecentlyUsedCategories.slice(0, maxRecentReportsToShow);

categorySections.push({
// "Recent" section
title: Localize.translateLocal('common.recent'),
shouldShow: true,
indexOffset,
data: getCategoryOptionTree(cutRecentlyUsedCategories, true),
});

indexOffset += filteredRecentlyUsedCategories.length;
}

categorySections.push({
// "All" section when items amount more than the threshold
title: Localize.translateLocal('common.all'),
shouldShow: true,
indexOffset,
data: getCategoryOptionTree(filteredCategories),
});

return categorySections;
}

/**
* Build the options
*
Expand Down Expand Up @@ -620,15 +757,31 @@ function getOptions(
includeMoneyRequests = false,
excludeUnknownUsers = false,
includeP2P = true,
includeCategories = false,
categories = {},
recentlyUsedCategories = [],
canInviteUser = true,
},
) {
if (includeCategories) {
const categoryOptions = getCategoryListSections(_.values(categories), recentlyUsedCategories, selectedOptions, searchInputValue, maxRecentReportsToShow);

return {
recentReports: [],
personalDetails: [],
userToInvite: null,
currentUserOption: null,
categoryOptions,
};
}

if (!isPersonalDetailsReady(personalDetails)) {
return {
recentReports: [],
personalDetails: [],
userToInvite: null,
currentUserOption: null,
categoryOptions: [],
};
}

Expand Down Expand Up @@ -878,6 +1031,7 @@ function getOptions(
recentReports: recentReportOptions,
userToInvite: canInviteUser ? userToInvite : null,
currentUserOption,
categoryOptions: [],
};
}

Expand Down Expand Up @@ -959,6 +1113,9 @@ function getIOUConfirmationOptionsFromParticipants(participants, amountText) {
* @param {Array} [excludeLogins]
* @param {Boolean} [includeOwnedWorkspaceChats]
* @param {boolean} [includeP2P]
* @param {boolean} [includeCategories]
* @param {Object} [categories]
* @param {Array<Object>} [recentlyUsedCategories]
* @param {boolean} [canInviteUser]
* @returns {Object}
*/
Expand All @@ -971,6 +1128,9 @@ function getNewChatOptions(
excludeLogins = [],
includeOwnedWorkspaceChats = false,
includeP2P = true,
includeCategories = false,
categories = {},
recentlyUsedCategories = [],
canInviteUser = true,
) {
return getOptions(reports, personalDetails, {
Expand All @@ -983,6 +1143,9 @@ function getNewChatOptions(
excludeLogins,
includeOwnedWorkspaceChats,
includeP2P,
includeCategories,
categories,
recentlyUsedCategories,
canInviteUser,
});
}
Expand Down Expand Up @@ -1152,5 +1315,6 @@ export {
isSearchStringMatch,
shouldOptionShowTooltip,
getLastMessageTextForReport,
getCategoryOptionTree,
formatMemberForList,
};
3 changes: 3 additions & 0 deletions src/pages/tasks/TaskAssigneeSelectorModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ function TaskAssigneeSelectorModal(props) {
false,
true,
false,
{},
[],
false,
);

setHeaderMessage(OptionsListUtils.getHeaderMessage(recentReports?.length + personalDetails?.length !== 0 || currentUserOption, Boolean(userToInvite), searchValue));
Expand Down
Loading
Loading