feat: Add document move and tag mutations, refine document upload logic
This commit is contained in:
@@ -214,7 +214,7 @@ const useDocumentsWorkspace = ({
|
||||
}
|
||||
const tagManager = tagManagerRef.current;
|
||||
|
||||
const selection = useWorkspaceSelection();
|
||||
const selectionState = useWorkspaceSelection();
|
||||
|
||||
const {
|
||||
selectedEntries,
|
||||
@@ -234,7 +234,7 @@ const useDocumentsWorkspace = ({
|
||||
clearSelection,
|
||||
promoteSelectionOrder: promoteSelectionOrderRaw,
|
||||
configureSelectionEnvironment,
|
||||
} = selection;
|
||||
} = selectionState;
|
||||
|
||||
const {
|
||||
documents,
|
||||
@@ -247,18 +247,22 @@ const useDocumentsWorkspace = ({
|
||||
fetchDocumentById,
|
||||
});
|
||||
|
||||
const foldersManagerRef = useRef<FoldersManager | null>(null);
|
||||
if (!foldersManagerRef.current) {
|
||||
foldersManagerRef.current = new FoldersManager();
|
||||
}
|
||||
const foldersManager = foldersManagerRef.current;
|
||||
|
||||
const documentLookup = useSyncExternalStore(
|
||||
(onStoreChange) => documentsManager.subscribe(onStoreChange),
|
||||
() => documentsManager.getSnapshot(),
|
||||
() => documentsManager.getSnapshot(),
|
||||
);
|
||||
|
||||
const foldersManagerRef = useRef<FoldersManager | null>(null);
|
||||
if (!foldersManagerRef.current) {
|
||||
foldersManagerRef.current = new FoldersManager();
|
||||
}
|
||||
const foldersManager = foldersManagerRef.current;
|
||||
|
||||
const folderStateRaw = useFolderTree({
|
||||
initialSelectedFolder: routeFolderId || 'root',
|
||||
foldersManager,
|
||||
});
|
||||
const {
|
||||
folderNodes,
|
||||
setFolderNodes,
|
||||
@@ -266,21 +270,75 @@ const useDocumentsWorkspace = ({
|
||||
setSelectedFolder,
|
||||
currentFolderName,
|
||||
folderOptions,
|
||||
folderLabelMap,
|
||||
isInvalidFolderDrop,
|
||||
} = useFolderTree({
|
||||
initialSelectedFolder: routeFolderId || 'root',
|
||||
foldersManager,
|
||||
});
|
||||
} = folderStateRaw;
|
||||
|
||||
const folderState = {
|
||||
...folderStateRaw,
|
||||
setCreatingFolder,
|
||||
};
|
||||
|
||||
const [currentSubfolders, setCurrentSubfolders] = useState<Array<{ id?: FolderNodeId; name?: string | null;[key: string]: unknown }>>([]);
|
||||
|
||||
const ensureFolderData = useCallback(
|
||||
const reconcileSelectionWithFolderData = useCallback(
|
||||
(currentSelection: string[], docs: Document[], subfolders: any[]) => {
|
||||
const availableDocKeys = docs
|
||||
.map((doc) => createDocumentEntryKey(doc?.id as Identifier))
|
||||
.filter(Boolean);
|
||||
const availableDocKeySet = new Set(availableDocKeys);
|
||||
const availableFolderKeys = new Set(
|
||||
subfolders
|
||||
.map((folder) => createFolderEntryKey(folder?.id as Identifier))
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
const previousFolderKeys = currentSelection
|
||||
.filter(isFolderEntry)
|
||||
.filter((key) => availableFolderKeys.has(key));
|
||||
const previousDocKeys = currentSelection.filter(isDocumentEntry);
|
||||
const nextDocKeys = previousDocKeys.filter((key) => availableDocKeySet.has(key));
|
||||
return [...previousFolderKeys, ...nextDocKeys];
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const selectedFolderRef = useRef<FolderNodeId>(selectedFolder);
|
||||
useEffect(() => {
|
||||
selectedFolderRef.current = selectedFolder;
|
||||
}, [selectedFolder]);
|
||||
|
||||
const updateViewState = useCallback(
|
||||
(folderId: FolderNodeId, data: any, includeDocuments: boolean) => {
|
||||
// Guard against race conditions: only update if the folder is still selected
|
||||
if (folderId === selectedFolderRef.current) {
|
||||
if (includeDocuments) {
|
||||
setDocuments((data.documents || []) as Document[]);
|
||||
}
|
||||
setCurrentSubfolders((data.subfolders || []) as any[]);
|
||||
|
||||
if (includeDocuments) {
|
||||
setSelectedEntries((prev) => reconcileSelectionWithFolderData(
|
||||
prev,
|
||||
(data.documents || []) as Document[],
|
||||
(data.subfolders || []) as any[]
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
setDocuments,
|
||||
setSelectedEntries,
|
||||
setCurrentSubfolders,
|
||||
reconcileSelectionWithFolderData,
|
||||
selectedFolderRef,
|
||||
]
|
||||
);
|
||||
|
||||
const fetchFolderData = useCallback(
|
||||
async (
|
||||
folderId: FolderNodeId,
|
||||
options: { includeDocuments?: boolean } = {}
|
||||
) => {
|
||||
|
||||
const path = folderId === 'root' ? 'root' : folderId;
|
||||
const includeDocuments = options.includeDocuments ?? true;
|
||||
const params: Record<string, unknown> = {
|
||||
@@ -290,61 +348,25 @@ const useDocumentsWorkspace = ({
|
||||
};
|
||||
|
||||
const data = await listFolderContents(path, params);
|
||||
|
||||
// Only update UI state if we are fetching for the currently selected folder
|
||||
if (folderId === selectedFolder) {
|
||||
// Update documents state if included
|
||||
if (includeDocuments) {
|
||||
setDocuments((data.documents || []) as Document[]);
|
||||
}
|
||||
setCurrentSubfolders((data.subfolders || []) as any[]);
|
||||
|
||||
// Update selection state based on new documents
|
||||
if (includeDocuments) {
|
||||
const docs = (data.documents || []) as Document[];
|
||||
const subfolders = (data.subfolders || []) as any[];
|
||||
|
||||
const availableDocKeys = docs
|
||||
.map((doc) => createDocumentEntryKey(doc?.id as Identifier))
|
||||
.filter(Boolean);
|
||||
const availableDocKeySet = new Set(availableDocKeys);
|
||||
const availableFolderKeys = new Set(
|
||||
subfolders
|
||||
.map((folder) => createFolderEntryKey(folder?.id as Identifier))
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
setSelectedEntries((previous) => {
|
||||
const previousFolderKeys = previous
|
||||
.filter(isFolderEntry)
|
||||
.filter((key) => availableFolderKeys.has(key));
|
||||
const previousDocKeys = previous.filter(isDocumentEntry);
|
||||
const nextDocKeys = previousDocKeys.filter((key) => availableDocKeySet.has(key));
|
||||
const mergedSelection = [...previousFolderKeys, ...nextDocKeys];
|
||||
return mergedSelection;
|
||||
});
|
||||
}
|
||||
}
|
||||
return data; // Return data for consumers (e.g. useDocumentMutations)
|
||||
|
||||
return { data, includeDocuments };
|
||||
},
|
||||
[
|
||||
activeSortFieldRef,
|
||||
activeSortDirectionRef,
|
||||
setDocuments,
|
||||
setSelectedEntries,
|
||||
setCurrentSubfolders,
|
||||
selectedFolder,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedFolder) {
|
||||
ensureFolderData(selectedFolder).catch((error) => {
|
||||
notifyApiError(error, 'Failed to fetch folder contents');
|
||||
});
|
||||
fetchFolderData(selectedFolder)
|
||||
.then(({ data, includeDocuments }) => {
|
||||
updateViewState(selectedFolder, data, includeDocuments);
|
||||
})
|
||||
.catch((error) => {
|
||||
notifyApiError(error, 'Failed to fetch folder contents');
|
||||
});
|
||||
}
|
||||
}, [selectedFolder, documentsSortField, documentsSortDirection, ensureFolderData, notifyApiError]);
|
||||
}, [selectedFolder, documentsSortField, documentsSortDirection, fetchFolderData, updateViewState, notifyApiError]);
|
||||
|
||||
const {
|
||||
searchQuery,
|
||||
@@ -472,6 +494,12 @@ const useDocumentsWorkspace = ({
|
||||
selectionInitializedRef,
|
||||
});
|
||||
|
||||
const tagsStateRaw = useTags({
|
||||
tenantIdRef,
|
||||
tagManager,
|
||||
setActiveTagFilters,
|
||||
mapDocumentCaches,
|
||||
});
|
||||
const {
|
||||
tags,
|
||||
refreshTags,
|
||||
@@ -479,13 +507,9 @@ const useDocumentsWorkspace = ({
|
||||
handleTagUpdate,
|
||||
handleTagDelete,
|
||||
setTags,
|
||||
} = useTags({
|
||||
tenantIdRef,
|
||||
tagManager,
|
||||
setActiveTagFilters,
|
||||
mapDocumentCaches,
|
||||
});
|
||||
} = tagsStateRaw;
|
||||
|
||||
// tagLookupById is derived locally
|
||||
useEffect(() => {
|
||||
tenantIdRef.current = currentTenantId;
|
||||
}, [currentTenantId, tenantIdRef]);
|
||||
@@ -500,6 +524,16 @@ const useDocumentsWorkspace = ({
|
||||
return map;
|
||||
}, [tags]);
|
||||
|
||||
const tagsState = {
|
||||
...tagsStateRaw,
|
||||
tagLookupById, // Add derived lookup
|
||||
tagManager,
|
||||
};
|
||||
|
||||
const correspondentsStateRaw = useCorrespondents({
|
||||
tenantIdRef,
|
||||
mapDocumentCaches,
|
||||
});
|
||||
const {
|
||||
correspondents,
|
||||
refreshCorrespondents,
|
||||
@@ -507,10 +541,7 @@ const useDocumentsWorkspace = ({
|
||||
handleCorrespondentUpdate,
|
||||
handleCorrespondentDelete,
|
||||
setCorrespondents,
|
||||
} = useCorrespondents({
|
||||
tenantIdRef,
|
||||
mapDocumentCaches,
|
||||
});
|
||||
} = correspondentsStateRaw;
|
||||
|
||||
const {
|
||||
correspondentLookupByName,
|
||||
@@ -549,9 +580,10 @@ const useDocumentsWorkspace = ({
|
||||
|
||||
const refreshCurrentFolder = useCallback(async () => {
|
||||
if (selectedFolder) {
|
||||
await ensureFolderData(selectedFolder);
|
||||
const { data, includeDocuments } = await fetchFolderData(selectedFolder);
|
||||
updateViewState(selectedFolder, data, includeDocuments);
|
||||
}
|
||||
}, [selectedFolder, ensureFolderData]);
|
||||
}, [selectedFolder, fetchFolderData, updateViewState]);
|
||||
|
||||
const {
|
||||
handleBulkTagAddFromDetail,
|
||||
@@ -575,7 +607,6 @@ const useDocumentsWorkspace = ({
|
||||
} = useDocumentUploads({
|
||||
selectedFolder,
|
||||
currentFolderName,
|
||||
ensureFolderData,
|
||||
refreshCurrentFolder,
|
||||
shellRef,
|
||||
});
|
||||
@@ -686,6 +717,22 @@ const useDocumentsWorkspace = ({
|
||||
],
|
||||
);
|
||||
|
||||
const documentsState = {
|
||||
documentLookup,
|
||||
setDocuments,
|
||||
setSearchResultIds,
|
||||
removeDocumentsFromCaches,
|
||||
updateDocumentCaches,
|
||||
mapDocumentCaches,
|
||||
extractDocumentFromResponse,
|
||||
ingestDocuments: (docs: unknown[]) => documentsManager.ingest(docs),
|
||||
};
|
||||
|
||||
const actionsState = {
|
||||
refreshCurrentFolder,
|
||||
closeDocumentPreview,
|
||||
};
|
||||
|
||||
const {
|
||||
moveDocumentsToFolder,
|
||||
handleThumbnailRegeneration,
|
||||
@@ -696,37 +743,21 @@ const useDocumentsWorkspace = ({
|
||||
handleDocumentIssuedUpdate,
|
||||
handleDocumentTagDetach,
|
||||
} = useDocumentMutations({
|
||||
documentLookup,
|
||||
folderLabelMap,
|
||||
ensureFolderData,
|
||||
selectedFolder,
|
||||
setSelectedFolder,
|
||||
setDocuments,
|
||||
setSearchResultIds,
|
||||
setSelectedEntries,
|
||||
setSelectionOrder,
|
||||
selectionOrderRef,
|
||||
selectionAnchorRef,
|
||||
setFocusedDocumentId,
|
||||
focusedDocumentId,
|
||||
setFocusedEntryKey,
|
||||
focusedEntryKey,
|
||||
mapDocumentCaches,
|
||||
folderNodes,
|
||||
setFolderNodes,
|
||||
removeDocumentsFromCaches,
|
||||
closeDocumentPreview,
|
||||
documentsState,
|
||||
folderState,
|
||||
selectionState,
|
||||
tagsState,
|
||||
actions: actionsState,
|
||||
previewDocumentId,
|
||||
refreshCurrentFolder,
|
||||
updateDocumentCaches,
|
||||
tagLookupById,
|
||||
tags,
|
||||
refreshTags,
|
||||
tagManager,
|
||||
extractDocumentFromResponse,
|
||||
ingestDocuments: (docs) => documentsManager.ingest(docs),
|
||||
});
|
||||
|
||||
const dragState = {
|
||||
draggedDocumentIds,
|
||||
draggedFolderId,
|
||||
setDraggedDocumentIds,
|
||||
setDraggedFolderId,
|
||||
};
|
||||
|
||||
const {
|
||||
loadFolder,
|
||||
selectFolder,
|
||||
@@ -735,18 +766,15 @@ const useDocumentsWorkspace = ({
|
||||
handleFolderDelete,
|
||||
folderClickHandlers,
|
||||
} = useFolderTreeActions({
|
||||
folderNodes,
|
||||
setFolderNodes,
|
||||
selectedFolder,
|
||||
setSelectedFolder,
|
||||
handleFileDrop,
|
||||
moveDocumentsToFolder,
|
||||
draggedDocumentIds,
|
||||
draggedFolderId,
|
||||
setDraggedDocumentIds,
|
||||
setDraggedFolderId,
|
||||
isInvalidFolderDrop,
|
||||
setCreatingFolder,
|
||||
folderState,
|
||||
dragState,
|
||||
actions: {
|
||||
handleFileDrop,
|
||||
moveDocumentsToFolder,
|
||||
},
|
||||
utils: {
|
||||
isInvalidFolderDrop,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -1017,7 +1045,6 @@ const useDocumentsWorkspace = ({
|
||||
documents: viewDocuments,
|
||||
documentLookup,
|
||||
folderNodes,
|
||||
ensureFolderData,
|
||||
detailPanelControlRef,
|
||||
detailFolderFetchRef,
|
||||
previewDocumentId,
|
||||
@@ -1188,7 +1215,7 @@ const useDocumentsWorkspace = ({
|
||||
handleDeleteSelection,
|
||||
handleEntryPointerCore,
|
||||
handleBulkSelectionReanalyze,
|
||||
selectionValue: selection,
|
||||
selectionValue: selectionState,
|
||||
};
|
||||
|
||||
const documentMutations = {
|
||||
|
||||
Reference in New Issue
Block a user