Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>

This commit is contained in:
mbrucedogs 2025-07-23 07:37:23 -05:00
parent 98e3633d31
commit 683050f271
6 changed files with 131 additions and 15 deletions

View File

@ -0,0 +1,40 @@
import React from 'react';
import { IonChip } from '@ionic/react';
import { getSongCountByArtistTitle } from '../../utils/dataProcessing';
import type { Song } from '../../types';
interface SongCountDisplayProps {
songs: Song[];
artist: string;
title: string;
showLabel?: boolean;
color?: 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'danger' | 'medium' | 'light' | 'dark';
}
export const SongCountDisplay: React.FC<SongCountDisplayProps> = ({
songs,
artist,
title,
showLabel = true,
color = 'primary'
}) => {
const count = getSongCountByArtistTitle(songs, artist, title);
if (count === 0) {
return null;
}
const label = showLabel
? `${count} version${count !== 1 ? 's' : ''}`
: count.toString();
return (
<IonChip
color={color}
>
{label}
</IonChip>
);
};
export default SongCountDisplay;

View File

@ -11,4 +11,5 @@ export { TwoLineDisplay } from './TwoLineDisplay';
export { default as ListItem } from './ListItem';
export { NumberDisplay } from './NumberDisplay';
export { ModalHeader } from './ModalHeader';
export { default as VirtualizedList } from './VirtualizedList';
export { default as VirtualizedList } from './VirtualizedList';
export { default as SongCountDisplay } from './SongCountDisplay';

View File

