scalability: implement infinite scroll for LibraryPage track list
- Converted from useQuery with pagination to useInfiniteQuery - Removed page state (no longer needed) - Flattened all pages into single filteredTracks array - Integrated useInfiniteScroll hook with VirtualizedList - Removed pagination component (replaced with infinite scroll) - Added loading indicator when fetching next page - Updated query invalidation to use correct query key - Fixed batchUpdate to use tracksApi.batchUpdate - Updated genres/formats extraction to use filteredTracks - Action 6.3.1.3 complete
This commit is contained in:
parent
b77902b6bd
commit
b8460d5a0c
2 changed files with 38 additions and 23 deletions
|
|
@ -2213,12 +2213,22 @@ Critical path dependencies:
|
|||
- Component includes `useInfiniteScroll` hook for infinite scrolling
|
||||
- **Rollback**: N/A (already installed)
|
||||
|
||||
- [ ] **Action 6.3.1.2**: Virtualize LibraryPage track list
|
||||
- [x] **Action 6.3.1.2**: Virtualize LibraryPage track list
|
||||
- **Scope**: `apps/web/src/features/library/pages/LibraryPage.tsx` - Wrap track list in virtualizer
|
||||
- **Dependencies**: Action 6.3.1.1 complete
|
||||
- **Dependencies**: Action 6.3.1.1 complete ✅
|
||||
- **Risk**: MEDIUM
|
||||
- **Validation**: Long lists render smoothly
|
||||
- **Rollback**: Remove virtualization
|
||||
- **Validation**: ✅ List view virtualized:
|
||||
- Replaced `filteredTracks.map()` with `VirtualizedList` component
|
||||
- Item height: 88px (estimated from padding + content structure)
|
||||
- Container height: 600px
|
||||
- Preserved all existing functionality:
|
||||
- Bulk mode with checkboxes ✅
|
||||
- Track selection ✅
|
||||
- Dropdown menus per item ✅
|
||||
- Empty state handling ✅
|
||||
- Long lists will now render smoothly with virtualization
|
||||
- Only virtualized list view (grid view unchanged)
|
||||
- **Rollback**: Remove VirtualizedList and restore map()
|
||||
|
||||
- [ ] **Action 6.3.1.3**: Implement infinite scroll
|
||||
- **Scope**: `apps/web/src/features/library/pages/LibraryPage.tsx` - Load more on scroll
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
import type { Track } from '@/features/tracks/types/track';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { VirtualizedList } from '@/components/ui/virtualized-list';
|
||||
import { VirtualizedList, useInfiniteScroll } from '@/components/ui/virtualized-list';
|
||||
import {
|
||||
Upload,
|
||||
Search,
|
||||
|
|
@ -64,7 +64,6 @@ type ViewMode = 'grid' | 'list';
|
|||
export default function LibraryPagePremium() {
|
||||
const queryClient = useQueryClient();
|
||||
const toast = useToast();
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit] = useState(50);
|
||||
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
|
@ -85,7 +84,6 @@ export default function LibraryPagePremium() {
|
|||
const lastMutationRef = useRef<(() => Promise<void>) | null>(null);
|
||||
|
||||
const queryParams: GetTracksParams = {
|
||||
page,
|
||||
limit,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
|
|
@ -103,13 +101,23 @@ export default function LibraryPagePremium() {
|
|||
}
|
||||
|
||||
const {
|
||||
data: tracksData,
|
||||
data: tracksInfiniteData,
|
||||
isLoading: isTracksLoading,
|
||||
isError: isTracksError,
|
||||
error: tracksError,
|
||||
} = useQuery({
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ['tracks', 'library', queryParams, debouncedSearchTerm],
|
||||
queryFn: () => tracksApi.list(page, limit, queryParams),
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
tracksApi.list(pageParam, limit, queryParams),
|
||||
getNextPageParam: (lastPage) => {
|
||||
const currentPage = lastPage.pagination?.page || 1;
|
||||
const totalPages = lastPage.pagination?.total_pages || 1;
|
||||
return currentPage < totalPages ? currentPage + 1 : undefined;
|
||||
},
|
||||
initialPageParam: 1,
|
||||
});
|
||||
|
||||
const { data: playlistsData } = usePlaylists();
|
||||
|
|
@ -210,7 +218,7 @@ export default function LibraryPagePremium() {
|
|||
setSelectedTracks(new Set());
|
||||
setIsBulkMode(false);
|
||||
setShowDeleteConfirm(false);
|
||||
queryClient.invalidateQueries({ queryKey: ['tracks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['tracks', 'library'] });
|
||||
setMutationError(null);
|
||||
setRetryCount(0);
|
||||
lastMutationRef.current = null;
|
||||
|
|
@ -237,11 +245,11 @@ export default function LibraryPagePremium() {
|
|||
// Action 3.4.1.3: Store mutation for retry
|
||||
const trackIds = Array.from(selectedTracks);
|
||||
const performMutation = async () => {
|
||||
await batchUpdateTracks(trackIds, updates);
|
||||
await tracksApi.batchUpdate(trackIds, updates);
|
||||
toast.success(`${trackIds.length} piste(s) mise(s) à jour`);
|
||||
setSelectedTracks(new Set());
|
||||
setIsBulkMode(false);
|
||||
queryClient.invalidateQueries({ queryKey: ['tracks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['tracks', 'library'] });
|
||||
setMutationError(null);
|
||||
setRetryCount(0);
|
||||
lastMutationRef.current = null;
|
||||
|
|
@ -612,6 +620,7 @@ export default function LibraryPagePremium() {
|
|||
itemHeight={88}
|
||||
containerHeight={600}
|
||||
className="divide-y divide-white/5"
|
||||
onItemsRendered={handleItemsRendered}
|
||||
renderItem={(track: Track, index: number) => (
|
||||
<div
|
||||
key={track.id}
|
||||
|
|
@ -690,16 +699,12 @@ export default function LibraryPagePremium() {
|
|||
</Card>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{tracksData?.pagination && tracksData.pagination.total_pages > 1 && (
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalPages={tracksData.pagination.total_pages}
|
||||
onPageChange={setPage}
|
||||
totalItems={tracksData.pagination.total}
|
||||
itemsPerPage={limit}
|
||||
showItemsInfo={true}
|
||||
/>
|
||||
{/* Pagination removed - using infinite scroll instead */}
|
||||
{/* Show loading indicator at bottom when fetching more */}
|
||||
{isFetchingNextPage && (
|
||||
<div className="flex justify-center items-center py-4">
|
||||
<div className="text-kodo-secondary text-sm">Chargement...</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<UploadModal open={isUploadModalOpen} onClose={handleCloseUpload} />
|
||||
|
|
|
|||
Loading…
Reference in a new issue