Remove asset/document hydration pipeline and introduce client-side cache updates for correspondents, tags, and search results

This commit is contained in:
2025-11-20 02:18:33 +01:00
parent b76f1df27f
commit 0eb4c0a294
14 changed files with 316 additions and 329 deletions
-7
View File
@@ -23,11 +23,6 @@ type DocumentLink = {
expiresAt?: number; expiresAt?: number;
}; };
interface AssetManagerLike {
hydrateDetail: (payload: unknown) => { document?: DocumentLike } | null;
hydrateDocument: (payload: unknown) => DocumentLike | null;
}
interface ApiClient { interface ApiClient {
get: <T = unknown>(path: string) => Promise<{ data: T }>; get: <T = unknown>(path: string) => Promise<{ data: T }>;
} }
@@ -44,7 +39,6 @@ interface UseDocumentPreviewArgs {
ingest: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean }; ingest: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
}; };
selectedFolder?: FolderId | null; selectedFolder?: FolderId | null;
assetManager: AssetManagerLike;
api: ApiClient; api: ApiClient;
resolveApiPath?: (path: string) => string; resolveApiPath?: (path: string) => string;
notifyApiError: (error: unknown, message: string) => void; notifyApiError: (error: unknown, message: string) => void;
@@ -72,7 +66,6 @@ const useDocumentPreview = ({
routeDocumentId, routeDocumentId,
documentsManager, documentsManager,
selectedFolder, selectedFolder,
assetManager: _assetManager,
api, api,
resolveApiPath, resolveApiPath,
notifyApiError, notifyApiError,
+8
View File
@@ -45,6 +45,7 @@ interface UseDocumentsSearchResult {
clearFilters: () => void; clearFilters: () => void;
handleSearchChange: (value: string) => void; handleSearchChange: (value: string) => void;
handleSearchSubmit: () => void; handleSearchSubmit: () => void;
refetchSearchResults: () => void;
documentsFilterValue: { documentsFilterValue: {
query: string; query: string;
searchResultIds: Identifier[] | null; searchResultIds: Identifier[] | null;
@@ -82,6 +83,7 @@ const useDocumentsSearch = ({
const [activeCorrespondentFilters, setActiveCorrespondentFilters] = useState<Identifier[]>([]); const [activeCorrespondentFilters, setActiveCorrespondentFilters] = useState<Identifier[]>([]);
const [searchResultIds, setSearchResultIds] = useState<Identifier[] | null>(null); const [searchResultIds, setSearchResultIds] = useState<Identifier[] | null>(null);
const [searchLoading, setSearchLoading] = useState<boolean>(false); const [searchLoading, setSearchLoading] = useState<boolean>(false);
const [searchTrigger, setSearchTrigger] = useState<number>(0);
const toggleTagFilter = useCallback((tagId: Identifier) => { const toggleTagFilter = useCallback((tagId: Identifier) => {
if (!tagId) return; if (!tagId) return;
@@ -171,6 +173,10 @@ const useDocumentsSearch = ({
], ],
); );
const refetchSearchResults = useCallback(() => {
setSearchTrigger(Date.now());
}, []);
useEffect(() => { useEffect(() => {
if (!token) return undefined; if (!token) return undefined;
@@ -269,6 +275,7 @@ const useDocumentsSearch = ({
notifyApiError, notifyApiError,
setLoading, setLoading,
documentsManager, documentsManager,
searchTrigger,
]); ]);
return { return {
@@ -288,6 +295,7 @@ const useDocumentsSearch = ({
clearFilters, clearFilters,
handleSearchChange, handleSearchChange,
handleSearchSubmit, handleSearchSubmit,
refetchSearchResults,
documentsFilterValue, documentsFilterValue,
}; };
}; };
-123
View File
@@ -259,129 +259,6 @@ class AssetManager {
} }
} }
hydrateAsset(asset?: Nullable<AssetLike>): Nullable<AssetLike> {
if (!asset || !asset.id) {
return asset;
}
const cached = this.assetCache.get(asset.id);
if (!cached) {
const normalized = mergeAssetObjects(null, asset.objects);
if (normalized.length) {
return { ...asset, objects: normalized };
}
return asset;
}
const merged = { ...cached, ...asset };
if (cached.url && !asset.url) {
merged.url = cached.url;
}
if (cached.expiresAt) {
const cachedExpires = Number(cached.expiresAt) || null;
const assetExpires = Number(asset.expiresAt) || null;
if (!assetExpires || (cachedExpires && cachedExpires > assetExpires)) {
merged.expiresAt = cachedExpires;
}
}
const mergedObjects = mergeAssetObjects(cached.objects, asset.objects);
if (mergedObjects.length) {
merged.objects = mergedObjects;
}
return merged;
}
hydrateDocument(document?: Nullable<DocumentLike>): Nullable<DocumentLike> {
if (!document) {
return document;
}
const currentVersion = document.current_version || null;
if (!currentVersion) {
return document;
}
let changed = false;
let nextAssets = currentVersion.assets;
if (nextAssets && !Array.isArray(nextAssets)) {
const hydrated = {};
Object.keys(nextAssets).forEach((key) => {
hydrated[key] = this.hydrateAsset(nextAssets[key]);
if (hydrated[key] !== nextAssets[key]) {
changed = true;
}
});
if (changed) {
nextAssets = { ...nextAssets, ...hydrated };
}
} else if (Array.isArray(nextAssets)) {
const hydratedList = nextAssets.map((item) => this.hydrateAsset(item));
if (
hydratedList.length !== nextAssets.length ||
hydratedList.some((item, index) => item !== nextAssets[index])
) {
changed = true;
nextAssets = hydratedList;
}
}
if (!changed) {
return document;
}
const nextCurrentVersion: DocumentVersionLike = { ...currentVersion, assets: nextAssets };
return { ...document, current_version: nextCurrentVersion };
}
hydrateDocuments(documents?: DocumentLike[] | null) {
if (!Array.isArray(documents)) {
return documents ?? [];
}
return documents.map((doc) => this.hydrateDocument(doc));
}
hydrateDetail(detail?: { document?: DocumentLike; assets?: AssetLike[] } | null) {
if (!detail) {
return detail;
}
let changed = false;
const next = { ...detail };
if (detail.document) {
const hydratedDocument = this.hydrateDocument(detail.document);
if (hydratedDocument !== detail.document) {
next.document = hydratedDocument;
changed = true;
}
}
if (Array.isArray(detail.assets)) {
const hydratedAssets = detail.assets.map((item) => this.hydrateAsset(item));
if (
hydratedAssets.length !== detail.assets.length ||
hydratedAssets.some((item, index) => item !== detail.assets[index])
) {
next.assets = hydratedAssets;
changed = true;
}
}
return changed ? next : detail;
}
hydrateFolderContents(contents?: { documents?: DocumentLike[]; document?: DocumentLike } | null) {
if (!contents) {
return contents;
}
const next = { ...contents };
if (Array.isArray(contents.documents)) {
next.documents = this.hydrateDocuments(contents.documents);
}
if (contents.document) {
next.document = this.hydrateDocument(contents.document);
}
return next;
}
ensureAsset( ensureAsset(
documentId?: Identifier | null, documentId?: Identifier | null,
asset?: Nullable<AssetLike>, asset?: Nullable<AssetLike>,
@@ -49,7 +49,6 @@ interface UseDetailWorkspaceArgs {
handleTagRemove?: (...args: unknown[]) => void; handleTagRemove?: (...args: unknown[]) => void;
ensureAssetUrl?: EnsureAssetUrl; ensureAssetUrl?: EnsureAssetUrl;
getDocumentAsset?: GetDocumentAsset; getDocumentAsset?: GetDocumentAsset;
ensurePreviewData?: (docId: Identifier, options?: Record<string, unknown>) => Promise<DocumentLike | null>;
correspondents?: unknown[]; correspondents?: unknown[];
handleCorrespondentAdd?: (...args: unknown[]) => void; handleCorrespondentAdd?: (...args: unknown[]) => void;
handleCorrespondentRemove?: (...args: unknown[]) => void; handleCorrespondentRemove?: (...args: unknown[]) => void;
@@ -92,7 +91,6 @@ const useDetailWorkspace = ({
handleTagRemove, handleTagRemove,
ensureAssetUrl, ensureAssetUrl,
getDocumentAsset, getDocumentAsset,
ensurePreviewData,
correspondents, correspondents,
handleCorrespondentAdd, handleCorrespondentAdd,
handleCorrespondentRemove, handleCorrespondentRemove,
@@ -294,7 +292,6 @@ const useDetailWorkspace = ({
onUpdateIssued: handleDocumentIssuedUpdate, onUpdateIssued: handleDocumentIssuedUpdate,
ensureAssetUrl, ensureAssetUrl,
getDocumentAsset, getDocumentAsset,
hydrateDocument: ensurePreviewData,
correspondents, correspondents,
onCorrespondentAdd: handleCorrespondentAdd, onCorrespondentAdd: handleCorrespondentAdd,
onCorrespondentRemove: handleCorrespondentRemove, onCorrespondentRemove: handleCorrespondentRemove,
@@ -308,7 +305,6 @@ const useDetailWorkspace = ({
correspondents, correspondents,
detailPanelDocument, detailPanelDocument,
ensureAssetUrl, ensureAssetUrl,
ensurePreviewData,
getDocumentAsset, getDocumentAsset,
handleCorrespondentAdd, handleCorrespondentAdd,
handleCorrespondentRemove, handleCorrespondentRemove,
+2 -31
View File
@@ -5,37 +5,21 @@ type DocumentId = string | number;
export type ManagedDocument = { id?: DocumentId | null } & Record<string, unknown>; export type ManagedDocument = { id?: DocumentId | null } & Record<string, unknown>;
type FetchDocument = (id: DocumentId) => Promise<unknown>; type FetchDocument = (id: DocumentId) => Promise<unknown>;
type HydrateDocument<T extends ManagedDocument> = (payload: unknown) => T | null;
type HydrateDocuments<T extends ManagedDocument> = (payload: unknown[]) => T[];
class DocumentsManager<T extends ManagedDocument = ManagedDocument> { class DocumentsManager<T extends ManagedDocument = ManagedDocument> {
private byId: Map<DocumentId, T>; private byId: Map<DocumentId, T>;
private fetcher?: FetchDocument; private fetcher?: FetchDocument;
private hydrateDocument?: HydrateDocument<T>;
private hydrateDocuments?: HydrateDocuments<T>;
private extractDocument?: HydrateDocument<T>;
private inflight: Map<DocumentId, Promise<T | null>>; private inflight: Map<DocumentId, Promise<T | null>>;
private listeners: Set<() => void>; private listeners: Set<() => void>;
constructor( constructor(
fetchDocument?: FetchDocument, fetchDocument?: FetchDocument,
options?: {
hydrateDocument?: HydrateDocument<T>;
hydrateDocuments?: HydrateDocuments<T>;
extractDocument?: HydrateDocument<T>;
},
) { ) {
this.byId = new Map(); this.byId = new Map();
this.fetcher = fetchDocument; this.fetcher = fetchDocument;
this.hydrateDocument = options?.hydrateDocument;
this.hydrateDocuments = options?.hydrateDocuments;
this.extractDocument = options?.extractDocument;
this.inflight = new Map(); this.inflight = new Map();
this.listeners = new Set(); this.listeners = new Set();
} }
@@ -54,19 +38,7 @@ class DocumentsManager<T extends ManagedDocument = ManagedDocument> {
} }
ingest(rawDocs: unknown[] = []): { canonical: T[]; changed: boolean } { ingest(rawDocs: unknown[] = []): { canonical: T[]; changed: boolean } {
const normalize = (docs: unknown[]) => { const docs = rawDocs.map((doc) => doc as T).filter(Boolean);
if (this.hydrateDocuments) {
return this.hydrateDocuments(docs).filter(Boolean) as T[];
}
if (this.hydrateDocument) {
return docs
.map((doc) => this.hydrateDocument ? this.hydrateDocument(doc) : (doc as T | null))
.filter(Boolean) as T[];
}
return docs.filter(Boolean) as T[];
};
const docs = normalize(rawDocs);
let changed = false; let changed = false;
let nextById = this.byId; let nextById = this.byId;
const canonical: T[] = []; const canonical: T[] = [];
@@ -124,8 +96,7 @@ class DocumentsManager<T extends ManagedDocument = ManagedDocument> {
const request = (async () => { const request = (async () => {
try { try {
const fetched = await fetcher(id); const fetched = await fetcher(id);
const extracted = this.extractDocument ? this.extractDocument(fetched) : fetched; const { canonical } = this.ingest([fetched as unknown]);
const { canonical } = this.ingest([extracted as unknown]);
return canonical[0] ?? null; return canonical[0] ?? null;
} finally { } finally {
this.inflight.delete(id); this.inflight.delete(id);
@@ -21,7 +21,6 @@ interface UseBulkDocumentActionsArgs {
resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[]; resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[];
correspondentLookupByName: Map<string, { id?: Identifier }>; correspondentLookupByName: Map<string, { id?: Identifier }>;
handleCorrespondentCreate: (payload: { name: string }) => Promise<{ id?: Identifier } | null>; handleCorrespondentCreate: (payload: { name: string }) => Promise<{ id?: Identifier } | null>;
refreshCurrentFolder: () => Promise<void> | void;
setStatusMessage: (message: string, variant?: string) => void; setStatusMessage: (message: string, variant?: string) => void;
selectedDocumentIds?: Identifier[]; selectedDocumentIds?: Identifier[];
selectedFolderIds?: Identifier[]; selectedFolderIds?: Identifier[];
@@ -29,6 +28,7 @@ interface UseBulkDocumentActionsArgs {
handleFolderDelete: (id: Identifier, options?: { showMessage?: boolean; manageLoading?: boolean }) => Promise<boolean>; handleFolderDelete: (id: Identifier, options?: { showMessage?: boolean; manageLoading?: boolean }) => Promise<boolean>;
clearDocumentSelection: () => void; clearDocumentSelection: () => void;
setLoading: (value: boolean) => void; setLoading: (value: boolean) => void;
updateDocumentCaches?: (id: Identifier, updater: (doc: any) => any) => void;
} }
const useBulkDocumentActions = ({ const useBulkDocumentActions = ({
@@ -36,7 +36,6 @@ const useBulkDocumentActions = ({
resolveTargetDocumentIds, resolveTargetDocumentIds,
correspondentLookupByName, correspondentLookupByName,
handleCorrespondentCreate, handleCorrespondentCreate,
refreshCurrentFolder,
setStatusMessage, setStatusMessage,
selectedDocumentIds, selectedDocumentIds,
selectedFolderIds, selectedFolderIds,
@@ -44,6 +43,7 @@ const useBulkDocumentActions = ({
handleFolderDelete, handleFolderDelete,
clearDocumentSelection, clearDocumentSelection,
setLoading, setLoading,
updateDocumentCaches,
}: UseBulkDocumentActionsArgs) => { }: UseBulkDocumentActionsArgs) => {
const handleBulkCorrespondentAdd = useCallback( const handleBulkCorrespondentAdd = useCallback(
async ({ name, input, documentIds }: { name?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => { async ({ name, input, documentIds }: { name?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => {
@@ -84,7 +84,21 @@ const useBulkDocumentActions = ({
const { assigned = 0, removed = 0 } = 'data' in response ? response.data : response; const { assigned = 0, removed = 0 } = 'data' in response ? response.data : response;
await refreshCurrentFolder(); if (updateDocumentCaches && target.id) {
targets.forEach((docId) => {
updateDocumentCaches(docId, (doc) => {
if (!doc) return doc;
const current = Array.isArray((doc as any).correspondents) ? (doc as any).correspondents : [];
if (current.some((entry: any) => entry?.id === target.id)) {
return doc;
}
return {
...(doc as any),
correspondents: [...current, { id: target.id, name: (target as any).name }],
};
});
});
}
const assignedSuffix = assigned === 1 ? '' : 's'; const assignedSuffix = assigned === 1 ? '' : 's';
if (removed > 0) { if (removed > 0) {
const removedSuffix = removed === 1 ? '' : 's'; const removedSuffix = removed === 1 ? '' : 's';
@@ -107,9 +121,9 @@ const useBulkDocumentActions = ({
api, api,
correspondentLookupByName, correspondentLookupByName,
handleCorrespondentCreate, handleCorrespondentCreate,
refreshCurrentFolder,
resolveTargetDocumentIds, resolveTargetDocumentIds,
setStatusMessage, setStatusMessage,
updateDocumentCaches,
], ],
); );
@@ -138,7 +152,22 @@ const useBulkDocumentActions = ({
}); });
const { assigned = 0, removed = 0 } = 'data' in response ? response.data : response; const { assigned = 0, removed = 0 } = 'data' in response ? response.data : response;
await refreshCurrentFolder(); if (updateDocumentCaches) {
targets.forEach((docId) => {
updateDocumentCaches(docId, (doc) => {
if (!doc || !Array.isArray((doc as any).correspondents)) {
return doc;
}
const filtered = (doc as any).correspondents.filter(
(entry: any) =>
entry && !normalizedAssignments.some((assignment) => assignment.correspondent_id === entry.id),
);
return filtered.length === (doc as any).correspondents.length
? doc
: { ...(doc as any), correspondents: filtered };
});
});
}
if (removed > 0) { if (removed > 0) {
const removedSuffix = removed === 1 ? '' : 's'; const removedSuffix = removed === 1 ? '' : 's';
@@ -153,7 +182,7 @@ const useBulkDocumentActions = ({
setStatusMessage('No correspondents changed.', 'info'); setStatusMessage('No correspondents changed.', 'info');
} }
}, },
[api, refreshCurrentFolder, resolveTargetDocumentIds, setStatusMessage], [api, resolveTargetDocumentIds, setStatusMessage, updateDocumentCaches],
); );
const handleDeleteSelection = useCallback(async () => { const handleDeleteSelection = useCallback(async () => {
@@ -518,11 +518,33 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
); );
const gridIconSize = DEFAULT_GRID_ICON_SIZE; const gridIconSize = DEFAULT_GRID_ICON_SIZE;
useEffect(() => {
const scrollToTop = useCallback(() => {
if (scrollRef.current) { if (scrollRef.current) {
scrollRef.current.scrollTop = 0; scrollRef.current.scrollTop = 0;
} }
}, [viewMode]); }, []);
const searchKey = useMemo(
() => (Array.isArray(searchResultIds) ? searchResultIds.join(':') : 'none'),
[searchResultIds],
);
const breadcrumbKey = useMemo(
() => (Array.isArray(breadcrumbs) ? breadcrumbs.map((crumb) => crumb?.id ?? '').join(':') : 'none'),
[breadcrumbs],
);
useEffect(() => {
scrollToTop();
}, [
scrollToTop,
viewMode,
showingSearchResults,
searchKey,
breadcrumbKey,
]);
const isTagDragEvent = useCallback((event) => isTagTransferEvent(event), []); const isTagDragEvent = useCallback((event) => isTagTransferEvent(event), []);
const ensureFocusedRowVisible = useCallback(() => { const ensureFocusedRowVisible = useCallback(() => {
if (!focusedRowKey) return; if (!focusedRowKey) return;
@@ -18,18 +18,21 @@ interface UseDocumentCorrespondentActionsArgs {
apiClient: ApiClient; apiClient: ApiClient;
correspondents: CorrespondentOption[]; correspondents: CorrespondentOption[];
handleCorrespondentCreate: (payload: { name: string }) => Promise<CorrespondentOption | null>; handleCorrespondentCreate: (payload: { name: string }) => Promise<CorrespondentOption | null>;
refreshCurrentFolder: () => Promise<void>;
notifyApiError: (error: unknown, fallback: string) => void; notifyApiError: (error: unknown, fallback: string) => void;
setStatusMessage: (message: string, variant?: string) => void; setStatusMessage: (message: string, variant?: string) => void;
updateDocumentCaches?: (
id: Identifier,
updater: (doc: { correspondents?: CorrespondentOption[] } | null) => { correspondents?: CorrespondentOption[] } | null,
) => void;
} }
const useDocumentCorrespondentActions = ({ const useDocumentCorrespondentActions = ({
apiClient, apiClient,
correspondents, correspondents,
handleCorrespondentCreate, handleCorrespondentCreate,
refreshCurrentFolder,
notifyApiError, notifyApiError,
setStatusMessage, setStatusMessage,
updateDocumentCaches,
}: UseDocumentCorrespondentActionsArgs) => { }: UseDocumentCorrespondentActionsArgs) => {
const correspondentLookupByName = useMemo(() => { const correspondentLookupByName = useMemo(() => {
const map = new Map<string, CorrespondentOption>(); const map = new Map<string, CorrespondentOption>();
@@ -44,7 +47,7 @@ const useDocumentCorrespondentActions = ({
const handleDocumentCorrespondentAttach = useCallback( const handleDocumentCorrespondentAttach = useCallback(
async ( async (
{ documentId, correspondentId }: { documentId: Identifier; correspondentId: Identifier }, { documentId, correspondentId }: { documentId: Identifier; correspondentId: Identifier },
{ notify = true, refresh = true }: { notify?: boolean; refresh?: boolean } = {}, { notify = true }: { notify?: boolean } = {},
) => { ) => {
if (documentId == null || correspondentId == null) { if (documentId == null || correspondentId == null) {
throw new Error('Missing document or correspondent.'); throw new Error('Missing document or correspondent.');
@@ -54,8 +57,21 @@ const useDocumentCorrespondentActions = ({
assignments: [{ correspondent_id: correspondentId }], assignments: [{ correspondent_id: correspondentId }],
replace: false, replace: false,
}); });
if (refresh) { if (updateDocumentCaches) {
await refreshCurrentFolder(); const correspondent = correspondents.find((entry) => entry?.id === correspondentId) || null;
updateDocumentCaches(documentId, (doc) => {
if (!doc) {
return doc;
}
const current = Array.isArray(doc.correspondents) ? doc.correspondents : [];
if (current.some((entry) => entry?.id === correspondentId)) {
return doc;
}
const nextEntry = correspondent
? { id: correspondent.id, name: correspondent.name }
: { id: correspondentId };
return { ...doc, correspondents: [...current, nextEntry] };
});
} }
if (notify) { if (notify) {
setStatusMessage('Correspondent assigned.', 'success'); setStatusMessage('Correspondent assigned.', 'success');
@@ -67,21 +83,27 @@ const useDocumentCorrespondentActions = ({
throw new Error(message); throw new Error(message);
} }
}, },
[apiClient, refreshCurrentFolder, notifyApiError, setStatusMessage], [apiClient, correspondents, notifyApiError, setStatusMessage, updateDocumentCaches],
); );
const handleCorrespondentRemove = useCallback( const handleCorrespondentRemove = useCallback(
async ( async (
{ documentId, correspondentId }: { documentId: Identifier; correspondentId: Identifier }, { documentId, correspondentId }: { documentId: Identifier; correspondentId: Identifier },
{ notify = true, refresh = true }: { notify?: boolean; refresh?: boolean } = {}, { notify = true }: { notify?: boolean } = {},
) => { ) => {
if (documentId == null || correspondentId == null) { if (documentId == null || correspondentId == null) {
throw new Error('Missing document or correspondent.'); throw new Error('Missing document or correspondent.');
} }
try { try {
await apiClient.delete(`/documents/${documentId}/correspondents/${correspondentId}`); await apiClient.delete(`/documents/${documentId}/correspondents/${correspondentId}`);
if (refresh) { if (updateDocumentCaches) {
await refreshCurrentFolder(); updateDocumentCaches(documentId, (doc) => {
if (!doc || !Array.isArray(doc.correspondents)) {
return doc;
}
const filtered = doc.correspondents.filter((entry) => entry?.id !== correspondentId);
return filtered.length === doc.correspondents.length ? doc : { ...doc, correspondents: filtered };
});
} }
if (notify) { if (notify) {
setStatusMessage('Correspondent removed.', 'success'); setStatusMessage('Correspondent removed.', 'success');
@@ -93,7 +115,7 @@ const useDocumentCorrespondentActions = ({
throw new Error(message); throw new Error(message);
} }
}, },
[apiClient, refreshCurrentFolder, notifyApiError, setStatusMessage], [apiClient, notifyApiError, setStatusMessage, updateDocumentCaches],
); );
const normalizeOption = ( const normalizeOption = (
@@ -134,6 +134,7 @@ interface UseDocumentMutationsArgs {
closeDocumentPreview: CloseDocumentPreview; closeDocumentPreview: CloseDocumentPreview;
previewDocumentId?: DocumentId | null; previewDocumentId?: DocumentId | null;
refreshCurrentFolder: () => Promise<void>; refreshCurrentFolder: () => Promise<void>;
refreshVisibleDocuments: () => Promise<void>;
documentsViewMode?: string; documentsViewMode?: string;
updateDocumentCaches: UpdateDocumentCaches; updateDocumentCaches: UpdateDocumentCaches;
tagLookupById: Map<DocumentId, Tag>; tagLookupById: Map<DocumentId, Tag>;
@@ -141,6 +142,7 @@ interface UseDocumentMutationsArgs {
refreshTags: () => Promise<void>; refreshTags: () => Promise<void>;
tagManager: TagManager; tagManager: TagManager;
extractDocumentFromResponse?: (payload: unknown) => DocumentLike | null; extractDocumentFromResponse?: (payload: unknown) => DocumentLike | null;
ingestDocuments?: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
} }
interface UseDocumentMutationsResult { interface UseDocumentMutationsResult {
@@ -210,6 +212,7 @@ const useDocumentMutations = ({
closeDocumentPreview, closeDocumentPreview,
previewDocumentId, previewDocumentId,
refreshCurrentFolder, refreshCurrentFolder,
refreshVisibleDocuments,
documentsViewMode, documentsViewMode,
updateDocumentCaches, updateDocumentCaches,
tagLookupById, tagLookupById,
@@ -217,6 +220,7 @@ const useDocumentMutations = ({
refreshTags, refreshTags,
tagManager, tagManager,
extractDocumentFromResponse, extractDocumentFromResponse,
ingestDocuments,
}: UseDocumentMutationsArgs): UseDocumentMutationsResult => { }: UseDocumentMutationsArgs): UseDocumentMutationsResult => {
const moveDocumentsToFolder = useCallback( const moveDocumentsToFolder = useCallback(
async (documentIds: Array<DocumentId | DocumentLike>, targetFolderId?: NullableFolderId) => { async (documentIds: Array<DocumentId | DocumentLike>, targetFolderId?: NullableFolderId) => {
@@ -492,12 +496,16 @@ const useDocumentMutations = ({
const { data } = await api.patch(`/documents/${documentId}`, { title: trimmed }); const { data } = await api.patch(`/documents/${documentId}`, { title: trimmed });
const updatedDocument = extractDocumentFromResponse?.(data); const updatedDocument = extractDocumentFromResponse?.(data);
if (updatedDocument && ingestDocuments) {
ingestDocuments([updatedDocument]);
} else {
updateDocumentCaches(documentId, (doc) => { updateDocumentCaches(documentId, (doc) => {
if (updatedDocument) { if (updatedDocument) {
return { ...doc, ...updatedDocument }; return { ...doc, ...updatedDocument };
} }
return { ...doc, title: trimmed }; return { ...doc, title: trimmed };
}); });
}
setStatusMessage('Document title updated.', 'success'); setStatusMessage('Document title updated.', 'success');
return true; return true;
@@ -509,7 +517,15 @@ const useDocumentMutations = ({
setLoading(false); setLoading(false);
} }
}, },
[api, extractDocumentFromResponse, notifyApiError, setLoading, setStatusMessage, updateDocumentCaches], [
api,
extractDocumentFromResponse,
ingestDocuments,
notifyApiError,
setLoading,
setStatusMessage,
updateDocumentCaches,
],
); );
const handleDocumentIssuedUpdate = useCallback( const handleDocumentIssuedUpdate = useCallback(
@@ -520,12 +536,16 @@ const useDocumentMutations = ({
const { data } = await api.patch(`/documents/${documentId}`, payload); const { data } = await api.patch(`/documents/${documentId}`, payload);
const updatedDocument = extractDocumentFromResponse?.(data); const updatedDocument = extractDocumentFromResponse?.(data);
if (updatedDocument && ingestDocuments) {
ingestDocuments([updatedDocument]);
} else {
updateDocumentCaches(documentId, (doc) => { updateDocumentCaches(documentId, (doc) => {
if (updatedDocument) { if (updatedDocument) {
return { ...doc, ...updatedDocument }; return { ...doc, ...updatedDocument };
} }
return { ...doc, issued_at: payload.issued_at }; return { ...doc, issued_at: payload.issued_at };
}); });
}
const message = payload.issued_at ? 'Issued date updated.' : 'Issued date cleared.'; const message = payload.issued_at ? 'Issued date updated.' : 'Issued date cleared.';
setStatusMessage(message, 'success'); setStatusMessage(message, 'success');
@@ -538,7 +558,56 @@ const useDocumentMutations = ({
setLoading(false); setLoading(false);
} }
}, },
[api, extractDocumentFromResponse, notifyApiError, setLoading, setStatusMessage, updateDocumentCaches], [
api,
extractDocumentFromResponse,
ingestDocuments,
notifyApiError,
setLoading,
setStatusMessage,
updateDocumentCaches,
],
);
const attachTagToDocument = useCallback(
async ({
documentId,
tag,
}: {
documentId?: DocumentId;
tag?: Tag | null;
}) => {
if (!documentId || !tag?.id) {
return false;
}
const cachedTag: Tag = {
id: tag.id,
label: tag.label,
color: Object.prototype.hasOwnProperty.call(tag, 'color') ? tag.color ?? null : null,
};
try {
await api.post(`/documents/${documentId}/tags`, { tag_ids: [cachedTag.id] });
updateDocumentCaches(documentId, (doc) => {
if (!doc) {
return doc;
}
const currentTags = Array.isArray(doc.tags) ? doc.tags : [];
if (currentTags.some((entry) => entry?.id === cachedTag.id)) {
return doc;
}
return { ...doc, tags: [...currentTags, cachedTag] };
});
setStatusMessage('Tag assigned.', 'success');
return true;
} catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to assign tag.';
notifyApiError(error, message);
return false;
}
},
[api, notifyApiError, setStatusMessage, updateDocumentCaches],
); );
const handleDocumentTagAdd = useCallback( const handleDocumentTagAdd = useCallback(
@@ -561,17 +630,18 @@ const useDocumentMutations = ({
tag = data as Tag; tag = data as Tag;
await refreshTags(); await refreshTags();
} }
await api.post(`/documents/${document.id}/tags`, { tag_ids: [tag.id] }); await attachTagToDocument({
setStatusMessage('Tag assigned.', 'success'); documentId: document.id as DocumentId,
tag,
});
if (input && Object(input) === input && 'value' in (input as Record<string, unknown>)) { if (input && Object(input) === input && 'value' in (input as Record<string, unknown>)) {
(input as { value?: string }).value = ''; (input as { value?: string }).value = '';
} }
await refreshCurrentFolder();
} catch (error) { } catch (error) {
notifyApiError(error, 'Failed to assign tag.'); notifyApiError(error, 'Failed to assign tag.');
} }
}, },
[api, tags, refreshTags, refreshCurrentFolder, notifyApiError, setStatusMessage, tagManager], [api, tags, refreshTags, attachTagToDocument, notifyApiError, setStatusMessage, tagManager],
); );
const handleDocumentTagAttach = useCallback( const handleDocumentTagAttach = useCallback(
@@ -597,40 +667,14 @@ const useDocumentMutations = ({
}; };
}; };
try {
await api.post(`/documents/${documentId}/tags`, { tag_ids: [tagId] });
updateDocumentCaches(documentId, (doc) => {
if (!doc) {
return doc;
}
const currentTags = Array.isArray(doc.tags) ? doc.tags : [];
if (currentTags.some((existing) => existing?.id === tagId)) {
return doc;
}
const resolvedTag = resolveTagForCache(); const resolvedTag = resolveTagForCache();
if (!resolvedTag) { return attachTagToDocument({
return doc; documentId,
} tag: resolvedTag,
return { ...doc, tags: [...currentTags, resolvedTag] };
}); });
setStatusMessage('Tag assigned.', 'success');
if (documentsViewMode !== 'desk') {
await refreshCurrentFolder();
}
return true;
} catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to assign tag.';
notifyApiError(error, message);
return false;
}
}, },
[ [
api, attachTagToDocument,
refreshCurrentFolder,
documentsViewMode,
notifyApiError,
setStatusMessage,
updateDocumentCaches,
tagLookupById, tagLookupById,
], ],
); );
@@ -21,11 +21,11 @@ interface UseDocumentTaggingArgs {
tags: TagRecord[]; tags: TagRecord[];
tagManager: TagManager; tagManager: TagManager;
refreshTags: () => Promise<void> | void; refreshTags: () => Promise<void> | void;
refreshCurrentFolder: () => Promise<void> | void;
resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[]; resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[];
notifyApiError: (error: unknown, message: string) => void; notifyApiError: (error: unknown, message: string) => void;
setStatusMessage: (message: string, variant?: string) => void; setStatusMessage: (message: string, variant?: string) => void;
setLoading: (state: boolean) => void; setLoading: (state: boolean) => void;
updateDocumentCaches?: (id: Identifier, updater: (doc: TagRecord | null) => TagRecord | null) => void;
} }
interface BulkTagOperationArgs { interface BulkTagOperationArgs {
@@ -47,11 +47,11 @@ const useDocumentTagging = ({
tags, tags,
tagManager, tagManager,
refreshTags, refreshTags,
refreshCurrentFolder,
resolveTargetDocumentIds, resolveTargetDocumentIds,
notifyApiError, notifyApiError,
setStatusMessage, setStatusMessage,
setLoading, setLoading,
updateDocumentCaches,
}: UseDocumentTaggingArgs) => { }: UseDocumentTaggingArgs) => {
const bulkTagOperation = useCallback( const bulkTagOperation = useCallback(
async ({ labels, action, documentIds }: BulkTagOperationArgs): Promise<BulkTagOperationResult> => { async ({ labels, action, documentIds }: BulkTagOperationArgs): Promise<BulkTagOperationResult> => {
@@ -84,6 +84,7 @@ const useDocumentTagging = ({
try { try {
if (action === 'add') { if (action === 'add') {
const createdIds: Identifier[] = []; const createdIds: Identifier[] = [];
const createdTags: TagRecord[] = [];
for (const label of normalized) { for (const label of normalized) {
let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null; let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
if (!tag) { if (!tag) {
@@ -93,8 +94,44 @@ const useDocumentTagging = ({
await refreshTags(); await refreshTags();
} }
createdIds.push(tag.id); createdIds.push(tag.id);
createdTags.push(tag);
} }
tagIds = Array.from(new Set(createdIds)); tagIds = Array.from(new Set(createdIds));
if (updateDocumentCaches) {
const tagById = new Map<Identifier, TagRecord>();
tags.forEach((tag) => {
if (tag?.id != null) {
tagById.set(tag.id, tag);
}
});
createdTags.forEach((tag) => {
if (tag?.id != null) {
tagById.set(tag.id, tag);
}
});
targetDocumentIds.forEach((docId) => {
tagIds.forEach((tagId) => {
const cachedTag = tagById.get(tagId);
if (!cachedTag) {
return;
}
updateDocumentCaches(docId, (doc) => {
if (!doc) {
return doc;
}
const currentTags = Array.isArray((doc as any).tags) ? (doc as any).tags : [];
if (currentTags.some((entry: any) => entry?.id === tagId)) {
return doc;
}
return {
...(doc as any),
tags: [...currentTags, { ...cachedTag }],
};
});
});
});
}
} }
tagIds = Array.from(new Set(tagIds)); tagIds = Array.from(new Set(tagIds));
@@ -109,7 +146,23 @@ const useDocumentTagging = ({
action, action,
}); });
await refreshCurrentFolder(); if (updateDocumentCaches) {
targetDocumentIds.forEach((docId) => {
tagIds.forEach((tagId) => {
updateDocumentCaches(docId, (doc) => {
if (!doc || !Array.isArray((doc as any).tags)) {
return doc;
}
const currentTags = (doc as any).tags;
if (action === 'remove') {
const filtered = currentTags.filter((entry: any) => entry?.id !== tagId);
return filtered.length === currentTags.length ? doc : { ...(doc as any), tags: filtered };
}
return doc;
});
});
});
}
return { return {
ok: true, ok: true,
@@ -130,11 +183,11 @@ const useDocumentTagging = ({
resolveTargetDocumentIds, resolveTargetDocumentIds,
tags, tags,
refreshTags, refreshTags,
refreshCurrentFolder,
notifyApiError, notifyApiError,
setLoading, setLoading,
tagManager, tagManager,
apiClient, apiClient,
updateDocumentCaches,
], ],
); );
+1 -11
View File
@@ -23,24 +23,14 @@ interface FolderContentsEntry {
interface UseDocumentsOptions { interface UseDocumentsOptions {
setFolderContents: Dispatch<SetStateAction<Map<string, FolderContentsEntry>>>; setFolderContents: Dispatch<SetStateAction<Map<string, FolderContentsEntry>>>;
fetchDocumentById?: (id: DocumentId) => Promise<DocumentLike | null>; fetchDocumentById?: (id: DocumentId) => Promise<DocumentLike | null>;
hydrateDocument?: (payload: unknown) => DocumentLike | null;
hydrateDocuments?: (payload: unknown[]) => DocumentLike[];
extractDocument?: (payload: unknown) => DocumentLike | null;
} }
const useDocuments = ({ const useDocuments = ({
setFolderContents, setFolderContents,
fetchDocumentById, fetchDocumentById,
hydrateDocument,
hydrateDocuments,
extractDocument,
}: UseDocumentsOptions) => { }: UseDocumentsOptions) => {
const managerRef = useRef( const managerRef = useRef(
new DocumentsManager<DocumentLike>(fetchDocumentById, { new DocumentsManager<DocumentLike>(fetchDocumentById),
hydrateDocument,
hydrateDocuments,
extractDocument,
}),
); );
const [documents, setDocumentsState] = useState<DocumentLike[]>([]); const [documents, setDocumentsState] = useState<DocumentLike[]>([]);
@@ -318,9 +318,6 @@ const useDocumentsWorkspace = ({
} = useDocuments({ } = useDocuments({
setFolderContents, setFolderContents,
fetchDocumentById, fetchDocumentById,
hydrateDocument: (payload) => assetManager.hydrateDocument(payload),
hydrateDocuments: (payload) => assetManager.hydrateDocuments(payload),
extractDocument: extractDocumentFromResponse,
}); });
const documentLookup = useSyncExternalStore( const documentLookup = useSyncExternalStore(
@@ -348,7 +345,6 @@ const useDocumentsWorkspace = ({
isInvalidFolderDrop, isInvalidFolderDrop,
} = useFolderTree({ } = useFolderTree({
initialSelectedFolder: routeFolderId || 'root', initialSelectedFolder: routeFolderId || 'root',
assetManager,
apiClient: api, apiClient: api,
tenantIdRef, tenantIdRef,
documentsSortFieldRef: activeSortFieldRef, documentsSortFieldRef: activeSortFieldRef,
@@ -370,6 +366,7 @@ const useDocumentsWorkspace = ({
activeCorrespondentFilters, activeCorrespondentFilters,
setActiveCorrespondentFilters, setActiveCorrespondentFilters,
isFilterActive, isFilterActive,
refetchSearchResults,
documentsFilterValue, documentsFilterValue,
} = useDocumentsSearch({ } = useDocumentsSearch({
api, api,
@@ -444,7 +441,6 @@ const useDocumentsWorkspace = ({
const { const {
documentLinks, documentLinks,
ensurePreviewData,
ensureDownloadUrl, ensureDownloadUrl,
openDocumentPreview, openDocumentPreview,
closeDocumentPreview, closeDocumentPreview,
@@ -454,7 +450,6 @@ const useDocumentsWorkspace = ({
routeDocumentId: previewDocumentId, routeDocumentId: previewDocumentId,
documentsManager, documentsManager,
selectedFolder, selectedFolder,
assetManager,
api, api,
resolveApiPath, resolveApiPath,
notifyApiError, notifyApiError,
@@ -607,6 +602,14 @@ const useDocumentsWorkspace = ({
} }
}, [selectedFolder, ensureFolderData, applySelectedFolder, notifyApiError]); }, [selectedFolder, ensureFolderData, applySelectedFolder, notifyApiError]);
const refreshVisibleDocuments = useCallback(async () => {
if (showingSearchResults) {
await refetchSearchResults();
return;
}
await refreshCurrentFolder();
}, [showingSearchResults, refetchSearchResults, refreshCurrentFolder]);
const { const {
handleBulkTagAddFromDetail, handleBulkTagAddFromDetail,
handleBulkTagRemoveFromDetail, handleBulkTagRemoveFromDetail,
@@ -616,11 +619,11 @@ const useDocumentsWorkspace = ({
tags, tags,
tagManager, tagManager,
refreshTags, refreshTags,
refreshCurrentFolder,
resolveTargetDocumentIds, resolveTargetDocumentIds,
notifyApiError, notifyApiError,
setStatusMessage, setStatusMessage,
setLoading, setLoading,
updateDocumentCaches,
}); });
const { const {
@@ -671,9 +674,9 @@ const useDocumentsWorkspace = ({
apiClient: api, apiClient: api,
correspondents, correspondents,
handleCorrespondentCreate, handleCorrespondentCreate,
refreshCurrentFolder,
notifyApiError, notifyApiError,
setStatusMessage, setStatusMessage,
updateDocumentCaches,
}); });
useEffect(() => { useEffect(() => {
@@ -851,6 +854,7 @@ const useDocumentsWorkspace = ({
closeDocumentPreview, closeDocumentPreview,
previewDocumentId, previewDocumentId,
refreshCurrentFolder, refreshCurrentFolder,
refreshVisibleDocuments,
documentsViewMode, documentsViewMode,
updateDocumentCaches, updateDocumentCaches,
tagLookupById, tagLookupById,
@@ -858,6 +862,7 @@ const useDocumentsWorkspace = ({
refreshTags, refreshTags,
tagManager, tagManager,
extractDocumentFromResponse, extractDocumentFromResponse,
ingestDocuments: (docs) => documentsManager.ingest(docs),
}); });
const { const {
@@ -1007,7 +1012,6 @@ const useDocumentsWorkspace = ({
resolveTargetDocumentIds, resolveTargetDocumentIds,
correspondentLookupByName, correspondentLookupByName,
handleCorrespondentCreate, handleCorrespondentCreate,
refreshCurrentFolder,
setStatusMessage, setStatusMessage,
selectedDocumentIds, selectedDocumentIds,
selectedFolderIds, selectedFolderIds,
@@ -1015,6 +1019,7 @@ const useDocumentsWorkspace = ({
handleFolderDelete, handleFolderDelete,
clearDocumentSelection, clearDocumentSelection,
setLoading, setLoading,
updateDocumentCaches,
}); });
@@ -1036,9 +1041,7 @@ const useDocumentsWorkspace = ({
return null; return null;
} }
setDocuments((prev) => updateDocumentCaches(documentId, (doc) => mergeAssetIntoDocument(doc, entry));
prev.map((doc) => (doc.id === documentId ? mergeAssetIntoDocument(doc, entry) : doc)),
);
return entry; return entry;
} catch (error) { } catch (error) {
@@ -1046,7 +1049,7 @@ const useDocumentsWorkspace = ({
throw error; throw error;
} }
}, },
[assetManager, setDocuments, notifyApiError], [assetManager, updateDocumentCaches, notifyApiError],
); );
@@ -1264,7 +1267,6 @@ const useDocumentsWorkspace = ({
handleTagRemove, handleTagRemove,
ensureAssetUrl, ensureAssetUrl,
getDocumentAsset, getDocumentAsset,
ensurePreviewData,
correspondents, correspondents,
handleCorrespondentAdd, handleCorrespondentAdd,
handleCorrespondentRemove, handleCorrespondentRemove,
@@ -1617,7 +1619,6 @@ const useDocumentsWorkspace = ({
documentsTableProps, documentsTableProps,
detailPanelProps, detailPanelProps,
documentsViewMode, documentsViewMode,
ensurePreviewData,
ensureAssetUrl, ensureAssetUrl,
resolveFolderPath, resolveFolderPath,
getDocumentAsset, getDocumentAsset,
@@ -1669,7 +1670,6 @@ const useDocumentsWorkspace = ({
documentsTableProps, documentsTableProps,
detailPanelProps, detailPanelProps,
documentsViewMode, documentsViewMode,
ensurePreviewData,
ensureAssetUrl, ensureAssetUrl,
resolveFolderPath, resolveFolderPath,
getDocumentAsset, getDocumentAsset,
+4 -14
View File
@@ -48,11 +48,6 @@ interface FolderTreeNode extends FolderSummary {
hasChildren?: boolean; hasChildren?: boolean;
} }
interface AssetManagerLike {
hydrateDocuments: (docs: DocumentLike[]) => DocumentLike[];
hydrateFolderContents: (payload: FolderContentsEntry) => FolderContentsEntry;
}
interface ApiClient { interface ApiClient {
get<T = FolderContentsEntry>(path: string, config?: { params?: Record<string, unknown> }): Promise<{ data: T }>; get<T = FolderContentsEntry>(path: string, config?: { params?: Record<string, unknown> }): Promise<{ data: T }>;
} }
@@ -68,7 +63,6 @@ interface SelectionHelpers {
interface UseFolderTreeOptions { interface UseFolderTreeOptions {
initialSelectedFolder?: FolderId; initialSelectedFolder?: FolderId;
assetManager: AssetManagerLike;
apiClient: ApiClient; apiClient: ApiClient;
tenantIdRef: MutableRefObject<Identifier | null>; tenantIdRef: MutableRefObject<Identifier | null>;
documentsSortFieldRef: MutableRefObject<string>; documentsSortFieldRef: MutableRefObject<string>;
@@ -86,7 +80,6 @@ interface FolderOption {
const useFolderTree = ({ const useFolderTree = ({
initialSelectedFolder = 'root', initialSelectedFolder = 'root',
assetManager,
apiClient, apiClient,
tenantIdRef, tenantIdRef,
documentsSortFieldRef, documentsSortFieldRef,
@@ -117,7 +110,7 @@ const useFolderTree = ({
const applySelectedFolder = useCallback( const applySelectedFolder = useCallback(
(folderId: FolderId, contents?: FolderContentsEntry | null) => { (folderId: FolderId, contents?: FolderContentsEntry | null) => {
const subfolders = Array.isArray(contents?.subfolders) ? contents.subfolders : []; const subfolders = Array.isArray(contents?.subfolders) ? contents.subfolders : [];
const docs = assetManager.hydrateDocuments(contents?.documents ?? []); const docs = Array.isArray(contents?.documents) ? contents.documents : [];
const folderInfo = contents?.folder ?? null; const folderInfo = contents?.folder ?? null;
setCurrentSubfolders(subfolders); setCurrentSubfolders(subfolders);
@@ -166,7 +159,6 @@ const useFolderTree = ({
setSelectionOrder(mergedSelection); setSelectionOrder(mergedSelection);
}, },
[ [
assetManager,
focusedDocumentId, focusedDocumentId,
selectionAnchorRef, selectionAnchorRef,
selectionOrderRef, selectionOrderRef,
@@ -258,14 +250,13 @@ const useFolderTree = ({
} }
const requestConfig = Object.keys(params).length ? { params } : {}; const requestConfig = Object.keys(params).length ? { params } : {};
const { data } = await apiClient.get<FolderContentsEntry>(`/folders/${path}/contents`, requestConfig); const { data } = await apiClient.get<FolderContentsEntry>(`/folders/${path}/contents`, requestConfig);
const hydrated = assetManager.hydrateFolderContents(data);
const childFolders = Array.isArray(data.subfolders) ? data.subfolders : []; const childFolders = Array.isArray(data.subfolders) ? data.subfolders : [];
const childIds = childFolders const childIds = childFolders
.map((child) => (child?.id ?? null) as FolderId | null) .map((child) => (child?.id ?? null) as FolderId | null)
.filter((id): id is FolderId => Boolean(id)); .filter((id): id is FolderId => Boolean(id));
const enriched = { const enriched = {
...hydrated, ...data,
__includesDocuments: includeDocuments, __includesDocuments: includeDocuments,
__sortField: includeDocuments ? sortField : cachedSortField, __sortField: includeDocuments ? sortField : cachedSortField,
__sortDirection: includeDocuments ? sortDirection : cachedSortDirection, __sortDirection: includeDocuments ? sortDirection : cachedSortDirection,
@@ -353,10 +344,10 @@ const useFolderTree = ({
if (existingEntry) { if (existingEntry) {
next.set(folderId, { next.set(folderId, {
...existingEntry, ...existingEntry,
...hydrated, ...data,
documents: existingEntry.__includesDocuments documents: existingEntry.__includesDocuments
? existingEntry.documents ? existingEntry.documents
: hydrated.documents, : data.documents,
__includesDocuments: existingEntry.__includesDocuments || false, __includesDocuments: existingEntry.__includesDocuments || false,
__sortField: existingEntry.__sortField ?? enriched.__sortField, __sortField: existingEntry.__sortField ?? enriched.__sortField,
__sortDirection: existingEntry.__sortDirection ?? enriched.__sortDirection, __sortDirection: existingEntry.__sortDirection ?? enriched.__sortDirection,
@@ -372,7 +363,6 @@ const useFolderTree = ({
}, },
[ [
apiClient, apiClient,
assetManager,
documentsSortDirectionRef, documentsSortDirectionRef,
documentsSortFieldRef, documentsSortFieldRef,
tenantIdRef, tenantIdRef,
@@ -61,7 +61,6 @@ interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
contentType?: string | null; contentType?: string | null;
filename?: string | null; filename?: string | null;
} | null; } | null;
hydrateDocument?: (doc: DocumentLike | null) => DocumentLike | null;
ensureAssetUrl?: (docId: string | number, asset: AssetLike, options?: { start?: number; limit?: number }) => Promise<unknown>; ensureAssetUrl?: (docId: string | number, asset: AssetLike, options?: { start?: number; limit?: number }) => Promise<unknown>;
getDocumentAsset?: (doc: DocumentLike | null, type: string) => AssetLike | null; getDocumentAsset?: (doc: DocumentLike | null, type: string) => AssetLike | null;
ensurePreviewData?: (docId: string | number, options?: { signal?: AbortSignal }) => Promise<DocumentLike | null>; ensurePreviewData?: (docId: string | number, options?: { signal?: AbortSignal }) => Promise<DocumentLike | null>;
@@ -124,7 +123,6 @@ export const createDocumentViewerHeaderActions = ({
const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
document, document,
documentLink, documentLink,
hydrateDocument,
tagLookupById, tagLookupById,
tagOptions, tagOptions,
onTagAdd, onTagAdd,
@@ -291,12 +289,6 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
setZoomOverlayOpen(false); setZoomOverlayOpen(false);
}, [effectiveDocumentLink?.url, document?.id]); }, [effectiveDocumentLink?.url, document?.id]);
useEffect(() => {
if (hydrateDocument && document?.id) {
hydrateDocument(document.id);
}
}, [hydrateDocument, document?.id]);
const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null); const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null);
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id); const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);