feat: Refactor tag and correspondent management to use canonical types and dedicated managers.

This commit is contained in:
2025-12-09 18:56:11 +01:00
parent ce821c1817
commit 3ccc1f6698
34 changed files with 790 additions and 427 deletions
@@ -15,6 +15,7 @@ import {
import AssetManager, { getAssetFromVersion } from '../../lib/assets/AssetManager';
import useNotifyApiError from '../../hooks/useNotifyApiError';
import TagManager from '../../lib/assets/TagManager';
import CorrespondentManager from '../../lib/assets/CorrespondentManager';
import { fetchAsset } from '../../lib/api/apiClient';
import { useEntryPointer as useEntryPointerCore } from '../features/selection/useEntryPointer';
import useDocumentsSelection from '../features/selection/useDocumentsSelection';
@@ -46,7 +47,6 @@ import useDocumentMutations from './useDocumentMutations';
import useDetailWorkspace from '../../viewer/logic/useDetailWorkspace';
import useTags from './useTags';
import useCorrespondents from './useCorrespondents';
import useDocumentCorrespondentActions from '../features/correspondents/useDocumentCorrespondentActions';
import usePasskeys from '../../settings/usePasskeys';
import { resolveBreadcrumbs } from '../logic/breadcrumbs';
import useWorkspaceSelectionSync from '../features/selection/useWorkspaceSelectionSync';
@@ -58,6 +58,7 @@ import { useApi } from '../../lib/context/ApiContext';
import { useWorkspaceSelection } from '../../app/useWorkspaceSelection';
import useDocumentPreview from '../../app/useDocumentPreview';
import type { DocumentId, FolderNodeId, Identifier } from '../../types/identifiers';
import type { Document, Tag } from '../../types/documents';
const EntryType = Object.freeze({
document: 'document',
@@ -66,8 +67,6 @@ const EntryType = Object.freeze({
const noop = () => { };
import type { Document } from '../../types/documents';
interface TenantOption {
id?: Identifier | null;
name?: string | null;
@@ -198,6 +197,12 @@ const useDocumentsWorkspace = ({
}
const tagManager = tagManagerRef.current;
const correspondentManagerRef = useRef<CorrespondentManager | null>(null);
if (!correspondentManagerRef.current) {
correspondentManagerRef.current = new CorrespondentManager();
}
const correspondentManager = correspondentManagerRef.current;
const selectionState = useWorkspaceSelection();
const {
@@ -228,6 +233,15 @@ const useDocumentsWorkspace = ({
fetchDocumentById,
});
useEffect(() => {
if (tagManager) {
documentsManager.setTagManager(tagManager);
}
if (correspondentManager) {
documentsManager.setCorrespondentManager(correspondentManager);
}
}, [documentsManager, tagManager, correspondentManager]);
const documentLookup = useSyncExternalStore(
(onStoreChange) => documentsManager.subscribe(onStoreChange),
() => documentsManager.getSnapshot(),
@@ -476,13 +490,17 @@ const useDocumentsWorkspace = ({
setActiveTagFilters,
documentsManager,
});
useEffect(() => {
tagManager.ensureAll().catch((err) => console.warn('Failed to bootstrap tags', err));
}, [tagManager]);
const {
tags,
refreshTags,
handleTagCreate,
handleTagUpdate,
handleTagDelete,
setTags,
} = tagsStateRaw;
// tagLookupById is derived locally
@@ -490,7 +508,7 @@ const useDocumentsWorkspace = ({
tenantIdRef.current = currentTenantId;
}, [currentTenantId, tenantIdRef]);
const tagLookupById = new Map();
const tagLookupById = new Map<Identifier, Tag>();
tags.forEach((tag) => {
if (tag?.id) {
tagLookupById.set(tag.id, tag);
@@ -499,33 +517,30 @@ const useDocumentsWorkspace = ({
const tagsState = {
...tagsStateRaw,
tagLookupById, // Add derived lookup
tags,
tagLookupById,
tagManager,
};
const correspondentsStateRaw = useCorrespondents({
tenantIdRef,
correspondentManager,
documentsManager,
});
const {
correspondents,
correspondentLookupById,
refreshCorrespondents,
handleCorrespondentCreate,
handleCorrespondentUpdate,
handleCorrespondentDelete,
setCorrespondents,
} = correspondentsStateRaw;
const {
correspondentLookupByName,
handleDocumentCorrespondentAttach,
handleCorrespondentRemove,
handleCorrespondentAdd,
} = useDocumentCorrespondentActions({
correspondents,
handleCorrespondentCreate,
documentsManager,
});
// Prefetch tags/correspondents when tenant changes
useEffect(() => {
refreshTags();
refreshCorrespondents();
}, [refreshTags, refreshCorrespondents, currentTenantId]);
const {
passkeys,
@@ -623,8 +638,6 @@ const useDocumentsWorkspace = ({
setDraggedDocumentIds([]);
setDraggedFolderId(null);
setSearchResultIds(null);
setTags([]);
setCorrespondents([]);
setSearchQuery('');
setActiveTagFilters([]);
setActiveCorrespondentFilters([]);
@@ -653,8 +666,6 @@ const useDocumentsWorkspace = ({
setDraggedDocumentIds,
setDraggedFolderId,
setSearchResultIds,
setTags,
setCorrespondents,
setSearchQuery,
setActiveTagFilters,
setActiveCorrespondentFilters,
@@ -693,11 +704,21 @@ const useDocumentsWorkspace = ({
handleDocumentTitleUpdate,
handleDocumentIssuedUpdate,
handleDocumentTagDetach,
handleDocumentCorrespondentAttach,
handleDocumentCorrespondentDetach,
handleDocumentCorrespondentAdd,
correspondentLookupByName,
} = useDocumentMutations({
documentsState,
folderState,
selectionState,
tagsState,
correspondentsState: {
correspondents,
correspondentLookupById,
refreshCorrespondents,
correspondentManager,
},
actions: actionsState,
previewDocumentId,
});
@@ -895,13 +916,15 @@ const useDocumentsWorkspace = ({
tags,
refreshTags,
onTagCreate: handleTagCreate,
onTagUpdate: handleTagUpdate,
onTagDelete: handleTagDelete,
onTagUpdate: async (tagId: string, changes: any) => { await handleTagUpdate(tagId, changes); },
onTagDelete: async (tagId: string) => { await handleTagDelete(tagId); },
correspondents,
correspondentLookupById,
refreshCorrespondents,
onCorrespondentCreate: handleCorrespondentCreate,
onCorrespondentUpdate: handleCorrespondentUpdate,
onCorrespondentDelete: handleCorrespondentDelete,
correspondentManager,
});
const [settingsOpen, setSettingsOpen] = useState(false);
@@ -951,11 +974,12 @@ const useDocumentsWorkspace = ({
ensureAssetUrl,
getAsset: getDocumentAsset,
correspondents,
handleCorrespondentAdd,
handleCorrespondentRemove,
handleCorrespondentAdd: handleDocumentCorrespondentAdd,
handleCorrespondentRemove: handleDocumentCorrespondentDetach,
selectFolder,
tags,
tagLookupById,
correspondentLookupById,
});
const handleEntryPointerCore = useEntryPointerCore({
@@ -1019,6 +1043,7 @@ const useDocumentsWorkspace = ({
tagLookupById,
activeTagFilters,
handleTagUpdate,
handleTagCreate,
handleTagDelete,
handleDocumentTagAttach,
handleDocumentTagDetach,
@@ -1029,14 +1054,15 @@ const useDocumentsWorkspace = ({
const correspondentsContext = {
correspondents,
correspondentLookupById,
refreshCorrespondents,
activeCorrespondentFilters,
handleCorrespondentUpdate,
handleCorrespondentCreate,
handleCorrespondentDelete,
handleDocumentCorrespondentAttach,
handleCorrespondentRemove,
handleCorrespondentAdd,
handleDocumentCorrespondentDetach,
handleDocumentCorrespondentAdd,
handleBulkCorrespondentAdd,
handleBulkCorrespondentRemove,
openCorrespondentsModal,