import type { Tag } from "@shared/schema"; import { useQuery } from "@tanstack/react-query"; import { ChevronLeft, ChevronRight, Filter, Grid, List, Search as SearchIcon, X, } from "lucide-react"; import { useEffect, useState } from "react"; import { useLocation } from "wouter"; import { AuthorChip } from "@/components/common/AuthorChip"; import { WorkCard } from "@/components/common/WorkCard"; import { FilterSidebar } from "@/components/explore/FilterSidebar"; import { PageLayout } from "@/components/layout/PageLayout"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import type { SearchResults, WorkWithAuthor } from "@/lib/types"; export default function Search() { const [location, setLocation] = useLocation(); const [query, setQuery] = useState(""); const [activeTab, setActiveTab] = useState("all"); const [viewMode, setViewMode] = useState<"list" | "grid">("list"); const [showFilters, setShowFilters] = useState(false); const [filters, setFilters] = useState<{ language?: string; type?: string; yearStart?: number; yearEnd?: number; tags?: string[]; page: number; }>({ page: 1, }); // Parse URL parameters useEffect(() => { const searchParams = new URLSearchParams(location.split("?")[1]); const q = searchParams.get("q"); if (q) { setQuery(q); } // Parse other filter params const language = searchParams.get("language"); const type = searchParams.get("type"); const yearStart = searchParams.get("yearStart"); const yearEnd = searchParams.get("yearEnd"); const tags = searchParams.get("tags"); setFilters({ language: language || undefined, type: type || undefined, yearStart: yearStart ? parseInt(yearStart) : undefined, yearEnd: yearEnd ? parseInt(yearEnd) : undefined, tags: tags ? tags.split(",") : undefined, page: parseInt(searchParams.get("page") || "1"), }); }, [location]); // Search results query const { data: searchResults, isLoading: searchLoading } = useQuery({ queryKey: ["/api/search", query], queryFn: async (): Promise => { if (!query || query.length < 2) return { works: [], authors: [] }; // Since /api/search might not exist, we'll assume it returns SearchResults structure // If the backend route is missing, this will fail at runtime, but we are fixing types. const response = await fetch( `/api/search?q=${encodeURIComponent(query)}`, ); return await response.json(); }, enabled: query.length >= 2, select: (data) => ({ ...data, works: data.works.map((work) => ({ ...work, tags: work.tags?.map((tag) => typeof tag === "string" ? { name: tag, id: tag, type: "general", createdAt: "" } : tag, ), })), }), }); // Filter results query (for advanced filtering) const { data: filteredWorks, isLoading: filterLoading } = useQuery({ queryKey: ["/api/filter", filters], queryFn: async (): Promise => { const params = new URLSearchParams(); if (query) params.append("q", query); if (filters.language) params.append("language", filters.language); if (filters.type) params.append("type", filters.type); if (filters.yearStart) params.append("yearStart", filters.yearStart.toString()); if (filters.yearEnd) params.append("yearEnd", filters.yearEnd.toString()); if (filters.tags && filters.tags.length > 0) params.append("tags", filters.tags.join(",")); const response = await fetch(`/api/filter?${params.toString()}`); return await response.json(); }, enabled: activeTab === "advanced" && (!!filters.language || !!filters.type || !!filters.yearStart || !!filters.yearEnd || !!(filters.tags && filters.tags.length > 0)), select: (data) => data.map((work) => ({ ...work, tags: work.tags?.map((tag) => typeof tag === "string" ? { name: tag, id: tag, type: "general", createdAt: "" } : tag, ), })), }); // Get tags for filter sidebar const { data: tags } = useQuery({ queryKey: ["/api/tags"], }); const handleSearch = (e: React.FormEvent) => { e.preventDefault(); if (query.trim()) { const newParams = new URLSearchParams(); newParams.append("q", query.trim()); // Only include filter params if we're on advanced tab if (activeTab === "advanced") { if (filters.language) newParams.append("language", filters.language); if (filters.type) newParams.append("type", filters.type); if (filters.yearStart) newParams.append("yearStart", filters.yearStart.toString()); if (filters.yearEnd) newParams.append("yearEnd", filters.yearEnd.toString()); if (filters.tags && filters.tags.length > 0) newParams.append("tags", filters.tags.join(",")); } setLocation(`/search?${newParams.toString()}`); } }; const handleFilterChange = (newFilters: Partial) => { setFilters((prev) => ({ ...prev, ...newFilters, page: 1 })); }; const handleClearSearch = () => { setQuery(""); setLocation("/search"); }; const handlePageChange = (newPage: number) => { setFilters((prev) => ({ ...prev, page: newPage })); window.scrollTo({ top: 0, behavior: "smooth" }); }; // Calculate pagination values const totalItems = activeTab === "works" ? searchResults?.works.length || 0 : activeTab === "authors" ? searchResults?.authors.length || 0 : activeTab === "advanced" ? filteredWorks?.length || 0 : (searchResults?.works.length || 0) + (searchResults?.authors.length || 0); const itemsPerPage = 10; const totalPages = Math.ceil(totalItems / itemsPerPage); const currentPage = filters.page; // Generate pagination numbers const maxPageButtons = 5; let startPage = Math.max(1, currentPage - Math.floor(maxPageButtons / 2)); const endPage = Math.min(totalPages, startPage + maxPageButtons - 1); if (endPage - startPage + 1 < maxPageButtons) { startPage = Math.max(1, endPage - maxPageButtons + 1); } const pageNumbers = []; for (let i = startPage; i <= endPage; i++) { pageNumbers.push(i); } // Handle display based on current active tab // Use any cast here because of the complex type transformation in select causing inference issues with WorkCard props const displayWorks = activeTab === "advanced" ? filteredWorks || [] : searchResults?.works || []; const isLoading = (searchLoading && activeTab !== "advanced") || (filterLoading && activeTab === "advanced"); const hasNoResults = query.length >= 2 && !isLoading && (activeTab === "all" || activeTab === "works" || activeTab === "advanced") && displayWorks.length === 0; const hasNoAuthors = query.length >= 2 && !isLoading && (activeTab === "all" || activeTab === "authors") && (searchResults?.authors.length || 0) === 0; return (

Search Literary Works

Discover literary treasures from our extensive collection

{/* Search Form */}
setQuery(e.target.value)} className="pl-10 h-11 pr-10" /> {query && ( )}
{/* Tab Navigation */}
All Results Works Authors Advanced
View:
{/* Show filter sidebar in Advanced tab */} {(activeTab === "advanced" || showFilters) && ( )} {/* Results area */}
{/* Query info */} {query && query.length >= 2 && (
{isLoading ? (

Searching for "{query}"...

) : (

Showing results for " {query} " {activeTab !== "advanced" && searchResults && ( {" "} - {searchResults.works.length} works,{" "} {searchResults.authors.length} authors found )} {activeTab === "advanced" && filteredWorks && ( {" "} - {filteredWorks.length} works match your filters )}

)}
)} {/* Authors section in all or authors tab */} {(activeTab === "all" || activeTab === "authors") && query.length >= 2 && ( <> {activeTab === "all" && searchResults?.authors && searchResults.authors.length > 0 && (

Authors

)} {isLoading ? (
{Array.from({ length: 3 }).map((_, i) => (
))}
) : ( <> {searchResults?.authors && searchResults.authors.length > 0 && (
{searchResults.authors.map((author) => (

{author.biography?.slice(0, 120)}...

))}
)} {hasNoAuthors && activeTab === "authors" && (

No authors found matching "{query}"

Try a different search term or check your spelling

)} )} )} {/* Works section */} {(activeTab === "all" || activeTab === "works" || activeTab === "advanced") && ( <> {activeTab === "all" && searchResults?.works && searchResults.works.length > 0 && (

Works

)} {isLoading ? ( viewMode === "list" ? (
{Array.from({ length: 5 }).map((_, i) => (
))}
) : (
{Array.from({ length: 6 }).map((_, i) => (
))}
) ) : displayWorks.length > 0 ? ( viewMode === "list" ? (
{displayWorks.map((work) => ( // @ts-expect-error - Work type mismatch due to tag transformation ))}
) : (
{displayWorks.map((work) => ( // @ts-expect-error - Work type mismatch due to tag transformation ))}
) ) : ( hasNoResults && (

No works found matching "{query}"

{activeTab === "advanced" ? "Try adjusting your filters or using different search terms" : "Try a different search term or check your spelling"}

) )} )} {/* Entry prompt when search is empty */} {query.length < 2 && (

Start Your Literary Journey

Enter a search term above to discover works by title, author, or keyword

{[ "Poetry", "Novel", "Tolstoy", "Shakespeare", "Modernism", "Love", ].map((term) => ( ))}
)} {/* Pagination */} {totalPages > 1 && !isLoading && (
)}
); }