Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
e2d9297ffe
commit
cf78d4d4a5
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { ActionButton, SongItem, InfiniteScrollList } from '../../components/common';
|
import { ActionButton, SongItem } from '../../components/common';
|
||||||
import { useSongLists } from '../../hooks';
|
import { useSongLists } from '../../hooks';
|
||||||
import { useAppSelector } from '../../redux';
|
import { useAppSelector } from '../../redux';
|
||||||
import { selectSongList } from '../../redux';
|
import { selectSongList } from '../../redux';
|
||||||
@ -8,6 +8,7 @@ import type { SongListSong, Song } from '../../types';
|
|||||||
const SongLists: React.FC = () => {
|
const SongLists: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
songLists,
|
songLists,
|
||||||
|
allSongLists,
|
||||||
hasMore,
|
hasMore,
|
||||||
loadMore,
|
loadMore,
|
||||||
checkSongAvailability,
|
checkSongAvailability,
|
||||||
@ -17,14 +18,45 @@ const SongLists: React.FC = () => {
|
|||||||
|
|
||||||
const songListData = useAppSelector(selectSongList);
|
const songListData = useAppSelector(selectSongList);
|
||||||
const songListCount = Object.keys(songListData).length;
|
const songListCount = Object.keys(songListData).length;
|
||||||
|
const observerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Intersection Observer for infinite scrolling
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('SongLists - Setting up observer:', { hasMore, songListCount, itemsLength: songLists.length });
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
console.log('SongLists - Intersection detected:', {
|
||||||
|
isIntersecting: entries[0].isIntersecting,
|
||||||
|
hasMore,
|
||||||
|
songListCount
|
||||||
|
});
|
||||||
|
|
||||||
|
if (entries[0].isIntersecting && hasMore && songListCount > 0) {
|
||||||
|
console.log('SongLists - Loading more items');
|
||||||
|
loadMore();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (observerRef.current) {
|
||||||
|
observer.observe(observerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [loadMore, hasMore, songListCount]);
|
||||||
const [selectedSongList, setSelectedSongList] = useState<string | null>(null);
|
const [selectedSongList, setSelectedSongList] = useState<string | null>(null);
|
||||||
const [expandedSongs, setExpandedSongs] = useState<Set<string>>(new Set());
|
const [expandedSongs, setExpandedSongs] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging - only log when data changes
|
||||||
console.log('SongLists component - songList count:', songListCount);
|
useEffect(() => {
|
||||||
console.log('SongLists component - songLists:', songLists);
|
console.log('SongLists component - songList count:', songListCount);
|
||||||
|
console.log('SongLists component - songLists:', songLists);
|
||||||
|
}, [songListCount, songLists.length]);
|
||||||
|
|
||||||
const handleSongListClick = (songListKey: string) => {
|
const handleSongListClick = (songListKey: string) => {
|
||||||
|
console.log('SongLists - handleSongListClick called with key:', songListKey);
|
||||||
setSelectedSongList(songListKey);
|
setSelectedSongList(songListKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,7 +74,23 @@ const SongLists: React.FC = () => {
|
|||||||
setExpandedSongs(newExpanded);
|
setExpandedSongs(newExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedList = selectedSongList ? songLists.find(list => list.key === selectedSongList) : null;
|
const finalSelectedList = selectedSongList
|
||||||
|
? allSongLists.find(list => list.key === selectedSongList)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Debug logging for modal
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('SongLists - Modal state check:', {
|
||||||
|
selectedSongList,
|
||||||
|
finalSelectedList: !!finalSelectedList,
|
||||||
|
songListsLength: songLists.length
|
||||||
|
});
|
||||||
|
if (selectedSongList) {
|
||||||
|
console.log('SongLists - Modal opened for song list:', selectedSongList);
|
||||||
|
console.log('SongLists - Selected list data:', finalSelectedList);
|
||||||
|
console.log('SongLists - About to render modal, finalSelectedList:', !!finalSelectedList);
|
||||||
|
}
|
||||||
|
}, [selectedSongList, finalSelectedList, songLists.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
@ -59,49 +107,78 @@ const SongLists: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Song Lists */}
|
{/* Song Lists */}
|
||||||
<InfiniteScrollList
|
<div className="bg-white rounded-lg shadow">
|
||||||
items={songLists.map(songList => ({
|
{songListCount === 0 ? (
|
||||||
...songList,
|
<div className="p-8 text-center">
|
||||||
title: songList.title,
|
<div className="text-gray-400 mb-4">
|
||||||
artist: `${songList.songs.length} song${songList.songs.length !== 1 ? 's' : ''}`,
|
<svg className="h-12 w-12 mx-auto animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
path: '',
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
disabled: false,
|
</svg>
|
||||||
favorite: false,
|
</div>
|
||||||
}))}
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Loading song lists...</h3>
|
||||||
isLoading={songListCount === 0}
|
<p className="text-sm text-gray-500">Please wait while song lists are being loaded</p>
|
||||||
hasMore={hasMore}
|
</div>
|
||||||
onLoadMore={loadMore}
|
) : songLists.length === 0 ? (
|
||||||
onAddToQueue={() => {}} // Not applicable for song lists
|
<div className="p-8 text-center">
|
||||||
onToggleFavorite={() => {}} // Not applicable for song lists
|
<div className="text-gray-400 mb-4">
|
||||||
context="search"
|
<svg className="h-12 w-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
title="Song Lists"
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
|
||||||
subtitle={`${songLists.length} song list${songLists.length !== 1 ? 's' : ''} available`}
|
</svg>
|
||||||
emptyTitle="No song lists available"
|
</div>
|
||||||
emptyMessage="Song lists will appear here when they're available"
|
<h3 className="text-lg font-medium text-gray-900 mb-2">No song lists available</h3>
|
||||||
loadingTitle="Loading song lists..."
|
<p className="text-sm text-gray-500">Song lists will appear here when they're available</p>
|
||||||
loadingMessage="Please wait while song lists are being loaded"
|
</div>
|
||||||
debugInfo={`Song lists loaded: ${songListCount}`}
|
) : (
|
||||||
renderExtraContent={(item) => (
|
<div className="divide-y divide-gray-200">
|
||||||
<div className="flex-shrink-0 ml-4">
|
{songLists.map((songList) => (
|
||||||
<ActionButton
|
<div key={songList.key} className="flex items-center justify-between p-4 hover:bg-gray-50">
|
||||||
onClick={() => handleSongListClick(item.key!)}
|
<div className="flex-1">
|
||||||
variant="primary"
|
<h3 className="text-sm font-medium text-gray-900">
|
||||||
size="sm"
|
{songList.title}
|
||||||
>
|
</h3>
|
||||||
View Songs
|
<p className="text-sm text-gray-500">
|
||||||
</ActionButton>
|
{songList.songs.length} song{songList.songs.length !== 1 ? 's' : ''}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0 ml-4">
|
||||||
|
<ActionButton
|
||||||
|
onClick={() => handleSongListClick(songList.key!)}
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
View Songs
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Infinite scroll trigger */}
|
||||||
|
{hasMore && (
|
||||||
|
<div
|
||||||
|
ref={observerRef}
|
||||||
|
className="py-4 text-center text-gray-500"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center">
|
||||||
|
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
Loading more song lists...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
{/* Song List Modal */}
|
{/* Song List Modal */}
|
||||||
{selectedList && (
|
{finalSelectedList && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999]" style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full mx-4 max-h-[80vh] overflow-hidden">
|
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full mx-4 max-h-[80vh] overflow-hidden" style={{ backgroundColor: 'white', zIndex: 10000 }}>
|
||||||
<div className="p-6 border-b border-gray-200">
|
<div className="p-6 border-b border-gray-200">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-xl font-bold text-gray-900">
|
<h2 className="text-xl font-bold text-gray-900">
|
||||||
{selectedList.title}
|
TEST MODAL - {finalSelectedList.title}
|
||||||
</h2>
|
</h2>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={handleCloseSongList}
|
onClick={handleCloseSongList}
|
||||||
@ -115,13 +192,13 @@ const SongLists: React.FC = () => {
|
|||||||
|
|
||||||
<div className="overflow-y-auto max-h-[60vh]">
|
<div className="overflow-y-auto max-h-[60vh]">
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200">
|
||||||
{selectedList.songs.map((songListSong: SongListSong) => {
|
{finalSelectedList.songs.map((songListSong: SongListSong, idx) => {
|
||||||
const availableSongs = checkSongAvailability(songListSong);
|
const availableSongs = checkSongAvailability(songListSong);
|
||||||
const isExpanded = expandedSongs.has(songListSong.key!);
|
const isExpanded = expandedSongs.has(songListSong.key!);
|
||||||
const isAvailable = availableSongs.length > 0;
|
const isAvailable = availableSongs.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={songListSong.key}>
|
<div key={songListSong.key || `${songListSong.title}-${songListSong.position}-${idx}`}>
|
||||||
{/* Song List Song Row */}
|
{/* Song List Song Row */}
|
||||||
<div className={`flex items-center justify-between p-4 ${!isAvailable ? 'opacity-50' : ''}`}>
|
<div className={`flex items-center justify-between p-4 ${!isAvailable ? 'opacity-50' : ''}`}>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@ -153,8 +230,8 @@ const SongLists: React.FC = () => {
|
|||||||
{/* Available Songs (when expanded) */}
|
{/* Available Songs (when expanded) */}
|
||||||
{isExpanded && isAvailable && (
|
{isExpanded && isAvailable && (
|
||||||
<div className="bg-gray-50 border-t border-gray-200">
|
<div className="bg-gray-50 border-t border-gray-200">
|
||||||
{availableSongs.map((song: Song) => (
|
{availableSongs.map((song: Song, sidx) => (
|
||||||
<div key={song.key} className="p-4 border-b border-gray-200 last:border-b-0">
|
<div key={song.key || `${song.title}-${song.artist}-${sidx}`} className="p-4 border-b border-gray-200 last:border-b-0">
|
||||||
<SongItem
|
<SongItem
|
||||||
song={song}
|
song={song}
|
||||||
context="search"
|
context="search"
|
||||||
|
|||||||
@ -33,19 +33,23 @@ export const useSongLists = () => {
|
|||||||
}, [songLists.length, allSongLists.length, currentPage]);
|
}, [songLists.length, allSongLists.length, currentPage]);
|
||||||
|
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
|
const endIndex = currentPage * ITEMS_PER_PAGE;
|
||||||
|
const hasMoreItems = endIndex < allSongLists.length;
|
||||||
|
|
||||||
console.log('useSongLists - loadMore called:', {
|
console.log('useSongLists - loadMore called:', {
|
||||||
hasMore,
|
hasMoreItems,
|
||||||
currentPage,
|
currentPage,
|
||||||
allSongListsLength: allSongLists.length,
|
allSongListsLength: allSongLists.length,
|
||||||
songListsLength: songLists.length
|
endIndex
|
||||||
});
|
});
|
||||||
if (hasMore) {
|
|
||||||
|
if (hasMoreItems) {
|
||||||
console.log('useSongLists - Incrementing page from', currentPage, 'to', currentPage + 1);
|
console.log('useSongLists - Incrementing page from', currentPage, 'to', currentPage + 1);
|
||||||
setCurrentPage(prev => prev + 1);
|
setCurrentPage(prev => prev + 1);
|
||||||
} else {
|
} else {
|
||||||
console.log('useSongLists - Not loading more because hasMore is false');
|
console.log('useSongLists - Not loading more because hasMore is false');
|
||||||
}
|
}
|
||||||
}, [hasMore, currentPage, allSongLists.length, songLists.length]);
|
}, [currentPage, allSongLists.length]);
|
||||||
|
|
||||||
// Check if a song exists in the catalog
|
// Check if a song exists in the catalog
|
||||||
const checkSongAvailability = useCallback((songListSong: SongListSong) => {
|
const checkSongAvailability = useCallback((songListSong: SongListSong) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user