From e132d564904308b14d2134bec9d54473d5960b86 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Mon, 23 Feb 2026 16:01:36 +0500 Subject: [PATCH 01/11] Multi-level categories with colons are shown with hierarchical indentation --- src/CONST/index.ts | 2 +- src/libs/CategoryOptionListUtils.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 0f882fbc2f12..5e51e125a861 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -6354,7 +6354,7 @@ const CONST = { PM: 'PM', }, INDENTS: ' ', - PARENT_CHILD_SEPARATOR: ': ', + PARENT_CHILD_SEPARATOR: ':', DISTANCE_MERCHANT_SEPARATOR: '@', COLON: ':', MAPBOX: { diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index 595d7ad8b9fa..dbb335017d18 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -124,7 +124,7 @@ function getCategoryListSections({ } if (numberOfEnabledCategories === 0 && selectedOptions.length > 0) { - const data = getCategoryOptionTree(selectedOptionsWithDisabledState, true); + const data = getCategoryOptionTree(selectedOptionsWithDisabledState, false); categorySections.push({ // "Selected" section title: '', @@ -143,7 +143,7 @@ function getCategoryListSections({ isSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name), })); - const data = getCategoryOptionTree(searchCategories, true); + const data = getCategoryOptionTree(searchCategories, false); categorySections.push({ // "Search" section title: '', @@ -155,7 +155,7 @@ function getCategoryListSections({ } if (selectedOptions.length > 0) { - const data = getCategoryOptionTree(selectedOptionsWithDisabledState, true); + const data = getCategoryOptionTree(selectedOptionsWithDisabledState, false); categorySections.push({ // "Selected" section title: '', @@ -192,7 +192,7 @@ function getCategoryListSections({ if (filteredRecentlyUsedCategories.length > 0) { const cutRecentlyUsedCategories = filteredRecentlyUsedCategories.slice(0, maxRecentReportsToShow); - const data = getCategoryOptionTree(cutRecentlyUsedCategories, true); + const data = getCategoryOptionTree(cutRecentlyUsedCategories, false); categorySections.push({ // "Recent" section title: translate('common.recent'), From e162cf1a34db2e39ed8535cfc5fcd96c62756705 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Sun, 8 Mar 2026 11:58:15 +0500 Subject: [PATCH 02/11] Multi-level categories - trim leaf --- src/libs/CategoryOptionListUtils.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index dbb335017d18..c5fe53588d91 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -70,13 +70,14 @@ function getCategoryOptionTree(options: Record | Category[], i if (optionCollection.has(searchText)) { continue; } - - const decodedCategoryName = getDecodedCategoryName(optionName); + const leafName = getDecodedCategoryName(optionName.trim()); + const decodedCategoryName = getDecodedCategoryName(option.name); + const tooltipText = isChild ? decodedCategoryName : getDecodedCategoryName(searchText); optionCollection.set(searchText, { - text: `${indents}${decodedCategoryName}`, + text: `${indents}${leafName}`, keyForList: searchText, searchText, - tooltipText: decodedCategoryName, + tooltipText, isDisabled: isChild ? !option.enabled || option.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : isParentOptionDisabled, isSelected: isChild ? !!option.isSelected : !!selectedParentOption, pendingAction: option.pendingAction, From 5fc69e92583c8142e4148e1989e70ea94998b6a9 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Sun, 8 Mar 2026 11:58:42 +0500 Subject: [PATCH 03/11] Multi-level categories - Updated tests --- tests/unit/CategoryOptionListUtilsTest.ts | 44 +++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/unit/CategoryOptionListUtilsTest.ts b/tests/unit/CategoryOptionListUtilsTest.ts index ec10284f052b..6b7e65e656f0 100644 --- a/tests/unit/CategoryOptionListUtilsTest.ts +++ b/tests/unit/CategoryOptionListUtilsTest.ts @@ -104,7 +104,7 @@ describe('CategoryOptionListUtils', () => { text: ' Meat', keyForList: 'Food: Meat', searchText: 'Food: Meat', - tooltipText: 'Meat', + tooltipText: 'Food: Meat', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -136,7 +136,7 @@ describe('CategoryOptionListUtils', () => { pendingAction: undefined, }, { - text: 'Food: Meat', + text: ' Meat', keyForList: 'Food: Meat', searchText: 'Food: Meat', tooltipText: 'Food: Meat', @@ -357,7 +357,7 @@ describe('CategoryOptionListUtils', () => { text: ' Audi', keyForList: 'Cars: Audi', searchText: 'Cars: Audi', - tooltipText: 'Audi', + tooltipText: 'Cars: Audi', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -366,7 +366,7 @@ describe('CategoryOptionListUtils', () => { text: ' Mercedes-Benz', keyForList: 'Cars: Mercedes-Benz', searchText: 'Cars: Mercedes-Benz', - tooltipText: 'Mercedes-Benz', + tooltipText: 'Cars: Mercedes-Benz', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -393,7 +393,7 @@ describe('CategoryOptionListUtils', () => { text: ' Meat', keyForList: 'Food: Meat', searchText: 'Food: Meat', - tooltipText: 'Meat', + tooltipText: 'Food: Meat', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -402,7 +402,7 @@ describe('CategoryOptionListUtils', () => { text: ' Milk', keyForList: 'Food: Milk', searchText: 'Food: Milk', - tooltipText: 'Milk', + tooltipText: 'Food: Milk', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -438,7 +438,7 @@ describe('CategoryOptionListUtils', () => { text: ' Meals', keyForList: 'Travel: Meals', searchText: 'Travel: Meals', - tooltipText: 'Meals', + tooltipText: 'Travel: Meals', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -447,7 +447,7 @@ describe('CategoryOptionListUtils', () => { text: ' Breakfast', keyForList: 'Travel: Meals: Breakfast', searchText: 'Travel: Meals: Breakfast', - tooltipText: 'Breakfast', + tooltipText: 'Travel: Meals: Breakfast', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -456,7 +456,7 @@ describe('CategoryOptionListUtils', () => { text: ' Lunch', keyForList: 'Travel: Meals: Lunch', searchText: 'Travel: Meals: Lunch', - tooltipText: 'Lunch', + tooltipText: 'Travel: Meals: Lunch', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -488,7 +488,7 @@ describe('CategoryOptionListUtils', () => { pendingAction: undefined, }, { - text: 'Food: Meat', + text: ' Meat', keyForList: 'Food: Meat', searchText: 'Food: Meat', tooltipText: 'Food: Meat', @@ -497,7 +497,7 @@ describe('CategoryOptionListUtils', () => { pendingAction: undefined, }, { - text: 'Food: Milk', + text: ' Milk', keyForList: 'Food: Milk', searchText: 'Food: Milk', tooltipText: 'Food: Milk', @@ -698,7 +698,7 @@ describe('CategoryOptionListUtils', () => { text: ' Meat', keyForList: 'Food: Meat', searchText: 'Food: Meat', - tooltipText: 'Meat', + tooltipText: 'Food: Meat', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -707,7 +707,7 @@ describe('CategoryOptionListUtils', () => { text: ' Milk', keyForList: 'Food: Milk', searchText: 'Food: Milk', - tooltipText: 'Milk', + tooltipText: 'Food: Milk', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -725,7 +725,7 @@ describe('CategoryOptionListUtils', () => { text: ' Audi', keyForList: 'Cars: Audi', searchText: 'Cars: Audi', - tooltipText: 'Audi', + tooltipText: 'Cars: Audi', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -734,7 +734,7 @@ describe('CategoryOptionListUtils', () => { text: ' Mercedes-Benz', keyForList: 'Cars: Mercedes-Benz', searchText: 'Cars: Mercedes-Benz', - tooltipText: 'Mercedes-Benz', + tooltipText: 'Cars: Mercedes-Benz', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -752,7 +752,7 @@ describe('CategoryOptionListUtils', () => { text: ' Meals', keyForList: 'Travel: Meals', searchText: 'Travel: Meals', - tooltipText: 'Meals', + tooltipText: 'Travel: Meals', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -761,7 +761,7 @@ describe('CategoryOptionListUtils', () => { text: ' Breakfast', keyForList: 'Travel: Meals: Breakfast', searchText: 'Travel: Meals: Breakfast', - tooltipText: 'Breakfast', + tooltipText: 'Travel: Meals: Breakfast', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -770,7 +770,7 @@ describe('CategoryOptionListUtils', () => { text: ' Lunch', keyForList: 'Travel: Meals: Lunch', searchText: 'Travel: Meals: Lunch', - tooltipText: 'Lunch', + tooltipText: 'Travel: Meals: Lunch', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -815,7 +815,7 @@ describe('CategoryOptionListUtils', () => { text: ' B', keyForList: 'A: B', searchText: 'A: B', - tooltipText: 'B', + tooltipText: 'A: B', isDisabled: true, isSelected: false, pendingAction: undefined, @@ -824,7 +824,7 @@ describe('CategoryOptionListUtils', () => { text: ' C', keyForList: 'A: B: C', searchText: 'A: B: C', - tooltipText: 'C', + tooltipText: 'A: B: C', isDisabled: false, isSelected: false, pendingAction: undefined, @@ -833,7 +833,7 @@ describe('CategoryOptionListUtils', () => { text: ' D', keyForList: 'A: B: C: D', searchText: 'A: B: C: D', - tooltipText: 'D', + tooltipText: 'A: B: C: D', isDisabled: true, isSelected: false, pendingAction: undefined, @@ -842,7 +842,7 @@ describe('CategoryOptionListUtils', () => { text: ' E', keyForList: 'A: B: C: D: E', searchText: 'A: B: C: D: E', - tooltipText: 'E', + tooltipText: 'A: B: C: D: E', isDisabled: false, isSelected: false, pendingAction: undefined, From 313c087221b16b27cba4d77e9e9f9ede2f185a47 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Wed, 11 Mar 2026 23:06:56 +0500 Subject: [PATCH 04/11] Multi-level categories - Removed OneLine option --- src/libs/CategoryOptionListUtils.ts | 34 ++---- tests/unit/CategoryOptionListUtilsTest.ts | 139 ---------------------- 2 files changed, 7 insertions(+), 166 deletions(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index c5fe53588d91..a28efd7b6a92 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -29,30 +29,10 @@ type Hierarchy = Record | Category[], isOneLine = false, selectedOptions: Category[] = []): OptionTree[] { +function getCategoryOptionTree(options: Record | Category[], selectedOptions: Category[] = []): OptionTree[] { const optionCollection = new Map(); for (const option of Object.values(options)) { - if (isOneLine) { - if (optionCollection.has(option.name)) { - continue; - } - - const decodedCategoryName = getDecodedCategoryName(option.name); - optionCollection.set(option.name, { - text: decodedCategoryName, - keyForList: option.name, - searchText: option.name, - tooltipText: decodedCategoryName, - isDisabled: !option.enabled || option.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - isSelected: !!option.isSelected, - pendingAction: option.pendingAction, - }); - - continue; - } - const array = option.name.split(CONST.PARENT_CHILD_SEPARATOR); for (let index = 0; index < array.length; index++) { @@ -125,7 +105,7 @@ function getCategoryListSections({ } if (numberOfEnabledCategories === 0 && selectedOptions.length > 0) { - const data = getCategoryOptionTree(selectedOptionsWithDisabledState, false); + const data = getCategoryOptionTree(selectedOptionsWithDisabledState); categorySections.push({ // "Selected" section title: '', @@ -144,7 +124,7 @@ function getCategoryListSections({ isSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name), })); - const data = getCategoryOptionTree(searchCategories, false); + const data = getCategoryOptionTree(searchCategories); categorySections.push({ // "Search" section title: '', @@ -156,7 +136,7 @@ function getCategoryListSections({ } if (selectedOptions.length > 0) { - const data = getCategoryOptionTree(selectedOptionsWithDisabledState, false); + const data = getCategoryOptionTree(selectedOptionsWithDisabledState); categorySections.push({ // "Selected" section title: '', @@ -169,7 +149,7 @@ function getCategoryListSections({ const filteredCategories = enabledCategories.filter((category) => !selectedOptionNames.has(category.name)); if (numberOfEnabledCategories < CONST.STANDARD_LIST_ITEM_LIMIT) { - const data = getCategoryOptionTree(filteredCategories, false, selectedOptionsWithDisabledState); + const data = getCategoryOptionTree(filteredCategories, selectedOptionsWithDisabledState); categorySections.push({ // "All" section when items amount less than the threshold title: '', @@ -193,7 +173,7 @@ function getCategoryListSections({ if (filteredRecentlyUsedCategories.length > 0) { const cutRecentlyUsedCategories = filteredRecentlyUsedCategories.slice(0, maxRecentReportsToShow); - const data = getCategoryOptionTree(cutRecentlyUsedCategories, false); + const data = getCategoryOptionTree(cutRecentlyUsedCategories); categorySections.push({ // "Recent" section title: translate('common.recent'), @@ -202,7 +182,7 @@ function getCategoryListSections({ }); } - const data = getCategoryOptionTree(filteredCategories, false, selectedOptionsWithDisabledState); + const data = getCategoryOptionTree(filteredCategories, selectedOptionsWithDisabledState); categorySections.push({ // "All" section when items amount more than the threshold title: translate('common.all'), diff --git a/tests/unit/CategoryOptionListUtilsTest.ts b/tests/unit/CategoryOptionListUtilsTest.ts index 6b7e65e656f0..ada043b7510c 100644 --- a/tests/unit/CategoryOptionListUtilsTest.ts +++ b/tests/unit/CategoryOptionListUtilsTest.ts @@ -848,146 +848,7 @@ describe('CategoryOptionListUtils', () => { pendingAction: undefined, }, ]; - const resultOneLine = [ - { - text: 'Meals', - keyForList: 'Meals', - searchText: 'Meals', - tooltipText: 'Meals', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Restaurant', - keyForList: 'Restaurant', - searchText: 'Restaurant', - tooltipText: 'Restaurant', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Food', - keyForList: 'Food', - searchText: 'Food', - tooltipText: 'Food', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Food: Meat', - keyForList: 'Food: Meat', - searchText: 'Food: Meat', - tooltipText: 'Food: Meat', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Food: Milk', - keyForList: 'Food: Milk', - searchText: 'Food: Milk', - tooltipText: 'Food: Milk', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Cars: Audi', - keyForList: 'Cars: Audi', - searchText: 'Cars: Audi', - tooltipText: 'Cars: Audi', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Cars: Mercedes-Benz', - keyForList: 'Cars: Mercedes-Benz', - searchText: 'Cars: Mercedes-Benz', - tooltipText: 'Cars: Mercedes-Benz', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Travel: Meals', - keyForList: 'Travel: Meals', - searchText: 'Travel: Meals', - tooltipText: 'Travel: Meals', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Travel: Meals: Breakfast', - keyForList: 'Travel: Meals: Breakfast', - searchText: 'Travel: Meals: Breakfast', - tooltipText: 'Travel: Meals: Breakfast', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Travel: Meals: Lunch', - keyForList: 'Travel: Meals: Lunch', - searchText: 'Travel: Meals: Lunch', - tooltipText: 'Travel: Meals: Lunch', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Plain', - keyForList: 'Plain', - searchText: 'Plain', - tooltipText: 'Plain', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Audi', - keyForList: 'Audi', - searchText: 'Audi', - tooltipText: 'Audi', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'Health', - keyForList: 'Health', - searchText: 'Health', - tooltipText: 'Health', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'A: B: C', - keyForList: 'A: B: C', - searchText: 'A: B: C', - tooltipText: 'A: B: C', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - { - text: 'A: B: C: D: E', - keyForList: 'A: B: C: D: E', - searchText: 'A: B: C: D: E', - tooltipText: 'A: B: C: D: E', - isDisabled: false, - isSelected: false, - pendingAction: undefined, - }, - ]; - expect(getCategoryOptionTree(categories)).toStrictEqual(result); - expect(getCategoryOptionTree(categories, true)).toStrictEqual(resultOneLine); }); it('sortCategories', () => { From 68f29b2787959bcdca4453e23d71027a0f12c7f0 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Mon, 16 Mar 2026 22:45:02 +0500 Subject: [PATCH 05/11] Resolved: Category - Parent category is disabled when selected#85359 --- src/libs/CategoryOptionListUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index a28efd7b6a92..f60930af4c69 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -45,7 +45,10 @@ function getCategoryOptionTree(options: Record | Category[], s const isChild = array.length - 1 === index; const searchText = array.slice(0, index + 1).join(CONST.PARENT_CHILD_SEPARATOR); const selectedParentOption = !isChild && Object.values(selectedOptions).find((op) => op.name === searchText); - const isParentOptionDisabled = !selectedParentOption || !selectedParentOption.enabled || selectedParentOption.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + const optionParent = !isChild && Object.values(options).find((op) => op.name === searchText); + const parentOption = selectedParentOption ?? optionParent; + + const isParentOptionDisabled = !parentOption || !parentOption.enabled || parentOption.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; if (optionCollection.has(searchText)) { continue; From c0ddacbf3099433eb96fc5f0c597c254f442253c Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Thu, 19 Mar 2026 14:23:09 +0500 Subject: [PATCH 06/11] Resolved: Category - Parent category is separated from subcategories when searched #85364 --- src/libs/CategoryOptionListUtils.ts | 32 +++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index f60930af4c69..f952d61047ac 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -120,14 +120,42 @@ function getCategoryListSections({ } if (searchValue) { + // Step 1: Combine selected and enabled categories for searching const categoriesForSearch = [...selectedOptionsWithDisabledState, ...enabledCategories]; - const searchCategories: Category[] = tokenizedSearch(categoriesForSearch, searchValue, (category) => [category.name]).map((category) => ({ + // Step 2: Get search results using tokenizedSearch + let searchCategories: Category[] = tokenizedSearch(categoriesForSearch, searchValue, (category) => [category.name]).map((category) => ({ + ...category, + // Temporarily store if it was selected + wasSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name), + })); + + // Step 3: Deduplicate by name (keep first occurrence, which is likely the selected one if present) + const seen = new Set(); + searchCategories = searchCategories.filter((category) => { + if (seen.has(category.name)) { + return false; + } + seen.add(category.name); + return true; + }); + + // Step 4: Re-sort to restore hierarchical grouping + // Convert back to Record format expected by sortCategories + const categoriesRecord: Record = {}; + searchCategories.forEach((category) => { + categoriesRecord[category.name] = category; + }); + const sortedCategories = sortCategories(categoriesRecord, localeCompare); + + // Step 5: Re-apply the isSelected flag (lost during sortCategories) + const finalSearchCategories: Category[] = sortedCategories.map((category) => ({ ...category, isSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name), })); - const data = getCategoryOptionTree(searchCategories); + // Step 6: Generate the option tree and push the section + const data = getCategoryOptionTree(finalSearchCategories); categorySections.push({ // "Search" section title: '', From e3aa7317135df2112e40603dee302b30a20a8019 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Fri, 20 Mar 2026 23:11:16 +0500 Subject: [PATCH 07/11] resolved: Category - Category appears as disabled when the category is A: B: #85357 --- src/libs/CategoryOptionListUtils.ts | 30 ++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index f952d61047ac..90b76d755031 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -33,8 +33,20 @@ type Hierarchy = Record | Category[], selectedOptions: Category[] = []): OptionTree[] { const optionCollection = new Map(); for (const option of Object.values(options)) { - const array = option.name.split(CONST.PARENT_CHILD_SEPARATOR); - + const parts = option.name.split(CONST.PARENT_CHILD_SEPARATOR); + + // Process the split to handle trailing colon (e.g., "A: B:") + const array: string[] = []; + for (let i = 0; i < parts.length; i++) { + if (parts[i] === '' && i === parts.length - 1) { + // Trailing colon: merge the colon back to the last part + if (array.length > 0) { + array[array.length - 1] = array[array.length - 1] + CONST.PARENT_CHILD_SEPARATOR; + } + } else { + array.push(parts[i]); + } + } for (let index = 0; index < array.length; index++) { const optionName = array.at(index); if (!optionName) { @@ -251,7 +263,19 @@ function sortCategories(categories: Record, localeCompare: Loc * } */ for (const category of sortedCategories) { - const path = category.name.split(CONST.PARENT_CHILD_SEPARATOR); + const parts = category.name.split(CONST.PARENT_CHILD_SEPARATOR); + + // Process the split to handle trailing colon + const path: string[] = []; + for (let i = 0; i < parts.length; i++) { + if (parts[i] === '' && i === parts.length - 1) { + if (path.length > 0) { + path[path.length - 1] = path[path.length - 1] + CONST.PARENT_CHILD_SEPARATOR; + } + } else { + path.push(parts[i]); + } + } const existedValue = lodashGet(hierarchy, path, {}) as Hierarchy; lodashSet(hierarchy, path, { ...existedValue, From 6b682b19f207726902435d40615854eee6b9d61a Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Sun, 22 Mar 2026 10:09:51 +0500 Subject: [PATCH 08/11] Eslint Resolved - rulesdir/prefer-at --- src/libs/CategoryOptionListUtils.ts | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index 90b76d755031..6f0dd165d7ba 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -38,13 +38,17 @@ function getCategoryOptionTree(options: Record | Category[], s // Process the split to handle trailing colon (e.g., "A: B:") const array: string[] = []; for (let i = 0; i < parts.length; i++) { - if (parts[i] === '' && i === parts.length - 1) { + const part = parts.at(i); + if (part === undefined) { + continue; + } + if (part === '' && i === parts.length - 1) { // Trailing colon: merge the colon back to the last part if (array.length > 0) { - array[array.length - 1] = array[array.length - 1] + CONST.PARENT_CHILD_SEPARATOR; + array[array.length - 1] = array.at(array.length - 1) + CONST.PARENT_CHILD_SEPARATOR; } } else { - array.push(parts[i]); + array.push(part); } } for (let index = 0; index < array.length; index++) { @@ -155,9 +159,9 @@ function getCategoryListSections({ // Step 4: Re-sort to restore hierarchical grouping // Convert back to Record format expected by sortCategories const categoriesRecord: Record = {}; - searchCategories.forEach((category) => { + for (const category of searchCategories) { categoriesRecord[category.name] = category; - }); + } const sortedCategories = sortCategories(categoriesRecord, localeCompare); // Step 5: Re-apply the isSelected flag (lost during sortCategories) @@ -268,12 +272,17 @@ function sortCategories(categories: Record, localeCompare: Loc // Process the split to handle trailing colon const path: string[] = []; for (let i = 0; i < parts.length; i++) { - if (parts[i] === '' && i === parts.length - 1) { + const part = parts.at(i); + if (part === undefined) { + continue; + } + if (part === '' && i === parts.length - 1) { + // trailing colon: merge the colon back to the last part if (path.length > 0) { - path[path.length - 1] = path[path.length - 1] + CONST.PARENT_CHILD_SEPARATOR; + path[path.length - 1] = path.at(path.length - 1) + CONST.PARENT_CHILD_SEPARATOR; } } else { - path.push(parts[i]); + path.push(part); } } const existedValue = lodashGet(hierarchy, path, {}) as Hierarchy; From 3cc08bfaf3431f885763a183b7fe0329063daf70 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Sun, 22 Mar 2026 10:21:13 +0500 Subject: [PATCH 09/11] Eslint Resolved - no-shadow --- src/libs/CategoryOptionListUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index 6f0dd165d7ba..72f43c929e56 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -162,10 +162,10 @@ function getCategoryListSections({ for (const category of searchCategories) { categoriesRecord[category.name] = category; } - const sortedCategories = sortCategories(categoriesRecord, localeCompare); + const searchSortedCategories = sortCategories(categoriesRecord, localeCompare); // Step 5: Re-apply the isSelected flag (lost during sortCategories) - const finalSearchCategories: Category[] = sortedCategories.map((category) => ({ + const finalSearchCategories: Category[] = searchSortedCategories.map((category) => ({ ...category, isSelected: selectedOptions.some((selectedOption) => selectedOption.name === category.name), })); From a63e945c7a2a77d0ee58ccdd37d61cbdf928cfe6 Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Thu, 2 Apr 2026 00:09:22 +0500 Subject: [PATCH 10/11] feat: Centralize category segment processing and display leaf in selected field --- .../ReportActionItem/MoneyRequestView.tsx | 4 +- src/libs/CategoryOptionListUtils.ts | 45 +++---------------- src/libs/CategoryUtils.ts | 45 +++++++++++++++++++ 3 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index e35ab6384a3e..deb0b8cb9eb8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -41,7 +41,7 @@ import {updateMoneyRequestBillable, updateMoneyRequestReimbursable, updateMoneyR import initSplitExpense from '@libs/actions/SplitExpenses'; import {getIsMissingAttendeesViolation} from '@libs/AttendeeUtils'; import {getBrokenConnectionUrlToFixPersonalCard, getCompanyCardDescription} from '@libs/CardUtils'; -import {getDecodedCategoryName, isCategoryMissing} from '@libs/CategoryUtils'; +import {getDecodedLeafCategoryName, isCategoryMissing} from '@libs/CategoryUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {getRateFromMerchant} from '@libs/MergeTransactionUtils'; @@ -677,7 +677,7 @@ function MoneyRequestView({ const merchantCopyValue = !canEditMerchant ? updatedMerchantTitle : undefined; const dateCopyValue = !canEditDate ? transactionDate : undefined; const categoryValue = updatedTransaction?.category ?? categoryForDisplay; - const decodedCategoryName = getDecodedCategoryName(categoryValue); + const decodedCategoryName = getDecodedLeafCategoryName(categoryValue); const categoryCopyValue = !canEdit ? decodedCategoryName : undefined; const cardCopyValue = cardProgramName; const taxRateValue = hasTaxValueChanged ? taxValue : (transaction?.taxName ?? taxRateTitle ?? fallbackTaxRateTitle ?? ''); diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index 72f43c929e56..4e8e033ed16c 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -8,7 +8,7 @@ import type {PolicyCategories} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import times from '@src/utils/times'; -import {getDecodedCategoryName} from './CategoryUtils'; +import {getDecodedCategoryName, processCategoryNameSegments} from './CategoryUtils'; import type {OptionTree} from './OptionsListUtils'; import tokenizedSearch from './tokenizedSearch'; @@ -33,24 +33,7 @@ type Hierarchy = Record | Category[], selectedOptions: Category[] = []): OptionTree[] { const optionCollection = new Map(); for (const option of Object.values(options)) { - const parts = option.name.split(CONST.PARENT_CHILD_SEPARATOR); - - // Process the split to handle trailing colon (e.g., "A: B:") - const array: string[] = []; - for (let i = 0; i < parts.length; i++) { - const part = parts.at(i); - if (part === undefined) { - continue; - } - if (part === '' && i === parts.length - 1) { - // Trailing colon: merge the colon back to the last part - if (array.length > 0) { - array[array.length - 1] = array.at(array.length - 1) + CONST.PARENT_CHILD_SEPARATOR; - } - } else { - array.push(part); - } - } + const array = processCategoryNameSegments(option.name); for (let index = 0; index < array.length; index++) { const optionName = array.at(index); if (!optionName) { @@ -59,7 +42,10 @@ function getCategoryOptionTree(options: Record | Category[], s const indents = times(index, () => CONST.INDENTS).join(''); const isChild = array.length - 1 === index; - const searchText = array.slice(0, index + 1).join(CONST.PARENT_CHILD_SEPARATOR); + + // For leaf categories, use the original full name so it matches the policy. + // For parent categories, build the path from the processed segments. + const searchText = isChild ? option.name : array.slice(0, index + 1).join(CONST.PARENT_CHILD_SEPARATOR); const selectedParentOption = !isChild && Object.values(selectedOptions).find((op) => op.name === searchText); const optionParent = !isChild && Object.values(options).find((op) => op.name === searchText); const parentOption = selectedParentOption ?? optionParent; @@ -267,24 +253,7 @@ function sortCategories(categories: Record, localeCompare: Loc * } */ for (const category of sortedCategories) { - const parts = category.name.split(CONST.PARENT_CHILD_SEPARATOR); - - // Process the split to handle trailing colon - const path: string[] = []; - for (let i = 0; i < parts.length; i++) { - const part = parts.at(i); - if (part === undefined) { - continue; - } - if (part === '' && i === parts.length - 1) { - // trailing colon: merge the colon back to the last part - if (path.length > 0) { - path[path.length - 1] = path.at(path.length - 1) + CONST.PARENT_CHILD_SEPARATOR; - } - } else { - path.push(part); - } - } + const path = processCategoryNameSegments(category.name); const existedValue = lodashGet(hierarchy, path, {}) as Hierarchy; lodashSet(hierarchy, path, { ...existedValue, diff --git a/src/libs/CategoryUtils.ts b/src/libs/CategoryUtils.ts index af694ccf44fe..7c9c41da8e2e 100644 --- a/src/libs/CategoryUtils.ts +++ b/src/libs/CategoryUtils.ts @@ -137,6 +137,49 @@ function getDecodedCategoryName(categoryName: string) { return Str.htmlDecode(categoryName); } +/** + * Splits a category name on the colon separator, removes empty middle segments, + * and merges a trailing empty segment into the previous part (preserving trailing colons). + * + * Examples: + * "Food: Meat" → ["Food", "Meat"] + * "A: B:" → ["A", "B:"] + * "A: B: :" → ["A", "B:"] + * "A: B::" → ["A", "B:"] + * "A: B:::" → ["A", "B:"] + * ":D" → ["D"] + * "Plain" → ["Plain"] + */ +function processCategoryNameSegments(categoryName: string): string[] { + const parts = categoryName.split(CONST.PARENT_CHILD_SEPARATOR); + const result: string[] = []; + + // Keep only parts that contain at least one non‑whitespace character. + for (let i = 0; i < parts.length; i++) { + const part = parts.at(i); + if (part === undefined) { + continue; + } + if (part.trim() !== '') { + result.push(part); + } + } + + // If the original name ends with a colon (allowing trailing spaces), append a colon to the last segment. + const endsWithColon = categoryName.trim().endsWith(CONST.PARENT_CHILD_SEPARATOR); + if (endsWithColon && result.length > 0) { + result[result.length - 1] = result.at(result.length - 1) + CONST.PARENT_CHILD_SEPARATOR; + } + + return result; +} + +function getDecodedLeafCategoryName(categoryName: string): string { + const segments = processCategoryNameSegments(categoryName); + const leaf = segments.at(segments.length - 1) ?? ''; + return Str.htmlDecode(leaf.trim()); +} + function getAvailableNonPersonalPolicyCategories(policyCategories: OnyxCollection, personalPolicyID: string | undefined) { return Object.fromEntries( Object.entries(policyCategories ?? {}).filter(([key, categories]) => { @@ -161,5 +204,7 @@ export { isCategoryMissing, isCategoryDescriptionRequired, getDecodedCategoryName, + getDecodedLeafCategoryName, + processCategoryNameSegments, getAvailableNonPersonalPolicyCategories, }; From b45d40a9c8fc58f60a103d510fa4d0152f7413fd Mon Sep 17 00:00:00 2001 From: Imran Jamil Malik Date: Thu, 16 Apr 2026 16:18:12 +0500 Subject: [PATCH 11/11] feat: Display leaf category name in main expense UI (excludes change logs & thread headers) --- .../MoneyRequestConfirmationList/sections/CategoryField.tsx | 4 ++-- .../MoneyRequestReportGroupHeader.tsx | 4 ++-- .../TransactionPreview/TransactionPreviewContent.tsx | 4 ++-- src/components/SelectionList/ListItem/SplitListItem.tsx | 6 +++--- .../TransactionItemRow/DataCells/CategoryCell.tsx | 4 ++-- src/libs/MergeTransactionUtils.ts | 5 +++++ src/pages/iou/SplitExpenseEditPage.tsx | 4 ++-- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList/sections/CategoryField.tsx b/src/components/MoneyRequestConfirmationList/sections/CategoryField.tsx index d8b5787aec85..e47f3d84330b 100644 --- a/src/components/MoneyRequestConfirmationList/sections/CategoryField.tsx +++ b/src/components/MoneyRequestConfirmationList/sections/CategoryField.tsx @@ -4,7 +4,7 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getDecodedCategoryName} from '@libs/CategoryUtils'; +import {getDecodedLeafCategoryName} from '@libs/CategoryUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getCategory, willFieldBeAutomaticallyFilled} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; @@ -50,7 +50,7 @@ function CategoryField({ const shouldDisplayCategoryError = formError === 'violations.categoryOutOfPolicy'; const iouCategory = getCategory(transaction); - const decodedCategoryName = getDecodedCategoryName(iouCategory); + const decodedCategoryName = getDecodedLeafCategoryName(iouCategory); const getCategoryRightLabelIcon = () => (willFieldBeAutomaticallyFilled(transaction, 'category') ? icons.Sparkles : undefined); const getCategoryRightLabel = () => { diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportGroupHeader.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportGroupHeader.tsx index 98dd74154f70..59abff39c047 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportGroupHeader.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportGroupHeader.tsx @@ -7,6 +7,7 @@ import {useCurrencyListActions} from '@hooks/useCurrencyList'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP'; import useThemeStyles from '@hooks/useThemeStyles'; +import {getDecodedLeafCategoryName} from '@libs/CategoryUtils'; import {getCommaSeparatedTagNameWithSanitizedColons} from '@libs/PolicyUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -67,10 +68,9 @@ function MoneyRequestReportGroupHeader({ const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayoutOnWideRHP(); - const cleanedGroupName = isGroupedByTag && group.groupName ? getCommaSeparatedTagNameWithSanitizedColons(group.groupName) : group.groupName; + const cleanedGroupName = isGroupedByTag && group.groupName ? getCommaSeparatedTagNameWithSanitizedColons(group.groupName) : getDecodedLeafCategoryName(group.groupName); const displayName = cleanedGroupName || translate(isGroupedByTag ? 'reportLayout.noTag' : 'reportLayout.uncategorized'); const formattedAmount = convertToDisplayString(group.subTotalAmount, currency); - const shouldShowCheckbox = isSelectionModeEnabled || !shouldUseNarrowLayout; const conditionalHeight = useMemo( diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index af57dbfa37d7..077518b5c9aa 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -22,7 +22,7 @@ import useReportIsArchived from '@hooks/useReportIsArchived'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {getBrokenConnectionUrlToFixPersonalCard} from '@libs/CardUtils'; -import {getDecodedCategoryName} from '@libs/CategoryUtils'; +import {getDecodedLeafCategoryName} from '@libs/CategoryUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {calculateAmount} from '@libs/IOUUtils'; import Parser from '@libs/Parser'; @@ -390,7 +390,7 @@ function TransactionPreviewContent({ numberOfLines={1} style={[isDeleted && styles.lineThrough, styles.textMicroSupporting, styles.pre, styles.flexShrink1]} > - {getDecodedCategoryName(category ?? '')} + {getDecodedLeafCategoryName(category ?? '')} )} diff --git a/src/components/SelectionList/ListItem/SplitListItem.tsx b/src/components/SelectionList/ListItem/SplitListItem.tsx index f05101c2904e..8e9f5df0e0f4 100644 --- a/src/components/SelectionList/ListItem/SplitListItem.tsx +++ b/src/components/SelectionList/ListItem/SplitListItem.tsx @@ -11,7 +11,7 @@ import useLocalize from '@hooks/useLocalize'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getDecodedCategoryName} from '@libs/CategoryUtils'; +import {getDecodedLeafCategoryName} from '@libs/CategoryUtils'; import {convertToDisplayStringWithoutCurrency} from '@libs/CurrencyUtils'; import {getCommaSeparatedTagNameWithSanitizedColons} from '@libs/PolicyUtils'; import variables from '@styles/variables'; @@ -100,7 +100,7 @@ function SplitListItem({ const textContentAccessibilityLabel = [ splitItem.headerText, splitItem.merchant, - splitItem.category ? getDecodedCategoryName(splitItem.category) : undefined, + splitItem.category ? getDecodedLeafCategoryName(splitItem.category) : undefined, splitItem.tags?.at(0) ? getCommaSeparatedTagNameWithSanitizedColons(splitItem.tags.at(0) ?? '') : undefined, ] .filter(Boolean) @@ -181,7 +181,7 @@ function SplitListItem({ numberOfLines={1} style={[styles.textMicroSupporting, styles.pre, styles.flexShrink1]} > - {getDecodedCategoryName(splitItem.category)} + {getDecodedLeafCategoryName(splitItem.category)} )} diff --git a/src/components/TransactionItemRow/DataCells/CategoryCell.tsx b/src/components/TransactionItemRow/DataCells/CategoryCell.tsx index a9ed438d043e..22e36fa836c2 100644 --- a/src/components/TransactionItemRow/DataCells/CategoryCell.tsx +++ b/src/components/TransactionItemRow/DataCells/CategoryCell.tsx @@ -7,7 +7,7 @@ import type {EditableProps} from '@components/Table/EditableCell'; import TextWithTooltip from '@components/TextWithTooltip'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getDecodedCategoryName, isCategoryMissing} from '@libs/CategoryUtils'; +import {getDecodedLeafCategoryName, isCategoryMissing} from '@libs/CategoryUtils'; import type TransactionDataCellProps from './TransactionDataCellProps'; type CategoryCellProps = TransactionDataCellProps & @@ -21,7 +21,7 @@ function CategoryCell({shouldUseNarrowLayout, shouldShowTooltip, transactionItem const {isEditing, anchorRef, isPopoverVisible, popoverPosition, isInverted, startEditing, cancelEditing} = usePopoverEditState({canEdit}); // For display: decoded category name for user-readable text - const categoryForDisplay = isCategoryMissing(transactionItem?.category) ? '' : getDecodedCategoryName(transactionItem?.category ?? ''); + const categoryForDisplay = isCategoryMissing(transactionItem?.category) ? '' : getDecodedLeafCategoryName(transactionItem?.category ?? ''); // For picker comparison: raw category name (empty if missing, matches IOURequestStepCategory) const categoryForComparison = isCategoryMissing(transactionItem?.category) ? '' : (transactionItem?.category ?? ''); diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts index 5b376d909029..41aeb70148e5 100644 --- a/src/libs/MergeTransactionUtils.ts +++ b/src/libs/MergeTransactionUtils.ts @@ -9,6 +9,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {MergeTransaction, Policy, Report, SearchResults, Transaction} from '@src/types/onyx'; import type {Attendee} from '@src/types/onyx/IOU'; import SafeString from '@src/utils/SafeString'; +import {getDecodedLeafCategoryName} from './CategoryUtils'; import {convertToBackendAmount, convertToDisplayString} from './CurrencyUtils'; import Parser from './Parser'; import {getCommaSeparatedTagNameWithSanitizedColons} from './PolicyUtils'; @@ -535,6 +536,10 @@ function getDisplayValue(field: MergeFieldKey, transaction: Transaction, policy: return getTaxName(policy, transaction) ?? transaction.taxValue ?? ''; } + if (field === 'category') { + return getDecodedLeafCategoryName(SafeString(fieldValue)); + } + return SafeString(fieldValue); } /** diff --git a/src/pages/iou/SplitExpenseEditPage.tsx b/src/pages/iou/SplitExpenseEditPage.tsx index 67dc3f3ff497..b8b8107aa3ef 100644 --- a/src/pages/iou/SplitExpenseEditPage.tsx +++ b/src/pages/iou/SplitExpenseEditPage.tsx @@ -21,7 +21,7 @@ import type {ViolationField} from '@hooks/useViolations'; import {initDraftSplitExpenseDataForEdit, removeSplitExpenseField, updateSplitExpenseField} from '@libs/actions/IOU/Split'; import {openPolicyCategoriesPage} from '@libs/actions/Policy/Category'; import {openPolicyTagsPage} from '@libs/actions/Policy/Tag'; -import {getDecodedCategoryName, isCategoryDescriptionRequired} from '@libs/CategoryUtils'; +import {getDecodedLeafCategoryName, isCategoryDescriptionRequired} from '@libs/CategoryUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Navigation from '@libs/Navigation/Navigation'; @@ -259,7 +259,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { shouldShowRightIcon key={translate('common.category')} description={translate('common.category')} - title={getDecodedCategoryName(splitExpenseDraftTransactionDetails?.category ?? '')} + title={getDecodedLeafCategoryName(splitExpenseDraftTransactionDetails?.category ?? '')} numberOfLinesTitle={2} rightLabel={isCategoryRequired ? translate('common.required') : ''} onPress={() => {