From 5b756eec8b89f281db4986dd7c885171c21a076c Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 20 Aug 2024 14:41:50 +0530 Subject: [PATCH 01/11] Implemented chat type in search --- ...mple-illustration__commentbubbles_blue.svg | 22 ++++ src/CONST.ts | 9 ++ src/ROUTES.ts | 9 +- src/components/Icon/Illustrations.ts | 2 + src/components/Search/SearchPageHeader.tsx | 7 +- src/components/Search/SearchStatusBar.tsx | 44 ++++++- src/components/Search/index.tsx | 38 +++--- src/components/Search/types.ts | 4 +- src/components/SelectionList/ChatListItem.tsx | 105 +++++++++++++++++ .../SelectionList/SearchTableHeader.tsx | 13 ++- src/components/SelectionList/types.ts | 31 ++++- src/languages/en.ts | 5 + src/languages/es.ts | 5 + src/libs/Navigation/types.ts | 1 + src/libs/SearchUtils.ts | 109 +++++++++++++++--- src/pages/Search/EmptySearchView.tsx | 1 + src/pages/Search/SearchTypeMenu.tsx | 6 + src/types/onyx/SearchResults.ts | 54 ++++++++- 18 files changed, 420 insertions(+), 45 deletions(-) create mode 100644 assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg create mode 100644 src/components/SelectionList/ChatListItem.tsx diff --git a/assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg b/assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg new file mode 100644 index 000000000000..9c0711fcaedc --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index b31bcc424c84..ad9529b2fc34 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5253,6 +5253,7 @@ const CONST = { EXPENSE: 'expense', INVOICE: 'invoice', TRIP: 'trip', + CHAT: 'chat', }, ACTION_TYPES: { VIEW: 'view', @@ -5295,6 +5296,14 @@ const CONST = { APPROVED: 'approved', PAID: 'paid', }, + CHAT: { + ALL: 'all', + UNREAD: 'unread', + DRAFTS: 'drafts', + SENT: 'sent', + ATTACHMENTS: 'attachments', + LINKS: 'links', + }, }, TABLE_COLUMNS: { RECEIPT: 'receipt', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 893fd59e38b4..4d410ed4b76d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -54,8 +54,13 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', SEARCH_REPORT: { - route: 'search/view/:reportID', - getRoute: (reportID: string) => `search/view/${reportID}` as const, + route: 'search/view/:reportID/:reportActionID?', + getRoute: (reportID: string, reportActionID?: string) => { + if (reportActionID) { + return `search/view/${reportID}/${reportActionID}` as const; + } + return `search/view/${reportID}` as const; + }, }, TRANSACTION_HOLD_REASON_RHP: 'search/hold', diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 64058a495231..0325c6624b30 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -51,6 +51,7 @@ import CheckmarkCircle from '@assets/images/simple-illustrations/simple-illustra import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg'; import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg'; import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg'; +import CommentBubblesBlue from '@assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg'; import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__company-card.svg'; import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg'; import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg'; @@ -173,6 +174,7 @@ export { SmartScan, Hourglass, CommentBubbles, + CommentBubblesBlue, TrashCan, TeleScope, Profile, diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index 2b59e2046484..c2035d6f6a3c 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -9,7 +9,7 @@ import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/typ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; -import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import Text from '@components/Text'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; @@ -95,7 +95,7 @@ type SearchPageHeaderProps = { isCustomQuery: boolean; setOfflineModalOpen?: () => void; setDownloadErrorModalOpen?: () => void; - data?: TransactionListItemType[] | ReportListItemType[]; + data?: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[]; }; type SearchHeaderOptionValue = DeepValueOf | undefined; @@ -111,6 +111,8 @@ function getHeaderContent(type: SearchDataTypes): HeaderContent { return {icon: Illustrations.EnvelopeReceipt, titleText: 'workspace.common.invoices'}; case CONST.SEARCH.DATA_TYPES.TRIP: return {icon: Illustrations.Luggage, titleText: 'travel.trips'}; + case CONST.SEARCH.DATA_TYPES.CHAT: + return {icon: Illustrations.CommentBubblesBlue, titleText: 'common.chats'}; case CONST.SEARCH.DATA_TYPES.EXPENSE: default: return {icon: Illustrations.MoneyReceipts, titleText: 'common.expenses'}; @@ -135,6 +137,7 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa .filter( (item) => !SearchUtils.isTransactionListItemType(item) && + !SearchUtils.isReportActionListItemType(item) && item.reportID && item.transactions.every((transaction: {keyForList: string | number}) => selectedTransactions[transaction.keyForList]?.isSelected), ) diff --git a/src/components/Search/SearchStatusBar.tsx b/src/components/Search/SearchStatusBar.tsx index 7c1ffeff1818..9ef1f2abb84a 100644 --- a/src/components/Search/SearchStatusBar.tsx +++ b/src/components/Search/SearchStatusBar.tsx @@ -13,7 +13,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type IconAsset from '@src/types/utils/IconAsset'; -import type {ExpenseSearchStatus, InvoiceSearchStatus, SearchQueryString, SearchStatus, TripSearchStatus} from './types'; +import type {ChatSearchStatus, ExpenseSearchStatus, InvoiceSearchStatus, SearchQueryString, SearchStatus, TripSearchStatus} from './types'; type SearchStatusBarProps = { type: SearchDataTypes; @@ -107,12 +107,54 @@ const tripOptions: Array<{key: TripSearchStatus; icon: IconAsset; text: Translat }, ]; +const chatOptions: Array<{key: ChatSearchStatus; icon: IconAsset; text: TranslationPaths; query: SearchQueryString}> = [ + { + key: CONST.SEARCH.STATUS.CHAT.ALL, + icon: Expensicons.All, + text: 'common.all', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.ALL), + }, + { + key: CONST.SEARCH.STATUS.CHAT.UNREAD, + icon: Expensicons.ChatBubbleUnread, + text: 'common.unread', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.UNREAD), + }, + // This will be added back in a future PR when we sync the draft across all platforms + // { + // key: CONST.SEARCH.STATUS.CHAT.DRAFTS, + // icon: Expensicons.Pencil, + // text: 'common.drafts', + // query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.DRAFTS), + // }, + { + key: CONST.SEARCH.STATUS.CHAT.SENT, + icon: Expensicons.Send, + text: 'common.sent', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.SENT), + }, + { + key: CONST.SEARCH.STATUS.CHAT.ATTACHMENTS, + icon: Expensicons.Document, + text: 'common.attachments', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.ATTACHMENTS), + }, + { + key: CONST.SEARCH.STATUS.CHAT.LINKS, + icon: Expensicons.Paperclip, + text: 'common.links', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.LINKS), + }, +]; + function getOptions(type: SearchDataTypes) { switch (type) { case CONST.SEARCH.DATA_TYPES.INVOICE: return invoiceOptions; case CONST.SEARCH.DATA_TYPES.TRIP: return tripOptions; + case CONST.SEARCH.DATA_TYPES.CHAT: + return chatOptions; case CONST.SEARCH.DATA_TYPES.EXPENSE: default: return expenseOptions; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index db729a9aa77d..6973284c5c59 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -6,7 +6,7 @@ import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import SearchTableHeader from '@components/SelectionList/SearchTableHeader'; -import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; import useLocalize from '@hooks/useLocalize'; @@ -54,7 +54,10 @@ function mapToTransactionItemWithSelectionInfo(item: TransactionListItemType, se return {...item, isSelected: selectedTransactions[item.keyForList]?.isSelected && canSelectMultiple}; } -function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean) { +function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListItemType | ReportActionListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean) { + if (SearchUtils.isReportActionListItemType(item)) { + return item; + } return SearchUtils.isTransactionListItemType(item) ? mapToTransactionItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple) : { @@ -142,8 +145,8 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { }; const getItemHeight = useCallback( - (item: TransactionListItemType | ReportListItemType) => { - if (SearchUtils.isTransactionListItemType(item)) { + (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { + if (SearchUtils.isTransactionListItemType(item) || SearchUtils.isReportActionListItemType(item)) { return isLargeScreenWidth ? variables.optionRowHeight + listItemPadding : transactionItemMobileHeight + listItemPadding; } @@ -161,7 +164,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { [isLargeScreenWidth], ); - const getItemHeightMemoized = memoize((item: TransactionListItemType | ReportListItemType) => getItemHeight(item), { + const getItemHeightMemoized = memoize((item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => getItemHeight(item), { transformKey: ([item]) => { // List items are displayed differently on "L"arge and "N"arrow screens so the height will differ // in addition the same items might be displayed as part of different Search screens ("Expenses", "All", "Finished") @@ -210,9 +213,9 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { return null; } - const ListItem = SearchUtils.getListItem(status); - const data = SearchUtils.getSections(status, searchResults.data, searchResults.search); - const sortedData = SearchUtils.getSortedSections(status, data, sortBy, sortOrder); + const ListItem = SearchUtils.getListItem(type, status); + const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search); + const sortedData = SearchUtils.getSortedSections(type, status, data, sortBy, sortOrder); const sortedSelectedData = sortedData.map((item) => mapToItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple)); const shouldShowEmptyState = !isDataLoaded || data.length === 0; @@ -234,7 +237,10 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { ); } - const toggleTransaction = (item: TransactionListItemType | ReportListItemType) => { + const toggleTransaction = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { + if (SearchUtils.isReportActionListItemType(item)) { + return; + } if (SearchUtils.isTransactionListItemType(item)) { if (!item.keyForList) { return; @@ -261,7 +267,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { }); }; - const openReport = (item: TransactionListItemType | ReportListItemType) => { + const openReport = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { let reportID = SearchUtils.isTransactionListItemType(item) && !item.isFromOneTransactionReport ? item.transactionThreadReportID : item.reportID; if (!reportID) { @@ -274,6 +280,12 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); } + if (SearchUtils.isReportActionListItemType(item)) { + const reportActionID = item.reportActionID; + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID, reportActionID)); + return; + } + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID)); }; @@ -326,9 +338,9 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { type={type} status={status} /> - + sections={[{data: sortedSelectedData, isDisabled: false}]} - turnOnSelectionModeOnLongPress + turnOnSelectionModeOnLongPress={type !== CONST.SEARCH.DATA_TYPES.CHAT} onTurnOnSelectionMode={(item) => item && toggleTransaction(item)} onCheckboxPress={toggleTransaction} onSelectAll={toggleAllTransactions} @@ -345,7 +357,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { /> ) } - canSelectMultiple={canSelectMultiple} + canSelectMultiple={type !== CONST.SEARCH.DATA_TYPES.CHAT && canSelectMultiple} customListHeaderHeight={searchHeaderHeight} // To enhance the smoothness of scrolling and minimize the risk of encountering blank spaces during scrolling, // we have configured a larger windowSize and a longer delay between batch renders. diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index c933deb8ee03..50564a9977e8 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -28,7 +28,8 @@ type SearchColumnType = ValueOf; type ExpenseSearchStatus = ValueOf; type InvoiceSearchStatus = ValueOf; type TripSearchStatus = ValueOf; -type SearchStatus = ExpenseSearchStatus | InvoiceSearchStatus | TripSearchStatus; +type ChatSearchStatus = ValueOf; +type SearchStatus = ExpenseSearchStatus | InvoiceSearchStatus | TripSearchStatus | ChatSearchStatus; type SearchContext = { currentSearchHash: number; @@ -87,4 +88,5 @@ export type { ExpenseSearchStatus, InvoiceSearchStatus, TripSearchStatus, + ChatSearchStatus, }; diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx new file mode 100644 index 000000000000..553fd7f0ec09 --- /dev/null +++ b/src/components/SelectionList/ChatListItem.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import {View} from 'react-native'; +import MultipleAvatars from '@components/MultipleAvatars'; +import TextWithTooltip from '@components/TextWithTooltip'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import ReportActionItemDate from '@pages/home/report/ReportActionItemDate'; +import ReportActionItemFragment from '@pages/home/report/ReportActionItemFragment'; +import CONST from '@src/CONST'; +import BaseListItem from './BaseListItem'; +import type {ChatListItemProps, ListItem, ReportActionListItemType} from './types'; + +function ChatListItem({ + item, + isFocused, + showTooltip, + isDisabled, + canSelectMultiple, + onSelectRow, + onDismissError, + onFocus, + onLongPressRow, + shouldSyncFocus, +}: ChatListItemProps) { + const reportActionItem = item as unknown as ReportActionListItemType; + const from = reportActionItem.from; + const icons = [ + { + type: CONST.ICON_TYPE_AVATAR, + source: from.avatar, + name: reportActionItem.formattedFrom, + id: from.accountID, + }, + ]; + const fragment = { + ...reportActionItem.message, + type: 'COMMENT', + }; + const styles = useThemeStyles(); + const theme = useTheme(); + const StyleUtils = useStyleUtils(); + + const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; + const hoveredBackgroundColor = styles.sidebarLinkHover?.backgroundColor ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; + + return ( + + {(hovered) => ( + <> + + + + + + + + + + + + )} + + ); +} + +ChatListItem.displayName = 'ChatListItem'; + +export default ChatListItem; diff --git a/src/components/SelectionList/SearchTableHeader.tsx b/src/components/SelectionList/SearchTableHeader.tsx index a5547eb9e710..d9565697a87f 100644 --- a/src/components/SelectionList/SearchTableHeader.tsx +++ b/src/components/SelectionList/SearchTableHeader.tsx @@ -85,6 +85,13 @@ const expenseHeaders: SearchColumnConfig[] = [ }, ]; +const SearchColumns = { + [CONST.SEARCH.DATA_TYPES.EXPENSE]: expenseHeaders, + [CONST.SEARCH.DATA_TYPES.INVOICE]: expenseHeaders, + [CONST.SEARCH.DATA_TYPES.TRIP]: expenseHeaders, + [CONST.SEARCH.DATA_TYPES.CHAT]: null, +}; + type SearchTableHeaderProps = { data: OnyxTypes.SearchResults['data']; metadata: OnyxTypes.SearchResults['search']; @@ -102,6 +109,10 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, onSortPress, shou const {translate} = useLocalize(); const displayNarrowVersion = isMediumScreenWidth || isSmallScreenWidth; + if (SearchColumns[metadata.type] === null) { + return; + } + if (displayNarrowVersion) { return; } @@ -109,7 +120,7 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, onSortPress, shou return ( - {expenseHeaders.map(({columnName, translationKey, shouldShow, isColumnSortable}) => { + {SearchColumns[metadata.type]?.map(({columnName, translationKey, shouldShow, isColumnSortable}) => { if (!shouldShow(data, metadata)) { return null; } diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index d1997aedfdf7..ab4193503f37 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -5,10 +5,11 @@ import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; import type CursorStyles from '@styles/utils/cursor/types'; import type CONST from '@src/CONST'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {SearchPersonalDetails, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; +import type {SearchPersonalDetails, SearchReport, SearchReportAction, SearchTransaction} from '@src/types/onyx/SearchResults'; import type {ReceiptErrors} from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type IconAsset from '@src/types/utils/IconAsset'; +import type ChatListItem from './ChatListItem'; import type InviteMemberListItem from './InviteMemberListItem'; import type RadioListItem from './RadioListItem'; import type ReportListItem from './Search/ReportListItem'; @@ -197,6 +198,21 @@ type TransactionListItemType = ListItem & keyForList: string; }; +type ReportActionListItemType = ListItem & + SearchReportAction & { + /** The personal details of the user posting comment */ + from: SearchPersonalDetails; + + /** final and formatted "from" value used for displaying and sorting */ + formattedFrom: string; + + /** final "date" value used for sorting */ + date: string; + + /** Key used internally by React */ + keyForList: string; + }; + type ReportListItemType = ListItem & SearchReport & { /** The personal details of the user requesting money */ @@ -268,7 +284,16 @@ type TransactionListItemProps = ListItemProps; type ReportListItemProps = ListItemProps; -type ValidListItem = typeof RadioListItem | typeof UserListItem | typeof TableListItem | typeof InviteMemberListItem | typeof TransactionListItem | typeof ReportListItem; +type ChatListItemProps = ListItemProps; + +type ValidListItem = + | typeof RadioListItem + | typeof UserListItem + | typeof TableListItem + | typeof InviteMemberListItem + | typeof TransactionListItem + | typeof ReportListItem + | typeof ChatListItem; type Section = { /** Title of the section */ @@ -544,4 +569,6 @@ export type { TransactionListItemType, UserListItemProps, ValidListItem, + ReportActionListItemType, + ChatListItemProps, }; diff --git a/src/languages/en.ts b/src/languages/en.ts index baba4bebd653..7715d87bae2b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -144,6 +144,7 @@ export default { buttonConfirm: 'Got it', name: 'Name', attachment: 'Attachment', + attachments: 'Attachments', center: 'Center', from: 'From', to: 'To', @@ -376,6 +377,10 @@ export default { network: 'Network', reportID: 'Report ID', outstanding: 'Outstanding', + chats: 'Chats', + unread: 'Unread', + sent: 'Sent', + links: 'Links', }, location: { useCurrent: 'Use current location', diff --git a/src/languages/es.ts b/src/languages/es.ts index 877783a84725..8dd069f1fa6f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -134,6 +134,7 @@ export default { buttonConfirm: 'Ok, entendido', name: 'Nombre', attachment: 'Archivo adjunto', + attachments: 'Archivos adjuntos', from: 'De', to: 'A', optional: 'Opcional', @@ -366,6 +367,10 @@ export default { network: 'La red', reportID: 'ID del informe', outstanding: 'Pendiente', + chats: 'Chats', + unread: 'No leído', + sent: 'Enviado', + links: 'Enlaces', }, connectionComplete: { title: 'Conexión completa', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b689f36d8a35..4098a0212aa4 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1301,6 +1301,7 @@ type AuthScreensParamList = CentralPaneScreensParamList & type SearchReportParamList = { [SCREENS.SEARCH.REPORT_RHP]: { reportID: string; + reportActionID?: string; }; }; diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 48af780eab5b..8a7e1ac2603c 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -1,8 +1,9 @@ import type {ValueOf} from 'type-fest'; import type {ASTNode, QueryFilter, QueryFilters, SearchColumnType, SearchQueryJSON, SearchQueryString, SearchStatus, SortOrder} from '@components/Search/types'; +import ChatListItem from '@components/SelectionList/ChatListItem'; import ReportListItem from '@components/SelectionList/Search/ReportListItem'; import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; -import type {ListItem, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ListItem, ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -76,10 +77,32 @@ function getTransactionItemCommonFormattedProperties( }; } +type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; + +type TransactionKey = `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`; + +type ReportActionKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS}${string}`; + +function isReportEntry(key: string): key is ReportKey { + return key.startsWith(ONYXKEYS.COLLECTION.REPORT); +} + +function isTransactionEntry(key: string): key is TransactionKey { + return key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION); +} + +function isReportActionEntry(key: string): key is ReportActionKey { + return key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS); +} + function getShouldShowMerchant(data: OnyxTypes.SearchResults['data']): boolean { - return Object.values(data).some((item) => { - const merchant = item.modifiedMerchant ? item.modifiedMerchant : item.merchant ?? ''; - return merchant !== '' && merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && merchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; + return Object.keys(data).some((key) => { + if (isTransactionEntry(key)) { + const item = data[key]; + const merchant = item.modifiedMerchant ? item.modifiedMerchant : item.merchant ?? ''; + return merchant !== '' && merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && merchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; + } + return false; }); } @@ -89,11 +112,16 @@ function isReportListItemType(item: ListItem): item is ReportListItemType { return 'transactions' in item; } -function isTransactionListItemType(item: TransactionListItemType | ReportListItemType): item is TransactionListItemType { +function isTransactionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is TransactionListItemType { const transactionListItem = item as TransactionListItemType; return transactionListItem.transactionID !== undefined; } +function isReportActionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is ReportActionListItemType { + const reportActionListItem = item as ReportActionListItemType; + return reportActionListItem.reportActionID !== undefined; +} + function shouldShowYear(data: TransactionListItemType[] | ReportListItemType[] | OnyxTypes.SearchResults['data']): boolean { if (Array.isArray(data)) { return data.some((item: TransactionListItemType | ReportListItemType) => { @@ -110,14 +138,23 @@ function shouldShowYear(data: TransactionListItemType[] | ReportListItemType[] | }); } - for (const [key, transactionItem] of Object.entries(data)) { - if (key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)) { - const item = transactionItem as SearchTransaction; + for (const key in data) { + if (isTransactionEntry(key)) { + const item = data[key]; const date = TransactionUtils.getCreated(item); if (DateUtils.doesDateBelongToAPastYear(date)) { return true; } + } else if (isReportActionEntry(key)) { + const item = data[key]; + for (const action of Object.values(item)) { + const date = action.created; + + if (DateUtils.doesDateBelongToAPastYear(date)) { + return true; + } + } } } return false; @@ -128,9 +165,10 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata const doesDataContainAPastYearTransaction = shouldShowYear(data); - return Object.entries(data) - .filter(([key]) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)) - .map(([, transactionItem]) => { + return Object.keys(data) + .filter(isTransactionEntry) + .map((key) => { + const transactionItem = data[key]; const from = data.personalDetailsList?.[transactionItem.accountID]; const to = data.personalDetailsList?.[transactionItem.managerID]; @@ -155,6 +193,31 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata }); } +function getReportActionsSections(data: OnyxTypes.SearchResults['data']): ReportActionListItemType[] { + const reportActionItems: ReportActionListItemType[] = []; + for (const key in data) { + if (isReportActionEntry(key)) { + const reportActions = data[key]; + for (const reportAction of Object.values(reportActions)) { + const from = data.personalDetailsList?.[reportAction.accountID] ?? { + accountID: 16802863, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_8.png', + displayName: 'Shubham Agrawal', + login: 'work.sa1206+sdm3@gmail.com', + }; + reportActionItems.push({ + ...reportAction, + from, + formattedFrom: from?.displayName ?? from?.login ?? '', + date: reportAction.created, + keyForList: reportAction.reportActionID, + }); + } + } + } + return reportActionItems; +} + function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: SearchReport) { const payerPersonalDetails = data.personalDetailsList?.[reportItem.managerID ?? 0]; const payerName = payerPersonalDetails?.displayName ?? payerPersonalDetails?.login ?? translateLocal('common.hidden'); @@ -183,7 +246,7 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx const reportIDToTransactions: Record = {}; for (const key in data) { - if (key.startsWith(ONYXKEYS.COLLECTION.REPORT)) { + if (isReportEntry(key)) { const reportItem = {...data[key]}; const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportItem.reportID}`; const transactions = reportIDToTransactions[reportKey]?.transactions ?? []; @@ -192,12 +255,12 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx reportIDToTransactions[reportKey] = { ...reportItem, keyForList: reportItem.reportID, - from: data.personalDetailsList?.[reportItem.accountID], - to: data.personalDetailsList?.[reportItem.managerID], + from: data.personalDetailsList?.[reportItem.accountID ?? -1], + to: data.personalDetailsList?.[reportItem.managerID ?? -1], transactions, reportName: isIOUReport ? getIOUReportName(data, reportItem) : reportItem.reportName, }; - } else if (key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)) { + } else if (isTransactionEntry(key)) { const transactionItem = {...data[key]}; const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`; @@ -233,15 +296,24 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx return Object.values(reportIDToTransactions); } -function getListItem(status: SearchStatus): ListItemType { +function getListItem(type: SearchDataTypes, status: SearchStatus): ListItemType { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return ChatListItem; + } return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? TransactionListItem : ReportListItem; } -function getSections(status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { +function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return getReportActionsSections(data); + } return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getTransactionsSections(data, metadata) : getReportSections(data, metadata); } -function getSortedSections(status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { +function getSortedSections(type: SearchDataTypes, status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return data; + } return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder) : getSortedReportData(data as ReportListItemType[]); } @@ -544,6 +616,7 @@ export { isReportListItemType, isSearchResultsEmpty, isTransactionListItemType, + isReportActionListItemType, normalizeQuery, shouldShowYear, buildCannedSearchQuery, diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index 88b2a38b3603..1f259c96d625 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -32,6 +32,7 @@ function EmptySearchView({type}: EmptySearchViewProps) { buttonText: translate('search.searchResults.emptyTripResults.buttonText'), buttonAction: () => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS), }; + case CONST.SEARCH.DATA_TYPES.CHAT: case CONST.SEARCH.DATA_TYPES.EXPENSE: case CONST.SEARCH.DATA_TYPES.INVOICE: default: diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index a2d84209114b..45d136fe5f25 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -43,6 +43,12 @@ function SearchTypeMenu({queryJSON, isCustomQuery}: SearchTypeMenuProps) { icon: Expensicons.Receipt, route: ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()}), }, + { + title: translate('common.chats'), + type: CONST.SEARCH.DATA_TYPES.CHAT, + icon: Expensicons.ChatBubbles, + route: ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.TRIP.ALL)}), + }, { title: translate('workspace.common.invoices'), type: CONST.SEARCH.DATA_TYPES.INVOICE, diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index e67b5b30f1d6..332d74b8a9bb 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -1,18 +1,28 @@ import type {ValueOf} from 'type-fest'; import type {SearchStatus} from '@components/Search/types'; +import type ChatListItem from '@components/SelectionList/ChatListItem'; import type ReportListItem from '@components/SelectionList/Search/ReportListItem'; import type TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; -import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import type CONST from '@src/CONST'; +import type ONYXKEYS from '@src/ONYXKEYS'; /** Types of search data */ type SearchDataTypes = ValueOf; /** Model of search result list item */ -type ListItemType = T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL ? typeof TransactionListItem : typeof ReportListItem; +type ListItemType = C extends typeof CONST.SEARCH.DATA_TYPES.CHAT + ? typeof ChatListItem + : T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL + ? typeof TransactionListItem + : typeof ReportListItem; /** Model of search list item data type */ -type ListItemDataType = T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL ? TransactionListItemType[] : ReportListItemType[]; +type ListItemDataType = C extends typeof CONST.SEARCH.DATA_TYPES.CHAT + ? ReportActionListItemType[] + : T extends typeof CONST.SEARCH.STATUS.EXPENSE.ALL + ? TransactionListItemType[] + : ReportListItemType[]; /** Model of columns to show for search results */ type ColumnsToShow = { @@ -98,6 +108,30 @@ type SearchReport = { action?: SearchTransactionAction; }; +/** Model of report action search result */ +type SearchReportAction = { + /** The report action sender ID */ + accountID: number; + + /** The report action created date */ + created: string; + + /** report action message */ + message: { + /** The text content of the fragment. */ + text: string; + + /** The html content of the fragment. */ + html: string; + }; + + /** The ID of the report action */ + reportActionID: string; + + /** The ID of the report */ + reportID: string; +}; + /** Model of transaction search result */ type SearchTransaction = { /** The ID of the transaction */ @@ -212,13 +246,23 @@ type SearchTransaction = { /** Types of searchable transactions */ type SearchTransactionType = ValueOf; +/** + * + */ +type PrefixedRecord = { + [Key in `${Prefix}${string}`]: ValueType; +}; + /** Model of search results */ type SearchResults = { /** Current search results state */ search: SearchResultsInfo; /** Search results data */ - data: Record> & Record; + data: PrefixedRecord & + Record> & + PrefixedRecord> & + PrefixedRecord; /** Whether search data is being fetched from server */ isLoading?: boolean; @@ -226,4 +270,4 @@ type SearchResults = { export default SearchResults; -export type {ListItemType, ListItemDataType, SearchTransaction, SearchTransactionType, SearchTransactionAction, SearchPersonalDetails, SearchDataTypes, SearchReport}; +export type {ListItemType, ListItemDataType, SearchTransaction, SearchTransactionType, SearchTransactionAction, SearchPersonalDetails, SearchDataTypes, SearchReport, SearchReportAction}; From 6c5c25f8ce02c9ffce72664fad772a1c9f207913 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 20 Aug 2024 14:47:05 +0530 Subject: [PATCH 02/11] Added docstring --- src/types/onyx/SearchResults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 332d74b8a9bb..744d3289beca 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -247,7 +247,7 @@ type SearchTransaction = { type SearchTransactionType = ValueOf; /** - * + * A utility type that creates a record where all keys are strings that start with a specified prefix. */ type PrefixedRecord = { [Key in `${Prefix}${string}`]: ValueType; From 91acac1243cb6a6f5a74285f7cc23d4369f990c3 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Tue, 27 Aug 2024 20:56:01 +0530 Subject: [PATCH 03/11] Fix ts --- src/components/Icon/Illustrations.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 4374013db4cc..8e24f643d50b 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -55,7 +55,6 @@ import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__ import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg'; import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg'; import CommentBubblesBlue from '@assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg'; -import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__company-card.svg'; import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg'; import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg'; import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; From a7e0672ef22edeb7b8f867008da2d3ed47529948 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 28 Aug 2024 08:58:20 +0530 Subject: [PATCH 04/11] Removing the default fallback data --- src/libs/SearchUtils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 1fb5ff4454e1..0a74b43d0504 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -199,12 +199,7 @@ function getReportActionsSections(data: OnyxTypes.SearchResults['data']): Report if (isReportActionEntry(key)) { const reportActions = data[key]; for (const reportAction of Object.values(reportActions)) { - const from = data.personalDetailsList?.[reportAction.accountID] ?? { - accountID: 16802863, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_8.png', - displayName: 'Shubham Agrawal', - login: 'work.sa1206+sdm3@gmail.com', - }; + const from = data.personalDetailsList?.[reportAction.accountID]; reportActionItems.push({ ...reportAction, from, From 125747734466d84502a36c2deebeb9e89f659eb1 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 28 Aug 2024 12:20:02 +0530 Subject: [PATCH 05/11] Partially fix of styles --- src/components/SelectionList/ChatListItem.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index 553fd7f0ec09..3a288df8f927 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -86,13 +86,15 @@ function ChatListItem({ - + + + )} From 04d9ff180ca062a3b28cff595d3d3ba8b812a999 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 31 Aug 2024 10:34:03 +0530 Subject: [PATCH 06/11] Fixed centered links --- src/components/SelectionList/ChatListItem.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index 3a288df8f927..5825965aa146 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -47,7 +47,9 @@ function ChatListItem({ return ( Date: Sun, 1 Sep 2024 20:00:02 +0530 Subject: [PATCH 07/11] Added opening attachment from search window --- src/CONST.ts | 1 + src/components/AttachmentModal.tsx | 7 ++-- .../HTMLRenderers/ImageRenderer.tsx | 6 ++-- .../HTMLRenderers/VideoRenderer.tsx | 34 ++++++++++++------- src/components/SelectionList/ChatListItem.tsx | 7 ++-- src/pages/home/report/ReportAttachments.tsx | 4 ++- 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0c28f3daeaec..0858fbeec742 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1255,6 +1255,7 @@ const CONST = { ATTACHMENT_TYPE: { REPORT: 'r', NOTE: 'n', + SEARCH: 's', }, IMAGE_HIGH_RESOLUTION_THRESHOLD: 7000, diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index d57eed225580..0191c6ac2066 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -246,13 +246,14 @@ function AttachmentModal({ } if (typeof sourceURL === 'string') { - fileDownload(sourceURL, file?.name ?? ''); + const fileName = type === CONST.ATTACHMENT_TYPE.SEARCH ? FileUtils.getFileName(`${sourceURL}`) : file?.name; + fileDownload(sourceURL, fileName ?? ''); } // At ios, if the keyboard is open while opening the attachment, then after downloading // the attachment keyboard will show up. So, to fix it we need to dismiss the keyboard. Keyboard.dismiss(); - }, [isAuthTokenRequiredState, sourceState, file]); + }, [isAuthTokenRequiredState, sourceState, file, type]); /** * Execute the onConfirm callback and close the modal. @@ -459,7 +460,7 @@ function AttachmentModal({ let headerTitleNew = headerTitle; let shouldShowDownloadButton = false; let shouldShowThreeDotsButton = false; - if (!isEmptyObject(report)) { + if (!isEmptyObject(report) || type === CONST.ATTACHMENT_TYPE.SEARCH) { headerTitleNew = translate(isReceiptAttachment ? 'common.receipt' : 'common.attachment'); shouldShowDownloadButton = allowDownload && isDownloadButtonReadyToBeShown && !shouldShowNotFoundPage && !isReceiptAttachment && !isOffline && !isLocalSource; shouldShowThreeDotsButton = isReceiptAttachment && isModalOpen && threeDotsMenuItems.length !== 0; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 771d2631379e..99699b9ef3c6 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -90,10 +90,8 @@ function ImageRenderer({tnode}: ImageRendererProps) { return; } - if (reportID) { - const route = ROUTES.ATTACHMENTS?.getRoute(reportID, type, source, accountID); - Navigation.navigate(route); - } + const route = ROUTES.ATTACHMENTS?.getRoute(reportID ?? '-1', type, source, accountID); + Navigation.navigate(route); }} onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx index e0df7e7081c5..ce822af14cb8 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type {CustomRendererProps, TBlock} from 'react-native-render-html'; +import {AttachmentContext} from '@components/AttachmentContext'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import VideoPlayerPreview from '@components/VideoPlayerPreview'; import useCurrentReportID from '@hooks/useCurrentReportID'; @@ -28,19 +29,26 @@ function VideoRenderer({tnode, key}: VideoRendererProps) { return ( {({report}) => ( - { - const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID ?? '-1', CONST.ATTACHMENT_TYPE.REPORT, sourceURL); - Navigation.navigate(route); - }} - /> + + {({accountID, type}) => ( + { + if (!sourceURL || !type) { + return; + } + const route = ROUTES.ATTACHMENTS.getRoute(report?.reportID ?? '-1', type, sourceURL, accountID); + Navigation.navigate(route); + }} + /> + )} + )} ); diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index 5825965aa146..eda6b934f384 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import {AttachmentContext} from '@components/AttachmentContext'; import MultipleAvatars from '@components/MultipleAvatars'; import TextWithTooltip from '@components/TextWithTooltip'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -41,6 +42,8 @@ function ChatListItem({ const theme = useTheme(); const StyleUtils = useStyleUtils(); + const attachmentContextValue = {type: CONST.ATTACHMENT_TYPE.SEARCH}; + const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hoveredBackgroundColor = styles.sidebarLinkHover?.backgroundColor ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; @@ -67,7 +70,7 @@ function ChatListItem({ hoverStyle={item.isSelected && styles.activeComponentBG} > {(hovered) => ( - <> + ({ /> - + )} ); diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index 7140cd2d45c4..369d5cef6ee4 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -6,6 +6,7 @@ import type {Attachment} from '@components/Attachments/types'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -55,7 +56,8 @@ function ReportAttachments({route}: ReportAttachmentsProps) { ComposerFocusManager.setReadyToFocus(); }} onCarouselAttachmentChange={onCarouselAttachmentChange} - shouldShowNotFoundPage={!isLoadingApp && !report?.reportID} + shouldShowNotFoundPage={!isLoadingApp && type !== CONST.ATTACHMENT_TYPE.SEARCH && !report?.reportID} + isAuthTokenRequired={type === CONST.ATTACHMENT_TYPE.SEARCH} /> ); } From b5904b213d5e8be2c74d1849f8ab77d4852d151c Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Sep 2024 12:00:35 +0530 Subject: [PATCH 08/11] Changed the structure of message --- src/components/SelectionList/ChatListItem.tsx | 22 +++++++++---------- src/types/onyx/SearchResults.ts | 14 ++++++++++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index eda6b934f384..1a27e0ecbfcf 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -34,10 +34,6 @@ function ChatListItem({ id: from.accountID, }, ]; - const fragment = { - ...reportActionItem.message, - type: 'COMMENT', - }; const styles = useThemeStyles(); const theme = useTheme(); const StyleUtils = useStyleUtils(); @@ -92,13 +88,17 @@ function ChatListItem({ - + {reportActionItem.message.map((fragment, index) => ( + + ))} diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 744d3289beca..98e3aa66fa00 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -6,6 +6,7 @@ import type TransactionListItem from '@components/SelectionList/Search/Transacti import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import type CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; +import type ReportActionName from './ReportActionName'; /** Types of search data */ type SearchDataTypes = ValueOf; @@ -113,17 +114,26 @@ type SearchReportAction = { /** The report action sender ID */ accountID: number; + /** The name (or type) of the action */ + actionName: ReportActionName; + /** The report action created date */ created: string; /** report action message */ - message: { + message: Array<{ + /** The type of the action item fragment. Used to render a corresponding component */ + type: string; + /** The text content of the fragment. */ text: string; /** The html content of the fragment. */ html: string; - }; + + /** Collection of accountIDs of users mentioned in message */ + whisperedTo?: number[]; + }>; /** The ID of the report action */ reportActionID: string; From 2e3ec6ff95a50b130085a3c97e30719780532975 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:03:50 +0530 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Carlos Martins --- src/CONST.ts | 1 - src/components/Search/SearchStatusBar.tsx | 7 ------- 2 files changed, 8 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d396ac7a88b4..86c5bb8d2a0c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5383,7 +5383,6 @@ const CONST = { CHAT: { ALL: 'all', UNREAD: 'unread', - DRAFTS: 'drafts', SENT: 'sent', ATTACHMENTS: 'attachments', LINKS: 'links', diff --git a/src/components/Search/SearchStatusBar.tsx b/src/components/Search/SearchStatusBar.tsx index 9ef1f2abb84a..27c91bbf3ee0 100644 --- a/src/components/Search/SearchStatusBar.tsx +++ b/src/components/Search/SearchStatusBar.tsx @@ -120,13 +120,6 @@ const chatOptions: Array<{key: ChatSearchStatus; icon: IconAsset; text: Translat text: 'common.unread', query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.UNREAD), }, - // This will be added back in a future PR when we sync the draft across all platforms - // { - // key: CONST.SEARCH.STATUS.CHAT.DRAFTS, - // icon: Expensicons.Pencil, - // text: 'common.drafts', - // query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.CHAT, CONST.SEARCH.STATUS.CHAT.DRAFTS), - // }, { key: CONST.SEARCH.STATUS.CHAT.SENT, icon: Expensicons.Send, From c62d93bfabd46e3633dc48e8788322c92e30efc6 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Sep 2024 19:49:49 +0530 Subject: [PATCH 10/11] Fixed offset not reseting on switching tabs --- src/components/Search/SearchStatusBar.tsx | 8 ++++++-- src/components/Search/index.tsx | 7 +++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchStatusBar.tsx b/src/components/Search/SearchStatusBar.tsx index 27c91bbf3ee0..0d2b8120609f 100644 --- a/src/components/Search/SearchStatusBar.tsx +++ b/src/components/Search/SearchStatusBar.tsx @@ -18,6 +18,7 @@ import type {ChatSearchStatus, ExpenseSearchStatus, InvoiceSearchStatus, SearchQ type SearchStatusBarProps = { type: SearchDataTypes; status: SearchStatus; + resetOffset: () => void; }; const expenseOptions: Array<{key: ExpenseSearchStatus; icon: IconAsset; text: TranslationPaths; query: SearchQueryString}> = [ @@ -154,7 +155,7 @@ function getOptions(type: SearchDataTypes) { } } -function SearchStatusBar({type, status}: SearchStatusBarProps) { +function SearchStatusBar({type, status, resetOffset}: SearchStatusBarProps) { const {singleExecution} = useSingleExecution(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -169,7 +170,10 @@ function SearchStatusBar({type, status}: SearchStatusBarProps) { showsHorizontalScrollIndicator={false} > {options.map((item, index) => { - const onPress = singleExecution(() => Navigation.setParams({q: item.query})); + const onPress = singleExecution(() => { + resetOffset(); + Navigation.setParams({q: item.query}); + }); const isActive = status === item.key; const isFirstItem = index === 0; const isLastItem = index === options.length - 1; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index ca7ad94a13be..f2094b49d4b1 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -164,6 +164,10 @@ function Search({queryJSON}: SearchProps) { [isLargeScreenWidth], ); + const resetOffset = useCallback(() => { + setOffset(0); + }, []); + const getItemHeightMemoized = memoize(getItemHeight, { transformKey: ([item]) => { // List items are displayed differently on "L"arge and "N"arrow screens so the height will differ @@ -208,6 +212,7 @@ function Search({queryJSON}: SearchProps) { ) : ( @@ -239,6 +244,7 @@ function Search({queryJSON}: SearchProps) { @@ -344,6 +350,7 @@ function Search({queryJSON}: SearchProps) { sections={[{data: sortedSelectedData, isDisabled: false}]} From a8b5914b048987296e0288765b8511feefa91da2 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Sep 2024 20:13:37 +0530 Subject: [PATCH 11/11] Fixed react-compiler error --- src/components/Search/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index f2094b49d4b1..4a98d651e2b2 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -164,9 +164,7 @@ function Search({queryJSON}: SearchProps) { [isLargeScreenWidth], ); - const resetOffset = useCallback(() => { - setOffset(0); - }, []); + const resetOffset = () => setOffset(0); const getItemHeightMemoized = memoize(getItemHeight, { transformKey: ([item]) => {