refactor: Decouple frontend API calls from a shared client instance
This commit is contained in:
@@ -250,8 +250,34 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
|||||||
const fetchPromise = (async () => {
|
const fetchPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getFolderTree();
|
const data = await getFolderTree();
|
||||||
setRemoteFolderTree(data);
|
|
||||||
return data;
|
// Convert flat list to tree
|
||||||
|
const nodeMap = new Map<string, FolderTreeNode>();
|
||||||
|
data.forEach((item) => {
|
||||||
|
nodeMap.set(item.id, {
|
||||||
|
...item,
|
||||||
|
children: [],
|
||||||
|
} as FolderTreeNode);
|
||||||
|
});
|
||||||
|
|
||||||
|
const roots: FolderTreeNode[] = [];
|
||||||
|
data.forEach((item) => {
|
||||||
|
const node = nodeMap.get(item.id);
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
node.children = item.children
|
||||||
|
.map((id) => nodeMap.get(id))
|
||||||
|
.filter((n): n is FolderTreeNode => Boolean(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.parent_id) {
|
||||||
|
roots.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setRemoteFolderTree(roots);
|
||||||
|
return roots;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[selection] Failed to load folder tree', error);
|
console.warn('[selection] Failed to load folder tree', error);
|
||||||
setRemoteFolderTree([]);
|
setRemoteFolderTree([]);
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import { MutableRefObject, useCallback, useState } from 'react';
|
import { MutableRefObject, useCallback, useState } from 'react';
|
||||||
import type { Correspondent } from '../../types/documents';
|
import type { Correspondent } from '../../types/documents';
|
||||||
|
|
||||||
type ApiClient = {
|
import { listCorrespondents, createCorrespondent, updateCorrespondent, deleteCorrespondent } from '../../lib/apiClient';
|
||||||
get: (path: string) => Promise<{ data: unknown }>;
|
|
||||||
post: (path: string, body: unknown) => Promise<{ data: unknown }>;
|
|
||||||
patch: (path: string, body: unknown) => Promise<{ data: unknown }>;
|
|
||||||
delete: (path: string) => Promise<{ data: unknown }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UseCorrespondentsOptions {
|
interface UseCorrespondentsOptions {
|
||||||
apiClient: ApiClient;
|
|
||||||
notifyApiError: (error: unknown, fallback: string) => void;
|
notifyApiError: (error: unknown, fallback: string) => void;
|
||||||
setStatusMessage: (message: string, variant?: string) => void;
|
setStatusMessage: (message: string, variant?: string) => void;
|
||||||
tenantIdRef: MutableRefObject<string | null>;
|
tenantIdRef: MutableRefObject<string | null>;
|
||||||
@@ -17,7 +11,6 @@ interface UseCorrespondentsOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useCorrespondents = ({
|
const useCorrespondents = ({
|
||||||
apiClient,
|
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
tenantIdRef,
|
tenantIdRef,
|
||||||
@@ -28,7 +21,7 @@ const useCorrespondents = ({
|
|||||||
const refreshCorrespondents = useCallback(async () => {
|
const refreshCorrespondents = useCallback(async () => {
|
||||||
const requestTenantId = tenantIdRef.current;
|
const requestTenantId = tenantIdRef.current;
|
||||||
try {
|
try {
|
||||||
const { data } = await apiClient.get('/correspondents');
|
const data = await listCorrespondents();
|
||||||
if (tenantIdRef.current !== requestTenantId) {
|
if (tenantIdRef.current !== requestTenantId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -39,7 +32,7 @@ const useCorrespondents = ({
|
|||||||
}
|
}
|
||||||
notifyApiError(error, 'Unable to load correspondents.');
|
notifyApiError(error, 'Unable to load correspondents.');
|
||||||
}
|
}
|
||||||
}, [apiClient, notifyApiError, tenantIdRef]);
|
}, [notifyApiError, tenantIdRef]);
|
||||||
|
|
||||||
const handleCorrespondentUpdate = useCallback(
|
const handleCorrespondentUpdate = useCallback(
|
||||||
async (correspondentId: string, changes: { name?: string }) => {
|
async (correspondentId: string, changes: { name?: string }) => {
|
||||||
@@ -61,7 +54,7 @@ const useCorrespondents = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiClient.patch(`/correspondents/${correspondentId}`, payload);
|
await updateCorrespondent(correspondentId, payload);
|
||||||
await refreshCorrespondents();
|
await refreshCorrespondents();
|
||||||
setStatusMessage('Correspondent updated.', 'success');
|
setStatusMessage('Correspondent updated.', 'success');
|
||||||
return true;
|
return true;
|
||||||
@@ -71,7 +64,7 @@ const useCorrespondents = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, notifyApiError, refreshCorrespondents, setStatusMessage],
|
[notifyApiError, refreshCorrespondents, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCorrespondentCreate = useCallback(
|
const handleCorrespondentCreate = useCallback(
|
||||||
@@ -81,7 +74,7 @@ const useCorrespondents = ({
|
|||||||
throw new Error('Correspondent name is required.');
|
throw new Error('Correspondent name is required.');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { data } = await apiClient.post('/correspondents', { name: trimmed });
|
const data = await createCorrespondent({ name: trimmed });
|
||||||
await refreshCorrespondents();
|
await refreshCorrespondents();
|
||||||
setStatusMessage('Correspondent created.', 'success');
|
setStatusMessage('Correspondent created.', 'success');
|
||||||
return data;
|
return data;
|
||||||
@@ -91,7 +84,7 @@ const useCorrespondents = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, notifyApiError, refreshCorrespondents, setStatusMessage],
|
[notifyApiError, refreshCorrespondents, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCorrespondentDelete = useCallback(
|
const handleCorrespondentDelete = useCallback(
|
||||||
@@ -112,7 +105,7 @@ const useCorrespondents = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiClient.delete(`/correspondents/${correspondentId}`);
|
await deleteCorrespondent(correspondentId);
|
||||||
await refreshCorrespondents();
|
await refreshCorrespondents();
|
||||||
|
|
||||||
mapDocumentCaches?.(stripFromDoc);
|
mapDocumentCaches?.(stripFromDoc);
|
||||||
@@ -125,7 +118,7 @@ const useCorrespondents = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, mapDocumentCaches, notifyApiError, refreshCorrespondents, setStatusMessage],
|
[mapDocumentCaches, notifyApiError, refreshCorrespondents, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ import { useCallback, useMemo } from 'react';
|
|||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
|
|
||||||
|
|
||||||
type ApiClient = {
|
import { addDocumentCorrespondent, removeDocumentCorrespondent } from '../../lib/apiClient';
|
||||||
post: (path: string, body?: unknown) => Promise<{ data: unknown }>;
|
|
||||||
delete: (path: string) => Promise<{ data: unknown }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface CorrespondentOption {
|
interface CorrespondentOption {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -14,7 +11,6 @@ interface CorrespondentOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface UseDocumentCorrespondentActionsArgs {
|
interface UseDocumentCorrespondentActionsArgs {
|
||||||
apiClient: ApiClient;
|
|
||||||
correspondents: CorrespondentOption[];
|
correspondents: CorrespondentOption[];
|
||||||
handleCorrespondentCreate: (payload: { name: string }) => Promise<CorrespondentOption | null>;
|
handleCorrespondentCreate: (payload: { name: string }) => Promise<CorrespondentOption | null>;
|
||||||
notifyApiError: (error: unknown, fallback: string) => void;
|
notifyApiError: (error: unknown, fallback: string) => void;
|
||||||
@@ -26,7 +22,6 @@ interface UseDocumentCorrespondentActionsArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useDocumentCorrespondentActions = ({
|
const useDocumentCorrespondentActions = ({
|
||||||
apiClient,
|
|
||||||
correspondents,
|
correspondents,
|
||||||
handleCorrespondentCreate,
|
handleCorrespondentCreate,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
@@ -56,10 +51,7 @@ const useDocumentCorrespondentActions = ({
|
|||||||
throw new Error('Missing document or correspondent.');
|
throw new Error('Missing document or correspondent.');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await apiClient.post(`/documents/${documentId}/correspondents`, {
|
await addDocumentCorrespondent(documentId, correspondentId);
|
||||||
assignments: [{ correspondent_id: correspondentId }],
|
|
||||||
replace: false,
|
|
||||||
});
|
|
||||||
if (updateDocumentCaches) {
|
if (updateDocumentCaches) {
|
||||||
const resolved = correspondent
|
const resolved = correspondent
|
||||||
|| correspondents.find((entry) => entry?.id === correspondentId)
|
|| correspondents.find((entry) => entry?.id === correspondentId)
|
||||||
@@ -88,7 +80,7 @@ const useDocumentCorrespondentActions = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, correspondents, notifyApiError, setStatusMessage, updateDocumentCaches],
|
[correspondents, notifyApiError, setStatusMessage, updateDocumentCaches],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCorrespondentRemove = useCallback(
|
const handleCorrespondentRemove = useCallback(
|
||||||
@@ -100,7 +92,7 @@ const useDocumentCorrespondentActions = ({
|
|||||||
throw new Error('Missing document or correspondent.');
|
throw new Error('Missing document or correspondent.');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await apiClient.delete(`/documents/${documentId}/correspondents/${correspondentId}`);
|
await removeDocumentCorrespondent(documentId, correspondentId);
|
||||||
if (updateDocumentCaches) {
|
if (updateDocumentCaches) {
|
||||||
updateDocumentCaches(documentId, (doc) => {
|
updateDocumentCaches(documentId, (doc) => {
|
||||||
if (!doc || !Array.isArray(doc.correspondents)) {
|
if (!doc || !Array.isArray(doc.correspondents)) {
|
||||||
@@ -120,7 +112,7 @@ const useDocumentCorrespondentActions = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, notifyApiError, setStatusMessage, updateDocumentCaches],
|
[notifyApiError, setStatusMessage, updateDocumentCaches],
|
||||||
);
|
);
|
||||||
|
|
||||||
const normalizeOption = (
|
const normalizeOption = (
|
||||||
|
|||||||
@@ -8,16 +8,13 @@ interface TagRecord {
|
|||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiClient {
|
import { createTag, bulkTagDocuments, bulkReanalyzeDocuments } from '../../lib/apiClient';
|
||||||
post: <T = { data: unknown }>(path: string, payload: unknown) => Promise<{ data: T } | T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TagManager {
|
interface TagManager {
|
||||||
buildPayload: (input: { label: string }) => Record<string, unknown>;
|
buildPayload: (input: { label: string }) => Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseDocumentTaggingArgs {
|
interface UseDocumentTaggingArgs {
|
||||||
apiClient: ApiClient;
|
|
||||||
tags: TagRecord[];
|
tags: TagRecord[];
|
||||||
tagManager: TagManager;
|
tagManager: TagManager;
|
||||||
refreshTags: () => Promise<void> | void;
|
refreshTags: () => Promise<void> | void;
|
||||||
@@ -42,7 +39,6 @@ interface BulkTagOperationResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useDocumentTagging = ({
|
const useDocumentTagging = ({
|
||||||
apiClient,
|
|
||||||
tags,
|
tags,
|
||||||
tagManager,
|
tagManager,
|
||||||
refreshTags,
|
refreshTags,
|
||||||
@@ -85,9 +81,9 @@ const useDocumentTagging = ({
|
|||||||
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) {
|
||||||
const payload = tagManager.buildPayload({ label });
|
const payload = tagManager.buildPayload({ label }) as { label: string; color?: string | null };
|
||||||
const response = await apiClient.post<{ id?: Identifier; label: string }>('/tags', payload);
|
const response = await createTag(payload);
|
||||||
tag = 'data' in response ? response.data : response;
|
tag = response as TagRecord;
|
||||||
await refreshTags();
|
await refreshTags();
|
||||||
}
|
}
|
||||||
createdIds.push(tag.id);
|
createdIds.push(tag.id);
|
||||||
@@ -137,7 +133,7 @@ const useDocumentTagging = ({
|
|||||||
return { ok: false, reason: 'no-tags' };
|
return { ok: false, reason: 'no-tags' };
|
||||||
}
|
}
|
||||||
|
|
||||||
await apiClient.post('/documents/bulk/tags', {
|
await bulkTagDocuments({
|
||||||
document_ids: targetDocumentIds,
|
document_ids: targetDocumentIds,
|
||||||
tag_ids: tagIds,
|
tag_ids: tagIds,
|
||||||
action,
|
action,
|
||||||
@@ -180,7 +176,6 @@ const useDocumentTagging = ({
|
|||||||
refreshTags,
|
refreshTags,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
tagManager,
|
tagManager,
|
||||||
apiClient,
|
|
||||||
updateDocumentCaches,
|
updateDocumentCaches,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -259,14 +254,11 @@ const useDocumentTagging = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.post<{ queued?: number }>(
|
const response = await bulkReanalyzeDocuments({
|
||||||
'/documents/bulk/reanalyze',
|
document_ids: targetIds,
|
||||||
{
|
force: true,
|
||||||
document_ids: targetIds,
|
});
|
||||||
force: true,
|
const payload = response;
|
||||||
},
|
|
||||||
);
|
|
||||||
const payload = 'data' in response ? response.data : response;
|
|
||||||
const queued = payload?.queued != null
|
const queued = payload?.queued != null
|
||||||
? Number(payload.queued)
|
? Number(payload.queued)
|
||||||
: targetIds.length;
|
: targetIds.length;
|
||||||
@@ -280,7 +272,7 @@ const useDocumentTagging = ({
|
|||||||
notifyApiError(error, message);
|
notifyApiError(error, message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[resolveTargetDocumentIds, notifyApiError, setStatusMessage, apiClient],
|
[resolveTargetDocumentIds, notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback, useRef, useState } from 'react';
|
|||||||
import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
||||||
import useFileDrop from './useFileDrop';
|
import useFileDrop from './useFileDrop';
|
||||||
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils';
|
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils';
|
||||||
import { fetchDocument } from '../../lib/apiClient';
|
import { fetchDocument, uploadDocument, resolveFolderPath } from '../../lib/apiClient';
|
||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
|
|
||||||
type FolderId = Identifier | 'root' | null;
|
type FolderId = Identifier | 'root' | null;
|
||||||
@@ -26,17 +26,6 @@ type UploadQueueItem = {
|
|||||||
conflictDocumentId: Identifier | null;
|
conflictDocumentId: Identifier | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UploadResponse {
|
|
||||||
reused?: boolean;
|
|
||||||
document?: unknown;
|
|
||||||
folder?: { id?: FolderId };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ApiClient {
|
|
||||||
post<T = UploadResponse>(url: string, payload: unknown): Promise<{ data: T; status?: number }>;
|
|
||||||
get<T = { document?: unknown }>(url: string): Promise<{ data: T }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusLevel = 'success' | 'info' | 'warning' | 'error' | string;
|
type StatusLevel = 'success' | 'info' | 'warning' | 'error' | string;
|
||||||
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
||||||
type SetStatusMessage = (message: string, level?: StatusLevel) => void;
|
type SetStatusMessage = (message: string, level?: StatusLevel) => void;
|
||||||
@@ -59,7 +48,7 @@ interface FileSystemDirectoryReaderLike {
|
|||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileSystemFileEntryLike {
|
interface FileSystemFileEntryLike extends FileSystemEntry {
|
||||||
isFile: true;
|
isFile: true;
|
||||||
isDirectory: false;
|
isDirectory: false;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -69,12 +58,19 @@ interface FileSystemFileEntryLike {
|
|||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileSystemDirectoryEntryLike {
|
interface FileSystemDirectoryEntryLike extends FileSystemEntry {
|
||||||
isFile: false;
|
isFile: false;
|
||||||
isDirectory: true;
|
isDirectory: true;
|
||||||
name: string;
|
name: string;
|
||||||
createReader: () => FileSystemDirectoryReaderLike;
|
createReader: () => FileSystemDirectoryReaderLike;
|
||||||
}
|
}
|
||||||
|
const isFileEntry = (entry: FileSystemEntryLike): entry is FileSystemFileEntryLike => {
|
||||||
|
return entry.isFile && !entry.isDirectory;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDirectoryEntry = (entry: FileSystemEntryLike): entry is FileSystemDirectoryEntryLike => {
|
||||||
|
return entry.isDirectory && !entry.isFile && 'createReader' in entry;
|
||||||
|
};
|
||||||
|
|
||||||
const mapFilesToEntries = (filesInput?: FileList | File[] | null): FileEntry[] => {
|
const mapFilesToEntries = (filesInput?: FileList | File[] | null): FileEntry[] => {
|
||||||
if (!filesInput) {
|
if (!filesInput) {
|
||||||
@@ -96,7 +92,6 @@ const mapFilesToEntries = (filesInput?: FileList | File[] | null): FileEntry[] =
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface UseDocumentUploadsArgs {
|
interface UseDocumentUploadsArgs {
|
||||||
apiClient: ApiClient;
|
|
||||||
token?: string | null;
|
token?: string | null;
|
||||||
selectedFolder?: FolderId;
|
selectedFolder?: FolderId;
|
||||||
currentFolderName?: string | null;
|
currentFolderName?: string | null;
|
||||||
@@ -126,7 +121,6 @@ interface UseDocumentUploadsResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useDocumentUploads = ({
|
const useDocumentUploads = ({
|
||||||
apiClient,
|
|
||||||
token,
|
token,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
currentFolderName,
|
currentFolderName,
|
||||||
@@ -158,11 +152,10 @@ const useDocumentUploads = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, status } = await apiClient.post('/documents', formData);
|
const { reused, document, status } = await uploadDocument(formData);
|
||||||
const duplicate = data?.reused || status === 200;
|
const duplicate = reused || status === 200;
|
||||||
const document = data?.document ?? data ?? null;
|
|
||||||
return {
|
return {
|
||||||
document,
|
document: document ?? null,
|
||||||
duplicate,
|
duplicate,
|
||||||
statusCode: status ?? (duplicate ? 200 : 201),
|
statusCode: status ?? (duplicate ? 200 : 201),
|
||||||
conflictDocumentId: null,
|
conflictDocumentId: null,
|
||||||
@@ -192,7 +185,7 @@ const useDocumentUploads = ({
|
|||||||
throw wrapped;
|
throw wrapped;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, notifyApiError, setStatusMessage],
|
[notifyApiError, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const appendQueueItems = useCallback((entries: FileEntry[], targetFolderId?: FolderId) => {
|
const appendQueueItems = useCallback((entries: FileEntry[], targetFolderId?: FolderId) => {
|
||||||
@@ -244,15 +237,12 @@ const useDocumentUploads = ({
|
|||||||
segments: trimmedSegments,
|
segments: trimmedSegments,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data } = await apiClient.post<{ folder?: { id?: FolderId | null } }>(
|
const { folder } = await resolveFolderPath(payload);
|
||||||
'/folders/path',
|
const resolvedId = (folder?.id ?? null) as FolderId;
|
||||||
payload,
|
|
||||||
);
|
|
||||||
const resolvedId = (data?.folder?.id ?? null) as FolderId;
|
|
||||||
cache.set(cacheKey, resolvedId);
|
cache.set(cacheKey, resolvedId);
|
||||||
return resolvedId;
|
return resolvedId;
|
||||||
},
|
},
|
||||||
[apiClient],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const extractFilesFromDataTransfer = useCallback(async (dataTransfer: DataTransfer) => {
|
const extractFilesFromDataTransfer = useCallback(async (dataTransfer: DataTransfer) => {
|
||||||
@@ -294,10 +284,10 @@ const useDocumentUploads = ({
|
|||||||
|
|
||||||
const walkEntry = async (entry: FileSystemEntryLike | null, ancestors: string[] = []) => {
|
const walkEntry = async (entry: FileSystemEntryLike | null, ancestors: string[] = []) => {
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
if (entry.isFile) {
|
if (isFileEntry(entry)) {
|
||||||
const file = await new Promise<File>((resolve, reject) => {
|
const file = await new Promise<File>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
(entry as unknown as FileSystemFileEntryLike).file(resolve, reject);
|
entry.file(resolve, reject);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[Uploads] entry.file failed', error);
|
console.warn('[Uploads] entry.file failed', error);
|
||||||
reject(error as Error);
|
reject(error as Error);
|
||||||
@@ -306,9 +296,9 @@ const useDocumentUploads = ({
|
|||||||
pushFile(file, ancestors);
|
pushFile(file, ancestors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (entry.isDirectory) {
|
if (isDirectoryEntry(entry)) {
|
||||||
const nextAncestors = entry.name ? [...ancestors, entry.name] : [...ancestors];
|
const nextAncestors = entry.name ? [...ancestors, entry.name] : [...ancestors];
|
||||||
const reader = (entry as unknown as FileSystemDirectoryEntryLike).createReader();
|
const reader = entry.createReader();
|
||||||
const entries = await readAllEntries(reader);
|
const entries = await readAllEntries(reader);
|
||||||
for (const child of entries) {
|
for (const child of entries) {
|
||||||
await walkEntry(child, nextAncestors);
|
await walkEntry(child, nextAncestors);
|
||||||
|
|||||||
@@ -338,7 +338,6 @@ const useDocumentsWorkspace = ({
|
|||||||
isInvalidFolderDrop,
|
isInvalidFolderDrop,
|
||||||
} = useFolderTree({
|
} = useFolderTree({
|
||||||
initialSelectedFolder: routeFolderId || 'root',
|
initialSelectedFolder: routeFolderId || 'root',
|
||||||
apiClient,
|
|
||||||
tenantIdRef,
|
tenantIdRef,
|
||||||
documentsSortFieldRef: activeSortFieldRef,
|
documentsSortFieldRef: activeSortFieldRef,
|
||||||
documentsSortDirectionRef: activeSortDirectionRef,
|
documentsSortDirectionRef: activeSortDirectionRef,
|
||||||
@@ -506,7 +505,6 @@ const useDocumentsWorkspace = ({
|
|||||||
registerPasskey,
|
registerPasskey,
|
||||||
revokePasskey,
|
revokePasskey,
|
||||||
} = useWorkspaceTaxonomies({
|
} = useWorkspaceTaxonomies({
|
||||||
apiClient,
|
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
tagManager,
|
tagManager,
|
||||||
@@ -544,7 +542,6 @@ const useDocumentsWorkspace = ({
|
|||||||
handleBulkTagRemoveFromDetail,
|
handleBulkTagRemoveFromDetail,
|
||||||
handleBulkSelectionReanalyze,
|
handleBulkSelectionReanalyze,
|
||||||
} = useDocumentTagging({
|
} = useDocumentTagging({
|
||||||
apiClient,
|
|
||||||
tags,
|
tags,
|
||||||
tagManager,
|
tagManager,
|
||||||
refreshTags,
|
refreshTags,
|
||||||
@@ -562,7 +559,6 @@ const useDocumentsWorkspace = ({
|
|||||||
clearUploadQueue,
|
clearUploadQueue,
|
||||||
resetUploadsState,
|
resetUploadsState,
|
||||||
} = useDocumentUploads({
|
} = useDocumentUploads({
|
||||||
apiClient,
|
|
||||||
token,
|
token,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
currentFolderName,
|
currentFolderName,
|
||||||
@@ -1124,7 +1120,6 @@ const useDocumentsWorkspace = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { handleTenantSelect } = useTenantManager({
|
const { handleTenantSelect } = useTenantManager({
|
||||||
apiClient,
|
|
||||||
appDispatch,
|
appDispatch,
|
||||||
currentTenantId,
|
currentTenantId,
|
||||||
resetWorkspaceState,
|
resetWorkspaceState,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { getFolderTree, listFolderContents } from '../../lib/apiClient';
|
||||||
import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
||||||
import { createRootNode, DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
|
import { createRootNode, DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
|
||||||
import {
|
import {
|
||||||
@@ -43,10 +44,6 @@ interface FolderTreeNode extends FolderSummary {
|
|||||||
hasChildren?: boolean;
|
hasChildren?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiClient {
|
|
||||||
get<T = FolderContentsEntry>(path: string, config?: { params?: Record<string, unknown> }): Promise<{ data: T }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectionHelpers {
|
interface SelectionHelpers {
|
||||||
focusedDocumentId: Identifier | null;
|
focusedDocumentId: Identifier | null;
|
||||||
setFocusedDocumentId: Dispatch<SetStateAction<Identifier | null>>;
|
setFocusedDocumentId: Dispatch<SetStateAction<Identifier | null>>;
|
||||||
@@ -58,7 +55,6 @@ interface SelectionHelpers {
|
|||||||
|
|
||||||
interface UseFolderTreeOptions {
|
interface UseFolderTreeOptions {
|
||||||
initialSelectedFolder?: FolderId;
|
initialSelectedFolder?: FolderId;
|
||||||
apiClient: ApiClient;
|
|
||||||
tenantIdRef: MutableRefObject<Identifier | null>;
|
tenantIdRef: MutableRefObject<Identifier | null>;
|
||||||
documentsSortFieldRef: MutableRefObject<string>;
|
documentsSortFieldRef: MutableRefObject<string>;
|
||||||
documentsSortDirectionRef: MutableRefObject<string>;
|
documentsSortDirectionRef: MutableRefObject<string>;
|
||||||
@@ -75,7 +71,6 @@ interface FolderOption {
|
|||||||
|
|
||||||
const useFolderTree = ({
|
const useFolderTree = ({
|
||||||
initialSelectedFolder = 'root',
|
initialSelectedFolder = 'root',
|
||||||
apiClient,
|
|
||||||
tenantIdRef,
|
tenantIdRef,
|
||||||
documentsSortFieldRef,
|
documentsSortFieldRef,
|
||||||
documentsSortDirectionRef,
|
documentsSortDirectionRef,
|
||||||
@@ -89,6 +84,52 @@ const useFolderTree = ({
|
|||||||
return new Map([[rootNode.id, rootNode]]);
|
return new Map([[rootNode.id, rootNode]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTree = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getFolderTree();
|
||||||
|
setFolderNodes((prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
const rootChildren: FolderId[] = [];
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
const id = item.id as FolderId;
|
||||||
|
const parentId = (item.parent_id || 'root') as FolderId;
|
||||||
|
const children = (item.children || []).map((c) => c as FolderId);
|
||||||
|
|
||||||
|
next.set(id, {
|
||||||
|
id,
|
||||||
|
name: item.name,
|
||||||
|
parentId,
|
||||||
|
children,
|
||||||
|
expanded: false,
|
||||||
|
loaded: true,
|
||||||
|
hasChildren: children.length > 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parentId === 'root') {
|
||||||
|
rootChildren.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const root = next.get('root');
|
||||||
|
if (root) {
|
||||||
|
next.set('root', {
|
||||||
|
...(root as FolderTreeNode),
|
||||||
|
children: rootChildren,
|
||||||
|
hasChildren: rootChildren.length > 0,
|
||||||
|
loaded: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch folder tree', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchTree();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [selectedFolder, setSelectedFolder] = useState<FolderId>(initialSelectedFolder || 'root');
|
const [selectedFolder, setSelectedFolder] = useState<FolderId>(initialSelectedFolder || 'root');
|
||||||
const [currentFolder, setCurrentFolder] = useState<FolderSummary | null>(null);
|
const [currentFolder, setCurrentFolder] = useState<FolderSummary | null>(null);
|
||||||
const [currentSubfolders, setCurrentSubfolders] = useState<FolderSummary[]>([]);
|
const [currentSubfolders, setCurrentSubfolders] = useState<FolderSummary[]>([]);
|
||||||
@@ -245,8 +286,7 @@ const useFolderTree = ({
|
|||||||
params.sort = sortField;
|
params.sort = sortField;
|
||||||
params.dir = sortDirection;
|
params.dir = sortDirection;
|
||||||
}
|
}
|
||||||
const requestConfig = Object.keys(params).length ? { params } : {};
|
const data = await listFolderContents<FolderContentsEntry>(path, params);
|
||||||
const { data } = await apiClient.get<FolderContentsEntry>(`/folders/${path}/contents`, requestConfig);
|
|
||||||
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)
|
||||||
@@ -359,7 +399,6 @@ const useFolderTree = ({
|
|||||||
return enriched;
|
return enriched;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
apiClient,
|
|
||||||
documentsSortDirectionRef,
|
documentsSortDirectionRef,
|
||||||
documentsSortFieldRef,
|
documentsSortFieldRef,
|
||||||
tenantIdRef,
|
tenantIdRef,
|
||||||
|
|||||||
@@ -2,19 +2,14 @@ import { MutableRefObject, useCallback, useState } from 'react';
|
|||||||
import type { TagId, TenantId } from '../../types/identifiers';
|
import type { TagId, TenantId } from '../../types/identifiers';
|
||||||
import type { Tag } from '../../types/documents';
|
import type { Tag } from '../../types/documents';
|
||||||
|
|
||||||
type ApiClient = {
|
import { listTags, updateTag, createTag, deleteTag } from '../../lib/apiClient';
|
||||||
get: (path: string) => Promise<{ data: unknown }>
|
|
||||||
post: (path: string, body: unknown) => Promise<{ data: unknown }>
|
|
||||||
patch: (path: string, body: unknown) => Promise<{ data: unknown }>
|
|
||||||
delete: (path: string) => Promise<{ data: unknown }>
|
|
||||||
};
|
|
||||||
|
|
||||||
interface TagManagerInterface {
|
interface TagManagerInterface {
|
||||||
buildPayload: (input: { label?: string; color?: string | null }) => { label: string; color: string | null };
|
buildPayload: (input: { label?: string; color?: string | null }) => { label: string; color: string | null };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseTagsOptions {
|
interface UseTagsOptions {
|
||||||
apiClient: ApiClient;
|
// apiClient removed
|
||||||
notifyApiError: (error: unknown, fallback: string) => void;
|
notifyApiError: (error: unknown, fallback: string) => void;
|
||||||
setStatusMessage: (message: string, variant?: string) => void;
|
setStatusMessage: (message: string, variant?: string) => void;
|
||||||
tagManager: TagManagerInterface;
|
tagManager: TagManagerInterface;
|
||||||
@@ -24,7 +19,7 @@ interface UseTagsOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useTags = ({
|
const useTags = ({
|
||||||
apiClient,
|
// apiClient removed
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
tagManager,
|
tagManager,
|
||||||
@@ -37,7 +32,7 @@ const useTags = ({
|
|||||||
const refreshTags = useCallback(async () => {
|
const refreshTags = useCallback(async () => {
|
||||||
const requestTenantId = tenantIdRef.current;
|
const requestTenantId = tenantIdRef.current;
|
||||||
try {
|
try {
|
||||||
const { data } = await apiClient.get('/tags');
|
const data = await listTags();
|
||||||
if (tenantIdRef.current !== requestTenantId) {
|
if (tenantIdRef.current !== requestTenantId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -48,7 +43,7 @@ const useTags = ({
|
|||||||
}
|
}
|
||||||
notifyApiError(error, 'Unable to load tags.');
|
notifyApiError(error, 'Unable to load tags.');
|
||||||
}
|
}
|
||||||
}, [apiClient, notifyApiError, tenantIdRef]);
|
}, [notifyApiError, tenantIdRef]);
|
||||||
|
|
||||||
const handleTagUpdate = useCallback(
|
const handleTagUpdate = useCallback(
|
||||||
async (tagId: TagId, changes: { label?: string; color?: string | null }) => {
|
async (tagId: TagId, changes: { label?: string; color?: string | null }) => {
|
||||||
@@ -69,7 +64,7 @@ const useTags = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiClient.patch(`/tags/${tagId}`, payload);
|
await updateTag(tagId, payload);
|
||||||
await refreshTags();
|
await refreshTags();
|
||||||
setStatusMessage('Tag updated.', 'success');
|
setStatusMessage('Tag updated.', 'success');
|
||||||
return true;
|
return true;
|
||||||
@@ -79,14 +74,14 @@ const useTags = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, notifyApiError, refreshTags, setStatusMessage],
|
[notifyApiError, refreshTags, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTagCreate = useCallback(
|
const handleTagCreate = useCallback(
|
||||||
async ({ label, color }: { label?: string; color?: string | null } = {}) => {
|
async ({ label, color }: { label?: string; color?: string | null } = {}) => {
|
||||||
const payload = tagManager.buildPayload({ label, color });
|
const payload = tagManager.buildPayload({ label, color });
|
||||||
try {
|
try {
|
||||||
await apiClient.post('/tags', payload);
|
await createTag(payload);
|
||||||
await refreshTags();
|
await refreshTags();
|
||||||
setStatusMessage('Tag created.', 'success');
|
setStatusMessage('Tag created.', 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -95,7 +90,7 @@ const useTags = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, notifyApiError, refreshTags, setStatusMessage, tagManager],
|
[notifyApiError, refreshTags, setStatusMessage, tagManager],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTagDelete = useCallback(
|
const handleTagDelete = useCallback(
|
||||||
@@ -105,7 +100,7 @@ const useTags = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiClient.delete(`/tags/${tagId}`);
|
await deleteTag(tagId);
|
||||||
setActiveTagFilters((prev) => prev.filter((id) => id !== tagId));
|
setActiveTagFilters((prev) => prev.filter((id) => id !== tagId));
|
||||||
|
|
||||||
const stripTagFromDoc = (doc: any) => {
|
const stripTagFromDoc = (doc: any) => {
|
||||||
@@ -130,7 +125,7 @@ const useTags = ({
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[apiClient, mapDocumentCaches, notifyApiError, refreshTags, setActiveTagFilters, setStatusMessage],
|
[mapDocumentCaches, notifyApiError, refreshTags, setActiveTagFilters, setStatusMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import { MutableRefObject, useCallback } from 'react';
|
|||||||
import type { NavigateFunction } from 'react-router-dom';
|
import type { NavigateFunction } from 'react-router-dom';
|
||||||
import type { FolderId, TenantId } from '../../types/identifiers';
|
import type { FolderId, TenantId } from '../../types/identifiers';
|
||||||
|
|
||||||
interface ApiClient {
|
import { api, listTenants, switchTenant } from '../../lib/apiClient';
|
||||||
get: (path: string) => Promise<{ data: unknown }>;
|
|
||||||
post: (path: string, body?: unknown) => Promise<{ data: any }>;
|
|
||||||
defaults: { headers: { common: Record<string, unknown> } };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TenantOption {
|
interface TenantOption {
|
||||||
id?: TenantId;
|
id?: TenantId;
|
||||||
@@ -14,7 +10,6 @@ interface TenantOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface UseTenantManagerOptions {
|
interface UseTenantManagerOptions {
|
||||||
apiClient: ApiClient;
|
|
||||||
appDispatch: (action: any) => void;
|
appDispatch: (action: any) => void;
|
||||||
currentTenantId: TenantId | null;
|
currentTenantId: TenantId | null;
|
||||||
resetWorkspaceState: () => void;
|
resetWorkspaceState: () => void;
|
||||||
@@ -30,7 +25,6 @@ interface UseTenantManagerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useTenantManager = ({
|
const useTenantManager = ({
|
||||||
apiClient,
|
|
||||||
appDispatch,
|
appDispatch,
|
||||||
currentTenantId,
|
currentTenantId,
|
||||||
resetWorkspaceState,
|
resetWorkspaceState,
|
||||||
@@ -53,17 +47,15 @@ const useTenantManager = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (refreshOnly) {
|
if (refreshOnly) {
|
||||||
const { data } = await apiClient.get('/tenants');
|
const data = await listTenants();
|
||||||
appDispatch({
|
appDispatch({
|
||||||
type: 'SET_TENANTS',
|
type: 'SET_TENANTS',
|
||||||
tenants: Array.isArray(data) ? data : [],
|
tenants: data,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await apiClient.post('/auth/select-tenant', {
|
const data = await switchTenant(requestedTenantId);
|
||||||
tenant_id: requestedTenantId,
|
|
||||||
});
|
|
||||||
if (!data?.access_token) {
|
if (!data?.access_token) {
|
||||||
throw new Error('Missing access token in tenant switch response.');
|
throw new Error('Missing access token in tenant switch response.');
|
||||||
}
|
}
|
||||||
@@ -77,7 +69,7 @@ const useTenantManager = ({
|
|||||||
tenant: data.tenant || null,
|
tenant: data.tenant || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
apiClient.defaults.headers.common.Authorization = `Bearer ${data.access_token}`;
|
api.defaults.headers.common.Authorization = `Bearer ${data.access_token}`;
|
||||||
if (tokenRef) {
|
if (tokenRef) {
|
||||||
tokenRef.current = data.access_token;
|
tokenRef.current = data.access_token;
|
||||||
}
|
}
|
||||||
@@ -102,7 +94,6 @@ const useTenantManager = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
apiClient,
|
|
||||||
appDispatch,
|
appDispatch,
|
||||||
currentTenantId,
|
currentTenantId,
|
||||||
handleDocumentsViewModeChange,
|
handleDocumentsViewModeChange,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import useTags from './useTags';
|
|||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
|
|
||||||
interface UseWorkspaceTaxonomiesArgs {
|
interface UseWorkspaceTaxonomiesArgs {
|
||||||
apiClient: any;
|
|
||||||
notifyApiError: (error: unknown, fallbackMessage?: string, variant?: string) => void;
|
notifyApiError: (error: unknown, fallbackMessage?: string, variant?: string) => void;
|
||||||
setStatusMessage: (message: string, variant?: string) => void;
|
setStatusMessage: (message: string, variant?: string) => void;
|
||||||
tagManager: TagManager;
|
tagManager: TagManager;
|
||||||
@@ -21,7 +20,6 @@ interface UseWorkspaceTaxonomiesArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useWorkspaceTaxonomies = ({
|
const useWorkspaceTaxonomies = ({
|
||||||
apiClient,
|
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
tagManager,
|
tagManager,
|
||||||
@@ -40,7 +38,6 @@ const useWorkspaceTaxonomies = ({
|
|||||||
handleTagDelete,
|
handleTagDelete,
|
||||||
setTags,
|
setTags,
|
||||||
} = useTags({
|
} = useTags({
|
||||||
apiClient,
|
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
tagManager,
|
tagManager,
|
||||||
@@ -71,7 +68,6 @@ const useWorkspaceTaxonomies = ({
|
|||||||
handleCorrespondentDelete,
|
handleCorrespondentDelete,
|
||||||
setCorrespondents,
|
setCorrespondents,
|
||||||
} = useCorrespondents({
|
} = useCorrespondents({
|
||||||
apiClient,
|
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
setStatusMessage,
|
setStatusMessage,
|
||||||
tenantIdRef,
|
tenantIdRef,
|
||||||
@@ -84,7 +80,6 @@ const useWorkspaceTaxonomies = ({
|
|||||||
handleCorrespondentRemove,
|
handleCorrespondentRemove,
|
||||||
handleCorrespondentAdd,
|
handleCorrespondentAdd,
|
||||||
} = useDocumentCorrespondentActions({
|
} = useDocumentCorrespondentActions({
|
||||||
apiClient,
|
|
||||||
correspondents,
|
correspondents,
|
||||||
handleCorrespondentCreate,
|
handleCorrespondentCreate,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import api from './api';
|
import api from './api';
|
||||||
|
export { api };
|
||||||
import type {
|
import type {
|
||||||
ApiTokenRecord,
|
ApiTokenRecord,
|
||||||
AssetResponse,
|
AssetResponse,
|
||||||
@@ -6,11 +7,12 @@ import type {
|
|||||||
CapabilitySetResponse,
|
CapabilitySetResponse,
|
||||||
DownloadLink,
|
DownloadLink,
|
||||||
DocumentResponse,
|
DocumentResponse,
|
||||||
FolderTreeNode,
|
|
||||||
Identifier,
|
Identifier,
|
||||||
PasskeySummary,
|
PasskeySummary,
|
||||||
TenantSnippet,
|
TenantSnippet,
|
||||||
TagResponse,
|
TagResponse,
|
||||||
|
CorrespondentResponse,
|
||||||
|
FolderTreeResponseItem,
|
||||||
} from './apiTypes';
|
} from './apiTypes';
|
||||||
import type { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosRequestConfig } from 'axios';
|
import type { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
@@ -56,7 +58,7 @@ const normalizeDownload = (input?: DownloadLink | null): DownloadLink | null =>
|
|||||||
|
|
||||||
export const fetchDocument = async (id: Identifier): Promise<DocumentResponse> => {
|
export const fetchDocument = async (id: Identifier): Promise<DocumentResponse> => {
|
||||||
const { data } = await api.get<{ document?: DocumentResponse }>(`/documents/${id}`);
|
const { data } = await api.get<{ document?: DocumentResponse }>(`/documents/${id}`);
|
||||||
const doc = data?.document || (data as unknown as DocumentResponse);
|
const doc = data?.document || (data as DocumentResponse);
|
||||||
if (doc?.current_version?.download) {
|
if (doc?.current_version?.download) {
|
||||||
doc.current_version.download = normalizeDownload(doc.current_version.download);
|
doc.current_version.download = normalizeDownload(doc.current_version.download);
|
||||||
}
|
}
|
||||||
@@ -77,8 +79,8 @@ export const listDocuments = async (params: Record<string, unknown> = {}): Promi
|
|||||||
return Array.isArray(data) ? data : [];
|
return Array.isArray(data) ? data : [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFolderTree = async (): Promise<FolderTreeNode[]> => {
|
export const getFolderTree = async (): Promise<FolderTreeResponseItem[]> => {
|
||||||
const { data } = await api.get<FolderTreeNode[]>('/folders/tree');
|
const { data } = await api.get<FolderTreeResponseItem[]>('/folders/tree');
|
||||||
return Array.isArray(data) ? data : [];
|
return Array.isArray(data) ? data : [];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -342,4 +344,102 @@ api.interceptors.response.use(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const uploadDocument = async (
|
||||||
|
formData: FormData,
|
||||||
|
): Promise<{ reused?: boolean; document?: unknown; status?: number }> => {
|
||||||
|
const { data, status } = await api.post<{ reused?: boolean; document?: unknown }>('/documents', formData);
|
||||||
|
return { ...data, status };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resolveFolderPath = async (
|
||||||
|
payload: { parent_id?: Identifier | null; segments: string[] },
|
||||||
|
): Promise<{ folder?: { id?: Identifier | null } }> => {
|
||||||
|
const { data } = await api.post<{ folder?: { id?: Identifier | null } }>('/folders/path', payload);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bulkTagDocuments = async (
|
||||||
|
payload: { document_ids: Identifier[]; tag_ids: Identifier[]; action: 'add' | 'remove' },
|
||||||
|
): Promise<void> => {
|
||||||
|
await api.post('/documents/bulk/tags', payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bulkReanalyzeDocuments = async (
|
||||||
|
payload: { document_ids: Identifier[]; force?: boolean },
|
||||||
|
): Promise<{ queued?: number }> => {
|
||||||
|
const { data } = await api.post<{ queued?: number }>('/documents/bulk/reanalyze', payload);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listTags = async (): Promise<TagResponse[]> => {
|
||||||
|
const { data } = await api.get<TagResponse[]>('/tags');
|
||||||
|
return Array.isArray(data) ? data : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateTag = async (
|
||||||
|
tagId: Identifier,
|
||||||
|
payload: { label?: string; color?: string | null },
|
||||||
|
): Promise<void> => {
|
||||||
|
await api.patch(`/tags/${tagId}`, payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteTag = async (tagId: Identifier): Promise<void> => {
|
||||||
|
await api.delete(`/tags/${tagId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listCorrespondents = async (): Promise<CorrespondentResponse[]> => {
|
||||||
|
const { data } = await api.get<CorrespondentResponse[]>('/correspondents');
|
||||||
|
return Array.isArray(data) ? data : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createCorrespondent = async (payload: { name: string }): Promise<CorrespondentResponse> => {
|
||||||
|
const { data } = await api.post<CorrespondentResponse>('/correspondents', payload);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateCorrespondent = async (
|
||||||
|
correspondentId: Identifier,
|
||||||
|
payload: { name?: string },
|
||||||
|
): Promise<void> => {
|
||||||
|
await api.patch(`/correspondents/${correspondentId}`, payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteCorrespondent = async (correspondentId: Identifier): Promise<void> => {
|
||||||
|
await api.delete(`/correspondents/${correspondentId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addDocumentCorrespondent = async (
|
||||||
|
documentId: Identifier,
|
||||||
|
correspondentId: Identifier,
|
||||||
|
): Promise<void> => {
|
||||||
|
await api.post(`/documents/${documentId}/correspondents`, {
|
||||||
|
assignments: [{ correspondent_id: correspondentId }],
|
||||||
|
replace: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeDocumentCorrespondent = async (
|
||||||
|
documentId: Identifier,
|
||||||
|
correspondentId: Identifier,
|
||||||
|
): Promise<void> => {
|
||||||
|
await api.delete(`/documents/${documentId}/correspondents/${correspondentId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const switchTenant = async (tenantId: Identifier): Promise<{ access_token: string; tenant: any; tenants?: any[] }> => {
|
||||||
|
const { data } = await api.post<{ access_token: string; tenant: any; tenants?: any[] }>('/auth/select-tenant', {
|
||||||
|
tenant_id: tenantId,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listFolderContents = async <T = any>(
|
||||||
|
path: string,
|
||||||
|
params?: Record<string, unknown>,
|
||||||
|
): Promise<T> => {
|
||||||
|
const { data } = await api.get<T>(`/folders/${path}/contents`, { params });
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export type { ApiTokenRecord } from './apiTypes';
|
export type { ApiTokenRecord } from './apiTypes';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface TagResponse {
|
|||||||
color?: string | null;
|
color?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CorrespondentResponse {
|
export interface CorrespondentResponse {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
metadata: Record<string, unknown>;
|
metadata: Record<string, unknown>;
|
||||||
@@ -69,6 +69,10 @@ export interface FolderTreeNode extends FolderInfo {
|
|||||||
children?: FolderTreeNode[];
|
children?: FolderTreeNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FolderTreeResponseItem extends FolderInfo {
|
||||||
|
children?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CapabilitySetResponse {
|
export interface CapabilitySetResponse {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.status-toast-container {
|
.status-toast-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 2rem;
|
top: 3rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 5000000;
|
z-index: 5000000;
|
||||||
|
|||||||
Reference in New Issue
Block a user