@ -33,7 +33,7 @@ export const UI_CONSTANTS = {
MAX_ITEMS: 100,
},
HISTORY: {
MAX_ITEMS: 50,
MAX_ITEMS: 250, // Increased from 50 to show more history items
},
TOP_PLAYED: {
MAX_ITEMS: 20,

View File

@ -16,6 +16,7 @@ const SongLists: React.FC = () => {
hasMore,
loadMore,
checkSongAvailability,
getSongCountForSongListSong,
} = useSongLists();
const songListData = useAppSelector(selectSongList);
@ -94,7 +95,8 @@ const SongLists: React.FC = () => {
<IonAccordionGroup value={expandedSongKey}>
{selectedListWithAvailability?.songs.map((songListSong: SongListSong & { availableSongs: Song[] }, index) => {
const availableSongs = songListSong.availableSongs;
const isAvailable = availableSongs.length > 0;
const songCount = getSongCountForSongListSong(songListSong);
const isAvailable = songCount > 0;
const songKey = songListSong.key || `${songListSong.title}-${songListSong.position}-${index}`;
if (isAvailable) {
@ -114,7 +116,7 @@ const SongLists: React.FC = () => {
onClick={() => handleSongItemClick(songKey)}
endContent={
<IonChip color="primary">
{availableSongs.length} version{availableSongs.length !== 1 ? 's' : ''}
{songCount} version{songCount !== 1 ? 's' : ''}
</IonChip>
}
/>

View File

@ -1,33 +1,66 @@
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { useAppSelector, selectSongListArray, selectSongsArray } from '../redux';
import { useActions } from './useActions';
import { usePaginatedData } from './index';
import type { SongListSong } from '../types';
import type { SongListSong, Song } from '../types';
export const useSongLists = () => {
const allSongLists = useAppSelector(selectSongListArray);
const allSongs = useAppSelector(selectSongsArray);
const { handleAddToQueue, handleToggleFavorite } = useActions();
// Pre-compute songs by artist and title combination for performance
const songsByArtistTitle = useMemo(() => {
const songsMap = new Map<string, Song[]>();
const countsMap = new Map<string, number>();
allSongs.forEach(song => {
const artist = (song.artist || '').toLowerCase();
const title = (song.title || '').toLowerCase();
const key = `${artist}|${title}`;
if (!songsMap.has(key)) {
songsMap.set(key, []);
countsMap.set(key, 0);
}
songsMap.get(key)!.push(song);
countsMap.set(key, countsMap.get(key)! + 1);
});
return { songsMap, countsMap };
}, [allSongs]);
// Use the composable pagination hook
const pagination = usePaginatedData(allSongLists, {
itemsPerPage: 20 // Default pagination size
});
// Check if a song exists in the catalog
// Get songs by artist and title (now using cached data)
const getSongsByArtistTitle = useCallback((artist: string, title: string) => {
const key = `${(artist || '').toLowerCase()}|${(title || '').toLowerCase()}`;
return songsByArtistTitle.songsMap.get(key) || [];
}, [songsByArtistTitle.songsMap]);
// Get song count by artist and title (now using cached data)
const getSongCountByArtistTitle = useCallback((artist: string, title: string) => {
const key = `${(artist || '').toLowerCase()}|${(title || '').toLowerCase()}`;
return songsByArtistTitle.countsMap.get(key) || 0;
}, [songsByArtistTitle.countsMap]);
// Check if a song exists in the catalog (enhanced version)
const checkSongAvailability = useCallback((songListSong: SongListSong) => {
if (songListSong.foundSongs && songListSong.foundSongs.length > 0) {
return songListSong.foundSongs;
}
// Search for songs by artist and title
const matchingSongs = allSongs.filter(song =>
(song.artist || '').toLowerCase() === (songListSong.artist || '').toLowerCase() &&
(song.title || '').toLowerCase() === (songListSong.title || '').toLowerCase()
);
return matchingSongs;
}, [allSongs]);
// Use the pre-computed data for better performance
return getSongsByArtistTitle(songListSong.artist, songListSong.title);
}, [getSongsByArtistTitle]);
// Get song count for a song list song
const getSongCountForSongListSong = useCallback((songListSong: SongListSong) => {
return getSongCountByArtistTitle(songListSong.artist, songListSong.title);
}, [getSongCountByArtistTitle]);
return {
songLists: pagination.items,
@ -37,6 +70,9 @@ export const useSongLists = () => {
currentPage: pagination.currentPage,
totalPages: pagination.totalPages,
checkSongAvailability,
getSongCountForSongListSong,
getSongsByArtistTitle,
getSongCountByArtistTitle,
handleAddToQueue,
handleToggleFavorite,
isLoading: pagination.isLoading,

View File

@ -121,4 +121,41 @@ export const getQueueStats = (queue: Record<string, QueueItem>) => {
singers: [...new Set(queueArray.map(item => item.singer.name))],
estimatedDuration: queueArray.length * 3, // Rough estimate: 3 minutes per song
};
};
// Get songs by artist and title combination
export const getSongsByArtistTitle = (songs: Song[], artist: string, title: string): Song[] => {
const artistLower = (artist || '').toLowerCase();
const titleLower = (title || '').toLowerCase();
return songs.filter(song =>
(song.artist || '').toLowerCase() === artistLower &&
(song.title || '').toLowerCase() === titleLower
);
};
// Get song count by artist and title combination
export const getSongCountByArtistTitle = (songs: Song[], artist: string, title: string): number => {
return getSongsByArtistTitle(songs, artist, title).length;
};
// Create a map of song counts by artist and title for performance
export const createSongCountMapByArtistTitle = (songs: Song[]): Map<string, number> => {
const countsMap = new Map<string, number>();
songs.forEach(song => {
const artist = (song.artist || '').toLowerCase();
const title = (song.title || '').toLowerCase();
const key = `${artist}|${title}`;
countsMap.set(key, (countsMap.get(key) || 0) + 1);
});
return countsMap;
};
// Get song count using a pre-computed map
export const getSongCountFromMap = (countsMap: Map<string, number>, artist: string, title: string): number => {
const key = `${(artist || '').toLowerCase()}|${(title || '').toLowerCase()}`;
return countsMap.get(key) || 0;
};