Remove asset/document hydration pipeline and introduce client-side cache updates for correspondents, tags, and search results
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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[] }) => {
|
||||||
@@ -70,21 +70,35 @@ const useBulkDocumentActions = ({
|
|||||||
if (!target?.id) {
|
if (!target?.id) {
|
||||||
setStatusMessage('Unable to resolve correspondent.', 'error');
|
setStatusMessage('Unable to resolve correspondent.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await api.post<BulkAssignmentResponse>('/documents/bulk/correspondents', {
|
const response = await api.post<BulkAssignmentResponse>('/documents/bulk/correspondents', {
|
||||||
document_ids: targets,
|
document_ids: targets,
|
||||||
assignments: [
|
assignments: [
|
||||||
{
|
{
|
||||||
correspondent_id: target.id,
|
correspondent_id: target.id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
action: 'add',
|
action: 'add',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { assigned = 0, removed = 0 } = 'data' in response ? response.data : response;
|
||||||
|
|
||||||
|
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 { assigned = 0, removed = 0 } = 'data' in response ? response.data : response;
|
|
||||||
|
|
||||||
await refreshCurrentFolder();
|
|
||||||
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';
|
||||||
@@ -99,19 +113,19 @@ const useBulkDocumentActions = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
api,
|
api,
|
||||||
correspondentLookupByName,
|
correspondentLookupByName,
|
||||||
handleCorrespondentCreate,
|
handleCorrespondentCreate,
|
||||||
refreshCurrentFolder,
|
resolveTargetDocumentIds,
|
||||||
resolveTargetDocumentIds,
|
setStatusMessage,
|
||||||
setStatusMessage,
|
updateDocumentCaches,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBulkCorrespondentRemove = useCallback(
|
const handleBulkCorrespondentRemove = useCallback(
|
||||||
async ({ assignments = [], documentIds }: { assignments?: CorrespondentAssignment[]; documentIds?: Identifier[] }) => {
|
async ({ assignments = [], documentIds }: { assignments?: CorrespondentAssignment[]; documentIds?: Identifier[] }) => {
|
||||||
@@ -131,14 +145,29 @@ const useBulkDocumentActions = ({
|
|||||||
correspondent_id: entry.correspondent_id,
|
correspondent_id: entry.correspondent_id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const response = await api.post<BulkAssignmentResponse>('/documents/bulk/correspondents', {
|
const response = await api.post<BulkAssignmentResponse>('/documents/bulk/correspondents', {
|
||||||
document_ids: targets,
|
document_ids: targets,
|
||||||
assignments: normalizedAssignments,
|
assignments: normalizedAssignments,
|
||||||
action: 'remove',
|
action: 'remove',
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
updateDocumentCaches(documentId, (doc) => {
|
if (updatedDocument && ingestDocuments) {
|
||||||
if (updatedDocument) {
|
ingestDocuments([updatedDocument]);
|
||||||
return { ...doc, ...updatedDocument };
|
} else {
|
||||||
}
|
updateDocumentCaches(documentId, (doc) => {
|
||||||
return { ...doc, title: trimmed };
|
if (updatedDocument) {
|
||||||
});
|
return { ...doc, ...updatedDocument };
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
updateDocumentCaches(documentId, (doc) => {
|
if (updatedDocument && ingestDocuments) {
|
||||||
if (updatedDocument) {
|
ingestDocuments([updatedDocument]);
|
||||||
return { ...doc, ...updatedDocument };
|
} else {
|
||||||
}
|
updateDocumentCaches(documentId, (doc) => {
|
||||||
return { ...doc, issued_at: payload.issued_at };
|
if (updatedDocument) {
|
||||||
});
|
return { ...doc, ...updatedDocument };
|
||||||
|
}
|
||||||
|
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 {
|
const resolvedTag = resolveTagForCache();
|
||||||
await api.post(`/documents/${documentId}/tags`, { tag_ids: [tagId] });
|
return attachTagToDocument({
|
||||||
updateDocumentCaches(documentId, (doc) => {
|
documentId,
|
||||||
if (!doc) {
|
tag: resolvedTag,
|
||||||
return doc;
|
});
|
||||||
}
|
|
||||||
const currentTags = Array.isArray(doc.tags) ? doc.tags : [];
|
|
||||||
if (currentTags.some((existing) => existing?.id === tagId)) {
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
const resolvedTag = resolveTagForCache();
|
|
||||||
if (!resolvedTag) {
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
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> => {
|
||||||
@@ -82,22 +82,59 @@ const useDocumentTagging = ({
|
|||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (action === 'add') {
|
if (action === 'add') {
|
||||||
const createdIds: Identifier[] = [];
|
const createdIds: Identifier[] = [];
|
||||||
for (const label of normalized) {
|
const createdTags: TagRecord[] = [];
|
||||||
let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
|
for (const label of normalized) {
|
||||||
if (!tag) {
|
let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
|
||||||
const payload = tagManager.buildPayload({ label });
|
if (!tag) {
|
||||||
const response = await apiClient.post<{ id?: Identifier; label: string }>('/tags', payload);
|
const payload = tagManager.buildPayload({ label });
|
||||||
tag = 'data' in response ? response.data : response;
|
const response = await apiClient.post<{ id?: Identifier; label: string }>('/tags', payload);
|
||||||
await refreshTags();
|
tag = 'data' in response ? response.data : response;
|
||||||
}
|
await refreshTags();
|
||||||
createdIds.push(tag.id);
|
|
||||||
}
|
}
|
||||||
tagIds = Array.from(new Set(createdIds));
|
createdIds.push(tag.id);
|
||||||
|
createdTags.push(tag);
|
||||||
}
|
}
|
||||||
|
tagIds = Array.from(new Set(createdIds));
|
||||||
|
|
||||||
tagIds = Array.from(new Set(tagIds));
|
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));
|
||||||
|
|
||||||
if (!tagIds.length) {
|
if (!tagIds.length) {
|
||||||
return { ok: false, reason: 'no-tags' };
|
return { ok: false, reason: 'no-tags' };
|
||||||
@@ -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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user