Files
papercrate/frontend/src/hooks/documents/useDocumentCorrespondentActions.ts
T
2025-11-14 02:35:17 +01:00

170 lines
5.3 KiB
TypeScript

import { useCallback, useMemo } from 'react';
import { isPlainObject, isStringValue } from '../../utils/typeGuards';
type ApiClient = {
post: (path: string, body?: unknown) => Promise<{ data: unknown }>;
delete: (path: string) => Promise<{ data: unknown }>;
};
interface CorrespondentOption {
id?: string | number;
name?: string;
[key: string]: unknown;
}
interface UseDocumentCorrespondentActionsArgs {
apiClient: ApiClient;
correspondents: CorrespondentOption[];
handleCorrespondentCreate: (payload: { name: string }) => Promise<CorrespondentOption | null | undefined>;
refreshCurrentFolder: () => Promise<void>;
notifyApiError: (error: unknown, fallback: string) => void;
setStatusMessage: (message: string, variant?: string) => void;
}
const useDocumentCorrespondentActions = ({
apiClient,
correspondents,
handleCorrespondentCreate,
refreshCurrentFolder,
notifyApiError,
setStatusMessage,
}: UseDocumentCorrespondentActionsArgs) => {
const correspondentLookupByName = useMemo(() => {
const map = new Map<string, CorrespondentOption>();
correspondents.forEach((correspondent) => {
if (correspondent?.name) {
map.set(correspondent.name.toLowerCase(), correspondent);
}
});
return map;
}, [correspondents]);
const handleDocumentCorrespondentAttach = useCallback(
async (
{ documentId, correspondentId }: { documentId?: string | number | null; correspondentId?: string | number | null },
{ notify = true, refresh = true }: { notify?: boolean; refresh?: boolean } = {},
) => {
if (!documentId || !correspondentId) {
throw new Error('Missing document or correspondent.');
}
try {
await apiClient.post(`/documents/${documentId}/correspondents`, {
assignments: [{ correspondent_id: correspondentId }],
replace: false,
});
if (refresh) {
await refreshCurrentFolder();
}
if (notify) {
setStatusMessage('Correspondent assigned.', 'success');
}
return true;
} catch (error) {
const message = error.response?.data?.error || 'Failed to assign correspondent.';
notifyApiError(error, message);
throw new Error(message);
}
},
[apiClient, refreshCurrentFolder, notifyApiError, setStatusMessage],
);
const handleCorrespondentRemove = useCallback(
async (
{ documentId, correspondentId }: { documentId?: string | number | null; correspondentId?: string | number | null },
{ notify = true, refresh = true }: { notify?: boolean; refresh?: boolean } = {},
) => {
if (!documentId || !correspondentId) {
throw new Error('Missing document or correspondent.');
}
try {
await apiClient.delete(`/documents/${documentId}/correspondents/${correspondentId}`);
if (refresh) {
await refreshCurrentFolder();
}
if (notify) {
setStatusMessage('Correspondent removed.', 'success');
}
return true;
} catch (error) {
const message = error.response?.data?.error || 'Failed to remove correspondent.';
notifyApiError(error, message);
throw new Error(message);
}
},
[apiClient, refreshCurrentFolder, notifyApiError, setStatusMessage],
);
const normalizeOption = (
option: CorrespondentOption | string | null,
): CorrespondentOption | null => {
if (!option) {
return null;
}
if (isPlainObject(option) && 'id' in option) {
return option as CorrespondentOption;
}
if (isStringValue(option)) {
const trimmed = option.trim();
if (trimmed) {
return { id: null, name: trimmed };
}
}
return null;
};
const handleCorrespondentAdd = useCallback(
async ({ document, name, input = null, option = null }: { document?: { id?: string | number }; name?: string; input?: HTMLInputElement | null; option?: CorrespondentOption | string | null }) => {
if (!document?.id) {
throw new Error('Missing document for correspondent assignment.');
}
const trimmed = name?.trim?.() || '';
if (!trimmed) {
setStatusMessage('Correspondent name is required.', 'error');
return;
}
let target = correspondentLookupByName.get(trimmed.toLowerCase()) || normalizeOption(option);
if (!target) {
try {
target = await handleCorrespondentCreate({ name: trimmed });
} catch {
return;
}
}
if (!target?.id) {
setStatusMessage('Unable to resolve correspondent.', 'error');
return;
}
try {
await handleDocumentCorrespondentAttach({
documentId: document.id,
correspondentId: target.id,
});
if (input) {
input.value = '';
}
} catch (error) {
setStatusMessage('Failed to assign correspondent.', 'error');
console.error('[documents] assign correspondent failed', error);
}
},
[
correspondentLookupByName,
handleCorrespondentCreate,
handleDocumentCorrespondentAttach,
setStatusMessage,
],
);
return {
correspondentLookupByName,
handleDocumentCorrespondentAttach,
handleCorrespondentRemove,
handleCorrespondentAdd,
};
};
export default useDocumentCorrespondentActions;