diff --git a/app/src/__tests__/__mocks__/utils/spotlight.ts b/app/src/__tests__/__mocks__/utils/spotlight.ts index aa9ea602..d09b7c89 100644 --- a/app/src/__tests__/__mocks__/utils/spotlight.ts +++ b/app/src/__tests__/__mocks__/utils/spotlight.ts @@ -1,22 +1,43 @@ import type { SpotlightItem } from '@/types/api'; -import type { SpotlightTemplateName } from '@/types/spotlight'; - -export const categories: { name: string; templates: SpotlightTemplateName[] }[] = [ - { name: 'orders', templates: ['savedOrdersClient', 'queryingOrders'] }, - { name: 'subscriptions', templates: ['renewingSubscriptions', 'expiringSubscriptions'] }, +import type { AppScreensParamList } from '@/types/navigation'; +import type { SpotlightTemplateName, SpotlightCategoryName } from '@/types/spotlight'; + +export const categories: { + name: SpotlightCategoryName; + templates: SpotlightTemplateName[]; + detailsScreenName: keyof AppScreensParamList; +}[] = [ + { + name: 'orders', + templates: ['savedOrdersClient', 'queryingOrders'], + detailsScreenName: 'orderDetails', + }, + { + name: 'subscriptions', + templates: ['renewingSubscriptions', 'expiringSubscriptions'], + detailsScreenName: 'subscriptionDetails', + }, ]; -export const duplicateCategories: { name: string; templates: SpotlightTemplateName[] }[] = [ - { name: 'cat1', templates: ['savedOrdersClient'] }, - { name: 'cat2', templates: ['savedOrdersClient'] }, +export const duplicateCategories: { + name: SpotlightCategoryName; + templates: SpotlightTemplateName[]; + detailsScreenName: keyof AppScreensParamList; +}[] = [ + { name: 'orders', templates: ['savedOrdersClient'], detailsScreenName: 'orderDetails' }, + { + name: 'subscriptions', + templates: ['savedOrdersClient'], + detailsScreenName: 'subscriptionDetails', + }, ]; -export const categoryLookup: Record = { +export const categoryLookup: Record = { savedOrdersClient: 'orders', queryingOrders: 'orders', renewingSubscriptions: 'subscriptions', expiringSubscriptions: 'subscriptions', -} as Record; +} as Record; export const spotlightItem1: SpotlightItem = { id: '1', diff --git a/app/src/__tests__/utils/spotlight.test.ts b/app/src/__tests__/utils/spotlight.test.ts index 7439785b..1f9bd304 100644 --- a/app/src/__tests__/utils/spotlight.test.ts +++ b/app/src/__tests__/utils/spotlight.test.ts @@ -34,7 +34,7 @@ describe('spotlightUtils', () => { it('should allow duplicate templates, using the last category definition', () => { const lookup = buildCategoryLookup(duplicateCategories); - expect(lookup.savedOrdersClient).toBe('cat2'); + expect(lookup.savedOrdersClient).toBe('subscriptions'); }); }); @@ -98,15 +98,24 @@ describe('spotlightUtils', () => { const ordered = orderSpotlightData(groupedSpotlightData, categories); expect(Object.keys(ordered)).toEqual(['orders', 'subscriptions']); - expect(ordered.orders[0].id).toBe('1'); - expect(ordered.subscriptions.map((item) => item.id)).toEqual(['2']); + expect(ordered.orders![0].id).toBe('1'); + expect(ordered.subscriptions!.map((item) => item.id)).toEqual(['2']); + + ordered.orders!.forEach((item) => { + expect(item.detailsScreenName).toBe('orderDetails'); + }); + ordered.subscriptions!.forEach((item) => { + expect(item.detailsScreenName).toBe('subscriptionDetails'); + }); }); it('should correctly handle single item per category', () => { const ordered = orderSpotlightData(groupedSpotlightDataSingleItemPerCategory, categories); expect(Object.keys(ordered)).toEqual(['orders', 'subscriptions']); - expect(ordered.orders[0].id).toBe('1'); + expect(ordered.orders![0].id).toBe('1'); expect(ordered.subscriptions[0].id).toBe('2'); + expect(ordered.orders![0].detailsScreenName).toBe('orderDetails'); + expect(ordered.subscriptions![0].detailsScreenName).toBe('subscriptionDetails'); }); }); @@ -119,6 +128,13 @@ describe('spotlightUtils', () => { expect(arrangedData.orders[0].id).toBe('1'); expect(arrangedData.subscriptions).toHaveLength(1); expect(arrangedData.subscriptions.map((item) => item.id)).toEqual(['2']); + + arrangedData.orders.forEach((item) => { + expect(item.detailsScreenName).toBe('orderDetails'); + }); + arrangedData.subscriptions.forEach((item) => { + expect(item.detailsScreenName).toBe('subscriptionDetails'); + }); }); it('should return empty object when spotlightData is empty', () => { diff --git a/app/src/components/list-item/DetailsListItem.tsx b/app/src/components/list-item/DetailsListItem.tsx index 2d7bae9e..1b79e19a 100644 --- a/app/src/components/list-item/DetailsListItem.tsx +++ b/app/src/components/list-item/DetailsListItem.tsx @@ -4,14 +4,7 @@ import type { DetailsListItemProps } from '@/types/lists'; const DetailsListItem = ({ label, data, hideImage, isLast, onPress }: DetailsListItemProps) => { if (!data) { - return ( - - ); + return ; } return ( diff --git a/app/src/components/list-item/ListItemWithLabelAndText.tsx b/app/src/components/list-item/ListItemWithLabelAndText.tsx index 0d5ad13d..ef61e636 100644 --- a/app/src/components/list-item/ListItemWithLabelAndText.tsx +++ b/app/src/components/list-item/ListItemWithLabelAndText.tsx @@ -1,12 +1,12 @@ import { View, Text, StyleSheet } from 'react-native'; +import { EMPTY_VALUE } from '@/constants/common'; import { listItemStyle } from '@/styles'; type Props = { title: string; subtitle?: string; isLast?: boolean; - onPress?: () => void; testID?: string; }; @@ -18,7 +18,7 @@ const ListItemWithLabelAndText = ({ title, subtitle, isLast, testID }: Props) => {title} - {subtitle ? subtitle : '-'} + {subtitle ? subtitle : EMPTY_VALUE} diff --git a/app/src/constants/spotlight.ts b/app/src/constants/spotlight.ts index 2faa7d8a..ceda36e6 100644 --- a/app/src/constants/spotlight.ts +++ b/app/src/constants/spotlight.ts @@ -1,4 +1,4 @@ -import type { SpotlightTemplateName } from '../types/spotlight'; +import type { SpotlightTemplateName, SpotlightCategory } from '../types/spotlight'; export const ORDERS_SPOTLIGHTS: Array = [ 'savedOrdersClient', @@ -42,12 +42,21 @@ export const BUYERS_SPOTLIGHTS: Array = [ 'buyersWithBlockedSellerConnectionsOfMyClients', ]; -export const SPOTLIGHT_CATEGORY: Array<{ name: string; templates: SpotlightTemplateName[] }> = [ - { name: 'orders', templates: ORDERS_SPOTLIGHTS }, - { name: 'subscriptions', templates: SUBSCRIPTION_SPOTLIGHTS }, - { name: 'users', templates: USERS_SPOTLIGHTS }, - { name: 'invoices', templates: INVOICES_SPOTLIGHTS }, - { name: 'enrollments', templates: ENROLLMENTS_SPOTLIGHTS }, - { name: 'journals', templates: JOURNALS_SPOTLIGHTS }, - { name: 'buyers', templates: BUYERS_SPOTLIGHTS }, +export const SPOTLIGHT_CATEGORY: Array = [ + { name: 'orders', templates: ORDERS_SPOTLIGHTS, detailsScreenName: 'orderDetails' }, + { + name: 'subscriptions', + templates: SUBSCRIPTION_SPOTLIGHTS, + detailsScreenName: 'subscriptionDetails', + }, + { name: 'users', templates: USERS_SPOTLIGHTS, detailsScreenName: 'userDetails' }, + { name: 'invoices', templates: INVOICES_SPOTLIGHTS, detailsScreenName: 'invoiceDetails' }, + { + name: 'enrollments', + templates: ENROLLMENTS_SPOTLIGHTS, + detailsScreenName: 'enrollmentDetails', + }, + // TODO: add Journals back when details screen is ready + // { name: 'journals', templates: JOURNALS_SPOTLIGHTS, detailsScreenName: 'journalDetails' }, + { name: 'buyers', templates: BUYERS_SPOTLIGHTS, detailsScreenName: 'buyerDetails' }, ]; diff --git a/app/src/screens/spotlight/SpotlightScreen.tsx b/app/src/screens/spotlight/SpotlightScreen.tsx index 7bb17fdc..8cb97f00 100644 --- a/app/src/screens/spotlight/SpotlightScreen.tsx +++ b/app/src/screens/spotlight/SpotlightScreen.tsx @@ -1,3 +1,5 @@ +import { useNavigation } from '@react-navigation/native'; +import type { StackNavigationProp } from '@react-navigation/stack'; import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { View, Text, ScrollView, StyleSheet, ActivityIndicator } from 'react-native'; @@ -8,20 +10,26 @@ import NavigationItemWithImage from '@/components/navigation-item/NavigationItem import { useAccount } from '@/context/AccountContext'; import { screenStyle, cardStyle, Spacing } from '@/styles'; import { Color } from '@/styles/tokens'; -import type { SpotlightItem } from '@/types/api'; +import type { SpotlightItemWithDetails } from '@/types/api'; +import type { RootStackParamList } from '@/types/navigation'; +import type { SpotlightCategoryName } from '@/types/spotlight'; import { TestIDs } from '@/utils/testID'; const DEFAULT_FILTER = 'all'; const SpotlightScreen = () => { const [selectedFilter, setSelectedFilter] = useState(DEFAULT_FILTER); - const [filteredData, setFilteredData] = useState>({}); + const [filteredData, setFilteredData] = useState< + Partial> + >({}); const [filterKeys, setFilterKeys] = useState([]); const { spotlightData, isSpotlightError, isSpotlightDataLoading, isSwitchingAccount } = useAccount(); const { t } = useTranslation(); + const navigation = useNavigation>(); + useEffect(() => { if (!spotlightData || Object.keys(spotlightData).length === 0) { setFilteredData({}); @@ -103,7 +111,7 @@ const SpotlightScreen = () => { > {Object.entries(filteredData).map(([categoryName, sections]) => ( - {(sections as SpotlightItem[]).map((section) => ( + {(sections as SpotlightItemWithDetails[]).map((section) => ( { title={itemName} subtitle={itemId} isLast={itemIndex === section.top.length - 1} - // TODO: Implement navigation on press of each spotlight item - onPress={() => {}} + onPress={() => { + navigation.navigate(section.detailsScreenName, { + id: itemId, + }); + }} /> ); })} diff --git a/app/src/types/api.ts b/app/src/types/api.ts index faeb2734..49c431cb 100644 --- a/app/src/types/api.ts +++ b/app/src/types/api.ts @@ -1,4 +1,5 @@ import type { Address } from './common'; +import type { AppScreensParamList } from './navigation'; export enum HttpMethod { GET = 'GET', @@ -89,6 +90,10 @@ export interface SpotlightItem { }; } +export type SpotlightItemWithDetails = SpotlightItem & { + detailsScreenName: keyof AppScreensParamList; +}; + export interface SpotlightTopItem { id: string; name?: string; diff --git a/app/src/types/navigation.ts b/app/src/types/navigation.ts index cc89c381..d205977c 100644 --- a/app/src/types/navigation.ts +++ b/app/src/types/navigation.ts @@ -32,9 +32,9 @@ export type SecondaryTabsParamList = { }; export type AppScreensParamList = { - creditMemoDetails: { id: string }; - orderDetails: { id: string }; - accountDetails: { id: string | undefined; type: 'client' | 'vendor' | 'operations' | 'account' }; + creditMemoDetails: { id: string | undefined }; + orderDetails: { id: string | undefined }; + accountDetails: { id: string | undefined; type?: 'client' | 'vendor' | 'operations' | 'account' }; userDetails: { id: string | undefined }; buyerDetails: { id: string | undefined }; sellerDetails: { id: string | undefined }; diff --git a/app/src/types/spotlight.ts b/app/src/types/spotlight.ts index 4f23eeec..d7d1294a 100644 --- a/app/src/types/spotlight.ts +++ b/app/src/types/spotlight.ts @@ -1,3 +1,5 @@ +import type { AppScreensParamList } from './navigation'; + export type SpotlightTemplateName = | 'savedOrdersClient' | 'queryingOrders' @@ -22,3 +24,18 @@ export type SpotlightTemplateName = | 'processingEnrollments' | 'longRunningEnrollmentsOfMyClients' | 'inProgressJournals'; + +export type SpotlightCategoryName = + | 'orders' + | 'subscriptions' + | 'users' + | 'invoices' + | 'enrollments' + | 'journals' + | 'buyers'; + +export type SpotlightCategory = { + name: SpotlightCategoryName; + templates: SpotlightTemplateName[]; + detailsScreenName: keyof AppScreensParamList; +}; diff --git a/app/src/utils/spotlight.ts b/app/src/utils/spotlight.ts index ba8beeaa..820e6267 100644 --- a/app/src/utils/spotlight.ts +++ b/app/src/utils/spotlight.ts @@ -1,6 +1,10 @@ -import type { SpotlightTemplateName } from '../types/spotlight'; +import type { + SpotlightCategory, + SpotlightCategoryName, + SpotlightTemplateName, +} from '../types/spotlight'; -import type { SpotlightItem } from '@/types/api'; +import type { SpotlightItem, SpotlightItemWithDetails } from '@/types/api'; /** * Build a lookup object, that maps spotlight template names to category names @@ -8,9 +12,12 @@ import type { SpotlightItem } from '@/types/api'; * @returns lookup object */ export const buildCategoryLookup = ( - categories: Array<{ name: string; templates: SpotlightTemplateName[] }>, -): Record => { - const lookup: Record = {} as Record; + categories: Array, +): Record => { + const lookup: Record = {} as Record< + SpotlightTemplateName, + SpotlightCategoryName + >; categories.forEach((category) => { category.templates.forEach((template) => { @@ -29,9 +36,12 @@ export const buildCategoryLookup = ( */ export const groupSpotlightData = ( spotlightData: SpotlightItem[], - templateLookup: Record, -): Record => { - const groupedData: Record = {}; + templateLookup: Record, +): Record => { + const groupedData: Record = {} as Record< + SpotlightCategoryName, + SpotlightItem[] + >; spotlightData.forEach((item) => { if (!item || item.total === 0) { @@ -44,7 +54,8 @@ export const groupSpotlightData = ( return; } - const categoryName: string | undefined = templateLookup[template as SpotlightTemplateName]; + const categoryName: SpotlightCategoryName | undefined = + templateLookup[template as SpotlightTemplateName]; if (categoryName === undefined || categoryName === null) { return; @@ -67,16 +78,19 @@ export const groupSpotlightData = ( * @returns spotlight data ordered same as categories object order */ export const orderSpotlightData = ( - groupedData: Record, - categories: Array<{ name: string; templates: SpotlightTemplateName[] }>, -): Record => { - const orderedData: Record = {}; + groupedData: Record, + categories: Array, +): Record => { + const orderedData: Record = {}; categories.forEach((category) => { const items = groupedData[category.name]; - if (items !== undefined && items !== null) { - orderedData[category.name] = items; + if (items && items.length > 0) { + orderedData[category.name] = items.map((item) => ({ + ...item, + detailsScreenName: category.detailsScreenName, + })); } }); @@ -91,8 +105,8 @@ export const orderSpotlightData = ( */ export const arrangeSpotlightData = ( spotlightData: SpotlightItem[], - categories: Array<{ name: string; templates: SpotlightTemplateName[] }>, -): Record => { + categories: Array, +): Record => { const templateLookup = buildCategoryLookup(categories); const groupedData = groupSpotlightData(spotlightData, templateLookup); const orderedData = orderSpotlightData(groupedData, categories);