refactor: introduce dedicated identifier types for improved clarity and type safety
This commit is contained in:
@@ -8,7 +8,7 @@ type ApiClient = {
|
||||
};
|
||||
|
||||
interface CorrespondentEntry {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ interface UseCorrespondentsOptions {
|
||||
apiClient: ApiClient;
|
||||
notifyApiError: (error: unknown, fallback: string) => void;
|
||||
setStatusMessage: (message: string, variant?: string) => void;
|
||||
tenantIdRef: MutableRefObject<string | number | null>;
|
||||
tenantIdRef: MutableRefObject<string | null>;
|
||||
mapDocumentCaches?: (mapper: (doc: any) => any) => void;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const useCorrespondents = ({
|
||||
}, [apiClient, notifyApiError, tenantIdRef]);
|
||||
|
||||
const handleCorrespondentUpdate = useCallback(
|
||||
async (correspondentId: string | number, changes: { name?: string }) => {
|
||||
async (correspondentId: string, changes: { name?: string }) => {
|
||||
if (correspondentId == null) {
|
||||
throw new Error('Missing correspondent identifier.');
|
||||
}
|
||||
@@ -100,7 +100,7 @@ const useCorrespondents = ({
|
||||
);
|
||||
|
||||
const handleCorrespondentDelete = useCallback(
|
||||
async (correspondentId: string | number) => {
|
||||
async (correspondentId: string) => {
|
||||
if (correspondentId == null) {
|
||||
throw new Error('Missing correspondent identifier.');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
|
||||
type ApiClient = {
|
||||
@@ -7,13 +8,11 @@ type ApiClient = {
|
||||
};
|
||||
|
||||
interface CorrespondentOption {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
type Identifier = string | number;
|
||||
|
||||
interface UseDocumentCorrespondentActionsArgs {
|
||||
apiClient: ApiClient;
|
||||
correspondents: CorrespondentOption[];
|
||||
@@ -135,7 +134,7 @@ const useDocumentCorrespondentActions = ({
|
||||
};
|
||||
|
||||
const handleCorrespondentAdd = useCallback(
|
||||
async ({ document, name, input = null, option = null }: { document?: { id?: string | number }; name?: string; input?: HTMLInputElement | null; option?: CorrespondentOption | string | null }) => {
|
||||
async ({ document, name, input = null, option = null }: { document?: { id?: string }; name?: string; input?: HTMLInputElement | null; option?: CorrespondentOption | string | null }) => {
|
||||
if (!document?.id) {
|
||||
throw new Error('Missing document for correspondent assignment.');
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import type { DragEvent } from 'react';
|
||||
import { createDocumentEntryKey, createFolderEntryKey } from '../../app/entryKey';
|
||||
import type { FolderId, Identifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderIdentifier = string | 'root';
|
||||
type FolderIdentifier = FolderId | 'root';
|
||||
type FolderInput = FolderIdentifier | number;
|
||||
|
||||
interface DocumentLike {
|
||||
@@ -14,7 +14,7 @@ interface DocumentLike {
|
||||
|
||||
type ApplySelectionFn = (
|
||||
keys: string[],
|
||||
options?: { anchor?: string | null; interactedKeys?: string[] },
|
||||
options?: { anchor: string | null; interactedKeys?: string[] },
|
||||
) => void;
|
||||
|
||||
type HandleEntrySelectionFn = (
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
trashDocument,
|
||||
updateDocument,
|
||||
} from '../../lib/apiClient';
|
||||
import type { DocumentId, FolderId as FolderIdentifier } from '../../types/identifiers';
|
||||
|
||||
type DocumentId = string | number;
|
||||
type FolderId = DocumentId | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
type NullableFolderId = FolderId | null;
|
||||
|
||||
type StatusLevel = 'success' | 'error' | 'info' | string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface TagRecord {
|
||||
id?: Identifier;
|
||||
@@ -79,59 +79,59 @@ const useDocumentTagging = ({
|
||||
}
|
||||
|
||||
try {
|
||||
if (action === 'add') {
|
||||
const createdIds: Identifier[] = [];
|
||||
const createdTags: TagRecord[] = [];
|
||||
for (const label of normalized) {
|
||||
let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
|
||||
if (!tag) {
|
||||
const payload = tagManager.buildPayload({ label });
|
||||
const response = await apiClient.post<{ id?: Identifier; label: string }>('/tags', payload);
|
||||
tag = 'data' in response ? response.data : response;
|
||||
await refreshTags();
|
||||
if (action === 'add') {
|
||||
const createdIds: Identifier[] = [];
|
||||
const createdTags: TagRecord[] = [];
|
||||
for (const label of normalized) {
|
||||
let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
|
||||
if (!tag) {
|
||||
const payload = tagManager.buildPayload({ label });
|
||||
const response = await apiClient.post<{ id?: Identifier; label: string }>('/tags', payload);
|
||||
tag = 'data' in response ? response.data : response;
|
||||
await refreshTags();
|
||||
}
|
||||
createdIds.push(tag.id);
|
||||
createdTags.push(tag);
|
||||
}
|
||||
createdIds.push(tag.id);
|
||||
createdTags.push(tag);
|
||||
}
|
||||
tagIds = Array.from(new Set(createdIds));
|
||||
tagIds = Array.from(new Set(createdIds));
|
||||
|
||||
if (updateDocumentCaches) {
|
||||
const tagById = new Map<Identifier, TagRecord>();
|
||||
tags.forEach((tag) => {
|
||||
if (tag?.id != null) {
|
||||
tagById.set(tag.id, tag);
|
||||
}
|
||||
});
|
||||
createdTags.forEach((tag) => {
|
||||
if (tag?.id != null) {
|
||||
tagById.set(tag.id, tag);
|
||||
}
|
||||
});
|
||||
targetDocumentIds.forEach((docId) => {
|
||||
tagIds.forEach((tagId) => {
|
||||
const cachedTag = tagById.get(tagId);
|
||||
if (!cachedTag) {
|
||||
return;
|
||||
if (updateDocumentCaches) {
|
||||
const tagById = new Map<Identifier, TagRecord>();
|
||||
tags.forEach((tag) => {
|
||||
if (tag?.id != null) {
|
||||
tagById.set(tag.id, tag);
|
||||
}
|
||||
updateDocumentCaches(docId, (doc) => {
|
||||
if (!doc) {
|
||||
return doc;
|
||||
});
|
||||
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;
|
||||
}
|
||||
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 }],
|
||||
};
|
||||
updateDocumentCaches(docId, (doc) => {
|
||||
if (!doc) {
|
||||
return doc;
|
||||
}
|
||||
const currentTags = Array.isArray((doc as any).tags) ? (doc as any).tags : [];
|
||||
if (currentTags.some((entry: any) => entry?.id === tagId)) {
|
||||
return doc;
|
||||
}
|
||||
return {
|
||||
...(doc as any),
|
||||
tags: [...currentTags, { ...cachedTag }],
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagIds = Array.from(new Set(tagIds));
|
||||
tagIds = Array.from(new Set(tagIds));
|
||||
|
||||
if (!tagIds.length) {
|
||||
return { ok: false, reason: 'no-tags' };
|
||||
@@ -205,8 +205,7 @@ const useDocumentTagging = ({
|
||||
if (result?.ok) {
|
||||
const { tagCount, docsCount } = result;
|
||||
setStatusMessage(
|
||||
`Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${
|
||||
docsCount === 1 ? '' : 's'
|
||||
`Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${docsCount === 1 ? '' : 's'
|
||||
}.`,
|
||||
'success',
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
||||
import useFileDrop from './useFileDrop';
|
||||
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils';
|
||||
import { fetchDocument } from '../../lib/apiClient';
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderId = Identifier | 'root' | null;
|
||||
|
||||
type FileEntry = {
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import DocumentsManager from '../../documents/DocumentsManager';
|
||||
|
||||
type DocumentId = string | number;
|
||||
import type { DocumentId } from '../../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId;
|
||||
|
||||
@@ -52,6 +52,7 @@ import useWorkspaceTaxonomies from './useWorkspaceTaxonomies';
|
||||
import useWorkspaceBreadcrumbs from './useWorkspaceBreadcrumbs';
|
||||
import useWorkspaceDeskProps from './useWorkspaceDeskProps';
|
||||
import useWorkspaceSelectionSync from './useWorkspaceSelectionSync';
|
||||
import type { DocumentId, FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
|
||||
|
||||
const EntryType = Object.freeze({
|
||||
document: 'document',
|
||||
@@ -60,9 +61,7 @@ const EntryType = Object.freeze({
|
||||
|
||||
const noop = () => { };
|
||||
|
||||
type Identifier = string | number;
|
||||
type DocumentId = Identifier;
|
||||
type FolderId = Identifier | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId | null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MutableRefObject, useEffect } from 'react';
|
||||
|
||||
type FolderId = string | number | 'root' | null;
|
||||
type FolderId = string | 'root' | null;
|
||||
|
||||
interface DropOverlayState {
|
||||
active: boolean;
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
createDocumentEntryKey,
|
||||
createFolderEntryKey,
|
||||
} from '../../app/entryKey';
|
||||
import type { FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderId = Identifier | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier | null;
|
||||
@@ -126,8 +126,8 @@ const useFolderTree = ({
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
let nextDocKeys = [];
|
||||
let mergedSelection = [];
|
||||
let nextDocKeys: string[] = [];
|
||||
let mergedSelection: string[] = [];
|
||||
|
||||
setSelectedEntries((previous) => {
|
||||
const previousFolderKeys = previous
|
||||
@@ -140,9 +140,11 @@ const useFolderTree = ({
|
||||
});
|
||||
|
||||
const nextFocus = (() => {
|
||||
const currentFocusedKey = createDocumentEntryKey(focusedDocumentId);
|
||||
if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) {
|
||||
return focusedDocumentId;
|
||||
if (focusedDocumentId) {
|
||||
const currentFocusedKey = createDocumentEntryKey(focusedDocumentId);
|
||||
if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) {
|
||||
return focusedDocumentId;
|
||||
}
|
||||
}
|
||||
if (nextDocKeys.length) {
|
||||
const lastDocKey = nextDocKeys[nextDocKeys.length - 1];
|
||||
@@ -172,7 +174,7 @@ const useFolderTree = ({
|
||||
if (!targetId || targetId === 'root') {
|
||||
setFolderNodes((prev: Map<FolderId, FolderTreeNode>) => {
|
||||
const root = prev.get('root');
|
||||
if (root?.expanded) return prev;
|
||||
if (!root || root.expanded) return prev;
|
||||
const next = new Map<FolderId, FolderTreeNode>(prev);
|
||||
next.set('root', { ...root, expanded: true });
|
||||
return next;
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
moveFolder as moveFolderRequest,
|
||||
renameFolder as renameFolderRequest,
|
||||
} from '../../lib/apiClient';
|
||||
import type { FolderId } from '../../types/identifiers';
|
||||
|
||||
type FolderId = string | number;
|
||||
type FolderKey = FolderId | 'root';
|
||||
|
||||
interface FolderNode {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MutableRefObject, useCallback, useState } from 'react';
|
||||
import type { TagId, TenantId } from '../../types/identifiers';
|
||||
|
||||
type ApiClient = {
|
||||
get: (path: string) => Promise<{ data: unknown }>
|
||||
@@ -12,7 +13,7 @@ interface TagManagerInterface {
|
||||
}
|
||||
|
||||
interface TagEntry {
|
||||
id?: string | number;
|
||||
id?: TagId;
|
||||
label?: string;
|
||||
color?: string | null;
|
||||
[key: string]: unknown;
|
||||
@@ -23,8 +24,8 @@ interface UseTagsOptions {
|
||||
notifyApiError: (error: unknown, fallback: string) => void;
|
||||
setStatusMessage: (message: string, variant?: string) => void;
|
||||
tagManager: TagManagerInterface;
|
||||
tenantIdRef: MutableRefObject<string | number | null>;
|
||||
setActiveTagFilters: (updater: (prev: Array<string | number>) => Array<string | number>) => void;
|
||||
tenantIdRef: MutableRefObject<TenantId | null>;
|
||||
setActiveTagFilters: (updater: (prev: Array<TagId>) => Array<TagId>) => void;
|
||||
mapDocumentCaches?: (mapper: (doc: any) => any) => void;
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ const useTags = ({
|
||||
}, [apiClient, notifyApiError, tenantIdRef]);
|
||||
|
||||
const handleTagUpdate = useCallback(
|
||||
async (tagId: string | number, changes: { label?: string; color?: string | null }) => {
|
||||
async (tagId: TagId, changes: { label?: string; color?: string | null }) => {
|
||||
if (tagId == null) {
|
||||
throw new Error('Missing tag identifier.');
|
||||
}
|
||||
@@ -104,7 +105,7 @@ const useTags = ({
|
||||
);
|
||||
|
||||
const handleTagDelete = useCallback(
|
||||
async (tagId: string | number) => {
|
||||
async (tagId: TagId) => {
|
||||
if (tagId == null) {
|
||||
throw new Error('Missing tag identifier.');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MutableRefObject, useCallback } from 'react';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import type { FolderId, TenantId } from '../../types/identifiers';
|
||||
|
||||
interface ApiClient {
|
||||
get: (path: string) => Promise<{ data: unknown }>;
|
||||
@@ -8,24 +9,24 @@ interface ApiClient {
|
||||
}
|
||||
|
||||
interface TenantOption {
|
||||
id?: string | number;
|
||||
id?: TenantId;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface UseTenantManagerOptions {
|
||||
apiClient: ApiClient;
|
||||
appDispatch: (action: any) => void;
|
||||
currentTenantId: string | number | null;
|
||||
currentTenantId: TenantId | null;
|
||||
resetWorkspaceState: () => void;
|
||||
setStatusMessage: (message: string, variant?: string) => void;
|
||||
notifyApiError: (error: unknown, message: string) => void;
|
||||
refreshTags: () => Promise<void>;
|
||||
refreshCorrespondents: () => Promise<void>;
|
||||
loadFolder: (folderId: string, options?: { preserveSearch?: boolean }) => Promise<void>;
|
||||
loadFolder: (folderId: FolderId, options?: { preserveSearch?: boolean }) => Promise<void>;
|
||||
handleDocumentsViewModeChange: (mode: string) => void;
|
||||
navigate: NavigateFunction;
|
||||
tokenRef?: MutableRefObject<string | null>;
|
||||
tenantIdRef?: MutableRefObject<string | number | null>;
|
||||
tenantIdRef?: MutableRefObject<TenantId | null>;
|
||||
}
|
||||
|
||||
const useTenantManager = ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
|
||||
import type { FolderId as FolderIdentifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderId = Identifier | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface UseWorkspaceBreadcrumbsArgs {
|
||||
selectedFolder: FolderId | null;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import { createDocumentEntryKey } from '../../app/entryKey';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface ApplySelectionFn {
|
||||
(keys: string[], options?: { anchor?: string | null; interactedKeys?: string[] }): unknown;
|
||||
(keys: string[], options?: { anchor: string | null; interactedKeys?: string[] }): unknown;
|
||||
}
|
||||
|
||||
interface UseWorkspaceDeskPropsArgs {
|
||||
@@ -17,8 +16,8 @@ interface UseWorkspaceDeskPropsArgs {
|
||||
applySelection: ApplySelectionFn;
|
||||
showingSearchResults: boolean;
|
||||
searchQuery: string;
|
||||
activeTagFilters: Array<string | number>;
|
||||
activeCorrespondentFilters: Array<string | number>;
|
||||
activeTagFilters: Array<string>;
|
||||
activeCorrespondentFilters: Array<string>;
|
||||
selectedFolder: Identifier | 'root' | null;
|
||||
promoteSelectionOrder: () => void;
|
||||
handleDocumentTagDrop: (docId: Identifier, tagId: Identifier) => Promise<void> | void;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { MutableRefObject } from 'react';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface UseWorkspaceSelectionSyncArgs {
|
||||
showingSearchResults: boolean;
|
||||
searchQuery: string;
|
||||
setSelectedEntries: (entries: Array<string | number>) => void;
|
||||
setSelectionOrder: (order: Array<string | number>) => void;
|
||||
selectionOrderRef: MutableRefObject<Array<string | number>>;
|
||||
setSelectedEntries: (entries: Array<string>) => void;
|
||||
setSelectionOrder: (order: Array<string>) => void;
|
||||
selectionOrderRef: MutableRefObject<Array<string>>;
|
||||
selectionAnchorRef: MutableRefObject<Identifier | string | null>;
|
||||
setFocusedDocumentId: (id: Identifier | null) => void;
|
||||
selectedDocumentIds: Identifier[];
|
||||
|
||||
@@ -5,8 +5,7 @@ import TagManager from '../../tag_manager';
|
||||
import useCorrespondents from './useCorrespondents';
|
||||
import useDocumentCorrespondentActions from './useDocumentCorrespondentActions';
|
||||
import useTags from './useTags';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface UseWorkspaceTaxonomiesArgs {
|
||||
apiClient: any;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { resolveAssetUrl } from '../asset_manager';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: Identifier;
|
||||
@@ -27,7 +26,7 @@ type AssetLike = {
|
||||
type EnsureAssetUrl = (
|
||||
documentId: Identifier,
|
||||
asset: AssetLike,
|
||||
options?: { force?: boolean; [key: string]: unknown },
|
||||
options?: { force?: boolean;[key: string]: unknown },
|
||||
) => Promise<unknown>;
|
||||
|
||||
type GetAsset = (document: DocumentLike, assetType: string) => AssetLike | null;
|
||||
@@ -92,7 +91,7 @@ export const useAssetNavigator = ({
|
||||
let cancelled = false;
|
||||
setIsLoading(true);
|
||||
ensureAssetUrl(documentId, asset, { force: true })
|
||||
.catch(() => {})
|
||||
.catch(() => { })
|
||||
.finally(() => {
|
||||
if (!cancelled) {
|
||||
setIsLoading(false);
|
||||
|
||||
Reference in New Issue
Block a user