"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; interface Document { id: string; title: string; uri: string; } interface DocumentSelectorProps { selected: string[]; onChange: (ids: string[]) => void; disabled: boolean; } export default function DocumentSelector({ selected, onChange, disabled, }: DocumentSelectorProps) { const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expanded, setExpanded] = useState(true); const [searchQuery, setSearchQuery] = useState(""); useEffect(() => { const fetchDocuments = async () => { try { const response = await fetch( `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/api/documents`, ); if (!response.ok) { throw new Error("Failed to fetch documents"); } const data = await response.json(); const docs = data.documents || []; setDocuments(docs); // Select all documents by default if none are selected if (selected.length === 0 && docs.length > 0) { onChange(docs.map((d: Document) => d.id)); } setError(null); } catch (err) { setError(err instanceof Error ? err.message : "Unknown error"); } finally { setLoading(false); } }; fetchDocuments(); }, []); const filteredDocuments = useMemo(() => { if (!searchQuery.trim()) return documents; const query = searchQuery.toLowerCase(); return documents.filter( (doc) => (doc.title || "").toLowerCase().includes(query) || (doc.uri || "").toLowerCase().includes(query), ); }, [documents, searchQuery]); const handleToggle = useCallback( (id: string) => { if (disabled) return; if (selected.includes(id)) { onChange(selected.filter((s) => s !== id)); } else { onChange([...selected, id]); } }, [selected, onChange, disabled], ); const handleSelectAll = useCallback(() => { if (disabled) return; const filteredIds = filteredDocuments.map((d) => d.id); const allFilteredSelected = filteredIds.every((id) => selected.includes(id), ); if (allFilteredSelected) { // Deselect all filtered documents onChange(selected.filter((id) => !filteredIds.includes(id))); } else { // Select all filtered documents (add to existing selection) const newSelection = [...new Set([...selected, ...filteredIds])]; onChange(newSelection); } }, [selected, filteredDocuments, onChange, disabled]); const selectedCount = selected.length; const totalCount = documents.length; const filterActive = selectedCount > 0 && selectedCount < totalCount; return (
{expanded && (
{loading && (
Loading documents...
)} {error && (
{error}
)} {!loading && !error && documents.length === 0 && (
No documents in database
)} {!loading && !error && documents.length > 0 && ( <> {/* Search Input */}
setSearchQuery(e.target.value)} disabled={disabled} style={{ width: "100%", padding: "0.5rem 0.75rem", fontSize: "0.875rem", border: "1px solid #e2e8f0", borderRadius: "4px", background: disabled ? "#f7fafc" : "white", color: disabled ? "#a0aec0" : "#2d3748", outline: "none", }} />
{/* Select All / Clear */}
{selectedCount} of {totalCount} selected
{searchQuery && ( Showing {filteredDocuments.length} of {totalCount} )}
{/* Document List */}
{filteredDocuments.map((doc) => { const isSelected = selected.includes(doc.id); return ( ); })}
)} {disabled && (
Filter locked during research
)}
)}
); }