Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions app/src/__tests__/__mocks__/utils/spotlight.ts
Original file line number Diff line number Diff line change
@@ -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<SpotlightTemplateName, string> = {
export const categoryLookup: Record<SpotlightTemplateName, SpotlightCategoryName> = {
savedOrdersClient: 'orders',
queryingOrders: 'orders',
renewingSubscriptions: 'subscriptions',
expiringSubscriptions: 'subscriptions',
} as Record<SpotlightTemplateName, string>;
} as Record<SpotlightTemplateName, SpotlightCategoryName>;

export const spotlightItem1: SpotlightItem = {
id: '1',
Expand Down
24 changes: 20 additions & 4 deletions app/src/__tests__/utils/spotlight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

Expand Down Expand Up @@ -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');
});
});

Expand All @@ -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', () => {
Expand Down
9 changes: 1 addition & 8 deletions app/src/components/list-item/DetailsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@ import type { DetailsListItemProps } from '@/types/lists';

const DetailsListItem = ({ label, data, hideImage, isLast, onPress }: DetailsListItemProps) => {
if (!data) {
return (
<ListItemWithLabelAndText
title={label}
subtitle={undefined}
isLast={isLast}
onPress={onPress}
/>
);
return <ListItemWithLabelAndText title={label} subtitle={undefined} isLast={isLast} />;
}

return (
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/list-item/ListItemWithLabelAndText.tsx
Original file line number Diff line number Diff line change
@@ -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;
};

Expand All @@ -18,7 +18,7 @@ const ListItemWithLabelAndText = ({ title, subtitle, isLast, testID }: Props) =>
{title}
</Text>
<Text style={styles.subtitle} numberOfLines={1} ellipsizeMode="tail">
{subtitle ? subtitle : '-'}
{subtitle ? subtitle : EMPTY_VALUE}
</Text>
</View>
</View>
Expand Down
27 changes: 18 additions & 9 deletions app/src/constants/spotlight.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SpotlightTemplateName } from '../types/spotlight';
import type { SpotlightTemplateName, SpotlightCategory } from '../types/spotlight';

export const ORDERS_SPOTLIGHTS: Array<SpotlightTemplateName> = [
'savedOrdersClient',
Expand Down Expand Up @@ -42,12 +42,21 @@ export const BUYERS_SPOTLIGHTS: Array<SpotlightTemplateName> = [
'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<SpotlightCategory> = [
{ 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' },
];
21 changes: 16 additions & 5 deletions app/src/screens/spotlight/SpotlightScreen.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<string>(DEFAULT_FILTER);
const [filteredData, setFilteredData] = useState<Record<string, SpotlightItem[]>>({});
const [filteredData, setFilteredData] = useState<
Partial<Record<SpotlightCategoryName, SpotlightItemWithDetails[]>>
>({});
const [filterKeys, setFilterKeys] = useState<string[]>([]);

const { spotlightData, isSpotlightError, isSpotlightDataLoading, isSwitchingAccount } =
useAccount();
const { t } = useTranslation();

const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();

useEffect(() => {
if (!spotlightData || Object.keys(spotlightData).length === 0) {
setFilteredData({});
Expand Down Expand Up @@ -103,7 +111,7 @@ const SpotlightScreen = () => {
>
{Object.entries(filteredData).map(([categoryName, sections]) => (
<View key={categoryName}>
{(sections as SpotlightItem[]).map((section) => (
{(sections as SpotlightItemWithDetails[]).map((section) => (
<View
key={section.id}
testID={`${TestIDs.SPOTLIGHT_CARD_PREFIX}-${categoryName}-${section.id}`}
Expand Down Expand Up @@ -134,8 +142,11 @@ const SpotlightScreen = () => {
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,
});
}}
/>
);
})}
Expand Down
5 changes: 5 additions & 0 deletions app/src/types/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Address } from './common';
import type { AppScreensParamList } from './navigation';

export enum HttpMethod {
GET = 'GET',
Expand Down Expand Up @@ -89,6 +90,10 @@ export interface SpotlightItem {
};
}

export type SpotlightItemWithDetails = SpotlightItem & {
detailsScreenName: keyof AppScreensParamList;
};

export interface SpotlightTopItem {
id: string;
name?: string;
Expand Down
6 changes: 3 additions & 3 deletions app/src/types/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
17 changes: 17 additions & 0 deletions app/src/types/spotlight.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { AppScreensParamList } from './navigation';

export type SpotlightTemplateName =
| 'savedOrdersClient'
| 'queryingOrders'
Expand All @@ -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;
};
48 changes: 31 additions & 17 deletions app/src/utils/spotlight.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
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
* @param categories - array of category objects
* @returns lookup object
*/
export const buildCategoryLookup = (
categories: Array<{ name: string; templates: SpotlightTemplateName[] }>,
): Record<SpotlightTemplateName, string> => {
const lookup: Record<SpotlightTemplateName, string> = {} as Record<SpotlightTemplateName, string>;
categories: Array<SpotlightCategory>,
): Record<SpotlightTemplateName, SpotlightCategoryName> => {
const lookup: Record<SpotlightTemplateName, SpotlightCategoryName> = {} as Record<
SpotlightTemplateName,
SpotlightCategoryName
>;

categories.forEach((category) => {
category.templates.forEach((template) => {
Expand All @@ -29,9 +36,12 @@ export const buildCategoryLookup = (
*/
export const groupSpotlightData = (
spotlightData: SpotlightItem[],
templateLookup: Record<SpotlightTemplateName, string>,
): Record<string, SpotlightItem[]> => {
const groupedData: Record<string, SpotlightItem[]> = {};
templateLookup: Record<SpotlightTemplateName, SpotlightCategoryName>,
): Record<SpotlightCategoryName, SpotlightItem[]> => {
const groupedData: Record<SpotlightCategoryName, SpotlightItem[]> = {} as Record<
SpotlightCategoryName,
SpotlightItem[]
>;

spotlightData.forEach((item) => {
if (!item || item.total === 0) {
Expand All @@ -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;
Expand All @@ -67,16 +78,19 @@ export const groupSpotlightData = (
* @returns spotlight data ordered same as categories object order
*/
export const orderSpotlightData = (
groupedData: Record<string, SpotlightItem[]>,
categories: Array<{ name: string; templates: SpotlightTemplateName[] }>,
): Record<string, SpotlightItem[]> => {
const orderedData: Record<string, SpotlightItem[]> = {};
groupedData: Record<SpotlightCategoryName, SpotlightItem[]>,
categories: Array<SpotlightCategory>,
): Record<string, SpotlightItemWithDetails[]> => {
const orderedData: Record<string, SpotlightItemWithDetails[]> = {};

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,
}));
}
});

Expand All @@ -91,8 +105,8 @@ export const orderSpotlightData = (
*/
export const arrangeSpotlightData = (
spotlightData: SpotlightItem[],
categories: Array<{ name: string; templates: SpotlightTemplateName[] }>,
): Record<string, SpotlightItem[]> => {
categories: Array<SpotlightCategory>,
): Record<string, SpotlightItemWithDetails[]> => {
const templateLookup = buildCategoryLookup(categories);
const groupedData = groupSpotlightData(spotlightData, templateLookup);
const orderedData = orderSpotlightData(groupedData, categories);
Expand Down