'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { authorApi, getImageUrl } from '../../lib/api'; import { Author } from '../../types/api'; import AppLayout from '../../components/layout/AppLayout'; import { Input } from '../../components/ui/Input'; import Button from '../../components/ui/Button'; import LoadingSpinner from '../../components/ui/LoadingSpinner'; export default function AuthorsPage() { const [authors, setAuthors] = useState([]); const [filteredAuthors, setFilteredAuthors] = useState([]); const [loading, setLoading] = useState(true); const [searchLoading, setSearchLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [sortBy, setSortBy] = useState('name'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); const [currentPage, setCurrentPage] = useState(0); const [totalHits, setTotalHits] = useState(0); const [hasMore, setHasMore] = useState(false); const ITEMS_PER_PAGE = 50; useEffect(() => { const debounceTimer = setTimeout(() => { const loadAuthors = async () => { try { // Use searchLoading for background search, loading only for initial load const isInitialLoad = authors.length === 0 && !searchQuery && currentPage === 0; if (isInitialLoad) { setLoading(true); } else { setSearchLoading(true); } // Use Solr search for all queries (including empty search) const searchResults = await authorApi.searchAuthors({ query: searchQuery || '*', // Use '*' for all authors when no search query page: currentPage, size: ITEMS_PER_PAGE, sortBy: sortBy, sortDir: sortOrder }); if (currentPage === 0) { // First page - replace all results setAuthors(searchResults.results || []); setFilteredAuthors(searchResults.results || []); } else { // Subsequent pages - append results setAuthors(prev => [...prev, ...(searchResults.results || [])]); setFilteredAuthors(prev => [...prev, ...(searchResults.results || [])]); } setTotalHits(searchResults.totalHits || 0); setHasMore((searchResults.results || []).length === ITEMS_PER_PAGE && (currentPage + 1) * ITEMS_PER_PAGE < (searchResults.totalHits || 0)); } catch (error) { console.error('Failed to search authors:', error); } finally { setLoading(false); setSearchLoading(false); } }; loadAuthors(); }, searchQuery ? 500 : 0); // 500ms debounce for search, immediate for other changes return () => clearTimeout(debounceTimer); }, [searchQuery, sortBy, sortOrder, currentPage]); // Reset pagination when search or sort changes useEffect(() => { if (currentPage !== 0) { setCurrentPage(0); } }, [searchQuery, sortBy, sortOrder]); const loadMore = () => { if (hasMore && !loading) { setCurrentPage(prev => prev + 1); } }; // No longer needed - Solr search handles filtering directly // Note: We no longer have individual story ratings in the author list // Average rating would need to be calculated on backend if needed if (loading) { return (
); } return (
{/* Header */}

Authors

{searchQuery ? `${totalHits} authors found` : `${totalHits} authors in your library`} {hasMore && ` (showing first ${filteredAuthors.length})`}

{/* View Mode Toggle */}
{/* Search and Sort Controls */}
setSearchQuery(e.target.value)} /> {searchLoading && (
)}
{/* Authors Display */} {filteredAuthors.length === 0 ? (
{searchQuery ? 'No authors match your search' : 'No authors in your library yet' }
{searchQuery ? ( ) : (

Authors will appear here when you add stories to your library.

)}
) : viewMode === 'grid' ? (
{filteredAuthors.map((author) => ( ))}
) : (
{filteredAuthors.map((author) => ( ))}
)} {/* Load More Button */} {hasMore && !searchQuery && (
)}
); } // Author Grid Card Component function AuthorGridCard({ author }: { author: Author }) { return ( {/* Avatar */}
{author.avatarImagePath ? ( {author.name} ) : (
👤
)}

{author.name}

{/* Author Rating */} {author.authorRating && (
{[1, 2, 3, 4, 5].map((star) => ( ))}
({author.authorRating}/5)
)} {/* Average Story Rating */} {author.averageStoryRating && (
{[1, 2, 3, 4, 5].map((star) => ( ))}
({author.averageStoryRating.toFixed(1)})
)}
{/* Stats */}
Stories: {author.storyCount || 0}
{author.urls && author.urls.length > 0 && (
Links: {author.urls.length}
)}
{/* Notes Preview */} {author.notes && (

{author.notes}

)} ); } // Author List Item Component function AuthorListItem({ author }: { author: Author }) { return ( {/* Avatar */}
{author.avatarImagePath ? ( {author.name} ) : (
👤
)}
{/* Author Info */}

{author.name}

{/* Ratings */}
{author.authorRating && (
{[1, 2, 3, 4, 5].map((star) => ( ))}
({author.authorRating})
)} {author.averageStoryRating && (
{[1, 2, 3, 4, 5].map((star) => ( ))}
Avg: {author.averageStoryRating.toFixed(1)}
)}
{/* Stats */}
{author.storyCount || 0} stories {author.urls && author.urls.length > 0 && ( {author.urls.length} links )}
{/* Notes Preview */} {author.notes && (

{author.notes}

)}
); }