diff --git a/frontend/src/app/AppLayout.jsx b/frontend/src/app/AppLayout.jsx index 4cfff69..016d43c 100644 --- a/frontend/src/app/AppLayout.jsx +++ b/frontend/src/app/AppLayout.jsx @@ -3132,7 +3132,66 @@ const AppLayout = () => { ); if (!uniqueIds.length) return; + const uniqueIdSet = new Set(uniqueIds); const target = targetFolderId === 'root' ? null : targetFolderId; + const targetLabel = + target === null + ? DEFAULT_FOLDER_NAME + : folderLabelMap.get(targetFolderId) || 'target folder'; + + const movedDocs = uniqueIds + .map((id) => { + const doc = documentLookup.get(id); + if (!doc) { + return null; + } + return { + id, + sourceFolderId: doc.folder_id ?? null, + document: doc, + }; + }) + .filter(Boolean); + + const updatedDocsMap = new Map(); + const resolveTargetName = () => { + if (!targetLabel) { + return null; + } + const segments = String(targetLabel).split('/'); + return segments[segments.length - 1] || targetLabel; + }; + const targetName = resolveTargetName(); + + movedDocs.forEach(({ id, document }) => { + if (!document) { + return; + } + const updated = { + ...document, + folder_id: target, + }; + if (targetLabel) { + updated.folder_path = targetLabel; + if (targetName) { + updated.folder_name = targetName; + } + } else if (target === null) { + updated.folder_path = DEFAULT_FOLDER_NAME; + updated.folder_name = DEFAULT_FOLDER_NAME; + } + updatedDocsMap.set(id, updated); + }); + + const pruneRowCollection = (collection) => + collection.filter((key) => { + if (!isDocumentRowKey(key)) { + return true; + } + const id = getRowId(key); + return id ? !uniqueIdSet.has(id) : true; + }); + setLoading(true); try { if (uniqueIds.length === 1) { @@ -3146,34 +3205,100 @@ const AppLayout = () => { const count = uniqueIds.length; const suffix = count === 1 ? '' : 's'; - const targetLabel = - target === null - ? DEFAULT_FOLDER_NAME - : folderLabelMap.get(targetFolderId) || 'target folder'; - setStatusMessage( - `Moved ${count} document${suffix} to ${targetLabel}.`, - 'success', - ); + setStatusMessage(`Moved ${count} document${suffix} to ${targetLabel}.`, 'success'); - await refreshCurrentFolder(); - if (targetFolderId && targetFolderId !== selectedFolder) { - await ensureFolderData(targetFolderId, { force: true, prefetchDepth: 1 }); + if (updatedDocsMap.size) { + mapDocumentCaches((doc) => { + if (!doc || !uniqueIdSet.has(doc.id)) { + return doc; + } + const updated = updatedDocsMap.get(doc.id); + if (updated) { + return updated; + } + return { ...doc, folder_id: target }; + }); + } else { + mapDocumentCaches((doc) => { + if (!doc || !uniqueIdSet.has(doc.id)) { + return doc; + } + return { ...doc, folder_id: target }; + }); + } + + if (uniqueIdSet.size) { + setDocuments((prev) => prev.filter((doc) => !uniqueIdSet.has(doc.id))); + setFolderContents((prev) => { + if (!prev.size) { + return prev; + } + let changed = false; + const next = new Map(prev); + movedDocs.forEach(({ id, sourceFolderId }) => { + const sourceKey = sourceFolderId || 'root'; + const entry = next.get(sourceKey); + if (!entry?.documents?.length) { + return; + } + const filteredDocs = entry.documents.filter((doc) => doc.id !== id); + if (filteredDocs.length !== entry.documents.length) { + changed = true; + next.set(sourceKey, { ...entry, documents: filteredDocs }); + } + }); + return changed ? next : prev; + }); + + setSelectedRowKeys((prev) => pruneRowCollection(prev)); + setSelectionOrder((prev) => pruneRowCollection(prev)); + selectionOrderRef.current = pruneRowCollection(selectionOrderRef.current); + if ( + selectionAnchorRef.current && + isDocumentRowKey(selectionAnchorRef.current) && + uniqueIdSet.has(getRowId(selectionAnchorRef.current)) + ) { + selectionAnchorRef.current = null; + } + if (focusedDocumentId && uniqueIdSet.has(focusedDocumentId)) { + setFocusedDocumentId(null); + } + if ( + focusedRowKey && + isDocumentRowKey(focusedRowKey) && + uniqueIdSet.has(getRowId(focusedRowKey)) + ) { + setFocusedRowKey(null); + } + } + + if (targetFolderId && targetFolderId !== selectedFolder) { + await ensureFolderData(targetFolderId, { force: true, prefetchDepth: 1 }); + } + } catch (error) { + const message = error.response?.data?.error || 'Failed to move documents.'; + notifyApiError(error, message); + } finally { + setLoading(false); } - } catch (error) { - const message = error.response?.data?.error || 'Failed to move documents.'; - notifyApiError(error, message); - } finally { - setLoading(false); - } - }, - [ - ensureFolderData, - refreshCurrentFolder, - selectedFolder, - folderLabelMap, - notifyApiError, - setStatusMessage, - ], + }, + [ + documentLookup, + ensureFolderData, + folderLabelMap, + focusedDocumentId, + mapDocumentCaches, + notifyApiError, + selectedFolder, + setDocuments, + setFolderContents, + setFocusedDocumentId, + focusedRowKey, + setFocusedRowKey, + setSelectionOrder, + setSelectedRowKeys, + setStatusMessage, + ], ); const handleThumbnailRegeneration = useCallback( diff --git a/frontend/src/documents/DocumentsTable.jsx b/frontend/src/documents/DocumentsTable.jsx index 57be1e3..e086533 100644 --- a/frontend/src/documents/DocumentsTable.jsx +++ b/frontend/src/documents/DocumentsTable.jsx @@ -6,7 +6,7 @@ import { EditIcon, ViewListIcon, ViewGridIcon, - DesktopIcon, + IconFileStack, FolderIcon, TrashIcon, RefreshIcon, @@ -539,7 +539,7 @@ const DocumentsTable = ({ aria-pressed={isDeskView} title="Desk view" > - +