edge-cases: improve empty state handling in LibraryPage (Edge 1.1, 1.3)

- Replaced custom empty state div with EmptyState component
- Added EmptyState import from @/components/ui/empty-state
- Improved empty state UI with icon, title, description, and action button
- Different messages for empty list vs empty search results
- Added upload action button when no tracks (not in search mode)
- Fixed pre-existing errors: added useInfiniteQuery import, fixed tracksData reference
- Edge 1.4: Marked as complete (EmptyState component already exists)
- Edge 1.1 & 1.3: In progress - improved empty state handling
This commit is contained in:
senke 2026-01-16 12:44:19 +01:00
parent 27ac12e0bb
commit 2ae9535009
2 changed files with 34 additions and 23 deletions

View file

@ -4590,12 +4590,17 @@ If any task fails:
- **Validation**: Empty search shows helpful message
- **Rollback**: Restore original empty state
- [ ] **Edge 1.4**: Create reusable EmptyState component
- [x] **Edge 1.4**: Create reusable EmptyState component
- **Scope**: `apps/web/src/components/ui/EmptyState.tsx` (create) - Reusable empty state component
- **Dependencies**: None
- **Dependencies**: None
- **Risk**: LOW 🔒
- **Validation**: Component works, reusable
- **Rollback**: Delete component
- **Validation**: ✅ Component already exists at `apps/web/src/components/ui/empty-state.tsx`:
- Fully functional EmptyState component with icon, title, description, action support
- Supports size variants (sm, md, lg)
- Uses Kodo design system (Card, Button components)
- Has test file: `empty-state.test.tsx`
- **Result**: Reusable EmptyState component available for use
- **Rollback**: N/A (already exists)
- [ ] **Edge 1.5**: Use EmptyState component everywhere
- **Scope**: All empty states - Use EmptyState component

View file

@ -1,7 +1,7 @@
import { useState, useEffect, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useDebounce } from '@/hooks/useDebounce';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
import {
usePlaylists,
useAddTrackToPlaylist,
@ -57,6 +57,7 @@ import { Sidebar } from '@/components/ui/Sidebar';
import { BulkModeBanner } from '@/components/BulkModeBanner';
import { Tooltip } from '@/components/ui/tooltip';
import { AdvancedFilters } from '@/components/AdvancedFilters';
import { EmptyState } from '@/components/ui/empty-state';
import { logger } from '@/utils/logger';
import { parseApiError } from '@/utils/apiErrorHandler';
import { cn } from '@/lib/utils';
@ -135,7 +136,8 @@ export default function LibraryPagePremium() {
// Action 2.2.1.1: Remove client-side filtering - backend handles all filters
// Backend returns filtered results, no need for client-side filtering
const filteredTracks: Track[] = tracksData?.tracks || [];
const filteredTracks: Track[] =
tracksInfiniteData?.pages.flatMap((page) => page.tracks || []) || [];
// Action 2.4.1.4: Use debounced search term for page reset to fix race condition
// Reset infinite query when search term changes (handled by query key)
@ -688,28 +690,33 @@ export default function LibraryPagePremium() {
</div>
)}
</div>
) : filteredTracks.length === 0 ? (
<EmptyState
icon={<Music />}
title={searchTerm ? 'Aucun résultat trouvé' : 'Aucune piste disponible'}
description={
searchTerm
? "Essayez avec d'autres termes de recherche"
: 'Commencez par uploader votre premier track'
}
action={
!searchTerm
? {
label: 'Uploader un track',
onClick: () => setIsUploadModalOpen(true),
variant: 'default',
}
: undefined
}
/>
) : (
<Card>
<CardContent className="p-0">
{filteredTracks.length === 0 ? (
<div className="p-12 text-center">
<Music className="w-16 h-16 text-kodo-secondary mx-auto mb-4" />
<p className="text-lg font-semibold text-white mb-2">
Aucun titre trouvé
</p>
<p className="text-base text-kodo-secondary mb-4">
{searchTerm
? "Essayez avec d'autres termes de recherche"
: 'Commencez par uploader votre premier track'}
</p>
</div>
) : (
<VirtualizedList
<VirtualizedList
items={filteredTracks}
itemHeight={88}
containerHeight={600}
className="divide-y divide-white/5"
onItemsRendered={handleItemsRendered}
renderItem={(track: Track, index: number) => (
<div
key={track.id}
@ -793,8 +800,7 @@ export default function LibraryPagePremium() {
</DropdownMenu>
</div>
)}
/>
)}
/>
</CardContent>
</Card>
)}