Spaces:
Runtime error
Runtime error
| import { useState } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { SortAsc, Filter, Circle } from "lucide-react"; | |
| import ResultCard from "./result-card"; | |
| import { type SearchResponse, type DocumentWithContext } from "@shared/schema"; | |
| interface SearchResultsProps { | |
| results?: SearchResponse; | |
| expandedResults: Set<number>; | |
| savedDocuments?: Set<number>; | |
| onToggleExpanded: (resultId: number) => void; | |
| onAddCitation: (documentId: number, citationText: string, section?: string, pageNumber?: number) => void; | |
| onSaveDocument?: (documentId: number) => void; | |
| isLoading?: boolean; | |
| error?: Error | null; | |
| } | |
| export default function SearchResults({ | |
| results, | |
| expandedResults, | |
| savedDocuments, | |
| onToggleExpanded, | |
| onAddCitation, | |
| onSaveDocument, | |
| isLoading, | |
| error | |
| }: SearchResultsProps) { | |
| const [sortBy, setSortBy] = useState<"relevance" | "date" | "title">("relevance"); | |
| if (error) { | |
| return null; // Error is handled in parent component | |
| } | |
| if (isLoading) { | |
| return ( | |
| <div className="space-y-4"> | |
| {[...Array(3)].map((_, i) => ( | |
| <div key={i} className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 animate-pulse"> | |
| <div className="flex items-start justify-between mb-4"> | |
| <div className="flex-1 space-y-2"> | |
| <div className="flex items-center gap-3 mb-2"> | |
| <div className="w-4 h-4 bg-slate-200 rounded"></div> | |
| <div className="w-20 h-4 bg-slate-200 rounded"></div> | |
| <div className="w-16 h-6 bg-slate-200 rounded-full"></div> | |
| </div> | |
| <div className="w-3/4 h-6 bg-slate-200 rounded"></div> | |
| <div className="w-1/2 h-4 bg-slate-200 rounded"></div> | |
| </div> | |
| <div className="w-8 h-8 bg-slate-200 rounded"></div> | |
| </div> | |
| <div className="bg-slate-50 rounded-lg p-4"> | |
| <div className="space-y-2"> | |
| <div className="w-full h-4 bg-slate-200 rounded"></div> | |
| <div className="w-5/6 h-4 bg-slate-200 rounded"></div> | |
| <div className="w-4/6 h-4 bg-slate-200 rounded"></div> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| } | |
| const sortedResults = results?.results ? [...results.results].sort((a, b) => { | |
| switch (sortBy) { | |
| case "relevance": | |
| return b.relevanceScore - a.relevanceScore; | |
| case "date": | |
| return new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime(); | |
| case "title": | |
| return a.title.localeCompare(b.title); | |
| default: | |
| return 0; | |
| } | |
| }) : []; | |
| if (!results) { | |
| return null; | |
| } | |
| return ( | |
| <div> | |
| {/* Results Statistics */} | |
| <div className="flex items-center justify-between mb-6"> | |
| <div className="flex items-center gap-4"> | |
| <span className="text-sm text-slate-600"> | |
| <span className="font-medium">{results?.totalCount || 0}</span> results found in{" "} | |
| <span className="font-medium">{results?.searchTime?.toFixed(2) || '0.00'}</span> seconds | |
| </span> | |
| <div className="flex items-center gap-2"> | |
| <Circle className="w-2 h-2 fill-emerald-500 text-emerald-500" /> | |
| <span className="text-sm text-slate-600">Vector index active</span> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => { | |
| const nextSort = sortBy === "relevance" ? "date" : sortBy === "date" ? "title" : "relevance"; | |
| setSortBy(nextSort); | |
| }} | |
| className="text-slate-400 hover:text-slate-600" | |
| > | |
| <SortAsc className="w-4 h-4" /> | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="text-slate-400 hover:text-slate-600" | |
| > | |
| <Filter className="w-4 h-4" /> | |
| </Button> | |
| </div> | |
| </div> | |
| {/* Search Results */} | |
| <div className="space-y-4"> | |
| {sortedResults.map((result) => ( | |
| <ResultCard | |
| key={result.id} | |
| document={result} | |
| isExpanded={expandedResults.has(result.id)} | |
| isSaved={savedDocuments?.has(result.id) || false} | |
| onToggleExpanded={() => onToggleExpanded(result.id)} | |
| onAddCitation={onAddCitation} | |
| onSaveDocument={onSaveDocument} | |
| /> | |
| ))} | |
| </div> | |
| {/* Load More */} | |
| {results?.results && results.results.length > 0 && results.totalCount > results.results.length && ( | |
| <div className="mt-8 text-center"> | |
| <Button | |
| variant="outline" | |
| className="px-6 py-3" | |
| > | |
| Load More Results | |
| </Button> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |