Skip to content

Commit e199f6b

Browse files
Feature/mpt 17255/navigate to details screens from spotlight (#185)
* Adding navigation to spotlight to navigate to details screens * Extra tests * Addressing PR comments
1 parent 6103e06 commit e199f6b

File tree

10 files changed

+144
-58
lines changed

10 files changed

+144
-58
lines changed

app/src/__tests__/__mocks__/utils/spotlight.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,43 @@
11
import type { SpotlightItem } from '@/types/api';
2-
import type { SpotlightTemplateName } from '@/types/spotlight';
3-
4-
export const categories: { name: string; templates: SpotlightTemplateName[] }[] = [
5-
{ name: 'orders', templates: ['savedOrdersClient', 'queryingOrders'] },
6-
{ name: 'subscriptions', templates: ['renewingSubscriptions', 'expiringSubscriptions'] },
2+
import type { AppScreensParamList } from '@/types/navigation';
3+
import type { SpotlightTemplateName, SpotlightCategoryName } from '@/types/spotlight';
4+
5+
export const categories: {
6+
name: SpotlightCategoryName;
7+
templates: SpotlightTemplateName[];
8+
detailsScreenName: keyof AppScreensParamList;
9+
}[] = [
10+
{
11+
name: 'orders',
12+
templates: ['savedOrdersClient', 'queryingOrders'],
13+
detailsScreenName: 'orderDetails',
14+
},
15+
{
16+
name: 'subscriptions',
17+
templates: ['renewingSubscriptions', 'expiringSubscriptions'],
18+
detailsScreenName: 'subscriptionDetails',
19+
},
720
];
821

9-
export const duplicateCategories: { name: string; templates: SpotlightTemplateName[] }[] = [
10-
{ name: 'cat1', templates: ['savedOrdersClient'] },
11-
{ name: 'cat2', templates: ['savedOrdersClient'] },
22+
export const duplicateCategories: {
23+
name: SpotlightCategoryName;
24+
templates: SpotlightTemplateName[];
25+
detailsScreenName: keyof AppScreensParamList;
26+
}[] = [
27+
{ name: 'orders', templates: ['savedOrdersClient'], detailsScreenName: 'orderDetails' },
28+
{
29+
name: 'subscriptions',
30+
templates: ['savedOrdersClient'],
31+
detailsScreenName: 'subscriptionDetails',
32+
},
1233
];
1334

14-
export const categoryLookup: Record<SpotlightTemplateName, string> = {
35+
export const categoryLookup: Record<SpotlightTemplateName, SpotlightCategoryName> = {
1536
savedOrdersClient: 'orders',
1637
queryingOrders: 'orders',
1738
renewingSubscriptions: 'subscriptions',
1839
expiringSubscriptions: 'subscriptions',
19-
} as Record<SpotlightTemplateName, string>;
40+
} as Record<SpotlightTemplateName, SpotlightCategoryName>;
2041

2142
export const spotlightItem1: SpotlightItem = {
2243
id: '1',

app/src/__tests__/utils/spotlight.test.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('spotlightUtils', () => {
3434
it('should allow duplicate templates, using the last category definition', () => {
3535
const lookup = buildCategoryLookup(duplicateCategories);
3636

37-
expect(lookup.savedOrdersClient).toBe('cat2');
37+
expect(lookup.savedOrdersClient).toBe('subscriptions');
3838
});
3939
});
4040

@@ -98,15 +98,24 @@ describe('spotlightUtils', () => {
9898
const ordered = orderSpotlightData(groupedSpotlightData, categories);
9999

100100
expect(Object.keys(ordered)).toEqual(['orders', 'subscriptions']);
101-
expect(ordered.orders[0].id).toBe('1');
102-
expect(ordered.subscriptions.map((item) => item.id)).toEqual(['2']);
101+
expect(ordered.orders![0].id).toBe('1');
102+
expect(ordered.subscriptions!.map((item) => item.id)).toEqual(['2']);
103+
104+
ordered.orders!.forEach((item) => {
105+
expect(item.detailsScreenName).toBe('orderDetails');
106+
});
107+
ordered.subscriptions!.forEach((item) => {
108+
expect(item.detailsScreenName).toBe('subscriptionDetails');
109+
});
103110
});
104111

105112
it('should correctly handle single item per category', () => {
106113
const ordered = orderSpotlightData(groupedSpotlightDataSingleItemPerCategory, categories);
107114
expect(Object.keys(ordered)).toEqual(['orders', 'subscriptions']);
108-
expect(ordered.orders[0].id).toBe('1');
115+
expect(ordered.orders![0].id).toBe('1');
109116
expect(ordered.subscriptions[0].id).toBe('2');
117+
expect(ordered.orders![0].detailsScreenName).toBe('orderDetails');
118+
expect(ordered.subscriptions![0].detailsScreenName).toBe('subscriptionDetails');
110119
});
111120
});
112121

@@ -119,6 +128,13 @@ describe('spotlightUtils', () => {
119128
expect(arrangedData.orders[0].id).toBe('1');
120129
expect(arrangedData.subscriptions).toHaveLength(1);
121130
expect(arrangedData.subscriptions.map((item) => item.id)).toEqual(['2']);
131+
132+
arrangedData.orders.forEach((item) => {
133+
expect(item.detailsScreenName).toBe('orderDetails');
134+
});
135+
arrangedData.subscriptions.forEach((item) => {
136+
expect(item.detailsScreenName).toBe('subscriptionDetails');
137+
});
122138
});
123139

124140
it('should return empty object when spotlightData is empty', () => {

app/src/components/list-item/DetailsListItem.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@ import type { DetailsListItemProps } from '@/types/lists';
44

55
const DetailsListItem = ({ label, data, hideImage, isLast, onPress }: DetailsListItemProps) => {
66
if (!data) {
7-
return (
8-
<ListItemWithLabelAndText
9-
title={label}
10-
subtitle={undefined}
11-
isLast={isLast}
12-
onPress={onPress}
13-
/>
14-
);
7+
return <ListItemWithLabelAndText title={label} subtitle={undefined} isLast={isLast} />;
158
}
169

1710
return (

app/src/components/list-item/ListItemWithLabelAndText.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { View, Text, StyleSheet } from 'react-native';
22

3+
import { EMPTY_VALUE } from '@/constants/common';
34
import { listItemStyle } from '@/styles';
45

56
type Props = {
67
title: string;
78
subtitle?: string;
89
isLast?: boolean;
9-
onPress?: () => void;
1010
testID?: string;
1111
};
1212

@@ -18,7 +18,7 @@ const ListItemWithLabelAndText = ({ title, subtitle, isLast, testID }: Props) =>
1818
{title}
1919
</Text>
2020
<Text style={styles.subtitle} numberOfLines={1} ellipsizeMode="tail">
21-
{subtitle ? subtitle : '-'}
21+
{subtitle ? subtitle : EMPTY_VALUE}
2222
</Text>
2323
</View>
2424
</View>

app/src/constants/spotlight.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { SpotlightTemplateName } from '../types/spotlight';
1+
import type { SpotlightTemplateName, SpotlightCategory } from '../types/spotlight';
22

33
export const ORDERS_SPOTLIGHTS: Array<SpotlightTemplateName> = [
44
'savedOrdersClient',
@@ -42,12 +42,21 @@ export const BUYERS_SPOTLIGHTS: Array<SpotlightTemplateName> = [
4242
'buyersWithBlockedSellerConnectionsOfMyClients',
4343
];
4444

45-
export const SPOTLIGHT_CATEGORY: Array<{ name: string; templates: SpotlightTemplateName[] }> = [
46-
{ name: 'orders', templates: ORDERS_SPOTLIGHTS },
47-
{ name: 'subscriptions', templates: SUBSCRIPTION_SPOTLIGHTS },
48-
{ name: 'users', templates: USERS_SPOTLIGHTS },
49-
{ name: 'invoices', templates: INVOICES_SPOTLIGHTS },
50-
{ name: 'enrollments', templates: ENROLLMENTS_SPOTLIGHTS },
51-
{ name: 'journals', templates: JOURNALS_SPOTLIGHTS },
52-
{ name: 'buyers', templates: BUYERS_SPOTLIGHTS },
45+
export const SPOTLIGHT_CATEGORY: Array<SpotlightCategory> = [
46+
{ name: 'orders', templates: ORDERS_SPOTLIGHTS, detailsScreenName: 'orderDetails' },
47+
{
48+
name: 'subscriptions',
49+
templates: SUBSCRIPTION_SPOTLIGHTS,
50+
detailsScreenName: 'subscriptionDetails',
51+
},
52+
{ name: 'users', templates: USERS_SPOTLIGHTS, detailsScreenName: 'userDetails' },
53+
{ name: 'invoices', templates: INVOICES_SPOTLIGHTS, detailsScreenName: 'invoiceDetails' },
54+
{
55+
name: 'enrollments',
56+
templates: ENROLLMENTS_SPOTLIGHTS,
57+
detailsScreenName: 'enrollmentDetails',
58+
},
59+
// TODO: add Journals back when details screen is ready
60+
// { name: 'journals', templates: JOURNALS_SPOTLIGHTS, detailsScreenName: 'journalDetails' },
61+
{ name: 'buyers', templates: BUYERS_SPOTLIGHTS, detailsScreenName: 'buyerDetails' },
5362
];

app/src/screens/spotlight/SpotlightScreen.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useNavigation } from '@react-navigation/native';
2+
import type { StackNavigationProp } from '@react-navigation/stack';
13
import { useState, useEffect } from 'react';
24
import { useTranslation } from 'react-i18next';
35
import { View, Text, ScrollView, StyleSheet, ActivityIndicator } from 'react-native';
@@ -8,20 +10,26 @@ import NavigationItemWithImage from '@/components/navigation-item/NavigationItem
810
import { useAccount } from '@/context/AccountContext';
911
import { screenStyle, cardStyle, Spacing } from '@/styles';
1012
import { Color } from '@/styles/tokens';
11-
import type { SpotlightItem } from '@/types/api';
13+
import type { SpotlightItemWithDetails } from '@/types/api';
14+
import type { RootStackParamList } from '@/types/navigation';
15+
import type { SpotlightCategoryName } from '@/types/spotlight';
1216
import { TestIDs } from '@/utils/testID';
1317

1418
const DEFAULT_FILTER = 'all';
1519

1620
const SpotlightScreen = () => {
1721
const [selectedFilter, setSelectedFilter] = useState<string>(DEFAULT_FILTER);
18-
const [filteredData, setFilteredData] = useState<Record<string, SpotlightItem[]>>({});
22+
const [filteredData, setFilteredData] = useState<
23+
Partial<Record<SpotlightCategoryName, SpotlightItemWithDetails[]>>
24+
>({});
1925
const [filterKeys, setFilterKeys] = useState<string[]>([]);
2026

2127
const { spotlightData, isSpotlightError, isSpotlightDataLoading, isSwitchingAccount } =
2228
useAccount();
2329
const { t } = useTranslation();
2430

31+
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
32+
2533
useEffect(() => {
2634
if (!spotlightData || Object.keys(spotlightData).length === 0) {
2735
setFilteredData({});
@@ -103,7 +111,7 @@ const SpotlightScreen = () => {
103111
>
104112
{Object.entries(filteredData).map(([categoryName, sections]) => (
105113
<View key={categoryName}>
106-
{(sections as SpotlightItem[]).map((section) => (
114+
{(sections as SpotlightItemWithDetails[]).map((section) => (
107115
<View
108116
key={section.id}
109117
testID={`${TestIDs.SPOTLIGHT_CARD_PREFIX}-${categoryName}-${section.id}`}
@@ -134,8 +142,11 @@ const SpotlightScreen = () => {
134142
title={itemName}
135143
subtitle={itemId}
136144
isLast={itemIndex === section.top.length - 1}
137-
// TODO: Implement navigation on press of each spotlight item
138-
onPress={() => {}}
145+
onPress={() => {
146+
navigation.navigate(section.detailsScreenName, {
147+
id: itemId,
148+
});
149+
}}
139150
/>
140151
);
141152
})}

app/src/types/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Address } from './common';
2+
import type { AppScreensParamList } from './navigation';
23

34
export enum HttpMethod {
45
GET = 'GET',
@@ -89,6 +90,10 @@ export interface SpotlightItem {
8990
};
9091
}
9192

93+
export type SpotlightItemWithDetails = SpotlightItem & {
94+
detailsScreenName: keyof AppScreensParamList;
95+
};
96+
9297
export interface SpotlightTopItem {
9398
id: string;
9499
name?: string;

app/src/types/navigation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ export type SecondaryTabsParamList = {
3232
};
3333

3434
export type AppScreensParamList = {
35-
creditMemoDetails: { id: string };
36-
orderDetails: { id: string };
37-
accountDetails: { id: string | undefined; type: 'client' | 'vendor' | 'operations' | 'account' };
35+
creditMemoDetails: { id: string | undefined };
36+
orderDetails: { id: string | undefined };
37+
accountDetails: { id: string | undefined; type?: 'client' | 'vendor' | 'operations' | 'account' };
3838
userDetails: { id: string | undefined };
3939
buyerDetails: { id: string | undefined };
4040
sellerDetails: { id: string | undefined };

app/src/types/spotlight.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { AppScreensParamList } from './navigation';
2+
13
export type SpotlightTemplateName =
24
| 'savedOrdersClient'
35
| 'queryingOrders'
@@ -22,3 +24,18 @@ export type SpotlightTemplateName =
2224
| 'processingEnrollments'
2325
| 'longRunningEnrollmentsOfMyClients'
2426
| 'inProgressJournals';
27+
28+
export type SpotlightCategoryName =
29+
| 'orders'
30+
| 'subscriptions'
31+
| 'users'
32+
| 'invoices'
33+
| 'enrollments'
34+
| 'journals'
35+
| 'buyers';
36+
37+
export type SpotlightCategory = {
38+
name: SpotlightCategoryName;
39+
templates: SpotlightTemplateName[];
40+
detailsScreenName: keyof AppScreensParamList;
41+
};

app/src/utils/spotlight.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import type { SpotlightTemplateName } from '../types/spotlight';
1+
import type {
2+
SpotlightCategory,
3+
SpotlightCategoryName,
4+
SpotlightTemplateName,
5+
} from '../types/spotlight';
26

3-
import type { SpotlightItem } from '@/types/api';
7+
import type { SpotlightItem, SpotlightItemWithDetails } from '@/types/api';
48

59
/**
610
* Build a lookup object, that maps spotlight template names to category names
711
* @param categories - array of category objects
812
* @returns lookup object
913
*/
1014
export const buildCategoryLookup = (
11-
categories: Array<{ name: string; templates: SpotlightTemplateName[] }>,
12-
): Record<SpotlightTemplateName, string> => {
13-
const lookup: Record<SpotlightTemplateName, string> = {} as Record<SpotlightTemplateName, string>;
15+
categories: Array<SpotlightCategory>,
16+
): Record<SpotlightTemplateName, SpotlightCategoryName> => {
17+
const lookup: Record<SpotlightTemplateName, SpotlightCategoryName> = {} as Record<
18+
SpotlightTemplateName,
19+
SpotlightCategoryName
20+
>;
1421

1522
categories.forEach((category) => {
1623
category.templates.forEach((template) => {
@@ -29,9 +36,12 @@ export const buildCategoryLookup = (
2936
*/
3037
export const groupSpotlightData = (
3138
spotlightData: SpotlightItem[],
32-
templateLookup: Record<SpotlightTemplateName, string>,
33-
): Record<string, SpotlightItem[]> => {
34-
const groupedData: Record<string, SpotlightItem[]> = {};
39+
templateLookup: Record<SpotlightTemplateName, SpotlightCategoryName>,
40+
): Record<SpotlightCategoryName, SpotlightItem[]> => {
41+
const groupedData: Record<SpotlightCategoryName, SpotlightItem[]> = {} as Record<
42+
SpotlightCategoryName,
43+
SpotlightItem[]
44+
>;
3545

3646
spotlightData.forEach((item) => {
3747
if (!item || item.total === 0) {
@@ -44,7 +54,8 @@ export const groupSpotlightData = (
4454
return;
4555
}
4656

47-
const categoryName: string | undefined = templateLookup[template as SpotlightTemplateName];
57+
const categoryName: SpotlightCategoryName | undefined =
58+
templateLookup[template as SpotlightTemplateName];
4859

4960
if (categoryName === undefined || categoryName === null) {
5061
return;
@@ -67,16 +78,19 @@ export const groupSpotlightData = (
6778
* @returns spotlight data ordered same as categories object order
6879
*/
6980
export const orderSpotlightData = (
70-
groupedData: Record<string, SpotlightItem[]>,
71-
categories: Array<{ name: string; templates: SpotlightTemplateName[] }>,
72-
): Record<string, SpotlightItem[]> => {
73-
const orderedData: Record<string, SpotlightItem[]> = {};
81+
groupedData: Record<SpotlightCategoryName, SpotlightItem[]>,
82+
categories: Array<SpotlightCategory>,
83+
): Record<string, SpotlightItemWithDetails[]> => {
84+
const orderedData: Record<string, SpotlightItemWithDetails[]> = {};
7485

7586
categories.forEach((category) => {
7687
const items = groupedData[category.name];
7788

78-
if (items !== undefined && items !== null) {
79-
orderedData[category.name] = items;
89+
if (items && items.length > 0) {
90+
orderedData[category.name] = items.map((item) => ({
91+
...item,
92+
detailsScreenName: category.detailsScreenName,
93+
}));
8094
}
8195
});
8296

@@ -91,8 +105,8 @@ export const orderSpotlightData = (
91105
*/
92106
export const arrangeSpotlightData = (
93107
spotlightData: SpotlightItem[],
94-
categories: Array<{ name: string; templates: SpotlightTemplateName[] }>,
95-
): Record<string, SpotlightItem[]> => {
108+
categories: Array<SpotlightCategory>,
109+
): Record<string, SpotlightItemWithDetails[]> => {
96110
const templateLookup = buildCategoryLookup(categories);
97111
const groupedData = groupSpotlightData(spotlightData, templateLookup);
98112
const orderedData = orderSpotlightData(groupedData, categories);

0 commit comments

Comments
 (0)