From 2d5f8fdb8f1f263312d0cf00151d984202be923b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 17 Jul 2025 18:18:54 -0500 Subject: [PATCH] Signed-off-by: Matt Bruce --- src/components/common/InfiniteScrollList.tsx | 42 ++------- src/features/Favorites/Favorites.tsx | 20 +++-- src/features/History/History.tsx | 25 ++++-- src/features/NewSongs/NewSongs.tsx | 20 +++-- src/features/Search/Search.tsx | 18 ++-- src/features/TopPlayed/Top100.tsx | 94 ++++++-------------- 6 files changed, 90 insertions(+), 129 deletions(-) diff --git a/src/components/common/InfiniteScrollList.tsx b/src/components/common/InfiniteScrollList.tsx index c47059a..140909c 100644 --- a/src/components/common/InfiniteScrollList.tsx +++ b/src/components/common/InfiniteScrollList.tsx @@ -1,46 +1,35 @@ import React, { useEffect, useRef } from 'react'; -import { SongItem, EmptyState } from './index'; -import type { Song } from '../../types'; +import { EmptyState } from './index'; -interface InfiniteScrollListProps { - items: Song[]; +interface InfiniteScrollListProps { + items: T[]; isLoading: boolean; hasMore: boolean; onLoadMore: () => void; - onAddToQueue: (song: Song) => void; - onToggleFavorite: (song: Song) => void; - onRemoveFromQueue?: (song: Song) => void; - context: 'search' | 'queue' | 'history' | 'topPlayed' | 'favorites'; + renderItem: (item: T, index: number) => React.ReactNode; title: string; subtitle?: string; emptyTitle: string; emptyMessage: string; loadingTitle?: string; loadingMessage?: string; - isAdmin?: boolean; - renderExtraContent?: (item: Song, index: number) => React.ReactNode; debugInfo?: string; } -const InfiniteScrollList: React.FC = ({ +const InfiniteScrollList = ({ items, isLoading, hasMore, onLoadMore, - onAddToQueue, - onToggleFavorite, - onRemoveFromQueue, - context, + renderItem, title, subtitle, emptyTitle, emptyMessage, loadingTitle = "Loading...", loadingMessage = "Please wait while data is being loaded", - isAdmin = false, - renderExtraContent, debugInfo, -}) => { +}: InfiniteScrollListProps) => { const observerRef = useRef(null); // Intersection Observer for infinite scrolling @@ -111,21 +100,8 @@ const InfiniteScrollList: React.FC = ({ ) : (
{items.map((item, index) => ( -
- {/* Song Info */} -
- onAddToQueue(item)} - onToggleFavorite={() => onToggleFavorite(item)} - onRemoveFromQueue={onRemoveFromQueue ? () => onRemoveFromQueue(item) : undefined} - isAdmin={isAdmin} - /> -
- - {/* Extra Content */} - {renderExtraContent && renderExtraContent(item, index)} +
+ {renderItem(item, index)}
))} diff --git a/src/features/Favorites/Favorites.tsx b/src/features/Favorites/Favorites.tsx index 6304f44..cb0d468 100644 --- a/src/features/Favorites/Favorites.tsx +++ b/src/features/Favorites/Favorites.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { IonHeader, IonToolbar, IonTitle, IonChip } from '@ionic/react'; -import { InfiniteScrollList } from '../../components/common'; +import { InfiniteScrollList, SongItem } from '../../components/common'; import { useFavorites } from '../../hooks'; import { useAppSelector } from '../../redux'; import { selectFavorites } from '../../redux'; +import type { Song } from '../../types'; const Favorites: React.FC = () => { const { @@ -34,16 +35,21 @@ const Favorites: React.FC = () => { - items={favoritesItems} isLoading={favoritesCount === 0} hasMore={hasMore} onLoadMore={loadMore} - onAddToQueue={handleAddToQueue} - onToggleFavorite={handleToggleFavorite} - context="favorites" - title="" - subtitle="" + renderItem={(song) => ( + handleAddToQueue(song)} + onToggleFavorite={() => handleToggleFavorite(song)} + /> + )} + title="Favorites" + subtitle={`${favoritesCount} items loaded`} emptyTitle="No favorites yet" emptyMessage="Add songs to your favorites to see them here" loadingTitle="Loading favorites..." diff --git a/src/features/History/History.tsx b/src/features/History/History.tsx index d6a4cb1..33cdffe 100644 --- a/src/features/History/History.tsx +++ b/src/features/History/History.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { IonHeader, IonToolbar, IonTitle, IonChip, IonIcon } from '@ionic/react'; import { time } from 'ionicons/icons'; -import { InfiniteScrollList } from '../../components/common'; +import { InfiniteScrollList, SongItem } from '../../components/common'; import { useHistory } from '../../hooks'; import { useAppSelector } from '../../redux'; import { selectHistory } from '../../redux'; @@ -51,22 +51,31 @@ const History: React.FC = () => {
- items={historyItems} isLoading={historyCount === 0} hasMore={hasMore} onLoadMore={loadMore} - onAddToQueue={handleAddToQueue} - onToggleFavorite={handleToggleFavorite} - context="history" - title="" - subtitle="" + renderItem={(song) => ( +
+
+ handleAddToQueue(song)} + onToggleFavorite={() => handleToggleFavorite(song)} + /> +
+ {renderExtraContent(song)} +
+ )} + title="Recently Played" + subtitle={`${historyCount} items loaded`} emptyTitle="No history yet" emptyMessage="Songs will appear here after they've been played" loadingTitle="Loading history..." loadingMessage="Please wait while history data is being loaded" debugInfo={`History items loaded: ${historyCount}`} - renderExtraContent={renderExtraContent} />
diff --git a/src/features/NewSongs/NewSongs.tsx b/src/features/NewSongs/NewSongs.tsx index 513dcf5..581f9d6 100644 --- a/src/features/NewSongs/NewSongs.tsx +++ b/src/features/NewSongs/NewSongs.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { IonHeader, IonToolbar, IonTitle, IonChip } from '@ionic/react'; -import { InfiniteScrollList } from '../../components/common'; +import { InfiniteScrollList, SongItem } from '../../components/common'; import { useNewSongs } from '../../hooks'; import { useAppSelector } from '../../redux'; import { selectNewSongs } from '../../redux'; +import type { Song } from '../../types'; const NewSongs: React.FC = () => { const { @@ -34,16 +35,21 @@ const NewSongs: React.FC = () => { - items={newSongsItems} isLoading={newSongsCount === 0} hasMore={hasMore} onLoadMore={loadMore} - onAddToQueue={handleAddToQueue} - onToggleFavorite={handleToggleFavorite} - context="search" - title="" - subtitle="" + renderItem={(song) => ( + handleAddToQueue(song)} + onToggleFavorite={() => handleToggleFavorite(song)} + /> + )} + title="New Songs" + subtitle={`${newSongsCount} items loaded`} emptyTitle="No new songs" emptyMessage="Check back later for new additions" loadingTitle="Loading new songs..." diff --git a/src/features/Search/Search.tsx b/src/features/Search/Search.tsx index ec03b3a..36efe69 100644 --- a/src/features/Search/Search.tsx +++ b/src/features/Search/Search.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { IonSearchbar } from '@ionic/react'; -import { InfiniteScrollList } from '../../components/common'; +import { InfiniteScrollList, SongItem } from '../../components/common'; import { useSearch } from '../../hooks'; import { useAppSelector } from '../../redux'; import { selectIsAdmin, selectSongs } from '../../redux'; +import type { Song } from '../../types'; const Search: React.FC = () => { const { @@ -55,20 +56,25 @@ const Search: React.FC = () => {
{/* Search Results */} - items={searchResults.songs} isLoading={songsCount === 0} hasMore={searchResults.hasMore} onLoadMore={loadMore} - onAddToQueue={handleAddToQueue} - onToggleFavorite={handleToggleFavorite} - context="search" + renderItem={(song) => ( + handleAddToQueue(song)} + onToggleFavorite={() => handleToggleFavorite(song)} + isAdmin={isAdmin} + /> + )} title="" emptyTitle={searchTerm ? "No songs found" : "No songs available"} emptyMessage={searchTerm ? "Try adjusting your search terms" : "Songs will appear here once loaded"} loadingTitle="Loading songs..." loadingMessage="Please wait while songs are being loaded from the database" - isAdmin={isAdmin} debugInfo="" /> diff --git a/src/features/TopPlayed/Top100.tsx b/src/features/TopPlayed/Top100.tsx index 92552d8..92f603c 100644 --- a/src/features/TopPlayed/Top100.tsx +++ b/src/features/TopPlayed/Top100.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { IonHeader, IonToolbar, IonTitle, IonChip, IonButton } from '@ionic/react'; +import { IonHeader, IonToolbar, IonTitle, IonChip } from '@ionic/react'; import { useTopPlayed } from '../../hooks'; import { useAppSelector } from '../../redux'; import { selectTopPlayed } from '../../redux'; +import { InfiniteScrollList } from '../../components/common'; import type { TopPlayed } from '../../types'; const Top100: React.FC = () => { @@ -109,78 +110,35 @@ const Top100: React.FC = () => { -
- {/* Debug info */} -
- Top played items loaded: {displayCount} (Mock Data) -
- - {/* Content */} -
- {displayCount === 0 ? ( -
-
- - - + + items={displayItems} + isLoading={false} + hasMore={displayHasMore} + onLoadMore={loadMore} + renderItem={(item, index) => ( +
+
+ + {index + 1}) + +
+
+
+
+ {item.title} ({item.count})
-

Loading top played songs...

-

Please wait while top played data is being loaded

-
- ) : displayItems.length === 0 ? ( -
-
- - - +
+ {item.artist}
-

No top played songs

-

Play some songs to see the top played list

- ) : ( -
- {displayItems.map((item, index) => ( -
-
- - {index + 1}) - -
-
-
-
- {item.title} ({item.count}) -
-
- {item.artist} -
-
-
- ))} - - {/* Load more button */} - {displayHasMore && ( -
- - Load More - -
- )} -
- )} -
- - {/* Stats */} - {displayItems.length > 0 && ( -
- Showing {displayItems.length} item{displayItems.length !== 1 ? 's' : ''} - {displayHasMore && ` • Click "Load More" to see more`}
)} -
+ title="Top 100 Played" + subtitle={`${displayCount} items loaded (Mock Data)`} + emptyTitle="No top played songs" + emptyMessage="Play some songs to see the top played list" + debugInfo={`Top played items loaded: ${displayCount} (Mock Data)`} + /> ); };