diff --git a/assets/images/simple-illustrations/simple-illustration__envelopereceipt.svg b/assets/images/simple-illustrations/simple-illustration__envelopereceipt.svg new file mode 100644 index 000000000000..eb2bad31620d --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__envelopereceipt.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CONST.ts b/src/CONST.ts index f1e4a3bb46d5..22eb8215c6f0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5243,8 +5243,6 @@ const CONST = { SEARCH: { RESULTS_PAGE_SIZE: 50, DATA_TYPES: { - TRANSACTION: 'transaction', - REPORT: 'report', EXPENSE: 'expense', INVOICE: 'invoice', TRIP: 'trip', @@ -5273,9 +5271,10 @@ const CONST = { STATUS: { EXPENSE: { ALL: 'all', - SHARED: 'shared', DRAFTS: 'drafts', - FINISHED: 'finished', + OUTSTANDING: 'outstanding', + APPROVED: 'approved', + PAID: 'paid', }, INVOICE: { ALL: 'all', @@ -5290,14 +5289,6 @@ const CONST = { PAID: 'paid', }, }, - TAB: { - EXPENSE: { - ALL: 'type:expense status:all', - SHARED: 'type:expense status:shared', - DRAFTS: 'type:expense status:drafts', - FINISHED: 'type:expense status:finished', - }, - }, TABLE_COLUMNS: { RECEIPT: 'receipt', DATE: 'date', diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 126c81961cee..1441266c76de 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -29,6 +29,9 @@ type ButtonProps = Partial & { /** The fill color to pass into the icon. */ iconFill?: string; + /** The fill color to pass into the icon when the button is hovered. */ + iconHoverFill?: string; + /** Any additional styles to pass to the left icon container. */ iconStyles?: StyleProp; @@ -80,9 +83,15 @@ type ButtonProps = Partial & { /** Additional text styles */ textStyles?: StyleProp; + /** Additional text styles when the button is hovered */ + textHoverStyles?: StyleProp; + /** Whether we should use the default hover style */ shouldUseDefaultHover?: boolean; + /** Additional hover styles */ + hoverStyles?: StyleProp; + /** Whether we should use the success theme color */ success?: boolean; @@ -170,6 +179,7 @@ function Button( iconRight = Expensicons.ArrowRight, iconFill, + iconHoverFill, icon = null, iconStyles = [], iconRightStyles = [], @@ -194,8 +204,10 @@ function Button( style = [], innerStyles = [], textStyles = [], + textHoverStyles = [], shouldUseDefaultHover = true, + hoverStyles = undefined, success = false, danger = false, @@ -238,6 +250,7 @@ function Button( danger && styles.buttonDangerText, !!icon && styles.textAlignLeft, textStyles, + isHovered && textHoverStyles, link && styles.link, link && isHovered && StyleUtils.getColorStyle(theme.linkHover), link && styles.fontWeightNormal, @@ -259,7 +272,7 @@ function Button( ) : ( & { subtitleStyles?: StyleProp; @@ -100,13 +100,23 @@ type SearchPageHeaderProps = { type SearchHeaderOptionValue = DeepValueOf | undefined; -const headerContent: {[key in SearchStatus]: {icon: IconAsset; titleText: TranslationPaths}} = { - all: {icon: Illustrations.MoneyReceipts, titleText: 'common.expenses'}, - shared: {icon: Illustrations.SendMoney, titleText: 'common.shared'}, - drafts: {icon: Illustrations.Pencil, titleText: 'common.drafts'}, - finished: {icon: Illustrations.CheckmarkCircle, titleText: 'common.finished'}, +type HeaderContent = { + icon: IconAsset; + titleText: TranslationPaths; }; +function getHeaderContent(type: SearchDataTypes): HeaderContent { + switch (type) { + case CONST.SEARCH.DATA_TYPES.INVOICE: + 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.EXPENSE: + default: + return {icon: Illustrations.MoneyReceipts, titleText: 'common.expenses'}; + } +} + function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModalOpen, setDownloadErrorModalOpen, isCustomQuery, data}: SearchPageHeaderProps) { const {translate} = useLocalize(); const theme = useTheme(); @@ -131,10 +141,10 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa .map((item) => item.reportID), [data, selectedTransactions], ); - const {status} = queryJSON; - const headerSubtitle = isCustomQuery ? SearchUtils.getSearchHeaderTitle(queryJSON) : translate(headerContent[status]?.titleText); + const {status, type} = queryJSON; + const headerSubtitle = isCustomQuery ? SearchUtils.getSearchHeaderTitle(queryJSON) : translate(getHeaderContent(type).titleText); const headerTitle = isCustomQuery ? translate('search.filtersHeader') : ''; - const headerIcon = isCustomQuery ? Illustrations.Filters : headerContent[status]?.icon; + const headerIcon = isCustomQuery ? Illustrations.Filters : getHeaderContent(type).icon; const subtitleStyles = isCustomQuery ? {} : styles.textHeadlineH2; diff --git a/src/components/Search/SearchStatusBar.tsx b/src/components/Search/SearchStatusBar.tsx new file mode 100644 index 000000000000..7c1ffeff1818 --- /dev/null +++ b/src/components/Search/SearchStatusBar.tsx @@ -0,0 +1,166 @@ +import React from 'react'; +import Button from '@components/Button'; +import * as Expensicons from '@components/Icon/Expensicons'; +import ScrollView from '@components/ScrollView'; +import useLocalize from '@hooks/useLocalize'; +import useSingleExecution from '@hooks/useSingleExecution'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import * as SearchUtils from '@libs/SearchUtils'; +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'; + +type SearchStatusBarProps = { + type: SearchDataTypes; + status: SearchStatus; +}; + +const expenseOptions: Array<{key: ExpenseSearchStatus; icon: IconAsset; text: TranslationPaths; query: SearchQueryString}> = [ + { + key: CONST.SEARCH.STATUS.EXPENSE.ALL, + icon: Expensicons.All, + text: 'common.all', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.EXPENSE, CONST.SEARCH.STATUS.EXPENSE.ALL), + }, + { + key: CONST.SEARCH.STATUS.EXPENSE.DRAFTS, + icon: Expensicons.Pencil, + text: 'common.drafts', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.EXPENSE, CONST.SEARCH.STATUS.EXPENSE.DRAFTS), + }, + { + key: CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING, + icon: Expensicons.Hourglass, + text: 'common.outstanding', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.EXPENSE, CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING), + }, + { + key: CONST.SEARCH.STATUS.EXPENSE.APPROVED, + icon: Expensicons.ThumbsUp, + text: 'iou.approved', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.EXPENSE, CONST.SEARCH.STATUS.EXPENSE.APPROVED), + }, + { + key: CONST.SEARCH.STATUS.EXPENSE.PAID, + icon: Expensicons.MoneyBag, + text: 'iou.settledExpensify', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.EXPENSE, CONST.SEARCH.STATUS.EXPENSE.PAID), + }, +]; + +const invoiceOptions: Array<{key: InvoiceSearchStatus; icon: IconAsset; text: TranslationPaths; query: SearchQueryString}> = [ + { + key: CONST.SEARCH.STATUS.INVOICE.ALL, + icon: Expensicons.All, + text: 'common.all', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.INVOICE, CONST.SEARCH.STATUS.INVOICE.ALL), + }, + { + key: CONST.SEARCH.STATUS.INVOICE.OUTSTANDING, + icon: Expensicons.Hourglass, + text: 'common.outstanding', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.INVOICE, CONST.SEARCH.STATUS.INVOICE.OUTSTANDING), + }, + { + key: CONST.SEARCH.STATUS.INVOICE.PAID, + icon: Expensicons.MoneyBag, + text: 'iou.settledExpensify', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.INVOICE, CONST.SEARCH.STATUS.INVOICE.PAID), + }, +]; + +const tripOptions: Array<{key: TripSearchStatus; icon: IconAsset; text: TranslationPaths; query: SearchQueryString}> = [ + { + key: CONST.SEARCH.STATUS.TRIP.ALL, + icon: Expensicons.All, + text: 'common.all', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.TRIP, CONST.SEARCH.STATUS.TRIP.ALL), + }, + { + key: CONST.SEARCH.STATUS.TRIP.DRAFTS, + icon: Expensicons.Pencil, + text: 'common.drafts', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.TRIP, CONST.SEARCH.STATUS.TRIP.DRAFTS), + }, + { + key: CONST.SEARCH.STATUS.TRIP.OUTSTANDING, + icon: Expensicons.Hourglass, + text: 'common.outstanding', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.TRIP, CONST.SEARCH.STATUS.TRIP.OUTSTANDING), + }, + { + key: CONST.SEARCH.STATUS.TRIP.APPROVED, + icon: Expensicons.ThumbsUp, + text: 'iou.approved', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.TRIP, CONST.SEARCH.STATUS.TRIP.APPROVED), + }, + { + key: CONST.SEARCH.STATUS.TRIP.PAID, + icon: Expensicons.MoneyBag, + text: 'iou.settledExpensify', + query: SearchUtils.buildCannedSearchQuery(CONST.SEARCH.DATA_TYPES.TRIP, CONST.SEARCH.STATUS.TRIP.PAID), + }, +]; + +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.EXPENSE: + default: + return expenseOptions; + } +} + +function SearchStatusBar({type, status}: SearchStatusBarProps) { + const {singleExecution} = useSingleExecution(); + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const theme = useTheme(); + const {translate} = useLocalize(); + const options = getOptions(type); + + return ( + + {options.map((item, index) => { + const onPress = singleExecution(() => Navigation.setParams({q: item.query})); + const isActive = status === item.key; + const isFirstItem = index === 0; + const isLastItem = index === options.length - 1; + + return ( +