Various improvements & Epub support
This commit is contained in:
@@ -11,16 +11,17 @@ import TagFilter from '../../components/stories/TagFilter';
|
||||
import LoadingSpinner from '../../components/ui/LoadingSpinner';
|
||||
|
||||
type ViewMode = 'grid' | 'list';
|
||||
type SortOption = 'createdAt' | 'title' | 'authorName' | 'rating' | 'wordCount';
|
||||
type SortOption = 'createdAt' | 'title' | 'authorName' | 'rating' | 'wordCount' | 'lastRead';
|
||||
|
||||
export default function LibraryPage() {
|
||||
const [stories, setStories] = useState<Story[]>([]);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchLoading, setSearchLoading] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('list');
|
||||
const [sortOption, setSortOption] = useState<SortOption>('createdAt');
|
||||
const [sortOption, setSortOption] = useState<SortOption>('lastRead');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||
const [page, setPage] = useState(0);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
@@ -47,7 +48,13 @@ export default function LibraryPage() {
|
||||
const debounceTimer = setTimeout(() => {
|
||||
const performSearch = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// Use searchLoading for background search, loading only for initial load
|
||||
const isInitialLoad = stories.length === 0 && !searchQuery && selectedTags.length === 0;
|
||||
if (isInitialLoad) {
|
||||
setLoading(true);
|
||||
} else {
|
||||
setSearchLoading(true);
|
||||
}
|
||||
|
||||
// Always use search API for consistency - use '*' for match-all when no query
|
||||
const result = await searchApi.search({
|
||||
@@ -73,11 +80,12 @@ export default function LibraryPage() {
|
||||
setStories([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setSearchLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
performSearch();
|
||||
}, searchQuery ? 300 : 0); // Debounce search, but not other changes
|
||||
}, searchQuery ? 500 : 0); // 500ms debounce for search, immediate for other changes
|
||||
|
||||
return () => clearTimeout(debounceTimer);
|
||||
}, [searchQuery, selectedTags, page, sortOption, sortDirection, refreshTrigger]);
|
||||
@@ -154,16 +162,21 @@ export default function LibraryPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button href="/add-story">
|
||||
Add New Story
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button href="/add-story">
|
||||
Add New Story
|
||||
</Button>
|
||||
<Button href="/stories/import/epub" variant="secondary">
|
||||
📖 Import EPUB
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search and Filters */}
|
||||
<div className="space-y-4">
|
||||
{/* Search Bar */}
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 relative">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search by title, author, or tags..."
|
||||
@@ -171,6 +184,11 @@ export default function LibraryPage() {
|
||||
onChange={handleSearchChange}
|
||||
className="w-full"
|
||||
/>
|
||||
{searchLoading && (
|
||||
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||
<div className="animate-spin h-4 w-4 border-2 border-theme-accent border-t-transparent rounded-full"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* View Mode Toggle */}
|
||||
@@ -215,6 +233,7 @@ export default function LibraryPage() {
|
||||
<option value="authorName">Author</option>
|
||||
<option value="rating">Rating</option>
|
||||
<option value="wordCount">Word Count</option>
|
||||
<option value="lastRead">Last Read</option>
|
||||
</select>
|
||||
|
||||
{/* Sort Direction Toggle */}
|
||||
|
||||
Reference in New Issue
Block a user