@@ -225,6 +279,110 @@ function UploadDialog({
);
}
+// ---------------------------------------------------------------------------
+// Document detail dialog
+// ---------------------------------------------------------------------------
+
+function DocumentDetailDialog({
+ open,
+ doc,
+ loading: loadingMeta,
+ onClose,
+}: {
+ open: boolean;
+ doc: DocumentMetadata | null;
+ loading?: boolean;
+ onClose: () => void;
+}) {
+ if (!doc) return null;
+
+ return (
+
+ );
+}
+
// ---------------------------------------------------------------------------
// Confirm delete dialog
// ---------------------------------------------------------------------------
@@ -286,14 +444,20 @@ export default function LibraryPage() {
error,
getDocuments,
uploadDocument,
+ uploadDocumentChunked,
removeDocument,
getProcessing,
+ getDocumentMetadata,
} = useLibrary();
const collection = useSettings((s) => s.settings.collection);
const notify = useNotification();
const [uploadOpen, setUploadOpen] = useState(false);
const [deleteTarget, setDeleteTarget] = useState
(null);
+ const [detailDoc, setDetailDoc] = useState(null);
+ const [detailOpen, setDetailOpen] = useState(false);
+ const [loadingDetail, setLoadingDetail] = useState(false);
+ const [searchTerm, setSearchTerm] = useState("");
// Load documents and processing on mount
useEffect(() => {
@@ -318,8 +482,28 @@ export default function LibraryPage() {
}
};
+ const handleUploadChunked = async (
+ data: string,
+ mimeType: string,
+ title: string,
+ comments: string,
+ tags: string[],
+ onProgress: (progress: UploadProgress) => void,
+ ) => {
+ try {
+ await uploadDocumentChunked(data, mimeType, title, comments, tags, onProgress);
+ notify.success("Document uploaded", `"${title}" is being processed.`);
+ getProcessing();
+ } catch {
+ notify.error("Upload failed", "Could not upload the document.");
+ }
+ };
+
const handleDelete = async () => {
- if (!deleteTarget?.id) return;
+ if (!deleteTarget?.id) {
+ setDeleteTarget(null);
+ return;
+ }
try {
await removeDocument(deleteTarget.id, collection);
notify.success("Document deleted");
@@ -329,6 +513,20 @@ export default function LibraryPage() {
setDeleteTarget(null);
};
+ const handleViewDetail = useCallback(
+ async (doc: DocumentMetadata) => {
+ setDetailDoc(doc);
+ setDetailOpen(true);
+ if (doc.id) {
+ setLoadingDetail(true);
+ const fullMeta = await getDocumentMetadata(doc.id);
+ if (fullMeta) setDetailDoc(fullMeta);
+ setLoadingDetail(false);
+ }
+ },
+ [getDocumentMetadata],
+ );
+
const handleRefresh = () => {
getDocuments();
getProcessing();
@@ -343,6 +541,24 @@ export default function LibraryPage() {
return kind || "--";
};
+ // Search/filter
+ const searchLower = searchTerm.toLowerCase();
+ const filteredDocuments = useMemo(() => {
+ if (!searchLower) return documents;
+ return documents.filter((doc) => {
+ const title = (doc.title ?? "").toLowerCase();
+ const id = (doc.id ?? "").toLowerCase();
+ const tags = (doc.tags ?? []).join(" ").toLowerCase();
+ const kind = (doc.kind ?? doc["document-type"] ?? "").toLowerCase();
+ return (
+ title.includes(searchLower) ||
+ id.includes(searchLower) ||
+ tags.includes(searchLower) ||
+ kind.includes(searchLower)
+ );
+ });
+ }, [documents, searchLower]);
+
return (
{/* Header */}
@@ -374,6 +590,31 @@ export default function LibraryPage() {
+ {/* Search bar */}
+ {documents.length > 0 && (
+
+ {filteredDocuments.length} of {documents.length} documents match
+
+ )}
+
+ {filteredDocuments.length > 0 && (