File size: 4,937 Bytes
7c012de |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
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>
);
}
|